diff --git a/README.md b/README.md index df4f7628f..6ccbc2f58 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,35 @@ To only build plugins that are of interest to you, add a `gradle.skip` file in a If you want to contribute a feature or fix browse our [issues](https://github.com/RADAR-base/radar-commons-android/issues), and please make a pull request. +## Publishing Schemas to Maven Local + +While the plugin is in development phase schemas are not published centrally. To access the java generated file from avro schemas first publish them to maven local. +To publish schemas locally to Maven, please follow these steps: + +1. Change your working directory to java-sdk in RADAR-Schemas. + +2. Run the following commands in your terminal: +```shell +./gradlew build +./gradlew publishToMavenLocal +``` + +3. In your build.gradle file, add the following to your repositories block: +```gradle +repositories { + mavenLocal() +} +``` + +4. In the same build.gradle file, add the following to your dependencies block, replacing `$radarSchemasVersion` with its corresponding value: +```gradle +dependencies { + implementation "org.radarbase:radar-schemas-commons:$radarSchemasVersion" +} +``` + +You can find the version value in the metadata of your local Maven repository. It should end in `SNAPSHOT`. + ## Licensing Code made in the RADAR-base platform is licensed under the Apache License 2.0, as listed in see the LICENSE file in this directory. Plugins that have additional licensing requirements list them in their README. diff --git a/avro-android/src/main/AndroidManifest.xml b/avro-android/src/main/AndroidManifest.xml index 9b65eb06c..d30de6534 100644 --- a/avro-android/src/main/AndroidManifest.xml +++ b/avro-android/src/main/AndroidManifest.xml @@ -1 +1,2 @@ + diff --git a/build.gradle b/build.gradle index afbf2af12..e05355f71 100644 --- a/build.gradle +++ b/build.gradle @@ -41,10 +41,10 @@ allprojects { ext.issueUrl = 'https://github.com/' + githubRepoName + '/issues' ext.website = 'http://radar-base.org' - version = "1.2.3" + version = "1.2.4" group = 'org.radarbase' - ext.versionCode = 51 + ext.versionCode = 52 } subprojects { diff --git a/plugins/radar-android-application-status/src/main/AndroidManifest.xml b/plugins/radar-android-application-status/src/main/AndroidManifest.xml index 8003fa0a4..635aabfd0 100644 --- a/plugins/radar-android-application-status/src/main/AndroidManifest.xml +++ b/plugins/radar-android-application-status/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ - + diff --git a/plugins/radar-android-application-status/src/main/res/values/strings.xml b/plugins/radar-android-application-status/src/main/res/values/strings.xml index eec4a9a52..bb4b5ae86 100644 --- a/plugins/radar-android-application-status/src/main/res/values/strings.xml +++ b/plugins/radar-android-application-status/src/main/res/values/strings.xml @@ -1,6 +1,4 @@ Application - Monitors how much data the app is collecting and - server status. - + Monitors how much data the app is collecting and server status. diff --git a/plugins/radar-android-audio/build.gradle b/plugins/radar-android-audio/build.gradle index fab0c3eb4..b860a0d55 100644 --- a/plugins/radar-android-audio/build.gradle +++ b/plugins/radar-android-audio/build.gradle @@ -10,9 +10,6 @@ android { ndk { moduleName rootProject.name } - packagingOptions { - doNotStrip '**.so' - } } // Encapsulates your external native build configurations. diff --git a/plugins/radar-android-audio/src/main/AndroidManifest.xml b/plugins/radar-android-audio/src/main/AndroidManifest.xml index e51b3a9b1..5059bf293 100644 --- a/plugins/radar-android-audio/src/main/AndroidManifest.xml +++ b/plugins/radar-android-audio/src/main/AndroidManifest.xml @@ -8,6 +8,8 @@ + android:foregroundServiceType="microphone" + android:exported="false" + android:description="@string/audio_description" /> diff --git a/plugins/radar-android-audio/src/main/java/org/radarbase/passive/audio/OpenSmileAudioService.kt b/plugins/radar-android-audio/src/main/java/org/radarbase/passive/audio/OpenSmileAudioService.kt index 120ae6466..9cb3a00a5 100644 --- a/plugins/radar-android-audio/src/main/java/org/radarbase/passive/audio/OpenSmileAudioService.kt +++ b/plugins/radar-android-audio/src/main/java/org/radarbase/passive/audio/OpenSmileAudioService.kt @@ -39,9 +39,9 @@ class OpenSmileAudioService : SourceService() { manager as OpensmileAudioManager manager.setRecordRate(config.getLong(AUDIO_RECORD_RATE_S, DEFAULT_RECORD_RATE)) manager.config = OpensmileAudioManager.AudioConfiguration( - config.getString(AUDIO_CONFIG_FILE, "ComParE_2016.conf"), - config.getLong(AUDIO_DURATION_S, 15L), - TimeUnit.SECONDS + config.getString(AUDIO_CONFIG_FILE, "ComParE_2016.conf"), + config.getLong(AUDIO_DURATION_S, 15L), + TimeUnit.SECONDS, ) } diff --git a/plugins/radar-android-audio/src/main/java/org/radarbase/passive/audio/OpensmileAudioManager.kt b/plugins/radar-android-audio/src/main/java/org/radarbase/passive/audio/OpensmileAudioManager.kt index 017fbf8ff..3f8bf249a 100644 --- a/plugins/radar-android-audio/src/main/java/org/radarbase/passive/audio/OpensmileAudioManager.kt +++ b/plugins/radar-android-audio/src/main/java/org/radarbase/passive/audio/OpensmileAudioManager.kt @@ -135,7 +135,7 @@ class OpensmileAudioManager constructor(service: OpenSmileAudioService) : Abstra ?.list { _, name -> name.startsWith("audio_") && name.endsWith(".bin") } ?.forEach { File(audioDir.parentFile, it).delete() } - audioDir.walk().filter { it.startsWith("audio_") && it.endsWith(".bin") } + audioDir.walk().filter { it.startsWith("audio_") && it.path.endsWith(".bin") } .forEach { it.delete() } } } diff --git a/plugins/radar-android-empatica/src/main/AndroidManifest.xml b/plugins/radar-android-empatica/src/main/AndroidManifest.xml index cacff25f5..b0266246b 100644 --- a/plugins/radar-android-empatica/src/main/AndroidManifest.xml +++ b/plugins/radar-android-empatica/src/main/AndroidManifest.xml @@ -4,16 +4,24 @@ - - - - - + + + + + + + android:foregroundServiceType="location" + android:exported="false" + android:description="@string/empatica_e4_explanation" /> diff --git a/plugins/radar-android-empatica/src/main/java/org/radarbase/passive/empatica/E4Manager.kt b/plugins/radar-android-empatica/src/main/java/org/radarbase/passive/empatica/E4Manager.kt index 15f4b7dfe..347069489 100644 --- a/plugins/radar-android-empatica/src/main/java/org/radarbase/passive/empatica/E4Manager.kt +++ b/plugins/radar-android-empatica/src/main/java/org/radarbase/passive/empatica/E4Manager.kt @@ -26,7 +26,6 @@ import com.empatica.empalink.config.EmpaStatus import com.empatica.empalink.delegate.EmpaDataDelegate import com.empatica.empalink.delegate.EmpaSessionManagerDelegate import com.empatica.empalink.delegate.EmpaStatusDelegate -import org.radarbase.android.RadarApplication.Companion.radarApp import org.radarbase.android.source.AbstractSourceManager import org.radarbase.android.source.SourceStatusListener import org.radarbase.android.util.BluetoothStateReceiver.Companion.bluetoothIsEnabled @@ -46,6 +45,9 @@ class E4Manager( EmpaDataDelegate, EmpaStatusDelegate, EmpaSessionManagerDelegate { + private val notificationHandler: NotificationHandler by lazy { + NotificationHandler(service) + } private var doNotify: Boolean = false private val accelerationTopic = createCache("android_empatica_e4_acceleration", EmpaticaE4Acceleration()) private val batteryLevelTopic = createCache("android_empatica_e4_battery_level", EmpaticaE4BatteryLevel()) @@ -122,7 +124,7 @@ class E4Manager( EmpaStatus.CONNECTING -> hasBeenConnecting = true EmpaStatus.CONNECTED -> { status = SourceStatusListener.Status.CONNECTED - service.radarApp.notificationHandler.manager?.cancel(EMPATICA_DISCONNECTED_NOTIFICATION_ID) + notificationHandler.manager?.cancel(EMPATICA_DISCONNECTED_NOTIFICATION_ID) } EmpaStatus.DISCONNECTED -> // The device manager disconnected from a device. Before it ever makes a connection, @@ -230,10 +232,11 @@ class E4Manager( override fun disconnect() { if (status != SourceStatusListener.Status.UNAVAILABLE && !isClosed && doNotify) { - service.radarApp.notificationHandler.notify( - id = EMPATICA_DISCONNECTED_NOTIFICATION_ID, - channel = NotificationHandler.NOTIFICATION_CHANNEL_ALERT, - includeStartIntent = true) { + notificationHandler.notify( + id = EMPATICA_DISCONNECTED_NOTIFICATION_ID, + channel = NotificationHandler.NOTIFICATION_CHANNEL_ALERT, + includeStartIntent = true, + ) { setContentTitle(service.getString(R.string.notification_empatica_disconnected_title)) setContentText(service.getString(R.string.notification_empatica_disconnected_text)) } diff --git a/plugins/radar-android-empatica/src/main/java/org/radarbase/passive/empatica/E4Provider.kt b/plugins/radar-android-empatica/src/main/java/org/radarbase/passive/empatica/E4Provider.kt index a4d262a8d..fbf5faa48 100644 --- a/plugins/radar-android-empatica/src/main/java/org/radarbase/passive/empatica/E4Provider.kt +++ b/plugins/radar-android-empatica/src/main/java/org/radarbase/passive/empatica/E4Provider.kt @@ -21,6 +21,7 @@ import android.content.pm.PackageManager import android.os.Build import org.radarbase.android.RadarService import org.radarbase.android.source.SourceProvider +import org.radarbase.android.util.BluetoothStateReceiver.Companion.bluetoothPermissionList open class E4Provider(radarService: RadarService) : SourceProvider(radarService) { override val serviceClass = E4Service::class.java @@ -32,19 +33,7 @@ open class E4Provider(radarService: RadarService) : SourceProvider(rada "org.radarbase.passive.empatica.E4Provider", "org.radarcns.empatica.E4ServiceProvider") - override val permissionsNeeded = buildList { - add(ACCESS_COARSE_LOCATION) - add(ACCESS_FINE_LOCATION) - add(BLUETOOTH) - add(BLUETOOTH_ADMIN) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - add(BLUETOOTH_SCAN) - add(BLUETOOTH_CONNECT) - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - add(ACCESS_BACKGROUND_LOCATION) - } - } + override val permissionsNeeded = bluetoothPermissionList override val featuresNeeded = listOf(PackageManager.FEATURE_BLUETOOTH, PackageManager.FEATURE_BLUETOOTH_LE) diff --git a/plugins/radar-android-faros/src/main/AndroidManifest.xml b/plugins/radar-android-faros/src/main/AndroidManifest.xml index 685b73533..0f27076ab 100644 --- a/plugins/radar-android-faros/src/main/AndroidManifest.xml +++ b/plugins/radar-android-faros/src/main/AndroidManifest.xml @@ -3,16 +3,24 @@ - - - - + + + + + + android:foregroundServiceType="location" + android:exported="false" + android:description="@string/farosDescription"/> diff --git a/plugins/radar-android-faros/src/main/java/org/radarbase/passive/bittium/FarosManager.kt b/plugins/radar-android-faros/src/main/java/org/radarbase/passive/bittium/FarosManager.kt index cfcdc72ce..5a20854d2 100644 --- a/plugins/radar-android-faros/src/main/java/org/radarbase/passive/bittium/FarosManager.kt +++ b/plugins/radar-android-faros/src/main/java/org/radarbase/passive/bittium/FarosManager.kt @@ -35,6 +35,9 @@ class FarosManager internal constructor( private val farosFactory: FarosSdkFactory, private val handler: SafeHandler ) : AbstractSourceManager(service), FarosDeviceListener, FarosSdkListener { + private val notificationHandler by lazy { + NotificationHandler(this.service) + } private var doNotify: Boolean = false private val accelerationTopic: DataCache = createCache("android_bittium_faros_acceleration", BittiumFarosAcceleration()) private val ecgTopic: DataCache = createCache("android_bittium_faros_ecg", BittiumFarosEcg()) @@ -78,7 +81,7 @@ class FarosManager internal constructor( startMeasurements() } } - service.radarApp.notificationHandler.manager?.cancel(FAROS_DISCONNECTED_NOTIFICATION_ID) + notificationHandler.manager?.cancel(FAROS_DISCONNECTED_NOTIFICATION_ID) SourceStatusListener.Status.CONNECTED } FarosDeviceListener.CONNECTING -> SourceStatusListener.Status.CONNECTING @@ -194,10 +197,11 @@ class FarosManager internal constructor( override fun disconnect() { if (!isClosed && doNotify) { - service.radarApp.notificationHandler.notify( - id = FAROS_DISCONNECTED_NOTIFICATION_ID, - channel = NotificationHandler.NOTIFICATION_CHANNEL_ALERT, - includeStartIntent = true) { + notificationHandler.notify( + id = FAROS_DISCONNECTED_NOTIFICATION_ID, + channel = NotificationHandler.NOTIFICATION_CHANNEL_ALERT, + includeStartIntent = true, + ) { setContentTitle(service.getString(R.string.notification_faros_disconnected_title)) setContentText(service.getString(R.string.notification_faros_disconnected_text)) } diff --git a/plugins/radar-android-faros/src/main/java/org/radarbase/passive/bittium/FarosProvider.kt b/plugins/radar-android-faros/src/main/java/org/radarbase/passive/bittium/FarosProvider.kt index 394df1293..20fc7cc40 100644 --- a/plugins/radar-android-faros/src/main/java/org/radarbase/passive/bittium/FarosProvider.kt +++ b/plugins/radar-android-faros/src/main/java/org/radarbase/passive/bittium/FarosProvider.kt @@ -16,12 +16,11 @@ package org.radarbase.passive.bittium -import android.Manifest.permission.* import android.content.pm.PackageManager -import android.os.Build import org.radarbase.android.BuildConfig import org.radarbase.android.RadarService import org.radarbase.android.source.SourceProvider +import org.radarbase.android.util.BluetoothStateReceiver.Companion.bluetoothPermissionList class FarosProvider(radarService: RadarService) : SourceProvider(radarService) { override val description: String? @@ -38,19 +37,7 @@ class FarosProvider(radarService: RadarService) : SourceProvider(rad override val hasDetailView: Boolean = true - override val permissionsNeeded: List = buildList { - add(ACCESS_COARSE_LOCATION) - add(ACCESS_FINE_LOCATION) - add(BLUETOOTH) - add(BLUETOOTH_ADMIN) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - add(BLUETOOTH_SCAN) - add(BLUETOOTH_CONNECT) - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - add(ACCESS_BACKGROUND_LOCATION) - } - } + override val permissionsNeeded: List = bluetoothPermissionList override val featuresNeeded: List = listOf(PackageManager.FEATURE_BLUETOOTH) diff --git a/plugins/radar-android-faros/src/main/java/org/radarbase/passive/bittium/FarosState.kt b/plugins/radar-android-faros/src/main/java/org/radarbase/passive/bittium/FarosState.kt index 1ff638c35..23d5432d9 100644 --- a/plugins/radar-android-faros/src/main/java/org/radarbase/passive/bittium/FarosState.kt +++ b/plugins/radar-android-faros/src/main/java/org/radarbase/passive/bittium/FarosState.kt @@ -48,12 +48,10 @@ class FarosState : BaseSourceState() { this.acceleration[2] = z } - override fun toString(): String { - return "FarosDeviceStatus{" + - "batteryLevel=" + batteryLevel + - ", heartRate=" + heartRate + - ", temperature=" + temperature + - ", acceleration=" + Arrays.toString(acceleration) + - '}'.toString() - } + override fun toString(): String = "FarosDeviceStatus{" + + "batteryLevel=" + batteryLevel + + ", heartRate=" + heartRate + + ", temperature=" + temperature + + ", acceleration=" + acceleration.contentToString() + + '}' } diff --git a/plugins/radar-android-login-oauth2/src/main/java/org/radarbase/android/auth/oauth2/OAuth2LoginManager.kt b/plugins/radar-android-login-oauth2/src/main/java/org/radarbase/android/auth/oauth2/OAuth2LoginManager.kt index 60054c3f7..9340ae320 100644 --- a/plugins/radar-android-login-oauth2/src/main/java/org/radarbase/android/auth/oauth2/OAuth2LoginManager.kt +++ b/plugins/radar-android-login-oauth2/src/main/java/org/radarbase/android/auth/oauth2/OAuth2LoginManager.kt @@ -20,15 +20,16 @@ import android.app.Activity import org.json.JSONException import org.radarbase.android.RadarApplication.Companion.radarApp import org.radarbase.android.auth.* +import org.radarbase.android.auth.portal.ManagementPortalLoginManager import org.radarbase.producer.AuthenticationException /** * Authenticates against the RADAR Management Portal. */ class OAuth2LoginManager( - private val service: AuthService, - private val projectIdClaim: String, - private val userIdClaim: String + private val service: AuthService, + private val projectIdClaim: String, + private val userIdClaim: String ) : LoginManager, LoginListener { private val stateManager: OAuth2StateManager = OAuth2StateManager(service) @@ -55,16 +56,8 @@ class OAuth2LoginManager( return true } - override fun invalidate(authState: AppAuthState, disableRefresh: Boolean): AppAuthState? { - return when { - authState.tokenType != LoginManager.AUTH_TYPE_BEARER -> return null - disableRefresh -> authState.alter { - attributes -= LOGIN_REFRESH_TOKEN - isPrivacyPolicyAccepted = false - } - else -> authState - } - } + override fun invalidate(authState: AppAuthState, disableRefresh: Boolean): AppAuthState? = + authState.takeIf { it.authenticationSource == OAUTH2_SOURCE_TYPE } override val sourceTypes: List = OAUTH2_SOURCE_TYPES @@ -89,16 +82,16 @@ class OAuth2LoginManager( override fun loginSucceeded(manager: LoginManager?, authState: AppAuthState) { val token = authState.token if (token == null) { - this.service.loginFailed(this, + loginFailed(this, IllegalArgumentException("Cannot login using OAuth2 without a token")) return } try { processJwt(authState, Jwt.parse(token)).let { - this.service.loginSucceeded(this, it) + service.loginSucceeded(this, it) } } catch (ex: JSONException) { - this.service.loginFailed(this, ex) + loginFailed(this, ex) } } diff --git a/plugins/radar-android-phone-telephony/src/main/AndroidManifest.xml b/plugins/radar-android-phone-telephony/src/main/AndroidManifest.xml index 6dd3ff31d..134fec4a0 100644 --- a/plugins/radar-android-phone-telephony/src/main/AndroidManifest.xml +++ b/plugins/radar-android-phone-telephony/src/main/AndroidManifest.xml @@ -5,6 +5,8 @@ - + diff --git a/plugins/radar-android-phone-usage/src/main/AndroidManifest.xml b/plugins/radar-android-phone-usage/src/main/AndroidManifest.xml index 9ed10e4b9..4c3ec3781 100644 --- a/plugins/radar-android-phone-usage/src/main/AndroidManifest.xml +++ b/plugins/radar-android-phone-usage/src/main/AndroidManifest.xml @@ -7,6 +7,8 @@ android:name="android.permission.PACKAGE_USAGE_STATS"/> - + diff --git a/plugins/radar-android-phone-usage/src/main/java/org/radarbase/passive/phone/usage/PhoneUsageManager.kt b/plugins/radar-android-phone-usage/src/main/java/org/radarbase/passive/phone/usage/PhoneUsageManager.kt index cefc06ad2..102d813b1 100644 --- a/plugins/radar-android-phone-usage/src/main/java/org/radarbase/passive/phone/usage/PhoneUsageManager.kt +++ b/plugins/radar-android-phone-usage/src/main/java/org/radarbase/passive/phone/usage/PhoneUsageManager.kt @@ -173,7 +173,7 @@ class PhoneUsageManager(context: PhoneUsageService) : AbstractSourceManager UsageEventType.BACKGROUND CONFIGURATION_CHANGE -> UsageEventType.CONFIG SHORTCUT_INVOCATION_COMPAT -> UsageEventType.SHORTCUT - USER_INTERACTION_COMPAT -> UsageEventType.INTERACTION + USER_INTERACTION -> UsageEventType.INTERACTION else -> UsageEventType.OTHER } @@ -223,8 +223,7 @@ class PhoneUsageManager(context: PhoneUsageService) : AbstractSourceManager= 25) SHORTCUT_INVOCATION else 8 - private val USER_INTERACTION_COMPAT = if (Build.VERSION.SDK_INT >= 23) USER_INTERACTION else 7 + private val SHORTCUT_INVOCATION_COMPAT = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) SHORTCUT_INVOCATION else 8 private const val LAST_PACKAGE_NAME = "org.radarcns.phone.packageName" private const val LAST_EVENT_TIMESTAMP = "org.radarcns.phone.timestamp" diff --git a/plugins/radar-android-phone-usage/src/main/java/org/radarbase/passive/phone/usage/PhoneUsageProvider.kt b/plugins/radar-android-phone-usage/src/main/java/org/radarbase/passive/phone/usage/PhoneUsageProvider.kt index 4efc54b42..188184de4 100644 --- a/plugins/radar-android-phone-usage/src/main/java/org/radarbase/passive/phone/usage/PhoneUsageProvider.kt +++ b/plugins/radar-android-phone-usage/src/main/java/org/radarbase/passive/phone/usage/PhoneUsageProvider.kt @@ -17,7 +17,6 @@ package org.radarbase.passive.phone.usage import android.Manifest.permission.PACKAGE_USAGE_STATS -import android.os.Build import org.radarbase.android.BuildConfig import org.radarbase.android.RadarService import org.radarbase.android.source.BaseSourceState diff --git a/plugins/radar-android-phone/src/main/AndroidManifest.xml b/plugins/radar-android-phone/src/main/AndroidManifest.xml index 3f25ff405..60f484d19 100644 --- a/plugins/radar-android-phone/src/main/AndroidManifest.xml +++ b/plugins/radar-android-phone/src/main/AndroidManifest.xml @@ -4,21 +4,34 @@ - - + + + - + + - + - + android:foregroundServiceType="location" + android:exported="false" + android:description="@string/phone_location_description" /> + + android:foregroundServiceType="location" + android:exported="false" + android:description="@string/phone_bluetooth_description" /> diff --git a/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneBluetoothManager.kt b/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneBluetoothManager.kt index c39348159..496d1b9a1 100644 --- a/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneBluetoothManager.kt +++ b/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneBluetoothManager.kt @@ -31,6 +31,7 @@ import org.radarbase.android.source.AbstractSourceManager import org.radarbase.android.source.BaseSourceState import org.radarbase.android.source.SourceStatusListener import org.radarbase.android.util.BluetoothStateReceiver.Companion.bluetoothAdapter +import org.radarbase.android.util.BluetoothStateReceiver.Companion.hasBluetoothPermission import org.radarbase.android.util.OfflineProcessor import org.radarcns.kafka.ObservationKey import org.radarcns.passive.phone.PhoneBluetoothDevices @@ -65,14 +66,11 @@ class PhoneBluetoothManager(service: PhoneBluetoothService) : AbstractSourceMana logger.error("Bluetooth is not available.") return } + if (!service.hasBluetoothPermission) { + logger.error("Cannot initiate Bluetooth scan without scan permissions") + return + } if (bluetoothAdapter.isEnabled) { - val scanPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - Manifest.permission.BLUETOOTH_SCAN - } else Manifest.permission.BLUETOOTH_ADMIN - if (ActivityCompat.checkSelfPermission(service, scanPermission) != PackageManager.PERMISSION_GRANTED) { - logger.error("Cannot initiate Bluetooth scan without scan permissions") - return - } val filter = IntentFilter().apply { addAction(BluetoothDevice.ACTION_FOUND) addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED) diff --git a/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneBluetoothProvider.kt b/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneBluetoothProvider.kt index 989f17de6..5755ba8d7 100644 --- a/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneBluetoothProvider.kt +++ b/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneBluetoothProvider.kt @@ -16,13 +16,12 @@ package org.radarbase.passive.phone -import android.Manifest.permission.* import android.content.pm.PackageManager -import android.os.Build import org.radarbase.android.BuildConfig import org.radarbase.android.RadarService import org.radarbase.android.source.BaseSourceState import org.radarbase.android.source.SourceProvider +import org.radarbase.android.util.BluetoothStateReceiver.Companion.bluetoothPermissionList import org.radarbase.passive.phone.PhoneSensorProvider.Companion.MODEL import org.radarbase.passive.phone.PhoneSensorProvider.Companion.PRODUCER @@ -44,19 +43,7 @@ open class PhoneBluetoothProvider(radarService: RadarService) : SourceProvider = buildList { - add(ACCESS_COARSE_LOCATION) - add(ACCESS_FINE_LOCATION) - add(BLUETOOTH) - add(BLUETOOTH_ADMIN) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - add(BLUETOOTH_SCAN) - add(BLUETOOTH_CONNECT) - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - add(ACCESS_BACKGROUND_LOCATION) - } - } + override val permissionsNeeded: List = bluetoothPermissionList override val featuresNeeded: List = listOf(PackageManager.FEATURE_BLUETOOTH) diff --git a/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneLocationManager.kt b/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneLocationManager.kt index 94f875abd..6fdcc021f 100644 --- a/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneLocationManager.kt +++ b/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneLocationManager.kt @@ -140,6 +140,7 @@ class PhoneLocationManager(context: PhoneLocationService) : AbstractSourceManage longitude, accuracy, altitude, speed, bearing, timestamp) } + @Deprecated("This callback will never be invoked on Android Q and above.") override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {} override fun onProviderEnabled(provider: String) {} diff --git a/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneSensorManager.kt b/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneSensorManager.kt index 3f19e35a7..88d1043ad 100644 --- a/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneSensorManager.kt +++ b/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneSensorManager.kt @@ -306,8 +306,9 @@ class PhoneSensorManager(context: PhoneSensorService) : AbstractSourceManager SparseArray.computeIfAbsent(key: Int, compute: () -> T) = get(key) diff --git a/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneSensorService.kt b/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneSensorService.kt index a966a1492..44be7f9e8 100644 --- a/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneSensorService.kt +++ b/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneSensorService.kt @@ -21,7 +21,6 @@ import android.util.SparseIntArray import org.radarbase.android.config.SingleRadarConfiguration import org.radarbase.android.source.SourceManager import org.radarbase.android.source.SourceService -import org.slf4j.LoggerFactory import java.util.concurrent.TimeUnit /** diff --git a/plugins/radar-android-ppg/src/main/AndroidManifest.xml b/plugins/radar-android-ppg/src/main/AndroidManifest.xml index 32999094e..2589b27db 100644 --- a/plugins/radar-android-ppg/src/main/AndroidManifest.xml +++ b/plugins/radar-android-ppg/src/main/AndroidManifest.xml @@ -8,7 +8,9 @@ - + diff --git a/plugins/radar-android-ppg/src/main/res/values/strings.xml b/plugins/radar-android-ppg/src/main/res/values/strings.xml index 818805178..f62827721 100644 --- a/plugins/radar-android-ppg/src/main/res/values/strings.xml +++ b/plugins/radar-android-ppg/src/main/res/values/strings.xml @@ -9,4 +9,5 @@ Stop PPG PPG measurement + Service that measure PPG using the camera of a phone. diff --git a/plugins/radar-android-weather/src/main/AndroidManifest.xml b/plugins/radar-android-weather/src/main/AndroidManifest.xml index d5e0d80bc..aef0517fc 100644 --- a/plugins/radar-android-weather/src/main/AndroidManifest.xml +++ b/plugins/radar-android-weather/src/main/AndroidManifest.xml @@ -8,6 +8,8 @@ + android:foregroundServiceType="location" + android:exported="false" + android:description="@string/weather_api_description" /> diff --git a/plugins/radar-android-weather/src/main/java/org/radarbase/passive/weather/WeatherApiProvider.kt b/plugins/radar-android-weather/src/main/java/org/radarbase/passive/weather/WeatherApiProvider.kt index 156bb4648..58ef33a50 100644 --- a/plugins/radar-android-weather/src/main/java/org/radarbase/passive/weather/WeatherApiProvider.kt +++ b/plugins/radar-android-weather/src/main/java/org/radarbase/passive/weather/WeatherApiProvider.kt @@ -19,11 +19,13 @@ package org.radarbase.passive.weather import android.Manifest.permission.* import android.content.pm.PackageManager import android.os.Build +import androidx.annotation.Keep import org.radarbase.android.BuildConfig import org.radarbase.android.RadarService import org.radarbase.android.source.BaseSourceState import org.radarbase.android.source.SourceProvider +@Keep open class WeatherApiProvider(radarService: RadarService) : SourceProvider(radarService) { override val description: String? get() = radarService.getString(R.string.weather_api_description) diff --git a/plugins/radar-android-weather/src/main/java/org/radarbase/passive/weather/WeatherApiService.kt b/plugins/radar-android-weather/src/main/java/org/radarbase/passive/weather/WeatherApiService.kt index 88cfd7f5f..8a72d598c 100644 --- a/plugins/radar-android-weather/src/main/java/org/radarbase/passive/weather/WeatherApiService.kt +++ b/plugins/radar-android-weather/src/main/java/org/radarbase/passive/weather/WeatherApiService.kt @@ -46,8 +46,9 @@ class WeatherApiService : SourceService() { manager as WeatherApiManager manager.setQueryInterval(config.getLong(WEATHER_QUERY_INTERVAL, WEATHER_QUERY_INTERVAL_DEFAULT), TimeUnit.SECONDS) manager.setSource( - config.getString(WEATHER_API_SOURCE, WEATHER_API_SOURCE_DEFAULT), - config.optString(WEATHER_API_KEY)) + config.getString(WEATHER_API_SOURCE, WEATHER_API_SOURCE_DEFAULT), + config.optString(WEATHER_API_KEY), + ) } companion object { diff --git a/radar-commons-android/src/main/java/org/radarbase/android/AbstractRadarApplication.kt b/radar-commons-android/src/main/java/org/radarbase/android/AbstractRadarApplication.kt index 63c6df5fa..d5f1f8230 100644 --- a/radar-commons-android/src/main/java/org/radarbase/android/AbstractRadarApplication.kt +++ b/radar-commons-android/src/main/java/org/radarbase/android/AbstractRadarApplication.kt @@ -31,7 +31,7 @@ abstract class AbstractRadarApplication : Application(), RadarApplication { private lateinit var innerNotificationHandler: NotificationHandler override val notificationHandler: NotificationHandler - get() = innerNotificationHandler.apply { onCreate() } + get() = innerNotificationHandler override lateinit var configuration: RadarConfiguration @@ -51,7 +51,7 @@ abstract class AbstractRadarApplication : Application(), RadarApplication { // initialize crashlytics HandroidLoggerAdapter.DEBUG = BuildConfig.DEBUG HandroidLoggerAdapter.APP_NAME = packageName - HandroidLoggerAdapter.enableLoggingToCrashlytics() + HandroidLoggerAdapter.enableLoggingToFirebaseCrashlytics() } /** diff --git a/radar-commons-android/src/main/java/org/radarbase/android/RadarApplication.kt b/radar-commons-android/src/main/java/org/radarbase/android/RadarApplication.kt index 56b63873d..ad747983a 100644 --- a/radar-commons-android/src/main/java/org/radarbase/android/RadarApplication.kt +++ b/radar-commons-android/src/main/java/org/radarbase/android/RadarApplication.kt @@ -30,11 +30,6 @@ interface RadarApplication { val configuration: RadarConfiguration - /** Large icon bitmap. */ - val largeIcon: Bitmap - /** Small icon drawable resource ID. */ - val smallIcon: Int - val mainActivity: Class val loginActivity: Class 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 bdf691f78..4eac4080c 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 @@ -108,6 +108,7 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis add(POST_NOTIFICATIONS) } } + private lateinit var notificationHandler: NotificationHandler override fun onBind(intent: Intent): IBinder? { super.onBind(intent) @@ -124,6 +125,7 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis override fun onCreate() { super.onCreate() serverStatus = ServerStatusListener.Status.DISABLED + notificationHandler = NotificationHandler(this) binder = createBinder() mHandler = SafeHandler.getInstance("RadarService", THREAD_PRIORITY_BACKGROUND).apply { start() @@ -186,7 +188,7 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis .filter { it.needsBluetooth() } .forEach { it.stopRecording() } - bluetoothNotification = radarApp.notificationHandler.notify( + bluetoothNotification = notificationHandler.notify( BLUETOOTH_NOTIFICATION, NotificationHandler.NOTIFICATION_CHANNEL_ALERT, false @@ -213,7 +215,7 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis protected open fun createForegroundNotification(): Notification { val mainIntent = Intent(this, radarApp.mainActivity) - return radarApp.notificationHandler.create( + return notificationHandler.create( NOTIFICATION_CHANNEL_INFO, true ) { diff --git a/radar-commons-android/src/main/java/org/radarbase/android/util/BluetoothEnforcer.kt b/radar-commons-android/src/main/java/org/radarbase/android/util/BluetoothEnforcer.kt index 4d0763a57..e4b05873b 100644 --- a/radar-commons-android/src/main/java/org/radarbase/android/util/BluetoothEnforcer.kt +++ b/radar-commons-android/src/main/java/org/radarbase/android/util/BluetoothEnforcer.kt @@ -13,6 +13,7 @@ import org.radarbase.android.RadarApplication.Companion.radarConfig import org.radarbase.android.RadarConfiguration.Companion.ENABLE_BLUETOOTH_REQUESTS import org.radarbase.android.RadarService import org.radarbase.android.util.BluetoothStateReceiver.Companion.bluetoothIsEnabled +import org.slf4j.LoggerFactory import java.util.concurrent.TimeUnit class BluetoothEnforcer( @@ -114,10 +115,14 @@ class BluetoothEnforcer( .putLong(LAST_REQUEST, System.currentTimeMillis()) .apply() - context.startActivityForResult(Intent().apply { - action = BluetoothAdapter.ACTION_REQUEST_ENABLE - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - }, REQUEST_ENABLE_BT) + try { + context.startActivityForResult(Intent().apply { + action = BluetoothAdapter.ACTION_REQUEST_ENABLE + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + }, REQUEST_ENABLE_BT) + } catch (ex: SecurityException) { + logger.warn("Cannot request Bluetooth to be enabled - no permission") + } } isRequestingBluetooth = false }, 1000L) @@ -134,5 +139,7 @@ class BluetoothEnforcer( const val REQUEST_ENABLE_BT: Int = 6944 private const val LAST_REQUEST: String = "lastRequest" private const val BLUETOOTH_REQUEST_COOLDOWN = "bluetooth_request_cooldown" + + private val logger = LoggerFactory.getLogger(BluetoothEnforcer::class.java) } } diff --git a/radar-commons-android/src/main/java/org/radarbase/android/util/BluetoothStateReceiver.kt b/radar-commons-android/src/main/java/org/radarbase/android/util/BluetoothStateReceiver.kt index da51f2f8f..d2b8403f8 100644 --- a/radar-commons-android/src/main/java/org/radarbase/android/util/BluetoothStateReceiver.kt +++ b/radar-commons-android/src/main/java/org/radarbase/android/util/BluetoothStateReceiver.kt @@ -1,5 +1,6 @@ package org.radarbase.android.util +import android.Manifest import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter.STATE_CONNECTED import android.bluetooth.BluetoothManager @@ -8,6 +9,9 @@ import android.content.Context import android.content.Context.BLUETOOTH_SERVICE import android.content.Intent import android.content.IntentFilter +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.app.ActivityCompat import org.slf4j.LoggerFactory class BluetoothStateReceiver( @@ -54,7 +58,11 @@ class BluetoothStateReceiver( @Synchronized override fun unregister() { if (isRegistered) { - context.unregisterReceiver(receiver) + try { + context.unregisterReceiver(receiver) + } catch (ex: Exception) { + logger.debug("Failed to unregister BluetoothStateReceiver: {}", ex.toString()) + } isRegistered = false } } @@ -67,5 +75,25 @@ class BluetoothStateReceiver( val Context.bluetoothIsEnabled: Boolean get() = bluetoothAdapter?.isEnabled == true + + val bluetoothPermissionList: List = buildList { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + add(Manifest.permission.BLUETOOTH_SCAN) + add(Manifest.permission.BLUETOOTH_CONNECT) + } else { + add(Manifest.permission.ACCESS_COARSE_LOCATION) + add(Manifest.permission.ACCESS_FINE_LOCATION) + add(Manifest.permission.BLUETOOTH) + add(Manifest.permission.BLUETOOTH_ADMIN) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + add(Manifest.permission.ACCESS_BACKGROUND_LOCATION) + } + } + } + + val Context.hasBluetoothPermission: Boolean + get() = bluetoothPermissionList.all { permission -> + ActivityCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED + } } } diff --git a/radar-commons-android/src/main/java/org/radarbase/android/util/NotificationHandler.kt b/radar-commons-android/src/main/java/org/radarbase/android/util/NotificationHandler.kt index 195c13789..f972c5a10 100644 --- a/radar-commons-android/src/main/java/org/radarbase/android/util/NotificationHandler.kt +++ b/radar-commons-android/src/main/java/org/radarbase/android/util/NotificationHandler.kt @@ -12,20 +12,21 @@ import android.media.AudioAttributes import android.media.RingtoneManager import android.os.Build import androidx.annotation.RequiresApi +import androidx.appcompat.content.res.AppCompatResources.getDrawable +import androidx.core.graphics.drawable.toBitmap import org.radarbase.android.R -import org.radarbase.android.RadarApplication import org.slf4j.LoggerFactory /** * Handle notifications and notification channels. */ -class NotificationHandler(private val context: Context) { - private var isCreated: Boolean = false - +class NotificationHandler( + private val context: Context +) { val manager : NotificationManager? get() = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager? - fun onCreate() { + init { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createNotificationChannels() } @@ -34,12 +35,7 @@ class NotificationHandler(private val context: Context) { @RequiresApi(api = Build.VERSION_CODES.O) private fun createNotificationChannels() { manager?.run { - synchronized(this) { - if (isCreated) { - return - } - isCreated = true - } + if (!shouldCreate()) return logger.debug("Creating notification channels") createNotificationChannel(NOTIFICATION_CHANNEL_INFO, NotificationManager.IMPORTANCE_LOW, @@ -164,10 +160,13 @@ class NotificationHandler(private val context: Context) { } private fun Notification.Builder.updateNotificationAppSettings(includeIntent: Boolean) { - (context.applicationContext as? RadarApplication)?.let { app -> - setLargeIcon(app.largeIcon) - setSmallIcon(app.smallIcon) - } + setLargeIcon( + getDrawable(context, R.mipmap.ic_launcher) + ?.toBitmap() + ) + setSmallIcon( + R.drawable.ic_bt_connected + ) if (includeIntent) { val intent = context.packageManager.getLaunchIntentForPackage(context.packageName) @@ -205,6 +204,18 @@ class NotificationHandler(private val context: Context) { const val NOTIFICATION_CHANNEL_FINAL_ALERT = "org.radarbase.android.NotificationHandler.FINAL_ALERT" private const val NOTIFICATION_REQUEST_CODE = 27581 + + private val syncObject = Any() + private var isCreated = false + + private fun shouldCreate(): Boolean = synchronized(syncObject) { + if (!isCreated) { + isCreated = true + true + } else { + false + } + } } data class NotificationRegistration( diff --git a/radar-commons-android/src/main/java/org/radarbase/android/util/PermissionHandler.kt b/radar-commons-android/src/main/java/org/radarbase/android/util/PermissionHandler.kt index ac3cc695d..517759197 100644 --- a/radar-commons-android/src/main/java/org/radarbase/android/util/PermissionHandler.kt +++ b/radar-commons-android/src/main/java/org/radarbase/android/util/PermissionHandler.kt @@ -13,19 +13,17 @@ import android.content.pm.PackageManager.PERMISSION_DENIED import android.content.pm.PackageManager.PERMISSION_GRANTED import android.location.LocationManager import android.net.Uri -import android.os.Build import android.os.Bundle import android.os.PowerManager import android.os.Process import android.provider.Settings -import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat -import androidx.lifecycle.LifecycleService import androidx.localbroadcastmanager.content.LocalBroadcastManager import org.radarbase.android.R import org.radarbase.android.RadarService +import org.radarbase.android.RadarService.Companion.ACCESS_BACKGROUND_LOCATION_COMPAT import org.slf4j.LoggerFactory @@ -47,7 +45,7 @@ open class PermissionHandler( val result = if (granted) PERMISSION_GRANTED else PERMISSION_DENIED broadcaster.send(RadarService.ACTION_PERMISSIONS_GRANTED) { - putExtra(RadarService.EXTRA_PERMISSIONS, arrayOf(Context.LOCATION_SERVICE)) + putExtra(RadarService.EXTRA_PERMISSIONS, arrayOf(LOCATION_SERVICE)) putExtra(RadarService.EXTRA_GRANT_RESULTS, intArrayOf(result)) } @@ -90,8 +88,24 @@ open class PermissionHandler( when { currentlyNeeded.isEmpty() -> logger.info("Already requested all permissions.") - Context.LOCATION_SERVICE in currentlyNeeded -> { - addRequestingPermissions(Context.LOCATION_SERVICE) + ACCESS_COARSE_LOCATION in currentlyNeeded || ACCESS_FINE_LOCATION in currentlyNeeded -> { + val locationPermissions = buildSet(2) { + if (ACCESS_COARSE_LOCATION in currentlyNeeded) { + add(ACCESS_COARSE_LOCATION) + } + if (ACCESS_FINE_LOCATION in currentlyNeeded) { + add(ACCESS_FINE_LOCATION) + } + } + addRequestingPermissions(locationPermissions) + requestLocationPermissions(locationPermissions) + } + ACCESS_BACKGROUND_LOCATION_COMPAT in currentlyNeeded -> { + addRequestingPermissions(ACCESS_BACKGROUND_LOCATION_COMPAT) + requestBackgroundLocationPermissions() + } + LOCATION_SERVICE in currentlyNeeded -> { + addRequestingPermissions(LOCATION_SERVICE) requestLocationProvider() } SYSTEM_ALERT_WINDOW in currentlyNeeded -> { @@ -108,19 +122,41 @@ open class PermissionHandler( } else -> { addRequestingPermissions(currentlyNeeded) - try { - ActivityCompat.requestPermissions( - activity, - currentlyNeeded.toTypedArray(), - REQUEST_ENABLE_PERMISSIONS, - ) - } catch (ex: IllegalStateException) { - logger.warn("Cannot request permission on closing activity") - } + requestPermissions(currentlyNeeded) + } + } + } + + private fun requestBackgroundLocationPermissions() { + requestPermissions(setOf(ACCESS_BACKGROUND_LOCATION_COMPAT)) + } + + private fun requestLocationPermissions(locationPermissions: Set) { + alertDialog { + setView(R.layout.location_dialog) + setPositiveButton(android.R.string.ok) { dialog, _ -> + dialog.dismiss() + requestPermissions(locationPermissions) + } + setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.cancel() + requestPermissions() } } } + private fun requestPermissions(permissions: Set) { + try { + ActivityCompat.requestPermissions( + activity, + permissions.toTypedArray(), + REQUEST_ENABLE_PERMISSIONS, + ) + } catch (ex: IllegalStateException) { + logger.warn("Cannot request permission on closing activity") + } + } + private fun resetRequestingPermission() { isRequestingPermissions.clear() isRequestingPermissionsTime = java.lang.Long.MAX_VALUE @@ -146,8 +182,8 @@ open class PermissionHandler( try { activity.runOnUiThread { AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert) - .apply(configure) - .show() + .apply(configure) + .show() } } catch (ex: IllegalStateException) { logger.warn("Cannot show dialog on closing activity") @@ -156,24 +192,36 @@ open class PermissionHandler( private fun requestLocationProvider() { alertDialog { - setTitle(R.string.enable_location_title) - setMessage(R.string.enable_location) + setView(R.layout.location_dialog) setPositiveButton(android.R.string.ok) { dialog, _ -> - dialog.cancel() + dialog.dismiss() Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) .startActivityForResult(LOCATION_REQUEST_CODE) } - setIcon(android.R.drawable.ic_dialog_alert) + setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.cancel() + requestPermissions() + } } } private fun requestSystemWindowPermissions() { // Show alert dialog to the user saying a separate permission is needed // Launch the settings activity if the user prefers - Intent( - Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:" + activity.packageName) - ).startActivityForResult(ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE) + alertDialog { + setView(R.layout.system_window_dialog) + setPositiveButton(android.R.string.ok) { dialog, _ -> + dialog.dismiss() + Intent( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + activity.packageName) + ).startActivityForResult(ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE) + } + setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.cancel() + requestPermissions() + } + } } private fun Intent.startActivityForResult(code: Int) { @@ -189,32 +237,44 @@ open class PermissionHandler( @SuppressLint("BatteryLife") private fun requestDisableBatteryOptimization() { - Intent( - Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, - Uri.parse("package:" + activity.packageName) - ).startActivityForResult(BATTERY_OPT_CODE) + alertDialog { + setView(R.layout.disable_battery_optimization_dialog) + setPositiveButton(android.R.string.ok) { dialog, _ -> + dialog.dismiss() + Intent( + Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + Uri.parse("package:" + activity.packageName) + ).startActivityForResult(BATTERY_OPT_CODE) + } + setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.cancel() + requestPermissions() + } + } } private fun requestPackageUsageStats() { alertDialog { - setTitle(R.string.enable_package_usage_title) - setMessage(R.string.enable_package_usage) + setView(R.layout.usage_tracking_dialog) setPositiveButton(android.R.string.ok) { dialog, _ -> - dialog.cancel() + dialog.dismiss() var intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS) if (intent.resolveActivity(activity.packageManager) == null) { intent = Intent(Settings.ACTION_SETTINGS) } intent.startActivityForResult(USAGE_REQUEST_CODE) } - setIcon(android.R.drawable.ic_dialog_alert) + setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.cancel() + requestPermissions() + } } } fun onActivityResult(requestCode: Int, resultCode: Int) { when (requestCode) { LOCATION_REQUEST_CODE -> onPermissionRequestResult( - Context.LOCATION_SERVICE, + LOCATION_SERVICE, resultCode == Activity.RESULT_OK ) USAGE_REQUEST_CODE -> onPermissionRequestResult( @@ -307,7 +367,7 @@ open class PermissionHandler( private const val ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE = 232619697 and 0xFFFF fun Context.isPermissionGranted(permission: String): Boolean = when (permission) { - LOCATION_SERVICE -> applySystemService(Context.LOCATION_SERVICE) { locationManager -> + LOCATION_SERVICE -> applySystemService(LOCATION_SERVICE) { locationManager -> locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) } ?: true diff --git a/radar-commons-android/src/main/res/drawable/baseline_apps_green_700_48dp.xml b/radar-commons-android/src/main/res/drawable/baseline_apps_green_700_48dp.xml new file mode 100644 index 000000000..e9fdb61f4 --- /dev/null +++ b/radar-commons-android/src/main/res/drawable/baseline_apps_green_700_48dp.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/radar-commons-android/src/main/res/drawable/baseline_battery_alert_green_700_48dp.xml b/radar-commons-android/src/main/res/drawable/baseline_battery_alert_green_700_48dp.xml new file mode 100644 index 000000000..dda78069a --- /dev/null +++ b/radar-commons-android/src/main/res/drawable/baseline_battery_alert_green_700_48dp.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/radar-commons-android/src/main/res/drawable/baseline_location_on_green_700_48dp.xml b/radar-commons-android/src/main/res/drawable/baseline_location_on_green_700_48dp.xml new file mode 100644 index 000000000..548eea793 --- /dev/null +++ b/radar-commons-android/src/main/res/drawable/baseline_location_on_green_700_48dp.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/radar-commons-android/src/main/res/drawable/baseline_warning_yellow_700_48dp.xml b/radar-commons-android/src/main/res/drawable/baseline_warning_yellow_700_48dp.xml new file mode 100644 index 000000000..81ed5dcb1 --- /dev/null +++ b/radar-commons-android/src/main/res/drawable/baseline_warning_yellow_700_48dp.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/radar-commons-android/src/main/res/layout/disable_battery_optimization_dialog.xml b/radar-commons-android/src/main/res/layout/disable_battery_optimization_dialog.xml new file mode 100644 index 000000000..7a4b03af2 --- /dev/null +++ b/radar-commons-android/src/main/res/layout/disable_battery_optimization_dialog.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/radar-commons-android/src/main/res/layout/location_dialog.xml b/radar-commons-android/src/main/res/layout/location_dialog.xml new file mode 100644 index 000000000..835227439 --- /dev/null +++ b/radar-commons-android/src/main/res/layout/location_dialog.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/radar-commons-android/src/main/res/layout/system_window_dialog.xml b/radar-commons-android/src/main/res/layout/system_window_dialog.xml new file mode 100644 index 000000000..783add06c --- /dev/null +++ b/radar-commons-android/src/main/res/layout/system_window_dialog.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/radar-commons-android/src/main/res/layout/usage_tracking_dialog.xml b/radar-commons-android/src/main/res/layout/usage_tracking_dialog.xml new file mode 100644 index 000000000..104d76640 --- /dev/null +++ b/radar-commons-android/src/main/res/layout/usage_tracking_dialog.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/radar-commons-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/radar-commons-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..036d09bc5 --- /dev/null +++ b/radar-commons-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/radar-commons-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/radar-commons-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..036d09bc5 --- /dev/null +++ b/radar-commons-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/radar-commons-android/src/main/res/mipmap-hdpi/ic_launcher.png b/radar-commons-android/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..7f0b6f262 Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/radar-commons-android/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/radar-commons-android/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..acbe056db Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/radar-commons-android/src/main/res/mipmap-hdpi/ic_launcher_round.png b/radar-commons-android/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..e4c2043d7 Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/radar-commons-android/src/main/res/mipmap-mdpi/ic_launcher.png b/radar-commons-android/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..8abf26e13 Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/radar-commons-android/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/radar-commons-android/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..dd3acc727 Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/radar-commons-android/src/main/res/mipmap-mdpi/ic_launcher_round.png b/radar-commons-android/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..2d25218a9 Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/radar-commons-android/src/main/res/mipmap-xhdpi/ic_launcher.png b/radar-commons-android/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..13bbbffa6 Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/radar-commons-android/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/radar-commons-android/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..dfb48176d Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/radar-commons-android/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/radar-commons-android/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..b6ac8fbad Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/radar-commons-android/src/main/res/mipmap-xxhdpi/ic_launcher.png b/radar-commons-android/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..bfff54799 Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/radar-commons-android/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/radar-commons-android/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..237c7b3c3 Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/radar-commons-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/radar-commons-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..1552bd9d6 Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/radar-commons-android/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/radar-commons-android/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..6d4112673 Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/radar-commons-android/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/radar-commons-android/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..6355c5fa4 Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/radar-commons-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/radar-commons-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..b0e505172 Binary files /dev/null and b/radar-commons-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/radar-commons-android/src/main/res/values/strings.xml b/radar-commons-android/src/main/res/values/strings.xml index fefb968e3..0736c56b5 100644 --- a/radar-commons-android/src/main/res/values/strings.xml +++ b/radar-commons-android/src/main/res/values/strings.xml @@ -8,9 +8,37 @@ , Failed to login to authorization service, please retry Failed to log in - - Location]]> + + pRMT App uses your \"Location in Background\" + This app collects location data to enable Bluetooth Low Energy and/or location tracking as + part of the study even when the app is closed or not in use. + The researcher(s) only use the collected data in the study you consented to. + + pRMT App uses your \"Location\" + Location information is needed to enable Bluetooth Low Energy and/or location tracking as part of the study. + + Enable \"Usage tracking\" + Enable usage access to keep track of what kind of applications you use. + After click \"OK\", select RADAR pRMT from the list and enable \"Usage access\" + + Enable \"Display over other apps\" + Allow this app to display on top of other apps. + After click \"OK\", select RADAR pRMT from the list and enable \"Display over other apps\". + + + Enable \"No Restrictions\" for background apps + \"No Restrictions for background apps\" means that the battery saver does not restrict app\'s activity. + After click \"OK\", select \"No restrictions\". + + Location information is needed to enable Bluetooth Low Energy and/or location tracking as part of the study. + + Background location is needed to keep passively collecting data while the app is in the background, either for Bluetooth devices or for actual, relative location. + + + + Location]]> Enable usage access to keep track of what kind of applications you use. + Enable location tracking Enable usage tracking