Skip to content

Commit

Permalink
Merge pull request #469 from RADAR-base/target-sdk-34
Browse files Browse the repository at this point in the history
Updated radar-commons-android to Target SDK 34 (Android 14)
  • Loading branch information
yatharthranjan authored Nov 18, 2024
2 parents 6347ede + 9f1cf5a commit 24c6d93
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 16 deletions.
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

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

0 comments on commit 24c6d93

Please sign in to comment.