summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jay Sullivan <jaysullivan@google.com> 2023-04-02 05:45:25 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-04-02 05:45:25 +0000
commit07b340eded0f79876997582e2791ca5d38b19e00 (patch)
tree80488d0788b932a49ebcda0ac25444de2bbfd0db
parent179f43765661fa8a17f7de6758f158d5e9a4a20b (diff)
parentf967d540fdddb552337aa61f06b81f7c8374b829 (diff)
Merge "[Safety Labels] Log metrics" into udc-dev
-rw-r--r--PermissionController/AndroidManifest.xml5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/v34/SafetyLabelChangesJobService.kt284
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFragment.kt101
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/AppDataSharingUpdatesViewModel.kt14
4 files changed, 318 insertions, 86 deletions
diff --git a/PermissionController/AndroidManifest.xml b/PermissionController/AndroidManifest.xml
index 627b9c224..b85e5ebdc 100644
--- a/PermissionController/AndroidManifest.xml
+++ b/PermissionController/AndroidManifest.xml
@@ -209,6 +209,11 @@
</receiver>
<receiver
+ android:name="com.android.permissioncontroller.permission.service.v34.SafetyLabelChangesJobService$NotificationDeleteHandler"
+ android:enabled="@bool/is_at_least_u">
+ </receiver>
+
+ <receiver
android:name="com.android.permissioncontroller.privacysources.AccessibilityOnBootReceiver"
android:enabled="@bool/is_at_least_t"
android:exported="true">
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 edd1be00f..2cbc43f1f 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/v34/SafetyLabelChangesJobService.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/v34/SafetyLabelChangesJobService.kt
@@ -43,11 +43,17 @@ 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.EXTRA_SESSION_ID
+import com.android.permissioncontroller.Constants.INVALID_SESSION_ID
+import com.android.permissioncontroller.Constants.PERMISSION_REMINDER_CHANNEL_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.PermissionControllerStatsLog
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION__ACTION__DISMISSED
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION__ACTION__NOTIFICATION_SHOWN
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.data.LightInstallSourceInfoLiveData
import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
@@ -67,15 +73,15 @@ import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistoryPersis
import java.time.Duration
import java.time.Instant
import java.time.ZoneId
+import java.util.Random
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
-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
+import kotlinx.coroutines.yield
/**
* Runs a monthly job that performs Safety Labels-related tasks. (E.g., data policy changes
@@ -84,12 +90,14 @@ import kotlinx.coroutines.sync.withLock
// TODO(b/265202443): Review support for safe cancellation of this Job. Currently this is
// implemented by implementing `onStopJob` method and including `yield()` calls in computation
// loops.
+// TODO(b/276511043): Refactor this class into separate components
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
class SafetyLabelChangesJobService : JobService() {
private val mutex = Mutex()
private var detectUpdatesJob: Job? = null
private var notificationJob: Job? = null
private val context = this@SafetyLabelChangesJobService
+ private val random = Random()
class Receiver : BroadcastReceiver() {
override fun onReceive(receiverContext: Context, intent: Intent) {
@@ -108,11 +116,14 @@ class SafetyLabelChangesJobService : JobService() {
Log.i(
LOG_TAG,
"onReceive: Received broadcast in profile, not scheduling safety label" +
- " change job")
+ " change job"
+ )
return
}
- if (intent.action != ACTION_BOOT_COMPLETED &&
- intent.action != ACTION_SET_UP_SAFETY_LABEL_CHANGES_JOB) {
+ if (
+ intent.action != ACTION_BOOT_COMPLETED &&
+ intent.action != ACTION_SET_UP_SAFETY_LABEL_CHANGES_JOB
+ ) {
return
}
scheduleDetectUpdatesJob(receiverContext)
@@ -125,6 +136,28 @@ class SafetyLabelChangesJobService : JobService() {
}
}
+ /** Handle the case where the notification is swiped away without further interaction. */
+ class NotificationDeleteHandler : BroadcastReceiver() {
+ override fun onReceive(receiverContext: Context, intent: Intent) {
+ Log.d(LOG_TAG, "NotificationDeleteHandler: received broadcast")
+ if (!KotlinUtils.isSafetyLabelChangeNotificationsEnabled()) {
+ Log.i(
+ LOG_TAG,
+ "NotificationDeleteHandler: " +
+ "safety label change notifications are not enabled."
+ )
+ return
+ }
+ val sessionId = intent.getLongExtra(EXTRA_SESSION_ID, INVALID_SESSION_ID)
+ val numberOfAppUpdates = intent.getIntExtra(EXTRA_NUMBER_OF_APP_UPDATES, 0)
+ logAppDataSharingUpdatesNotificationInteraction(
+ sessionId,
+ APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION__ACTION__DISMISSED,
+ numberOfAppUpdates
+ )
+ }
+ }
+
/**
* Called for two different jobs: the detect updates job
* [SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID] and the notification job
@@ -191,9 +224,7 @@ class SafetyLabelChangesJobService : JobService() {
}
private suspend fun runDetectUpdatesJob() {
- mutex.withLock {
- recordSafetyLabelsIfMissing()
- }
+ mutex.withLock { recordSafetyLabelsIfMissing() }
}
private suspend fun runNotificationJob() {
@@ -239,11 +270,13 @@ class SafetyLabelChangesJobService : JobService() {
"recording safety labels if missing:" +
" packagesRequestingLocation:" +
" $packagesRequestingLocation, packageNamesWithPersistedSafetyLabels:" +
- " $packageNamesWithPersistedSafetyLabels")
+ " $packageNamesWithPersistedSafetyLabels"
+ )
}
safetyLabelsToRecord.addAll(getSafetyLabels(packagesToInitialize))
safetyLabelsToRecord.addAll(
- getSafetyLabelsIfUpdatesMissed(packagesToConsiderUpdate, safetyLabelsLastUpdatedTimes))
+ getSafetyLabelsIfUpdatesMissed(packagesToConsiderUpdate, safetyLabelsLastUpdatedTimes)
+ )
AppsSafetyLabelHistoryPersistence.recordSafetyLabels(safetyLabelsToRecord, historyFile)
}
@@ -325,11 +358,15 @@ class SafetyLabelChangesJobService : JobService() {
AppMetadataSafetyLabel.getSafetyLabelFromMetadata(appMetadataBundle) ?: return null
val lastUpdateTime =
Instant.ofEpochMilli(
- LightPackageInfoLiveData[packageKey].getInitializedValue()?.lastUpdateTime ?: 0)
+ LightPackageInfoLiveData[packageKey].getInitializedValue()?.lastUpdateTime ?: 0
+ )
val safetyLabelForPersistence: SafetyLabelForPersistence =
AppsSafetyLabelHistory.SafetyLabel.extractLocationSharingSafetyLabel(
- packageName, lastUpdateTime, appMetadataSafetyLabel)
+ packageName,
+ lastUpdateTime,
+ appMetadataSafetyLabel
+ )
return safetyLabelForPersistence
}
@@ -370,10 +407,12 @@ class SafetyLabelChangesJobService : JobService() {
DeviceConfig.getLong(
DeviceConfig.NAMESPACE_PRIVACY,
DATA_SHARING_UPDATE_PERIOD_PROPERTY,
- Duration.ofDays(DEFAULT_DATA_SHARING_UPDATE_PERIOD_DAYS).toMillis())
+ Duration.ofDays(DEFAULT_DATA_SHARING_UPDATE_PERIOD_DAYS).toMillis()
+ )
AppsSafetyLabelHistoryPersistence.deleteSafetyLabelsOlderThan(
Instant.now().atZone(ZoneId.systemDefault()).toInstant().minusMillis(updatePeriod),
- historyFile)
+ historyFile
+ )
}
// TODO(b/261607291): Modify this logic when we enable safety label change notifications for
@@ -400,10 +439,12 @@ class SafetyLabelChangesJobService : JobService() {
LightInstallSourceInfoLiveData[pkg].getInitializedValue().initiatingPackageName == null
private suspend fun postSafetyLabelChangedNotification() {
- if (hasDataSharingChanged()) {
+ val numberOfAppUpdates = getNumberOfAppsWithDataSharingChanged()
+ if (numberOfAppUpdates > 0) {
Log.i(LOG_TAG, "Showing notification: data sharing has changed")
- showNotification()
+ showNotification(numberOfAppUpdates)
} else {
+ cancelNotification()
Log.i(LOG_TAG, "Not showing notification: data sharing has not changed")
}
}
@@ -413,51 +454,73 @@ class SafetyLabelChangesJobService : JobService() {
Log.d(LOG_TAG, "onStopJob called for job id: ${params?.jobId}")
}
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}")
- }
+ when (params?.jobId) {
+ SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID -> {
+ Log.i(LOG_TAG, "onStopJob: cancelling detect updates job")
+ detectUpdatesJob?.cancel()
+ detectUpdatesJob = null
+ }
+ SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID -> {
+ Log.i(LOG_TAG, "onStopJob: cancelling notification job")
+ notificationJob?.cancel()
+ notificationJob = null
+ }
+ else -> Log.w(LOG_TAG, "onStopJob: unexpected job Id: ${params?.jobId}")
+ }
}
return true
}
- private suspend fun hasDataSharingChanged(): Boolean {
+ /**
+ * Count the number of packages that have location granted and have location sharing updates.
+ */
+ private suspend fun getNumberOfAppsWithDataSharingChanged(): Int {
val appDataSharingUpdates =
AppDataSharingUpdatesLiveData(PermissionControllerApplication.get())
.getInitializedValue()
- val packageNamesWithLocationDataSharingUpdates: List<String> =
- appDataSharingUpdates
- .filter { it.containsLocationCategoryUpdate() }
- .map { it.packageName }
- val packageNamesWithLocationGranted: List<String> =
- getAllPackagesGrantedLocation().map { (packageName, _) -> packageName }
-
- val packageNamesWithLocationGrantedAndUpdates =
- packageNamesWithLocationDataSharingUpdates.intersect(packageNamesWithLocationGranted)
- if (DEBUG) {
- Log.i(
- LOG_TAG,
- "Checking whether data sharing has changed. Packages with location" +
- " updates: $packageNamesWithLocationDataSharingUpdates; Packages with" +
- " location permission granted: $packageNamesWithLocationGranted")
- }
- return packageNamesWithLocationGrantedAndUpdates.isNotEmpty()
+ return appDataSharingUpdates
+ .map { appDataSharingUpdate ->
+ val locationDataSharingUpdate =
+ appDataSharingUpdate.categorySharingUpdates[CATEGORY_LOCATION]
+
+ if (locationDataSharingUpdate == null) {
+ emptyList()
+ } else {
+ val users =
+ SinglePermGroupPackagesUiInfoLiveData[Manifest.permission_group.LOCATION]
+ .getUsersWithPermGrantedForApp(appDataSharingUpdate.packageName)
+ users
+ }
+ }
+ .flatten()
+ .count()
+ }
+
+ private fun SinglePermGroupPackagesUiInfoLiveData.getUsersWithPermGrantedForApp(
+ packageName: String
+ ): List<UserHandle> {
+ return value
+ ?.filter {
+ packageToPermInfoEntry: Map.Entry<Pair<String, UserHandle>, AppPermGroupUiInfo> ->
+ val appPermGroupUiInfo = packageToPermInfoEntry.value
+
+ appPermGroupUiInfo.isPermissionGranted()
+ }
+ ?.keys
+ ?.filter { packageUser: Pair<String, UserHandle> -> packageUser.first == packageName }
+ ?.map { packageUser: Pair<String, UserHandle> -> packageUser.second }
+ ?: listOf()
}
private fun AppDataSharingUpdate.containsLocationCategoryUpdate() =
categorySharingUpdates[CATEGORY_LOCATION] != null
- private fun showNotification() {
+ private fun showNotification(numberOfAppUpdates: Int) {
+ var sessionId = INVALID_SESSION_ID
+ while (sessionId == INVALID_SESSION_ID) {
+ sessionId = random.nextLong()
+ }
val context = PermissionControllerApplication.get() as Context
val notificationManager = getSystemServiceSafe(context, NotificationManager::class.java)
createNotificationChannel(context, notificationManager)
@@ -473,7 +536,14 @@ class SafetyLabelChangesJobService : JobService() {
.setLocalOnly(true)
.setAutoCancel(true)
.setSilent(true)
- .setContentIntent(createIntentToOpenAppDataSharingUpdates(context))
+ .setContentIntent(createIntentToOpenAppDataSharingUpdates(context, sessionId))
+ .setDeleteIntent(
+ createIntentToLogDismissNotificationEvent(
+ context,
+ sessionId,
+ numberOfAppUpdates
+ )
+ )
val settingsAppLabel =
Utils.getSettingsLabelForNotifications(applicationContext.packageManager)
@@ -484,24 +554,62 @@ class SafetyLabelChangesJobService : JobService() {
.addExtras(
Bundle().apply {
putString(
- Notification.EXTRA_SUBSTITUTE_APP_NAME, settingsAppLabel.toString())
- })
+ Notification.EXTRA_SUBSTITUTE_APP_NAME,
+ settingsAppLabel.toString()
+ )
+ }
+ )
}
notificationManager.notify(
- SAFETY_LABEL_CHANGES_NOTIFICATION_ID, notificationBuilder.build())
+ SAFETY_LABEL_CHANGES_NOTIFICATION_ID,
+ notificationBuilder.build()
+ )
+
+ logAppDataSharingUpdatesNotificationInteraction(
+ sessionId,
+ APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION__ACTION__NOTIFICATION_SHOWN,
+ numberOfAppUpdates
+ )
+ Log.v(LOG_TAG, "Safety label change notification sent.")
+ }
- if (DEBUG) {
- Log.v(LOG_TAG, "Safety label change notification sent.")
- }
+ private fun cancelNotification() {
+ val notificationManager = getSystemServiceSafe(context, NotificationManager::class.java)
+ notificationManager.cancel(SAFETY_LABEL_CHANGES_NOTIFICATION_ID)
+ Log.v(LOG_TAG, "Safety label change notification cancelled.")
}
- private fun createIntentToOpenAppDataSharingUpdates(context: Context): PendingIntent? {
+ private fun createIntentToOpenAppDataSharingUpdates(
+ context: Context,
+ sessionId: Long
+ ): PendingIntent {
return PendingIntent.getActivity(
context,
0,
- Intent(Intent.ACTION_REVIEW_APP_DATA_SHARING_UPDATES),
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
+ Intent(Intent.ACTION_REVIEW_APP_DATA_SHARING_UPDATES).apply {
+ putExtra(EXTRA_SESSION_ID, sessionId)
+ },
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ }
+
+ private fun createIntentToLogDismissNotificationEvent(
+ context: Context,
+ sessionId: Long,
+ numberOfAppUpdates: Int
+ ): PendingIntent {
+ return PendingIntent.getBroadcast(
+ context,
+ 0,
+ Intent(context, NotificationDeleteHandler::class.java).apply {
+ putExtra(EXTRA_SESSION_ID, sessionId)
+ putExtra(EXTRA_NUMBER_OF_APP_UPDATES, numberOfAppUpdates)
+ },
+ PendingIntent.FLAG_ONE_SHOT or
+ PendingIntent.FLAG_UPDATE_CURRENT or
+ PendingIntent.FLAG_IMMUTABLE
+ )
}
private fun createNotificationChannel(
@@ -512,7 +620,8 @@ class SafetyLabelChangesJobService : JobService() {
NotificationChannel(
PERMISSION_REMINDER_CHANNEL_ID,
context.getString(R.string.permission_reminders),
- NotificationManager.IMPORTANCE_LOW)
+ NotificationManager.IMPORTANCE_LOW
+ )
notificationManager.createNotificationChannel(notificationChannel)
}
@@ -523,6 +632,8 @@ class SafetyLabelChangesJobService : JobService() {
private const val ACTION_SET_UP_SAFETY_LABEL_CHANGES_JOB =
"com.android.permissioncontroller.action.SET_UP_SAFETY_LABEL_CHANGES_JOB"
+ private const val EXTRA_NUMBER_OF_APP_UPDATES =
+ "com.android.permissioncontroller.extra.NUMBER_OF_APP_UPDATES"
private const val DATA_SHARING_UPDATE_PERIOD_PROPERTY = "data_sharing_update_period_millis"
private const val DEFAULT_DATA_SHARING_UPDATE_PERIOD_DAYS: Long = 30
@@ -531,8 +642,9 @@ class SafetyLabelChangesJobService : JobService() {
try {
val jobScheduler = getSystemServiceSafe(context, JobScheduler::class.java)
- if (jobScheduler.getPendingJob(SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID)
- != null) {
+ if (
+ jobScheduler.getPendingJob(SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID) != null
+ ) {
Log.i(LOG_TAG, "Not scheduling detect updates job: already scheduled.")
return
}
@@ -540,15 +652,17 @@ class SafetyLabelChangesJobService : JobService() {
val job =
JobInfo.Builder(
SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID,
- ComponentName(context, SafetyLabelChangesJobService::class.java))
+ ComponentName(context, SafetyLabelChangesJobService::class.java)
+ )
.setRequiresDeviceIdle(
- KotlinUtils.runSafetyLabelChangesJobOnlyWhenDeviceIdle())
+ KotlinUtils.runSafetyLabelChangesJobOnlyWhenDeviceIdle()
+ )
.build()
- val result = jobScheduler.schedule(job)
+ val result = jobScheduler.schedule(job)
if (result != JobScheduler.RESULT_SUCCESS) {
- Log.w(LOG_TAG, "Detect updates job not scheduled, result code: $result")
+ Log.w(LOG_TAG, "Detect updates job not scheduled, result code: $result")
} else {
- Log.i(LOG_TAG, "Detect updates job scheduled successfully.")
+ Log.i(LOG_TAG, "Detect updates job scheduled successfully.")
}
} catch (e: Throwable) {
Log.e(LOG_TAG, "Failed to schedule detect updates job", e)
@@ -559,8 +673,10 @@ class SafetyLabelChangesJobService : JobService() {
private fun schedulePeriodicNotificationJob(context: Context) {
try {
val jobScheduler = getSystemServiceSafe(context, JobScheduler::class.java)
- if (jobScheduler.getPendingJob(SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID)
- != null) {
+ if (
+ jobScheduler.getPendingJob(SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID) !=
+ null
+ ) {
Log.i(LOG_TAG, "Not scheduling notification job: already scheduled.")
return
}
@@ -568,22 +684,44 @@ class SafetyLabelChangesJobService : JobService() {
val job =
JobInfo.Builder(
SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID,
- ComponentName(context, SafetyLabelChangesJobService::class.java))
+ ComponentName(context, SafetyLabelChangesJobService::class.java)
+ )
.setRequiresDeviceIdle(
- KotlinUtils.runSafetyLabelChangesJobOnlyWhenDeviceIdle())
+ KotlinUtils.runSafetyLabelChangesJobOnlyWhenDeviceIdle()
+ )
.setPeriodic(KotlinUtils.getSafetyLabelChangesJobIntervalMillis())
.setPersisted(true)
.build()
val result = jobScheduler.schedule(job)
if (result != JobScheduler.RESULT_SUCCESS) {
- Log.w(LOG_TAG, "Notification job not scheduled, result code: $result")
+ Log.w(LOG_TAG, "Notification job not scheduled, result code: $result")
} else {
- Log.i(LOG_TAG, "Notification job scheduled successfully.")
+ Log.i(LOG_TAG, "Notification job scheduled successfully.")
}
} catch (e: Throwable) {
Log.e(LOG_TAG, "Failed to schedule notification job", e)
throw e
}
}
+
+ private fun logAppDataSharingUpdatesNotificationInteraction(
+ sessionId: Long,
+ interactionType: Int,
+ numberOfAppUpdates: Int
+ ) {
+ PermissionControllerStatsLog.write(
+ APP_DATA_SHARING_UPDATES_NOTIFICATION_INTERACTION,
+ sessionId,
+ interactionType,
+ numberOfAppUpdates
+ )
+ Log.v(
+ LOG_TAG,
+ "Notification interaction occurred with" +
+ " sessionId=$sessionId" +
+ " action=$interactionType" +
+ " numberOfAppUpdates=$numberOfAppUpdates"
+ )
+ }
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFragment.kt
index 8d7102395..645ac45e9 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFragment.kt
@@ -2,18 +2,27 @@
package com.android.permissioncontroller.permission.ui.handheld.v34
+import android.app.Application
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.os.UserHandle
+import android.util.Log
import android.view.MenuItem
import android.view.View
import androidx.annotation.RequiresApi
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
+import com.android.permissioncontroller.Constants.INVALID_SESSION_ID
+import com.android.permissioncontroller.PermissionControllerStatsLog
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED__DATA_SHARING_CHANGE__ADDS_ADVERTISING_PURPOSE
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED__DATA_SHARING_CHANGE__ADDS_SHARING_WITHOUT_ADVERTISING_PURPOSE
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED__DATA_SHARING_CHANGE__ADDS_SHARING_WITH_ADVERTISING_PURPOSE
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_DATA_SHARING_UPDATES_FRAGMENT_VIEWED
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.model.v34.DataSharingUpdateType
import com.android.permissioncontroller.permission.ui.handheld.PermissionsFrameFragment
@@ -23,12 +32,14 @@ import com.android.permissioncontroller.permission.ui.model.v34.AppDataSharingUp
import com.android.permissioncontroller.permission.utils.KotlinUtils
import com.android.permissioncontroller.permission.utils.StringUtils
import java.text.Collator
+import java.util.Random
/** Fragment to display data sharing updates for installed apps. */
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
class AppDataSharingUpdatesFragment : PermissionsFrameFragment() {
private lateinit var viewModel: AppDataSharingUpdatesViewModel
private lateinit var collator: Collator
+ private var sessionId: Long = INVALID_SESSION_ID
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -47,6 +58,11 @@ class AppDataSharingUpdatesFragment : PermissionsFrameFragment() {
}
viewModel.appLocationDataSharingUpdateUiInfoLiveData.observe(this, this::updatePreferences)
+ sessionId =
+ savedInstanceState?.getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID) ?: INVALID_SESSION_ID
+ while (sessionId == INVALID_SESSION_ID) {
+ sessionId = Random().nextLong()
+ }
}
override fun setDivider(divider: Drawable?) {
@@ -65,6 +81,8 @@ class AppDataSharingUpdatesFragment : PermissionsFrameFragment() {
private fun updatePreferences(updateUiInfos: List<AppLocationDataSharingUpdateUiInfo>) {
setLoading(/* loading= */ false, /* animate= */ true)
+ logAppDataSharingUpdatesFragmentViewed(sessionId, updateUiInfos.size)
+
if (updateUiInfos.isNotEmpty()) {
showUpdatesPresentUi()
} else {
@@ -78,8 +96,8 @@ class AppDataSharingUpdatesFragment : PermissionsFrameFragment() {
val updatesCategory =
preferenceScreen.findPreference<PreferenceCategory>(
- LAST_PERIOD_UPDATES_PREFERENCE_CATEGORY_ID)
- ?: return
+ LAST_PERIOD_UPDATES_PREFERENCE_CATEGORY_ID
+ ) ?: return
val preferencesToRemove = mutableSetOf<Preference>()
for (i in 0 until (updatesCategory.preferenceCount)) {
@@ -95,7 +113,8 @@ class AppDataSharingUpdatesFragment : PermissionsFrameFragment() {
createUpdatePreferenceKey(
updateUiInfo.packageName,
updateUiInfo.userHandle,
- updateUiInfo.dataSharingUpdateType)
+ updateUiInfo.dataSharingUpdateType
+ )
if (updatesCategory.findPreference<AppDataSharingUpdatePreference>(key) != null) {
// If a preference is already shown, don't recreate it.
return@forEach
@@ -105,19 +124,30 @@ class AppDataSharingUpdatesFragment : PermissionsFrameFragment() {
requireActivity().application,
updateUiInfo.packageName,
updateUiInfo.userHandle,
- requireActivity().applicationContext)
+ requireActivity().applicationContext
+ )
appDataSharingUpdatePreference.apply {
this.key = key
title =
KotlinUtils.getPackageLabel(
requireActivity().application,
updateUiInfo.packageName,
- updateUiInfo.userHandle)
+ updateUiInfo.userHandle
+ )
summary = getSummaryForLocationUpdateType(updateUiInfo.dataSharingUpdateType)
preferenceClick =
View.OnClickListener { _ ->
+ logAppDataSharingUpdatesFragmentActionReported(
+ sessionId,
+ requireActivity().application,
+ updateUiInfo
+ )
viewModel.startAppLocationPermissionPage(
- requireActivity(), updateUiInfo.packageName, updateUiInfo.userHandle)
+ requireActivity(),
+ sessionId,
+ updateUiInfo.packageName,
+ updateUiInfo.userHandle
+ )
}
updatesCategory.addPreference(this)
}
@@ -144,10 +174,12 @@ class AppDataSharingUpdatesFragment : PermissionsFrameFragment() {
preferenceScreen?.findPreference<AppDataSharingDetailsPreference>(DETAILS_PREFERENCE_ID)
val footerPreference =
preferenceScreen?.findPreference<AppDataSharingUpdatesFooterPreference>(
- FOOTER_PREFERENCE_ID)
+ FOOTER_PREFERENCE_ID
+ )
val dataSharingUpdatesCategory =
preferenceScreen?.findPreference<PreferenceCategory>(
- LAST_PERIOD_UPDATES_PREFERENCE_CATEGORY_ID)
+ LAST_PERIOD_UPDATES_PREFERENCE_CATEGORY_ID
+ )
detailsPreference?.let {
it.showNoUpdates = false
@@ -182,10 +214,12 @@ class AppDataSharingUpdatesFragment : PermissionsFrameFragment() {
preferenceScreen?.findPreference<AppDataSharingDetailsPreference>(DETAILS_PREFERENCE_ID)
val footerPreference =
preferenceScreen?.findPreference<AppDataSharingUpdatesFooterPreference>(
- FOOTER_PREFERENCE_ID)
+ FOOTER_PREFERENCE_ID
+ )
val dataSharingUpdatesCategory =
preferenceScreen?.findPreference<PreferenceCategory>(
- LAST_PERIOD_UPDATES_PREFERENCE_CATEGORY_ID)
+ LAST_PERIOD_UPDATES_PREFERENCE_CATEGORY_ID
+ )
detailsPreference?.let {
it.showNoUpdates = true
@@ -232,8 +266,55 @@ class AppDataSharingUpdatesFragment : PermissionsFrameFragment() {
*/
fun createArgs(sessionId: Long) = Bundle().apply { putLong(EXTRA_SESSION_ID, sessionId) }
+ private val LOG_TAG = AppDataSharingUpdatesFragment::class.java.simpleName
+
private const val DETAILS_PREFERENCE_ID = "details"
private const val FOOTER_PREFERENCE_ID = "info_footer"
private const val LAST_PERIOD_UPDATES_PREFERENCE_CATEGORY_ID = "last_period_updates"
+
+ private fun logAppDataSharingUpdatesFragmentViewed(
+ sessionId: Long,
+ numberOfAppUpdates: Int
+ ) {
+ PermissionControllerStatsLog.write(
+ APP_DATA_SHARING_UPDATES_FRAGMENT_VIEWED,
+ sessionId,
+ numberOfAppUpdates
+ )
+ Log.v(
+ LOG_TAG,
+ "AppDataSharingUpdatesFragment viewed with" +
+ " sessionId=$sessionId" +
+ " numberOfAppUpdates=$numberOfAppUpdates"
+ )
+ }
+
+ private fun logAppDataSharingUpdatesFragmentActionReported(
+ sessionId: Long,
+ app: Application,
+ updateUiInfo: AppLocationDataSharingUpdateUiInfo
+ ) {
+ val uid: Int =
+ KotlinUtils.getPackageUid(app, updateUiInfo.packageName, updateUiInfo.userHandle)
+ ?: return
+ val dataSharingChangeValue: Int =
+ getStatsLogValueForLocationUpdateType(updateUiInfo.dataSharingUpdateType)
+ PermissionControllerStatsLog.write(
+ APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED,
+ sessionId,
+ uid,
+ dataSharingChangeValue
+ )
+ }
+
+ private fun getStatsLogValueForLocationUpdateType(type: DataSharingUpdateType): Int =
+ when (type) {
+ DataSharingUpdateType.ADDS_ADVERTISING_PURPOSE ->
+ APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED__DATA_SHARING_CHANGE__ADDS_ADVERTISING_PURPOSE
+ DataSharingUpdateType.ADDS_SHARING_WITHOUT_ADVERTISING_PURPOSE ->
+ APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED__DATA_SHARING_CHANGE__ADDS_SHARING_WITHOUT_ADVERTISING_PURPOSE
+ DataSharingUpdateType.ADDS_SHARING_WITH_ADVERTISING_PURPOSE ->
+ APP_DATA_SHARING_UPDATES_FRAGMENT_ACTION_REPORTED__DATA_SHARING_CHANGE__ADDS_SHARING_WITH_ADVERTISING_PURPOSE
+ }
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/AppDataSharingUpdatesViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/AppDataSharingUpdatesViewModel.kt
index fe50e5e6a..7f5a87552 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/AppDataSharingUpdatesViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/AppDataSharingUpdatesViewModel.kt
@@ -32,6 +32,7 @@ import android.os.UserHandle
import android.util.Log
import androidx.annotation.RequiresApi
import com.android.permission.safetylabel.DataCategoryConstants.CATEGORY_LOCATION
+import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.data.SinglePermGroupPackagesUiInfoLiveData
import com.android.permissioncontroller.permission.data.SmartAsyncMediatorLiveData
@@ -84,15 +85,18 @@ class AppDataSharingUpdatesViewModel(app: Application) {
*/
fun startAppLocationPermissionPage(
activity: Activity,
+ sessionId: Long,
packageName: String,
userHandle: UserHandle
) {
activity.startActivity(
Intent(ACTION_MANAGE_APP_PERMISSION).apply {
+ putExtra(EXTRA_SESSION_ID, sessionId)
putExtra(EXTRA_PERMISSION_GROUP_NAME, Manifest.permission_group.LOCATION)
putExtra(EXTRA_PACKAGE_NAME, packageName)
putExtra(EXTRA_USER, userHandle)
- })
+ }
+ )
}
/**
@@ -113,11 +117,15 @@ class AppDataSharingUpdatesViewModel(app: Application) {
} else {
val users =
locationPermGroupPackagesUiInfoLiveData.getUsersWithPermGrantedForApp(
- appDataSharingUpdate.packageName)
+ appDataSharingUpdate.packageName
+ )
users.map { user ->
// For each user profile under the current user, display one entry.
AppLocationDataSharingUpdateUiInfo(
- appDataSharingUpdate.packageName, user, locationDataSharingUpdate)
+ appDataSharingUpdate.packageName,
+ user,
+ locationDataSharingUpdate
+ )
}
}
}