diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 73952e766..7374f959f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/setup-java@v3 with: - java-version: 11 + java-version: 17 distribution: temurin - uses: gradle/gradle-build-action@v2 diff --git a/.github/workflows/publish_snapshots.yml b/.github/workflows/publish_snapshots.yml index 6e709d460..067c4f87e 100644 --- a/.github/workflows/publish_snapshots.yml +++ b/.github/workflows/publish_snapshots.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: temurin - java-version: 11 + java-version: 17 - uses: gradle/gradle-build-action@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 721b1be07..33f3b0e1c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: temurin - java-version: 11 + java-version: 17 - uses: gradle/gradle-build-action@v2 diff --git a/.github/workflows/scheduled_snyk.yaml b/.github/workflows/scheduled_snyk.yaml index e727787c0..ee5d6ca0b 100644 --- a/.github/workflows/scheduled_snyk.yaml +++ b/.github/workflows/scheduled_snyk.yaml @@ -21,7 +21,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: temurin - java-version: 11 + java-version: 17 - name: Decrypt libraries run: ./.github/scripts/decrypt_libraries.sh diff --git a/.github/workflows/snyk.yaml b/.github/workflows/snyk.yaml index 380521e58..00bde44d6 100644 --- a/.github/workflows/snyk.yaml +++ b/.github/workflows/snyk.yaml @@ -16,7 +16,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: temurin - java-version: 11 + java-version: 17 - name: Decrypt libraries run: ./.github/scripts/decrypt_libraries.sh diff --git a/avro-android/build.gradle b/avro-android/build.gradle index a796e7b57..a1760affd 100644 --- a/avro-android/build.gradle +++ b/avro-android/build.gradle @@ -7,7 +7,7 @@ android { description = "Version of Apache Avro compatible with Android" dependencies { - implementation("org.slf4j:slf4j-api:2.0.6") + implementation("org.slf4j:slf4j-api:2.0.7") testImplementation(project(":radar-commons-android")) { exclude group: 'org.apache.avro', module: 'avro' diff --git a/build.gradle b/build.gradle index 282c22796..afbf2af12 100644 --- a/build.gradle +++ b/build.gradle @@ -14,14 +14,14 @@ * limitations under the License. */ buildscript { - ext.kotlin_version = '1.8.0' - ext.dokka_version = '1.7.20' + ext.kotlin_version = '1.8.21' + ext.dokka_version = '1.8.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.0' + classpath 'com.android.tools.build:gradle:8.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.bjoernq:unmockplugin:0.7.9' classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.18" @@ -30,8 +30,8 @@ buildscript { } plugins { - id("io.github.gradle-nexus.publish-plugin") version "1.1.0" - id("com.github.ben-manes.versions") version "0.44.0" + id("io.github.gradle-nexus.publish-plugin") version "1.3.0" + id("com.github.ben-manes.versions") version "0.46.0" } allprojects { @@ -41,10 +41,10 @@ allprojects { ext.issueUrl = 'https://github.com/' + githubRepoName + '/issues' ext.website = 'http://radar-base.org' - version = "1.2.2" + version = "1.2.3" group = 'org.radarbase' - ext.versionCode = 50 + ext.versionCode = 51 } subprojects { @@ -62,7 +62,7 @@ subprojects { repositories { google() mavenCentral() - maven { url = "https://oss.sonatype.org/content/repositories/snapshots" } +// maven { url = "https://oss.sonatype.org/content/repositories/snapshots" } } dependencies { @@ -91,8 +91,8 @@ subprojects { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { jvmTarget = '11' - apiVersion = '1.7' - languageVersion = '1.7' + apiVersion = '1.8' + languageVersion = '1.8' } } } @@ -119,5 +119,5 @@ nexusPublishing { } wrapper { - gradleVersion '7.6' + gradleVersion '8.1.1' } diff --git a/gradle.properties b/gradle.properties index ddafd443f..641975c6a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,15 +10,15 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m -android.disableAutomaticComponentCreation=true android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx3072m -XX:MaxMetaspaceSize=1536m -Dfile.encoding=UTF-8 -XX:+UseParallelGC org.gradle.parallel=true org.gradle.vfs.watch=true - kotlin.code.style=official +android.defaults.buildfeatures.buildconfig=true + # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects diff --git a/gradle/android.gradle b/gradle/android.gradle index 417295e5e..3d309a312 100644 --- a/gradle/android.gradle +++ b/gradle/android.gradle @@ -1,10 +1,10 @@ android { - compileSdkVersion 32 + compileSdkVersion 33 buildToolsVersion '32.0.0' defaultConfig { - minSdkVersion 21 - targetSdkVersion 32 + minSdkVersion 24 + targetSdkVersion 33 versionCode versionCode versionName version } diff --git a/gradle/test.gradle b/gradle/test.gradle index 08c7bcafa..c5a33ec2b 100644 --- a/gradle/test.gradle +++ b/gradle/test.gradle @@ -18,8 +18,8 @@ ext { dependencies { testImplementation 'junit:junit:4.13.2' - testImplementation 'org.slf4j:slf4j-simple:2.0.6' - testImplementation 'com.squareup.okhttp3:mockwebserver:4.10.0' + testImplementation 'org.slf4j:slf4j-simple:2.0.7' + testImplementation 'com.squareup.okhttp3:mockwebserver:4.11.0' // Core library androidTestImplementation "androidx.test:core:$androidXCoreVersion" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa..c1962a79e 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f398c33c4..37aef8d3f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d6..aeb74cbb4 100755 --- a/gradlew +++ b/gradlew @@ -85,9 +85,6 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -144,7 +141,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +149,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,6 +194,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/plugins/radar-android-application-status/src/main/java/org/radarbase/monitor/application/ApplicationStatusManager.kt b/plugins/radar-android-application-status/src/main/java/org/radarbase/monitor/application/ApplicationStatusManager.kt index 8ce518143..dc5a676d8 100755 --- a/plugins/radar-android-application-status/src/main/java/org/radarbase/monitor/application/ApplicationStatusManager.kt +++ b/plugins/radar-android-application-status/src/main/java/org/radarbase/monitor/application/ApplicationStatusManager.kt @@ -74,9 +74,9 @@ class ApplicationStatusManager( private lateinit var tzOffsetCache: ChangeRunner private lateinit var deviceInfoCache: ChangeRunner - private lateinit var serverStatusReceiver: BroadcastRegistration - private lateinit var serverRecordsReceiver: BroadcastRegistration - private lateinit var cacheReceiver: BroadcastRegistration + private var serverStatusReceiver: BroadcastRegistration? = null + private var serverRecordsReceiver: BroadcastRegistration? = null + private var cacheReceiver: BroadcastRegistration? = null init { name = service.getString(R.string.applicationServiceDisplayName) @@ -266,9 +266,9 @@ class ApplicationStatusManager( override fun onClose() { this.processor.close() - cacheReceiver.unregister() - serverRecordsReceiver.unregister() - serverStatusReceiver.unregister() + cacheReceiver?.unregister() + serverRecordsReceiver?.unregister() + serverStatusReceiver?.unregister() } private fun processTimeZone() { diff --git a/plugins/radar-android-audio/build.gradle b/plugins/radar-android-audio/build.gradle index 9065d0923..fab0c3eb4 100644 --- a/plugins/radar-android-audio/build.gradle +++ b/plugins/radar-android-audio/build.gradle @@ -4,7 +4,7 @@ android { namespace "org.radarbase.passive.audio" // Matches Github Actions - ndkVersion "22.1.7171670" + ndkVersion "25.2.9519653" defaultConfig { ndk { diff --git a/plugins/radar-android-login-qr/build.gradle b/plugins/radar-android-login-qr/build.gradle index d1eefc721..74986901b 100644 --- a/plugins/radar-android-login-qr/build.gradle +++ b/plugins/radar-android-login-qr/build.gradle @@ -15,7 +15,7 @@ description = 'RADAR Android QR LoginManager.' dependencies { api project(':radar-commons-android') implementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false } - implementation 'androidx.appcompat:appcompat:1.6.0' + implementation 'androidx.appcompat:appcompat:1.6.1' // Support Android 14+ //noinspection GradleDependency implementation 'com.google.zxing:core:3.4.0' 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 5e11dbcb5..cefc06ad2 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 @@ -52,11 +52,7 @@ class PhoneUsageManager(context: PhoneUsageService) : AbstractSourceManager= Build.VERSION_CODES.LOLLIPOP_MR1) { - context.getSystemService(Context.USAGE_STATS_SERVICE) as? UsageStatsManager - } else { - null - } + this.usageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as? UsageStatsManager usageEventTopic = if (usageStatsManager != null) { createCache("android_phone_usage_event", PhoneUsageEvent()) 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 114729697..4efc54b42 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 @@ -16,6 +16,7 @@ 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 @@ -39,11 +40,7 @@ class PhoneUsageProvider(radarService: RadarService) : SourceProvider = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - listOf(android.Manifest.permission.PACKAGE_USAGE_STATS) - } else { - emptyList() - } + override val permissionsNeeded: List = listOf(PACKAGE_USAGE_STATS) override val sourceProducer: String = "ANDROID" override val sourceModel: String = "PHONE" diff --git a/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneContactListManager.kt b/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneContactListManager.kt index c310d2877..387cb38de 100644 --- a/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneContactListManager.kt +++ b/plugins/radar-android-phone/src/main/java/org/radarbase/passive/phone/PhoneContactListManager.kt @@ -40,7 +40,7 @@ class PhoneContactListManager(service: PhoneContactsListService) : AbstractSourc private val contactsTopic: DataCache = createCache("android_phone_contacts", PhoneContactList()) private val processor: OfflineProcessor private val db: ContentResolver = service.contentResolver - private lateinit var savedContactLookups: Set + private var savedContactLookups: Set = emptySet() init { name = service.getString(R.string.contact_list) diff --git a/plugins/radar-android-ppg/build.gradle b/plugins/radar-android-ppg/build.gradle index 1e5888d09..fb53dab37 100644 --- a/plugins/radar-android-ppg/build.gradle +++ b/plugins/radar-android-ppg/build.gradle @@ -16,9 +16,9 @@ description = 'Plugin for RADAR passive remote monitoring app for measuring PPG dependencies { api project(':radar-commons-android') - implementation 'androidx.appcompat:appcompat:1.6.0' + implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'com.google.android.material:material:1.7.0' + implementation 'com.google.android.material:material:1.8.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' } diff --git a/plugins/radar-android-ppg/src/main/java/org/radarbase/passive/ppg/RenderContext.kt b/plugins/radar-android-ppg/src/main/java/org/radarbase/passive/ppg/RenderContext.kt index 9ef527272..56c429c69 100644 --- a/plugins/radar-android-ppg/src/main/java/org/radarbase/passive/ppg/RenderContext.kt +++ b/plugins/radar-android-ppg/src/main/java/org/radarbase/passive/ppg/RenderContext.kt @@ -129,10 +129,8 @@ internal class RenderContext(context: Context, dimensions: Size) : Closeable { val RENDER_CONTEXT_RELEASER: CountedReference = CountedReference( creator = {}, destroyer = { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - logger.info("Releasing RenderScript context") - RenderScript.releaseAllContexts() - } + logger.info("Releasing RenderScript context") + RenderScript.releaseAllContexts() }) } } diff --git a/radar-commons-android/build.gradle b/radar-commons-android/build.gradle index 148d05a33..d1b080ec2 100644 --- a/radar-commons-android/build.gradle +++ b/radar-commons-android/build.gradle @@ -37,16 +37,16 @@ dependencies { exclude group: 'org.apache.avro', module: 'avro' } api(project(':avro-android')) - api("org.slf4j:slf4j-api:2.0.6") - api("androidx.appcompat:appcompat:1.6.0") + api("org.slf4j:slf4j-api:2.0.7") + api("androidx.appcompat:appcompat:1.6.1") - implementation "com.squareup.okhttp3:okhttp:4.10.0" + implementation "com.squareup.okhttp3:okhttp:4.11.0" implementation "androidx.localbroadcastmanager:localbroadcastmanager:1.1.0" implementation "androidx.legacy:legacy-support-v4:1.0.0" - api "androidx.lifecycle:lifecycle-service:2.5.1" + api "androidx.lifecycle:lifecycle-service:2.6.1" - implementation platform('com.google.firebase:firebase-bom:31.2.0') + implementation platform('com.google.firebase:firebase-bom:31.5.0') implementation 'com.google.firebase:firebase-analytics' implementation 'com.google.firebase:firebase-config' implementation 'com.google.firebase:firebase-crashlytics' diff --git a/radar-commons-android/src/main/AndroidManifest.xml b/radar-commons-android/src/main/AndroidManifest.xml index 01981abb0..6d533dba0 100644 --- a/radar-commons-android/src/main/AndroidManifest.xml +++ b/radar-commons-android/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + 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 05fb6625b..bdf691f78 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 @@ -98,8 +98,16 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis private val needsPermissions = LinkedHashSet() - protected open val servicePermissions: List - get() = listOf(ACCESS_NETWORK_STATE, INTERNET) + protected open val servicePermissions: List = buildList(4) { + add(ACCESS_NETWORK_STATE) + add(INTERNET) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + add(FOREGROUND_SERVICE) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + add(POST_NOTIFICATIONS) + } + } override fun onBind(intent: Intent): IBinder? { super.onBind(intent) @@ -673,10 +681,6 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis private const val BLUETOOTH_NOTIFICATION = 521290 - val REQUEST_IGNORE_BATTERY_OPTIMIZATIONS_COMPAT = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - REQUEST_IGNORE_BATTERY_OPTIMIZATIONS else "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" - val PACKAGE_USAGE_STATS_COMPAT = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - PACKAGE_USAGE_STATS else "android.permission.PACKAGE_USAGE_STATS" val ACCESS_BACKGROUND_LOCATION_COMPAT = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ACCESS_BACKGROUND_LOCATION else "android.permission.ACCESS_BACKGROUND_LOCATION" diff --git a/radar-commons-android/src/main/java/org/radarbase/android/util/AndroidKeyStore.kt b/radar-commons-android/src/main/java/org/radarbase/android/util/AndroidKeyStore.kt index e79f0c320..9788c9524 100644 --- a/radar-commons-android/src/main/java/org/radarbase/android/util/AndroidKeyStore.kt +++ b/radar-commons-android/src/main/java/org/radarbase/android/util/AndroidKeyStore.kt @@ -1,13 +1,10 @@ package org.radarbase.android.util -import android.os.Build import android.security.keystore.KeyGenParameterSpec -import androidx.annotation.RequiresApi import java.security.Key import java.security.KeyStore import javax.crypto.KeyGenerator -@RequiresApi(Build.VERSION_CODES.M) object AndroidKeyStore { fun getOrCreateSecretKey(name: String, algorithm: String, purposes: Int, generatorBuilder: KeyGenParameterSpec.Builder.() -> Unit = {}): Key { val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE) diff --git a/radar-commons-android/src/main/java/org/radarbase/android/util/BundleSerialization.kt b/radar-commons-android/src/main/java/org/radarbase/android/util/BundleSerialization.kt index 3d53d7142..1c23a7c41 100644 --- a/radar-commons-android/src/main/java/org/radarbase/android/util/BundleSerialization.kt +++ b/radar-commons-android/src/main/java/org/radarbase/android/util/BundleSerialization.kt @@ -29,11 +29,6 @@ import java.io.IOException object BundleSerialization { private val logger = LoggerFactory.getLogger(BundleSerialization::class.java) - fun bundleToString(bundle: Bundle): String { - return bundle.keySet() - .joinToString(prefix = "{", postfix = "}") { "$it: ${bundle.get(it)}" } - } - fun getPersistentExtras(intent: Intent?, context: Context): Bundle? { val prefs = context.getSharedPreferences(context.javaClass.name, Context.MODE_PRIVATE) val bundle: Bundle? = intent?.extras?.also { saveToPreferences(prefs, it) } diff --git a/radar-commons-android/src/main/java/org/radarbase/android/util/Extensions.kt b/radar-commons-android/src/main/java/org/radarbase/android/util/Extensions.kt index 4fc51f4ed..af211ca7a 100644 --- a/radar-commons-android/src/main/java/org/radarbase/android/util/Extensions.kt +++ b/radar-commons-android/src/main/java/org/radarbase/android/util/Extensions.kt @@ -9,7 +9,7 @@ fun String.takeTrimmedIfNotEmpty(): String? = trim { it <= ' ' } fun Int.toPendingIntentFlag(mutable: Boolean = false) = this or when { mutable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> PendingIntent.FLAG_MUTABLE - !mutable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> PendingIntent.FLAG_IMMUTABLE + !mutable -> PendingIntent.FLAG_IMMUTABLE else -> 0 } diff --git a/radar-commons-android/src/main/java/org/radarbase/android/util/HashGenerator.kt b/radar-commons-android/src/main/java/org/radarbase/android/util/HashGenerator.kt index 63c9dbcf1..5ca329cbc 100644 --- a/radar-commons-android/src/main/java/org/radarbase/android/util/HashGenerator.kt +++ b/radar-commons-android/src/main/java/org/radarbase/android/util/HashGenerator.kt @@ -63,17 +63,10 @@ class HashGenerator( private fun loadKey(): Key { val b64Salt = preferences.getString(HASH_KEY, null) - return when { - b64Salt != null -> SecretKeySpec(Base64.decode(b64Salt, Base64.NO_WRAP), HMAC_SHA256) - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> AndroidKeyStore.getOrCreateSecretKey("$name.$HASH_KEY", HMAC_SHA256, PURPOSE_SIGN) - else -> { - val byteSalt = ByteArray(16) - SecureRandom().nextBytes(byteSalt) - preferences.edit() - .putString(HASH_KEY, Base64.encodeToString(byteSalt, Base64.NO_WRAP)) - .apply() - SecretKeySpec(byteSalt, HMAC_SHA256) - } + return if (b64Salt != null) { + SecretKeySpec(Base64.decode(b64Salt, Base64.NO_WRAP), HMAC_SHA256) + } else { + AndroidKeyStore.getOrCreateSecretKey("$name.$HASH_KEY", HMAC_SHA256, PURPOSE_SIGN) } } diff --git a/radar-commons-android/src/main/java/org/radarbase/android/util/NetworkConnectedReceiver.kt b/radar-commons-android/src/main/java/org/radarbase/android/util/NetworkConnectedReceiver.kt index 08d847679..c0690dea7 100644 --- a/radar-commons-android/src/main/java/org/radarbase/android/util/NetworkConnectedReceiver.kt +++ b/radar-commons-android/src/main/java/org/radarbase/android/util/NetworkConnectedReceiver.kt @@ -16,48 +16,41 @@ package org.radarbase.android.util -import android.content.BroadcastReceiver import android.content.Context -import android.content.Intent -import android.content.IntentFilter import android.net.ConnectivityManager -import android.net.ConnectivityManager.* +import android.net.ConnectivityManager.NetworkCallback import android.net.Network import android.net.NetworkCapabilities -import android.os.Build -import androidx.annotation.RequiresApi import org.slf4j.LoggerFactory /** * Keeps track of whether there is a network connection (e.g., WiFi or Ethernet). */ -class NetworkConnectedReceiver(private val context: Context, private val listener: ((NetworkState) -> Unit)? = null) : SpecificReceiver { +class NetworkConnectedReceiver(context: Context, private val listener: ((NetworkState) -> Unit)? = null) : SpecificReceiver { private val connectivityManager: ConnectivityManager? = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? private var isReceiverRegistered: Boolean = false - private var receiver: BroadcastReceiver? = null - private val callback: NetworkCallback by lazy { - object : NetworkCallback() { - override fun onAvailable(network: Network) { - state = NetworkState(isConnected = true, hasWifiOrEthernet = state.hasWifiOrEthernet) - } - - override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities) { - state = NetworkState(state.isConnected, capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) - } - - override fun onUnavailable() { - // nothing happened - } - - override fun onLost(network: Network) { - state = NetworkState(isConnected = false, hasWifiOrEthernet = false) - } + private val callback = object : NetworkCallback() { + override fun onAvailable(network: Network) { + state = NetworkState(isConnected = true, hasWifiOrEthernet = state.hasWifiOrEthernet) + } + + override fun onCapabilitiesChanged(network: Network, capabilities: NetworkCapabilities) { + state = NetworkState(state.isConnected, capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) + } + + override fun onUnavailable() { + // nothing happened + } + + override fun onLost(network: Network) { + state = NetworkState(isConnected = false, hasWifiOrEthernet = false) } } constructor(context: Context, listener: NetworkConnectedListener) : this(context, listener::onNetworkConnectionChanged) private val _state = ChangeRunner(NetworkState(isConnected = false, hasWifiOrEthernet = false)) + var state: NetworkState get() = _state.value private set(value) { @@ -67,41 +60,17 @@ class NetworkConnectedReceiver(private val context: Context, private val listene fun hasConnection(wifiOrEthernetOnly: Boolean) = state.hasConnection(wifiOrEthernetOnly) override fun register() { - if (connectivityManager == null) { + connectivityManager ?: run { logger.warn("Connectivity cannot be checked: System ConnectivityManager is unavailable.") return } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - registerCallback(connectivityManager) - } else { - registerBroadcastReceiver(connectivityManager) - } + registerCallback(connectivityManager) } override fun notifyListener() { - listener?.let { it(state) } + listener?.invoke(state) } - @Suppress("DEPRECATION") - private fun registerBroadcastReceiver(cm: ConnectivityManager) { - val localReceiver = receiver ?: object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent?) { - if (intent?.action == CONNECTIVITY_ACTION) { - state = cm.activeNetworkInfo?.let { - val networkType = it.type - NetworkState(it.isConnected, networkType == TYPE_WIFI || networkType == TYPE_ETHERNET) - } ?: NetworkState(isConnected = false, hasWifiOrEthernet = false) - - } - } - }.also { receiver = it } - - val init = context.registerReceiver(localReceiver, IntentFilter(CONNECTIVITY_ACTION)) - isReceiverRegistered = true - localReceiver.onReceive(context, init) - } - - @RequiresApi(Build.VERSION_CODES.N) private fun registerCallback(cm: ConnectivityManager) { val network = cm.activeNetwork val networkInfo = network?.let { cm.getNetworkInfo(it) } @@ -122,18 +91,17 @@ class NetworkConnectedReceiver(private val context: Context, private val listene if (!isReceiverRegistered) { return } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + try { connectivityManager?.unregisterNetworkCallback(callback) - } else { - context.unregisterReceiver(receiver) + } catch (ex: Exception) { + logger.debug("Skipping unregistered receiver: {}", ex.toString()) } isReceiverRegistered = false } data class NetworkState(val isConnected: Boolean, val hasWifiOrEthernet: Boolean) { - fun hasConnection(wifiOrEthernetOnly: Boolean): Boolean { - return isConnected && (hasWifiOrEthernet || !wifiOrEthernetOnly) - } + fun hasConnection(wifiOrEthernetOnly: Boolean): Boolean = + isConnected && (hasWifiOrEthernet || !wifiOrEthernetOnly) } interface NetworkConnectedListener { 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 9a9db3f76..ac3cc695d 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 @@ -7,6 +7,7 @@ import android.app.AlertDialog import android.app.AppOpsManager import android.content.ActivityNotFoundException import android.content.Context +import android.content.Context.LOCATION_SERVICE import android.content.Intent import android.content.pm.PackageManager.PERMISSION_DENIED import android.content.pm.PackageManager.PERMISSION_GRANTED @@ -66,7 +67,7 @@ open class PermissionHandler( } private fun doRequestPermission() { - val externallyGrantedPermissions = needsPermissions.filter { activity.isPermissionGranted(it) } + val externallyGrantedPermissions = needsPermissions.filterTo(HashSet()) { activity.isPermissionGranted(it) } if (externallyGrantedPermissions.isNotEmpty()) { broadcaster.send(RadarService.ACTION_PERMISSIONS_GRANTED) { @@ -79,13 +80,11 @@ open class PermissionHandler( needsPermissions -= externallyGrantedPermissions } - val currentlyNeeded: Set = HashSet(needsPermissions).apply { - this -= isRequestingPermissions - if ( - ACCESS_COARSE_LOCATION in this - || ACCESS_FINE_LOCATION in this - ) { - this -= RadarService.ACCESS_BACKGROUND_LOCATION_COMPAT + val currentlyNeeded = buildSet(needsPermissions.size) { + addAll(needsPermissions) + removeAll(isRequestingPermissions) + if (contains(ACCESS_COARSE_LOCATION) || contains(ACCESS_FINE_LOCATION)) { + remove(RadarService.ACCESS_BACKGROUND_LOCATION_COMPAT) } } @@ -96,22 +95,16 @@ open class PermissionHandler( requestLocationProvider() } SYSTEM_ALERT_WINDOW in currentlyNeeded -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - addRequestingPermissions(SYSTEM_ALERT_WINDOW) - requestSystemWindowPermissions() - } + addRequestingPermissions(SYSTEM_ALERT_WINDOW) + requestSystemWindowPermissions() } - RadarService.PACKAGE_USAGE_STATS_COMPAT in currentlyNeeded -> { - addRequestingPermissions(RadarService.PACKAGE_USAGE_STATS_COMPAT) + PACKAGE_USAGE_STATS in currentlyNeeded -> { + addRequestingPermissions(PACKAGE_USAGE_STATS) requestPackageUsageStats() } - RadarService.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS_COMPAT in currentlyNeeded -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - addRequestingPermissions(RadarService.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS_COMPAT) - requestDisableBatteryOptimization() - } else { - needsPermissions.remove(RadarService.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS_COMPAT) - } + REQUEST_IGNORE_BATTERY_OPTIMIZATIONS in currentlyNeeded -> { + addRequestingPermissions(REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) + requestDisableBatteryOptimization() } else -> { addRequestingPermissions(currentlyNeeded) @@ -174,7 +167,6 @@ open class PermissionHandler( } } - @RequiresApi(api = Build.VERSION_CODES.M) private fun requestSystemWindowPermissions() { // Show alert dialog to the user saying a separate permission is needed // Launch the settings activity if the user prefers @@ -195,7 +187,6 @@ open class PermissionHandler( } } - @RequiresApi(api = Build.VERSION_CODES.M) @SuppressLint("BatteryLife") private fun requestDisableBatteryOptimization() { Intent( @@ -227,20 +218,18 @@ open class PermissionHandler( resultCode == Activity.RESULT_OK ) USAGE_REQUEST_CODE -> onPermissionRequestResult( - RadarService.PACKAGE_USAGE_STATS_COMPAT, + PACKAGE_USAGE_STATS, resultCode == Activity.RESULT_OK ) BATTERY_OPT_CODE -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val powerManager = - activity.getSystemService(Context.POWER_SERVICE) as PowerManager? - val granted = resultCode == Activity.RESULT_OK - || powerManager?.isIgnoringBatteryOptimizations(activity.applicationContext.packageName) != false - onPermissionRequestResult( - RadarService.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS_COMPAT, - granted - ) - } + val powerManager = + activity.getSystemService(Context.POWER_SERVICE) as PowerManager? + val granted = resultCode == Activity.RESULT_OK + || powerManager?.isIgnoringBatteryOptimizations(activity.applicationContext.packageName) != false + onPermissionRequestResult( + REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + granted + ) } ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE -> onPermissionRequestResult( SYSTEM_ALERT_WINDOW, @@ -268,18 +257,17 @@ open class PermissionHandler( mHandler.execute { needsPermissions.clear() - needsPermissions += ArrayList(newPermissions.size + 1).apply { - this += newPermissions - if (ACCESS_FINE_LOCATION in this || ACCESS_COARSE_LOCATION in this) { - this += LifecycleService.LOCATION_SERVICE + needsPermissions += buildList(newPermissions.size + 1) { + addAll(newPermissions) + if (contains(ACCESS_FINE_LOCATION) || contains(ACCESS_COARSE_LOCATION)) { + add(LOCATION_SERVICE) } - }.filterNot { activity.isPermissionGranted(it) } + }.filter { it.isNotEmpty() && !activity.isPermissionGranted(it) } requestPermissions() } } - fun permissionsGranted(requestCode: Int, permissions: Array, grantResults: IntArray) { if (requestCode == REQUEST_ENABLE_PERMISSIONS) { broadcaster.send(RadarService.ACTION_PERMISSIONS_GRANTED) { @@ -319,20 +307,18 @@ open class PermissionHandler( private const val ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE = 232619697 and 0xFFFF fun Context.isPermissionGranted(permission: String): Boolean = when (permission) { - LifecycleService.LOCATION_SERVICE -> applySystemService(Context.LOCATION_SERVICE) { locationManager -> + LOCATION_SERVICE -> applySystemService(Context.LOCATION_SERVICE) { locationManager -> locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) } ?: true - RadarService.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS_COMPAT -> applySystemService(Context.POWER_SERVICE) { powerManager -> - Build.VERSION.SDK_INT < Build.VERSION_CODES.M - || powerManager.isIgnoringBatteryOptimizations(applicationContext.packageName) + REQUEST_IGNORE_BATTERY_OPTIMIZATIONS -> applySystemService(Context.POWER_SERVICE) { powerManager -> + powerManager.isIgnoringBatteryOptimizations(applicationContext.packageName) } ?: true - RadarService.PACKAGE_USAGE_STATS_COMPAT -> applySystemService(Context.APP_OPS_SERVICE) { appOps -> + PACKAGE_USAGE_STATS -> applySystemService(Context.APP_OPS_SERVICE) { appOps -> @Suppress("DEPRECATION") - Build.VERSION.SDK_INT < Build.VERSION_CODES.M - || AppOpsManager.MODE_ALLOWED == appOps.checkOpNoThrow("android:get_usage_stats", Process.myUid(), packageName) + (AppOpsManager.MODE_ALLOWED == appOps.checkOpNoThrow("android:get_usage_stats", Process.myUid(), packageName)) } ?: true - SYSTEM_ALERT_WINDOW -> Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(this) + SYSTEM_ALERT_WINDOW -> Settings.canDrawOverlays(this) else -> PERMISSION_GRANTED == ContextCompat.checkSelfPermission(this, permission) } } diff --git a/radar-commons-android/src/main/java/org/radarbase/android/util/SafeHandler.kt b/radar-commons-android/src/main/java/org/radarbase/android/util/SafeHandler.kt index 76a536c5b..f6719582d 100644 --- a/radar-commons-android/src/main/java/org/radarbase/android/util/SafeHandler.kt +++ b/radar-commons-android/src/main/java/org/radarbase/android/util/SafeHandler.kt @@ -2,6 +2,7 @@ package org.radarbase.android.util import android.os.Handler import android.os.HandlerThread +import android.os.Looper import androidx.annotation.Keep import org.radarbase.android.util.SafeHandler.Companion.getInstance import org.slf4j.LoggerFactory @@ -15,7 +16,10 @@ import java.util.concurrent.SynchronousQueue * @constructor consider using [getInstance] instead for shared or reinitializing handlers. */ @Suppress("unused", "MemberVisibilityCanBePrivate") -class SafeHandler(val name: String, private val priority: Int) { +class SafeHandler( + val name: String, + private val priority: Int, +) { private var handlerThread: HandlerThread? = null /** Whether the handler has been started. */ @@ -277,7 +281,10 @@ class SafeHandler(val name: String, private val priority: Int) { * being moved to another thread. */ @Synchronized - fun getInstance(name: String, priority: Int): SafeHandler { + fun getInstance( + name: String, + priority: Int, + ): SafeHandler { val handlerRef = map[name]?.get() return handlerRef ?: run {