diff --git a/radar-commons-android/src/main/java/org/radarbase/android/RadarService.kt b/radar-commons-android/src/main/java/org/radarbase/android/RadarService.kt index ae78175f5..0bc433ea9 100644 --- a/radar-commons-android/src/main/java/org/radarbase/android/RadarService.kt +++ b/radar-commons-android/src/main/java/org/radarbase/android/RadarService.kt @@ -118,6 +118,7 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis } } private lateinit var notificationHandler: NotificationHandler + private lateinit var periodicPermissionObserver: PeriodicPermissionObserver override fun onBind(intent: Intent): IBinder? { super.onBind(intent) @@ -155,6 +156,9 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis configuration = radarConfig providerLoader = SourceProviderLoader(plugins) broadcaster = LocalBroadcastManager.getInstance(this) + periodicPermissionObserver = PeriodicPermissionObserver(this).apply { + start() + } broadcaster.run { permissionsBroadcastReceiver = register(ACTION_PERMISSIONS_GRANTED) { _, intent -> @@ -320,6 +324,8 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis } authConnection.unbind() + periodicPermissionObserver.stop() + mConnections.asSequence() .filter(SourceProvider<*>::isBound) .forEach(SourceProvider<*>::unbind) @@ -360,6 +366,8 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis } } } + + periodicPermissionObserver.configurePermissionObserver(config) } private fun hasFeatures(provider: SourceProvider<*>, packageManager: PackageManager?): Boolean { @@ -420,6 +428,10 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis } } + fun nonGrantedPermissions(): Set { + return needsPermissions + } + private fun updateBluetoothNeeded(newValue: Boolean) { needsBluetooth.applyIfChanged(newValue) { if (newValue) { diff --git a/radar-commons-android/src/main/java/org/radarbase/android/util/PeriodicPermissionObserver.kt b/radar-commons-android/src/main/java/org/radarbase/android/util/PeriodicPermissionObserver.kt new file mode 100644 index 000000000..4d43e1364 --- /dev/null +++ b/radar-commons-android/src/main/java/org/radarbase/android/util/PeriodicPermissionObserver.kt @@ -0,0 +1,75 @@ +package org.radarbase.android.util + +import com.google.firebase.crashlytics.ktx.crashlytics +import com.google.firebase.ktx.Firebase +import org.radarbase.android.RadarApplication.Companion.radarConfig +import org.radarbase.android.RadarService +import org.radarbase.android.config.SingleRadarConfiguration +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.util.concurrent.TimeUnit + +class PeriodicPermissionObserver(private val context: RadarService) { + private var count = 0 + private val isFirstAlarm: Boolean + get() = count <= 1 + + private val pObserverProcessor = OfflineProcessor(context) { + process = listOf(this@PeriodicPermissionObserver::observePermissions) + requestCode = PERIODIC_PERMISSION_OBSERVER_REQUEST_CODE + requestName = PERIODIC_PERMISSION_OBSERVER_REQUEST_NAME + wake = false + } + + private var observerInterval: Long = -1L + + fun start() { + configurePermissionObserver(context.radarConfig.latestConfig) + pObserverProcessor.start() + } + + private fun observePermissions() { + count += 1 + if (isFirstAlarm) { + logger.info("This is the first alarm, aborting it") + return + } + + context.nonGrantedPermissions().also { permissions -> + if (permissions.isNotEmpty()) { + logger.info("Some permissions are still not granted: ${permissions.joinToString(", ") { it }}") + Firebase.crashlytics.apply { + log("User has not granted some permissions") + setCustomKey("error_type", "permissions_not_granted") + setCustomKey("non_granted_permissions", permissions.joinToString(", ") { it }) + recordException(RuntimeException("Some permissions have not been granted.")) + } + } + } + } + + fun configurePermissionObserver(config: SingleRadarConfiguration) { + observerInterval = config.getLong( + PERMISSION_OBSERVER_INTERVAL_SECONDS, + PERMISSION_OBSERVER_INTERVAL_SECONDS_DEFAULT + ) + + pObserverProcessor.interval(observerInterval, TimeUnit.SECONDS) + } + + + fun stop() { + pObserverProcessor.close() + } + + companion object { + private val logger: Logger = LoggerFactory.getLogger(PeriodicPermissionObserver::class.java) + + private const val PERIODIC_PERMISSION_OBSERVER_REQUEST_CODE = 683768762 + private const val PERIODIC_PERMISSION_OBSERVER_REQUEST_NAME = + "org.radarbase.android.util.PeriodicPermissionObserver.PERIODIC_PERMISSION_OBSERVER_REQUEST_NAME" + private const val PERMISSION_OBSERVER_INTERVAL_SECONDS = + "org.radarbase.android.util.PeriodicPermissionObserver.PERMISSION_OBSERVER_INTERVAL_SECONDS" + private const val PERMISSION_OBSERVER_INTERVAL_SECONDS_DEFAULT = 2L * ((60 * 60) * 24) // 2-DAYS + } +} \ No newline at end of file