diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsConfirmActivity.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsConfirmActivity.kt index dc106094..225a0e02 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsConfirmActivity.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsConfirmActivity.kt @@ -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") diff --git a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt index ec89883c..5003182d 100644 --- a/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt +++ b/play-services-nearby-core-ui/src/main/kotlin/org/microg/gms/nearby/core/ui/ExposureNotificationsPreferencesFragment.kt @@ -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 @@ -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 @@ -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) @@ -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 @@ -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, 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() @@ -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 diff --git a/play-services-nearby-core-ui/src/main/res/values/strings.xml b/play-services-nearby-core-ui/src/main/res/values/strings.xml index ddf2dfcc..7aaa43e9 100644 --- a/play-services-nearby-core-ui/src/main/res/values/strings.xml +++ b/play-services-nearby-core-ui/src/main/res/values/strings.xml @@ -76,4 +76,6 @@ Your identity or test result won't be shared with other people." Bluetooth needs to be enabled. Location access is required. Enable + New Permissions required + Tap to grant required permissions to Exposure Notifications diff --git a/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications.xml b/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications.xml index 6c04ef7c..8981daa7 100644 --- a/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications.xml +++ b/play-services-nearby-core-ui/src/main/res/xml/preferences_exposure_notifications.xml @@ -31,6 +31,14 @@ app:isPreferenceVisible="false" tools:isPreferenceVisible="true" /> + + = 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) } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt index ca695aac..2f429384 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/Constants.kt @@ -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" diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/NotifyService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/NotifyService.kt index 48682281..0245752a 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/NotifyService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/NotifyService.kt @@ -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 @@ -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) @@ -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) }) } diff --git a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt index addc6c8c..9ee699e0 100644 --- a/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt +++ b/play-services-nearby-core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ScannerService.kt @@ -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) diff --git a/play-services-nearby-core/src/main/res/values/strings.xml b/play-services-nearby-core/src/main/res/values/strings.xml index 8cd79f35..16c274d9 100644 --- a/play-services-nearby-core/src/main/res/values/strings.xml +++ b/play-services-nearby-core/src/main/res/values/strings.xml @@ -7,4 +7,5 @@ Bluetooth needs to be enabled to receive Exposure Notifications. Location access is required to receive Exposure Notifications. Bluetooth and Location access need to be enabled to receive Exposure Notifications. + Exposure Notifications require additional permissions to work