Skip to content

Commit

Permalink
request BLUETOOTH_SCAN and ADVERTISE permissions on Android 12
Browse files Browse the repository at this point in the history
This also adds a warning notification when the app doesn't have the
required permission after an OS update.
  • Loading branch information
Bubu authored and mar-v-in committed Jan 24, 2022
1 parent 4a5c984 commit 6cfc0aa
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ class ExposureNotificationsConfirmActivity : AppCompatActivity() {
private var permissionNeedsHandling: Boolean = false
private var permissionRequestCode = 33
private val permissions by lazy {
if (Build.VERSION.SDK_INT >= 29) {
if (Build.VERSION.SDK_INT >= 31){
arrayOf("android.permission.BLUETOOTH_ADVERTISE", "android.permission.BLUETOOTH_SCAN", "android.permission.ACCESS_BACKGROUND_LOCATION", "android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION")

}
else if (Build.VERSION.SDK_INT >= 29) {
arrayOf("android.permission.ACCESS_BACKGROUND_LOCATION", "android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION")
} else {
arrayOf("android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ package org.microg.gms.nearby.core.ui
import android.bluetooth.BluetoothAdapter
import android.content.Context.LOCATION_SERVICE
import android.content.Intent
import android.content.pm.PackageManager
import android.location.LocationManager
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.provider.Settings
import android.util.Log
import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.location.LocationManagerCompat
import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
Expand All @@ -31,6 +32,7 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() {
private lateinit var exposureEnableInfo: Preference
private lateinit var exposureBluetoothOff: Preference
private lateinit var exposureLocationOff: Preference
private lateinit var exposureNearbyNotGranted: Preference
private lateinit var exposureBluetoothUnsupported: Preference
private lateinit var exposureBluetoothNoAdvertisement: Preference
private lateinit var exposureApps: PreferenceCategory
Expand All @@ -41,6 +43,7 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() {
private val handler = Handler()
private val updateStatusRunnable = Runnable { updateStatus() }
private val updateContentRunnable = Runnable { updateContent() }
private var permissionRequestCode = 33

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preferences_exposure_notifications)
Expand All @@ -50,6 +53,7 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() {
exposureEnableInfo = preferenceScreen.findPreference("pref_exposure_enable_info") ?: exposureEnableInfo
exposureBluetoothOff = preferenceScreen.findPreference("pref_exposure_error_bluetooth_off") ?: exposureBluetoothOff
exposureLocationOff = preferenceScreen.findPreference("pref_exposure_error_location_off") ?: exposureLocationOff
exposureNearbyNotGranted = preferenceScreen.findPreference("pref_exposure_error_nearby_not_granted") ?: exposureNearbyNotGranted
exposureBluetoothUnsupported = preferenceScreen.findPreference("pref_exposure_error_bluetooth_unsupported") ?: exposureBluetoothUnsupported
exposureBluetoothNoAdvertisement = preferenceScreen.findPreference("pref_exposure_error_bluetooth_no_advertise") ?: exposureBluetoothNoAdvertisement
exposureApps = preferenceScreen.findPreference("prefcat_exposure_apps") ?: exposureApps
Expand Down Expand Up @@ -80,12 +84,28 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() {
true
}

exposureNearbyNotGranted.onPreferenceClickListener = Preference.OnPreferenceClickListener {
val nearbyPermissions = arrayOf("android.permission.BLUETOOTH_ADVERTISE", "android.permission.BLUETOOTH_SCAN")
requestPermissions(nearbyPermissions, ++permissionRequestCode)
true
}

collectedRpis.onPreferenceClickListener = Preference.OnPreferenceClickListener {
findNavController().navigate(requireContext(), R.id.openExposureRpis)
true
}
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == this.permissionRequestCode) {
updateStatus()
// Tell the NotifyService that it should update the notification
val intent = Intent(NOTIFICATION_UPDATE_ACTION)
requireContext().sendBroadcast(intent)
}
}

override fun onResume() {
super.onResume()

Expand All @@ -110,6 +130,11 @@ class ExposureNotificationsPreferencesFragment : PreferenceFragmentCompat() {
val bluetoothSupported = ScannerService.isSupported(appContext)
val advertisingSupported = if (bluetoothSupported == true) AdvertiserService.isSupported(appContext) else bluetoothSupported

val nearbyPermissions = arrayOf("android.permission.BLUETOOTH_ADVERTISE", "android.permission.BLUETOOTH_SCAN")
val nearbyPermissionsGranted = Build.VERSION.SDK_INT >= 31 || nearbyPermissions.all {
ContextCompat.checkSelfPermission(appContext, it) == PackageManager.PERMISSION_GRANTED
}
exposureNearbyNotGranted.isVisible = enabled && !nearbyPermissionsGranted
exposureLocationOff.isVisible = enabled && bluetoothSupported != false && !LocationManagerCompat.isLocationEnabled(appContext.getSystemService(LOCATION_SERVICE) as LocationManager)
exposureBluetoothOff.isVisible = enabled && bluetoothSupported == null && !turningBluetoothOn
exposureBluetoothUnsupported.isVisible = enabled && bluetoothSupported == false
Expand Down
2 changes: 2 additions & 0 deletions play-services-nearby-core-ui/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,6 @@ Your identity or test result won&apos;t be shared with other people."</string>
<string name="exposure_confirm_bluetooth_description">Bluetooth needs to be enabled.</string>
<string name="exposure_confirm_location_description">Location access is required.</string>
<string name="exposure_confirm_button">Enable</string>
<string name="pref_exposure_error_nearby_not_granted_title">New Permissions required</string>
<string name="pref_exposure_error_nearby_not_granted_description">Tap to grant required permissions to Exposure Notifications</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
app:isPreferenceVisible="false"
tools:isPreferenceVisible="true" />

<Preference
android:icon="@drawable/ic_info_outline"
android:key="pref_exposure_error_nearby_not_granted"
android:title="@string/pref_exposure_error_nearby_not_granted_title"
android:summary="@string/pref_exposure_error_nearby_not_granted_description"
app:isPreferenceVisible="false"
tools:isPreferenceVisible="true" />

<Preference
android:icon="@drawable/ic_alert"
android:key="pref_exposure_error_bluetooth_unsupported"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,11 @@ class AdvertiserService : LifecycleService() {
.setTxPowerLevel(AdvertisingSetParameters.TX_POWER_LOW)
.setConnectable(false)
.build()
advertiser.startAdvertisingSet(params, data, null, null, null, setCallback as AdvertisingSetCallback)
try {
advertiser.startAdvertisingSet(params, data, null, null, null, setCallback as AdvertisingSetCallback)
} catch (e: SecurityException) {
Log.e(TAG, "Couldn't start advertising: Need android.permission.BLUETOOTH_ADVERTISE permission.", )
}
} else {
nextSend = nextSend.coerceAtMost(180000)
val settings = Builder()
Expand All @@ -156,7 +160,11 @@ class AdvertiserService : LifecycleService() {
.setTxPowerLevel(ADVERTISE_TX_POWER_LOW)
.setConnectable(false)
.build()
advertiser.startAdvertising(settings, data, callback)
try {
advertiser.startAdvertising(settings, data, callback)
} catch (e: SecurityException) {
Log.e(TAG, "Couldn't start advertising: Need android.permission.BLUETOOTH_ADVERTISE permission.", )
}
}
synchronized(this) { advertising = true }
sendingBytes = payload
Expand Down Expand Up @@ -204,7 +212,11 @@ class AdvertiserService : LifecycleService() {
advertising = false
if (Build.VERSION.SDK_INT >= 26) {
wantStartAdvertising = true
advertiser?.stopAdvertisingSet(setCallback as AdvertisingSetCallback)
try {
advertiser?.stopAdvertisingSet(setCallback as AdvertisingSetCallback)
} catch (e: SecurityException) {
Log.i(TAG, "Tried calling stopAdvertisingSet without android.permission.BLUETOOTH_ADVERTISE permission.", )
}
} else {
advertiser?.stopAdvertising(callback)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ const val CLEANUP_INTERVAL = 24 * 60 * 60 * 1000L

const val VERSION_1_0: Byte = 0x40
const val VERSION_1_1: Byte = 0x50

const val NOTIFICATION_UPDATE_ACTION = "org.microg.gms.nearby.UPDATE_NOTIFICATION"
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.Color
import android.location.LocationManager
import android.os.Build
Expand Down Expand Up @@ -53,9 +54,14 @@ class NotifyService : LifecycleService() {
private fun updateNotification() {
val location = !LocationManagerCompat.isLocationEnabled(getSystemService(Context.LOCATION_SERVICE) as LocationManager)
val bluetooth = BluetoothAdapter.getDefaultAdapter()?.state.let { it != BluetoothAdapter.STATE_ON && it != BluetoothAdapter.STATE_TURNING_ON }
Log.d(TAG, "notify: location: $location, bluetooth: $bluetooth")
val nearbyPermissions = arrayOf("android.permission.BLUETOOTH_ADVERTISE", "android.permission.BLUETOOTH_SCAN")
val permissionNeedsHandling = Build.VERSION.SDK_INT >= 31 && nearbyPermissions.any {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}
Log.d( TAG,"notify: location: $location, bluetooth: $bluetooth, permissionNeedsHandling: $permissionNeedsHandling")

val text: String = when {
permissionNeedsHandling -> getString(R.string.exposure_notify_off_nearby)
location && bluetooth -> getString(R.string.exposure_notify_off_bluetooth_location)
location -> getString(R.string.exposure_notify_off_location)
bluetooth -> getString(R.string.exposure_notify_off_bluetooth)
Expand Down Expand Up @@ -105,6 +111,7 @@ class NotifyService : LifecycleService() {
addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
if (Build.VERSION.SDK_INT >= 19) addAction(LocationManager.MODE_CHANGED_ACTION)
addAction(LocationManager.PROVIDERS_CHANGED_ACTION)
addAction(NOTIFICATION_UPDATE_ACTION)
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,18 @@ class ScannerService : LifecycleService() {
Log.i(TAG, "Starting scanner for service $SERVICE_UUID for ${SCANNING_TIME_MS}ms")
seenAdvertisements = 0
wakeLock.acquire()
scanner.startScan(
listOf(ScanFilter.Builder()
.setServiceUuid(SERVICE_UUID)
.setServiceData(SERVICE_UUID, byteArrayOf(0), byteArrayOf(0))
.build()),
ScanSettings.Builder().build(),
callback
)
try {
scanner.startScan(
listOf(ScanFilter.Builder()
.setServiceUuid(SERVICE_UUID)
.setServiceData(SERVICE_UUID, byteArrayOf(0), byteArrayOf(0))
.build()),
ScanSettings.Builder().build(),
callback
)
} catch (e: SecurityException) {
Log.e(TAG, "Couldn't start ScannerService, need android.permission.BLUETOOTH_SCAN permission.")
}
scanning = true
lastStartTime = System.currentTimeMillis()
handler.postDelayed(stopLaterRunnable, SCANNING_TIME_MS)
Expand Down
1 change: 1 addition & 0 deletions play-services-nearby-core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
<string name="exposure_notify_off_bluetooth">Bluetooth needs to be enabled to receive Exposure Notifications.</string>
<string name="exposure_notify_off_location">Location access is required to receive Exposure Notifications.</string>
<string name="exposure_notify_off_bluetooth_location">Bluetooth and Location access need to be enabled to receive Exposure Notifications.</string>
<string name="exposure_notify_off_nearby">Exposure Notifications require additional permissions to work</string>
</resources>

0 comments on commit 6cfc0aa

Please sign in to comment.