summaryrefslogtreecommitdiff
path: root/PermissionController/src/com/android/permissioncontroller/permission/service
diff options
context:
space:
mode:
authorJay Thomas Sullivan <jaysullivan@google.com>2023-02-23 20:37:02 -0800
committerJay Thomas Sullivan <jaysullivan@google.com>2023-03-05 23:07:20 -0800
commit5d09f4b5dfbf1e26c94afe85d51f762726e70f0e (patch)
tree1547c745f036e345f297b73b4e951b0fe213374f /PermissionController/src/com/android/permissioncontroller/permission/service
parente5ff09143f44b2b96eec5c461604d117d423218c (diff)
downloadPermission-5d09f4b5dfbf1e26c94afe85d51f762726e70f0e.tar.gz
[Safety Labels] Separate hygiene and notification jobs
Currently both hygiene and notification logic run inside the "runMainJob" function. But, we realized we actually want to run two jobs, on different schedules: 1. The first job is hygiene, which is a one-time job that runs after each boot (as soon as the device becomes idle). 2. The second job is for notifications. This should run periodically every 30 days, and the job schedule should persist across reboots. This job should also only run when the device is idle. The main motivation for this is that the "App Data Sharing" screen also depends on this data, and we need the hygiene logic to run after each boot. The notification job also runs hygiene, which may seem odd, but this is necessary to avoid the edge case where the notification job runs *before* the hygiene job. (This edge case is highly probable on first boot, but extremely unlikely thereafter.) Bug: 268685095 Test: atest PermissionControllerMockingTests:SafetyLabelChangesJobServiceTest Test: atest CtsPermission3TestCases:SafetyLabelChangesJobServiceTest Change-Id: I2f25d73fe13fb69a18d6bafd33cdce6118feaa53
Diffstat (limited to 'PermissionController/src/com/android/permissioncontroller/permission/service')
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/v34/SafetyLabelChangesJobService.kt187
1 files changed, 112 insertions, 75 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/v34/SafetyLabelChangesJobService.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/v34/SafetyLabelChangesJobService.kt
index c15741d56..4291e9f88 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/v34/SafetyLabelChangesJobService.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/v34/SafetyLabelChangesJobService.kt
@@ -42,10 +42,10 @@ import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import com.android.permission.safetylabel.DataCategoryConstants.CATEGORY_LOCATION
import com.android.permission.safetylabel.SafetyLabel as AppMetadataSafetyLabel
-import com.android.permissioncontroller.Constants.PERIODIC_SAFETY_LABEL_CHANGES_JOB_ID
-import com.android.permissioncontroller.Constants.PERMISSION_REMINDER_CHANNEL_ID
-import com.android.permissioncontroller.Constants.SAFETY_LABEL_CHANGES_JOB_ID
+import com.android.permissioncontroller.Constants.SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID
import com.android.permissioncontroller.Constants.SAFETY_LABEL_CHANGES_NOTIFICATION_ID
+import com.android.permissioncontroller.Constants.SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID
+import com.android.permissioncontroller.Constants.PERMISSION_REMINDER_CHANNEL_ID
import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.data.LightInstallSourceInfoLiveData
@@ -63,7 +63,6 @@ import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory
import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.AppInfo
import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.SafetyLabel as SafetyLabelForPersistence
import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistoryPersistence
-import java.io.File
import java.time.Duration
import java.time.Instant
import java.time.ZoneId
@@ -74,6 +73,8 @@ import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
/**
* Runs a monthly job that performs Safety Labels-related tasks. (E.g., data policy changes
@@ -84,7 +85,9 @@ import kotlinx.coroutines.yield
// loops.
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
class SafetyLabelChangesJobService : JobService() {
- private var mainJobTask: Job? = null
+ private val mutex = Mutex()
+ private var detectUpdatesJob: Job? = null
+ private var notificationJob: Job? = null
private val context = this@SafetyLabelChangesJobService
class Receiver : BroadcastReceiver() {
@@ -107,7 +110,8 @@ class SafetyLabelChangesJobService : JobService() {
intent.action != ACTION_SET_UP_SAFETY_LABEL_CHANGES_JOB) {
return
}
- schedulePeriodicJob(receiverContext)
+ scheduleDetectUpdatesJob(receiverContext)
+ schedulePeriodicNotificationJob(receiverContext)
}
private fun isContextInProfileUser(context: Context): Boolean {
@@ -118,38 +122,42 @@ class SafetyLabelChangesJobService : JobService() {
}
/**
- * Called twice each interval, first for the periodic job
- * [PERIODIC_SAFETY_LABEL_CHANGES_JOB_ID], then for the main job [SAFETY_LABEL_CHANGES_JOB_ID].
+ * Called for two different jobs: the detect updates job
+ * [SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID] and the notification job
+ * [SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID].
*/
override fun onStartJob(params: JobParameters): Boolean {
if (DEBUG) {
- Log.d(LOG_TAG, "Starting safety label change job Id: ${params.jobId}")
+ Log.d(LOG_TAG, "onStartJob called for job id: ${params.jobId}")
}
if (!KotlinUtils.isSafetyLabelChangeNotificationsEnabled()) {
- Log.w(LOG_TAG, "onStartJob: Safety label change notifications are not enabled.")
+ Log.w(LOG_TAG, "Not starting job: safety label change notifications are not enabled.")
return false
}
when (params.jobId) {
- PERIODIC_SAFETY_LABEL_CHANGES_JOB_ID -> scheduleMainJobWithDelay()
- SAFETY_LABEL_CHANGES_JOB_ID -> {
- dispatchMainJobTask(params)
+ SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID -> {
+ dispatchDetectUpdatesJob(params)
+ return true
+ }
+ SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID -> {
+ dispatchNotificationJob(params)
return true
}
else -> Log.w(LOG_TAG, "Unexpected job Id: ${params.jobId}")
}
- if (DEBUG) {
- Log.d(LOG_TAG, "safety label change job Id: ${params.jobId} completed.")
- }
return false
}
- private fun dispatchMainJobTask(params: JobParameters) {
- mainJobTask =
+ private fun dispatchDetectUpdatesJob(params: JobParameters) {
+ Log.i(LOG_TAG, "Dispatching detect updates job")
+ detectUpdatesJob =
GlobalScope.launch(Dispatchers.Default) {
try {
- runMainJob()
+ Log.i(LOG_TAG, "Detect updates job started")
+ runDetectUpdatesJob()
+ Log.i(LOG_TAG, "Detect updates job finished successfully")
} catch (e: Throwable) {
- Log.e(LOG_TAG, "Main Job failed", e)
+ Log.e(LOG_TAG, "Detect updates job failed", e)
throw e
} finally {
jobFinished(params, false)
@@ -157,25 +165,35 @@ class SafetyLabelChangesJobService : JobService() {
}
}
- private suspend fun runMainJob() {
- performHygieneOnSafetyLabelHistoryPersistence()
- postSafetyLabelChangedNotification()
+ private fun dispatchNotificationJob(params: JobParameters) {
+ Log.i(LOG_TAG, "Dispatching notification job")
+ notificationJob =
+ GlobalScope.launch(Dispatchers.Default) {
+ try {
+ Log.i(LOG_TAG, "Notification job started")
+ runNotificationJob()
+ Log.i(LOG_TAG, "Notification job finished successfully")
+ } catch (e: Throwable) {
+ Log.e(LOG_TAG, "Notification job failed", e)
+ throw e
+ } finally {
+ jobFinished(params, false)
+ }
+ }
}
- private suspend fun performHygieneOnSafetyLabelHistoryPersistence() {
- val historyFile = AppsSafetyLabelHistoryPersistence.getSafetyLabelHistoryFile(context)
- val safetyLabelsLastUpdatedTimes: Map<AppInfo, Instant> =
- AppsSafetyLabelHistoryPersistence.getSafetyLabelsLastUpdatedTimes(historyFile)
- // Retrieve all installed packages that are not pre-installed on the system and
- // that request the location permission; these are the packages that we care about for the
- // safety labels feature. The variable name does not specify all these filters for brevity.
- val packagesRequestingLocation: Set<Pair<String, UserHandle>> =
- getAllNonPreinstalledPackagesRequestingLocation()
+ private suspend fun runDetectUpdatesJob() {
+ mutex.withLock {
+ recordSafetyLabelsIfMissing()
+ }
+ }
- recordSafetyLabelsIfMissing(
- historyFile, packagesRequestingLocation, safetyLabelsLastUpdatedTimes)
- deleteSafetyLabelsNoLongerNeeded(
- historyFile, packagesRequestingLocation, safetyLabelsLastUpdatedTimes)
+ private suspend fun runNotificationJob() {
+ mutex.withLock {
+ recordSafetyLabelsIfMissing()
+ deleteSafetyLabelsNoLongerNeeded()
+ postSafetyLabelChangedNotification()
+ }
}
/**
@@ -188,11 +206,16 @@ class SafetyLabelChangesJobService : JobService() {
* 2. Update safety labels for apps that are relevant and have persisted safety labels, if we
* identify that we have missed an update for them.
*/
- private suspend fun recordSafetyLabelsIfMissing(
- historyFile: File,
- packagesRequestingLocation: Set<Pair<String, UserHandle>>,
- safetyLabelsLastUpdatedTimes: Map<AppInfo, Instant>
- ) {
+ private suspend fun recordSafetyLabelsIfMissing() {
+ val historyFile = AppsSafetyLabelHistoryPersistence.getSafetyLabelHistoryFile(context)
+ val safetyLabelsLastUpdatedTimes: Map<AppInfo, Instant> =
+ AppsSafetyLabelHistoryPersistence.getSafetyLabelsLastUpdatedTimes(historyFile)
+ // Retrieve all installed packages that are not pre-installed on the system and
+ // that request the location permission; these are the packages that we care about for the
+ // safety labels feature. The variable name does not specify all these filters for brevity.
+ val packagesRequestingLocation: Set<Pair<String, UserHandle>> =
+ getAllNonPreinstalledPackagesRequestingLocation()
+
val safetyLabelsToRecord = mutableSetOf<SafetyLabelForPersistence>()
val packageNamesWithPersistedSafetyLabels =
safetyLabelsLastUpdatedTimes.keys.map { it.packageName }
@@ -309,11 +332,16 @@ class SafetyLabelChangesJobService : JobService() {
* most one safety label is necessary to be persisted prior to the update period to determine
* updates to safety labels.
*/
- private fun deleteSafetyLabelsNoLongerNeeded(
- historyFile: File,
- packagesRequestingLocation: Set<Pair<String, UserHandle>>,
- safetyLabelsLastUpdatedTimes: Map<AppInfo, Instant>
- ) {
+ private suspend fun deleteSafetyLabelsNoLongerNeeded() {
+ val historyFile = AppsSafetyLabelHistoryPersistence.getSafetyLabelHistoryFile(context)
+ val safetyLabelsLastUpdatedTimes: Map<AppInfo, Instant> =
+ AppsSafetyLabelHistoryPersistence.getSafetyLabelsLastUpdatedTimes(historyFile)
+ // Retrieve all installed packages that are not pre-installed on the system and
+ // that request the location permission; these are the packages that we care about for the
+ // safety labels feature. The variable name does not specify all these filters for brevity.
+ val packagesRequestingLocation: Set<Pair<String, UserHandle>> =
+ getAllNonPreinstalledPackagesRequestingLocation()
+
val packageNamesWithPersistedSafetyLabels: List<String> =
safetyLabelsLastUpdatedTimes.keys.map { appInfo -> appInfo.packageName }
val packageNamesWithRelevantSafetyLabels: List<String> =
@@ -370,13 +398,22 @@ class SafetyLabelChangesJobService : JobService() {
override fun onStopJob(params: JobParameters?): Boolean {
if (DEBUG) {
- Log.d(LOG_TAG, "onStopJob: stopping job ${params?.jobId}")
+ Log.d(LOG_TAG, "onStopJob called for job id: ${params?.jobId}")
}
- if (params?.jobId == SAFETY_LABEL_CHANGES_JOB_ID) {
- runBlocking {
- mainJobTask?.cancelAndJoin()
- mainJobTask = null
- }
+ runBlocking {
+ when (params?.jobId) {
+ SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID -> {
+ Log.i(LOG_TAG, "onStopJob: cancelling detect updates job")
+ detectUpdatesJob?.cancelAndJoin()
+ detectUpdatesJob = null
+ }
+ SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID -> {
+ Log.i(LOG_TAG, "onStopJob: cancelling notification job")
+ notificationJob?.cancelAndJoin()
+ notificationJob = null
+ }
+ else -> Log.w(LOG_TAG, "onStopJob: unexpected job Id: ${params?.jobId}")
+ }
}
return true
}
@@ -478,61 +515,61 @@ class SafetyLabelChangesJobService : JobService() {
private const val DATA_SHARING_UPDATE_PERIOD_PROPERTY = "data_sharing_update_period_millis"
private const val DEFAULT_DATA_SHARING_UPDATE_PERIOD_DAYS: Long = 30
- private fun schedulePeriodicJob(context: Context) {
+ private fun scheduleDetectUpdatesJob(context: Context) {
try {
val jobScheduler = getSystemServiceSafe(context, JobScheduler::class.java)
- if (jobScheduler.getPendingJob(PERIODIC_SAFETY_LABEL_CHANGES_JOB_ID) != null) {
- Log.i(LOG_TAG, "Safety label change periodic job already scheduled.")
+
+ if (jobScheduler.getPendingJob(SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID)
+ != null) {
+ Log.i(LOG_TAG, "Not scheduling detect updates job: already scheduled.")
return
}
val job =
JobInfo.Builder(
- PERIODIC_SAFETY_LABEL_CHANGES_JOB_ID,
+ SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID,
ComponentName(context, SafetyLabelChangesJobService::class.java))
- .setPeriodic(KotlinUtils.getSafetyLabelChangesJobIntervalMillis())
- .setPersisted(true)
+ .setRequiresDeviceIdle(
+ KotlinUtils.runSafetyLabelChangesJobOnlyWhenDeviceIdle())
.build()
- val result = jobScheduler.schedule(job)
+ val result = jobScheduler.schedule(job)
if (result != JobScheduler.RESULT_SUCCESS) {
- Log.w(LOG_TAG, "Safety label job not scheduled, result code: $result")
+ Log.w(LOG_TAG, "Detect updates job not scheduled, result code: $result")
} else {
- Log.d(LOG_TAG, "Safety label job scheduled.")
+ Log.i(LOG_TAG, "Detect updates job scheduled successfully.")
}
} catch (e: Throwable) {
- Log.e(LOG_TAG, "Failed to schedule periodic job", e)
+ Log.e(LOG_TAG, "Failed to schedule detect updates job", e)
throw e
}
}
- private fun scheduleMainJobWithDelay() {
+ private fun schedulePeriodicNotificationJob(context: Context) {
try {
- val context = PermissionControllerApplication.get() as Context
val jobScheduler = getSystemServiceSafe(context, JobScheduler::class.java)
-
- if (jobScheduler.getPendingJob(SAFETY_LABEL_CHANGES_JOB_ID) != null) {
- Log.w(LOG_TAG, "safety label change job (one time) already scheduled")
+ if (jobScheduler.getPendingJob(SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID)
+ != null) {
+ Log.i(LOG_TAG, "Not scheduling notification job: already scheduled.")
return
}
val job =
JobInfo.Builder(
- SAFETY_LABEL_CHANGES_JOB_ID,
+ SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID,
ComponentName(context, SafetyLabelChangesJobService::class.java))
- .setPersisted(true)
.setRequiresDeviceIdle(
KotlinUtils.runSafetyLabelChangesJobOnlyWhenDeviceIdle())
- .setMinimumLatency(KotlinUtils.getSafetyLabelChangesJobDelayMillis())
+ .setPeriodic(KotlinUtils.getSafetyLabelChangesJobIntervalMillis())
+ .setPersisted(true)
.build()
val result = jobScheduler.schedule(job)
- if (result == JobScheduler.RESULT_SUCCESS) {
- Log.i(LOG_TAG, "main job scheduled successfully")
+ if (result != JobScheduler.RESULT_SUCCESS) {
+ Log.w(LOG_TAG, "Notification job not scheduled, result code: $result")
} else {
- Log.i(LOG_TAG, "main job not scheduled, result: $result")
+ Log.i(LOG_TAG, "Notification job scheduled successfully.")
}
- Log.i(LOG_TAG, "periodic job running completes.")
} catch (e: Throwable) {
- Log.e(LOG_TAG, "Failed to schedule job", e)
+ Log.e(LOG_TAG, "Failed to schedule notification job", e)
throw e
}
}