diff options
| author | 2023-04-02 05:45:25 +0000 | |
|---|---|---|
| committer | 2023-04-02 05:45:25 +0000 | |
| commit | 07b340eded0f79876997582e2791ca5d38b19e00 (patch) | |
| tree | 80488d0788b932a49ebcda0ac25444de2bbfd0db | |
| parent | 179f43765661fa8a17f7de6758f158d5e9a4a20b (diff) | |
| parent | f967d540fdddb552337aa61f06b81f7c8374b829 (diff) | |
Merge "[Safety Labels] Log metrics" into udc-dev
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 + ) } } } |