Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated radar-commons-android to Target SDK 34 (Android 14) #469

Merged
merged 8 commits into from
Nov 18, 2024
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ allprojects {
version = "$project_version"
group = 'org.radarbase'

ext.versionCode = 52
ext.versionCode = 54
}

subprojects {
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ android.defaults.buildfeatures.buildconfig=true
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

project_version=1.2.5
project_version=1.4.1-SNAPSHOT
Copy link
Member Author

@this-Aditya this-Aditya Oct 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am uncertain about which version to use in this context. Please feel free to make any necessary updates.

I have used the same version from this PR's gradle.properties file


java_version=17
kotlin_version=1.9.23
Expand Down
2 changes: 1 addition & 1 deletion gradle/android.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ android {

defaultConfig {
minSdkVersion 26
targetSdkVersion 33
targetSdkVersion 34
versionCode versionCode
versionName version
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ class GoogleActivityManager(context: GoogleActivityService) : AbstractSourceMana

private fun registerActivityTransitionReceiver() {
val filter = IntentFilter(ACTION_ACTIVITY_UPDATE)
service.registerReceiver(activityTransitionReceiver, filter)
ContextCompat.registerReceiver(service, activityTransitionReceiver, filter,
ContextCompat.RECEIVER_NOT_EXPORTED
)
logger.info("Registered activity transition receiver.")
}

Expand Down Expand Up @@ -117,7 +119,9 @@ class GoogleActivityManager(context: GoogleActivityService) : AbstractSourceMana
}

private fun createActivityPendingIntent(): PendingIntent {
val intent = Intent(ACTION_ACTIVITY_UPDATE)
val intent = Intent(ACTION_ACTIVITY_UPDATE).apply {
`package` = service.packageName
}
logger.info("Activity pending intent created")
return PendingIntent.getBroadcast(service, ACTIVITY_UPDATE_REQUEST_CODE, intent,
PendingIntent.FLAG_CANCEL_CURRENT.toPendingIntentFlag(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ package org.radarbase.passive.google.sleep
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context.RECEIVER_NOT_EXPORTED
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES
import android.os.Process
import androidx.core.content.ContextCompat
import com.google.android.gms.location.ActivityRecognition
Expand Down Expand Up @@ -69,7 +73,9 @@ class GoogleSleepManager(context: GoogleSleepService) : AbstractSourceManager<Go

private fun registerSleepReceiver() {
val filter = IntentFilter(ACTION_SLEEP_DATA)
service.registerReceiver(sleepBroadcastReceiver, filter)
ContextCompat.registerReceiver(service, sleepBroadcastReceiver, filter,
ContextCompat.RECEIVER_NOT_EXPORTED
)
logger.info("registering for the sleep receiver.")
}

Expand Down Expand Up @@ -124,7 +130,9 @@ class GoogleSleepManager(context: GoogleSleepService) : AbstractSourceManager<Go
}

private fun createSleepPendingIntent(): PendingIntent {
val intent = Intent(ACTION_SLEEP_DATA)
val intent = Intent(ACTION_SLEEP_DATA).apply {
`package` = service.packageName
}
logger.info("Sleep pending intent created")
return PendingIntent.getBroadcast(service, SLEEP_DATA_REQUEST_CODE, intent,
PendingIntent.FLAG_CANCEL_CURRENT.toPendingIntentFlag(true))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,24 @@ import android.Manifest.permission.*
import android.app.Notification
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
import android.os.*
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES
import android.os.Build.VERSION_CODES.Q
import android.os.Build.VERSION_CODES.S
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.Process.THREAD_PRIORITY_BACKGROUND
import android.widget.Toast
import androidx.annotation.CallSuper
import androidx.annotation.RequiresApi
import androidx.lifecycle.LifecycleService
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.apache.avro.specific.SpecificRecord
Expand All @@ -53,7 +63,6 @@ import org.radarcns.kafka.ObservationKey
import org.slf4j.LoggerFactory
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.collections.HashSet

abstract class RadarService : LifecycleService(), ServerStatusListener, LoginListener {
private var configurationUpdateFuture: SafeHandler.HandlerFuture? = null
Expand Down Expand Up @@ -101,10 +110,10 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis
protected open val servicePermissions: List<String> = buildList(4) {
add(ACCESS_NETWORK_STATE)
add(INTERNET)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (SDK_INT >= VERSION_CODES.P) {
add(FOREGROUND_SERVICE)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (SDK_INT >= VERSION_CODES.TIRAMISU) {
add(POST_NOTIFICATIONS)
}
}
Expand All @@ -119,6 +128,17 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis

private var bluetoothNotification: NotificationHandler.NotificationRegistration? = null

@RequiresApi(Q)
val fgsHealthPermissions: Set<String> = setOf(BODY_SENSORS, ACTIVITY_RECOGNITION)
@RequiresApi(S)
val fgsConnectDevicePermissions: Set<String> =
setOf(BLUETOOTH_CONNECT, BLUETOOTH_SCAN, BLUETOOTH_ADVERTISE, UWB_RANGING)
private val fgsLocationPermissions: Set<String> =
setOf(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION)
private val fgsMicrophonePermissions: Set<String> =
setOf(RECORD_AUDIO)


/** Defines callbacks for service binding, passed to bindService() */
private lateinit var bluetoothReceiver: BluetoothStateReceiver

Expand Down Expand Up @@ -208,11 +228,65 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis
super.onStartCommand(intent, flags, startId)
configure(configuration.latestConfig)
checkPermissions()
startForeground(1, createForegroundNotification())

if (SDK_INT < UPSIDE_DOWN_CAKE) {
// Below API 34: Start foreground without service types
startForeground(1, createForegroundNotification())
} else {

/**
* API 34+ (Android 14+): Adding DATA_SYNC type
* Currently this is not explicitly checking for android 14+ version.
* This need to be modified it in future when setting new targetSdkVersion
*/
startForeground(1, createForegroundNotification(),
FOREGROUND_SERVICE_TYPE_DATA_SYNC
)
}

return START_STICKY
}

private fun startForegroundIfNeeded(grantedPermissions: Set<String>) {
if (SDK_INT < Q) return

val fgsTypePermissions: MutableSet<Int> = mutableSetOf()

if (SDK_INT >= S) {
if (grantedPermissions.intersect(fgsConnectDevicePermissions).isNotEmpty()) {
fgsTypePermissions.add(FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE)
}
}

if (grantedPermissions.intersect(fgsHealthPermissions)
.isNotEmpty() && (SDK_INT >= UPSIDE_DOWN_CAKE)
) {
fgsTypePermissions.add(FOREGROUND_SERVICE_TYPE_HEALTH)
}

if (grantedPermissions.intersect(fgsLocationPermissions).isNotEmpty()) {
fgsTypePermissions.add(FOREGROUND_SERVICE_TYPE_LOCATION)
}

if (grantedPermissions.intersect(fgsMicrophonePermissions)
.isNotEmpty() && (SDK_INT >= VERSION_CODES.R)
) {
fgsTypePermissions.add(FOREGROUND_SERVICE_TYPE_MICROPHONE)
}

if (fgsTypePermissions.isNotEmpty()) {

fgsTypePermissions.add(FOREGROUND_SERVICE_TYPE_DATA_SYNC)

val combinedFgsType: Int = fgsTypePermissions.reduce { acc, type -> acc or type }

startForeground(
1, createForegroundNotification(),
combinedFgsType
)
}
}

protected open fun createForegroundNotification(): Notification {
val mainIntent = Intent(this, radarApp.mainActivity)
return notificationHandler.create(
Expand Down Expand Up @@ -317,6 +391,7 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis
}

if (grantedPermissions.isNotEmpty()) {
startForegroundIfNeeded(grantedPermissions)
mHandler.execute {
logger.info("Granted permissions {}", grantedPermissions)
// Permission granted.
Expand Down Expand Up @@ -683,7 +758,7 @@ abstract class RadarService : LifecycleService(), ServerStatusListener, LoginLis

private const val BLUETOOTH_NOTIFICATION = 521290

val ACCESS_BACKGROUND_LOCATION_COMPAT = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
val ACCESS_BACKGROUND_LOCATION_COMPAT = if (SDK_INT >= VERSION_CODES.Q)
ACCESS_BACKGROUND_LOCATION else "android.permission.ACCESS_BACKGROUND_LOCATION"

private const val BACKGROUND_REQUEST_CODE = 9559
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,30 @@ import android.os.Build
fun String.takeTrimmedIfNotEmpty(): String? = trim { it <= ' ' }
.takeUnless(String::isEmpty)

/**
* Converts an integer to a PendingIntent flag with appropriate mutability settings.
*
* Android 14 (API level 34) introduces stricter security requirements for `PendingIntents`.
* - By default, `PendingIntents` should be immutable (`FLAG_IMMUTABLE`) unless explicitly required to be mutable.
* - Using the mutable flag without necessity may lead to security vulnerabilities, as mutable `PendingIntents`
* can be modified by other apps if granted.
*
* This function checks the Android version to set flags appropriately:
* - For API level 34 and above (`UPSIDE_DOWN_CAKE`), includes `FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT` to bypass the
* implicit intent restriction if `mutable` is true.
* - For API level 31 (Android 12, `S`) to API level 33, `FLAG_MUTABLE` is used when `mutable` is true.
* - For any other case or if `mutable` is false, the flag defaults to `FLAG_IMMUTABLE`.
*
* @param mutable Determines if the `PendingIntent` needs to be mutable (default: false).
* @return The calculated `PendingIntent` flag with the correct mutability based on API level.
*/
fun Int.toPendingIntentFlag(mutable: Boolean = false) = this or when {
mutable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
mutable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> PendingIntent.FLAG_MUTABLE
!mutable -> PendingIntent.FLAG_IMMUTABLE
else -> 0
}

@Suppress("UNCHECKED_CAST")
inline fun <reified T> Context.applySystemService(type: String, callback: (T) -> Boolean): Boolean? {
return (getSystemService(type) as T?)?.let(callback)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import android.os.Debug
import android.os.PowerManager
import android.os.Process.THREAD_PRIORITY_BACKGROUND
import android.os.SystemClock
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.RECEIVER_EXPORTED
import org.radarbase.util.CountedReference
import org.slf4j.LoggerFactory
import java.io.Closeable
Expand Down Expand Up @@ -79,7 +81,9 @@ class OfflineProcessor(
this.alarmManager = context.getSystemService(ALARM_SERVICE) as AlarmManager

handler = config.handlerReference.acquire()
val intent = Intent(config.requestName)
val intent = Intent(config.requestName).apply {
`package` = context.packageName
}
pendingIntent = PendingIntent.getBroadcast(
context,
requireNotNull(config.requestCode) { "Cannot start processor without request code" },
Expand All @@ -103,7 +107,12 @@ class OfflineProcessor(
}
handler.execute {
didStart = true
context.registerReceiver(this.receiver, IntentFilter(requestName))
ContextCompat.registerReceiver(
context,
this.receiver,
IntentFilter(requestName),
ContextCompat.RECEIVER_NOT_EXPORTED
)
schedule()
initializer?.let { it() }
}
Expand Down
Loading