From 9d915fc6bab7011c7af369264f29b77271f4fe1e Mon Sep 17 00:00:00 2001 From: mrulhania Date: Tue, 10 Sep 2024 17:14:03 -0700 Subject: Auto: permission timeline page reuse modern architecture impl We have removed old permission timeline live data implementation for phone. This CL removes legacy impl for auto and reuse the new view model across all form factor. for more details see go/pc-modern-architecture Fix: 365634516 Test: Testing done by Swapnil. Flag: EXEMPT bug fix LOW_COVERAGE_REASON=OTHER_FORM_FACTOR Relnote: privacy dashboard (auto) remove legacy view model. Change-Id: I5b689654bb4397527ab309230996ef3d2a7071e1 --- .../dashboard/AutoPermissionHistoryPreference.kt | 16 +- .../AutoPermissionUsageDetailsFragment.kt | 127 ++--- .../auto/dashboard/AutoPermissionUsageFragment.kt | 12 +- .../PermissionUsageDetailsViewModelLegacy.kt | 596 --------------------- 4 files changed, 48 insertions(+), 703 deletions(-) delete mode 100644 PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/PermissionUsageDetailsViewModelLegacy.kt (limited to 'PermissionController/src') diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt index 2d14260a2..36597a3a3 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt @@ -23,30 +23,30 @@ import androidx.annotation.RequiresApi import androidx.preference.Preference.OnPreferenceClickListener import com.android.car.ui.preference.CarUiPreference import com.android.permissioncontroller.R -import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageDetailsViewModelLegacy import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel +import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.AppPermissionAccessUiInfo /** Preference that displays a permission usage for an app. */ @RequiresApi(Build.VERSION_CODES.S) class AutoPermissionHistoryPreference( context: Context, - historyPreferenceData: PermissionUsageDetailsViewModelLegacy.HistoryPreferenceData + historyPreferenceData: AppPermissionAccessUiInfo, ) : CarUiPreference(context) { init { - title = historyPreferenceData.preferenceTitle + title = historyPreferenceData.packageLabel summary = if (historyPreferenceData.summaryText != null) { context.getString( R.string.auto_permission_usage_timeline_summary, DateFormat.getTimeFormat(context).format(historyPreferenceData.accessEndTime), - historyPreferenceData.summaryText + historyPreferenceData.summaryText, ) } else { DateFormat.getTimeFormat(context).format(historyPreferenceData.accessEndTime) } - if (historyPreferenceData.appIcon != null) { - icon = historyPreferenceData.appIcon + if (historyPreferenceData.badgedPackageIcon != null) { + icon = historyPreferenceData.badgedPackageIcon } onPreferenceClickListener = OnPreferenceClickListener { @@ -56,12 +56,12 @@ class AutoPermissionHistoryPreference( PermissionUsageDetailsViewModel.createHistoryPreferenceClickIntent( context = context, userHandle = historyPreferenceData.userHandle, - packageName = historyPreferenceData.pkgName, + packageName = historyPreferenceData.packageName, permissionGroup = historyPreferenceData.permissionGroup, accessEndTime = historyPreferenceData.accessEndTime, accessStartTime = historyPreferenceData.accessStartTime, showingAttribution = historyPreferenceData.showingAttribution, - attributionTags = historyPreferenceData.attributionTags.toSet() + attributionTags = historyPreferenceData.attributionTags.toSet(), ) ) true diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt index 481543eb6..8edd39913 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt @@ -13,11 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:Suppress("DEPRECATION") - package com.android.permissioncontroller.permission.ui.auto.dashboard -import android.app.role.RoleManager import android.content.Intent import android.os.Build import android.os.Bundle @@ -36,16 +33,12 @@ import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_ import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED import com.android.permissioncontroller.R import com.android.permissioncontroller.auto.AutoSettingsFrameFragment -import com.android.permissioncontroller.permission.model.legacy.PermissionApps.AppDataLoader -import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage -import com.android.permissioncontroller.permission.model.v31.PermissionUsages -import com.android.permissioncontroller.permission.model.v31.PermissionUsages.PermissionsUsagesChangeCallback import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity import com.android.permissioncontroller.permission.ui.auto.AutoDividerPreference -import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageDetailsViewModelFactoryLegacy -import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageDetailsViewModelLegacy +import com.android.permissioncontroller.permission.ui.model.v31.BasePermissionUsageDetailsViewModel +import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel +import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.AppPermissionAccessUiInfo import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel -import com.android.permissioncontroller.permission.utils.Utils import java.time.Clock import java.time.Instant import java.time.ZoneId @@ -54,9 +47,7 @@ import java.time.temporal.ChronoUnit import java.util.concurrent.atomic.AtomicReference @RequiresApi(Build.VERSION_CODES.S) -class AutoPermissionUsageDetailsFragment : - AutoSettingsFrameFragment(), PermissionsUsagesChangeCallback { - +class AutoPermissionUsageDetailsFragment : AutoSettingsFrameFragment() { companion object { private const val LOG_TAG = "AutoPermissionUsageDetailsFragment" private const val KEY_SESSION_ID = "_session_id" @@ -70,14 +61,11 @@ class AutoPermissionUsageDetailsFragment : .truncatedTo(ChronoUnit.DAYS) .toEpochSecond() * 1000L - // Only show the last 24 hours on Auto right now - private const val SHOW_7_DAYS = false - /** Creates a new instance of [AutoPermissionUsageDetailsFragment]. */ fun newInstance( groupName: String?, showSystem: Boolean, - sessionId: Long + sessionId: Long, ): AutoPermissionUsageDetailsFragment { return AutoPermissionUsageDetailsFragment().apply { arguments = @@ -92,14 +80,10 @@ class AutoPermissionUsageDetailsFragment : private val SESSION_ID_KEY = (AutoPermissionUsageFragment::class.java.name + KEY_SESSION_ID) - private lateinit var permissionUsages: PermissionUsages - private lateinit var usageViewModel: PermissionUsageDetailsViewModelLegacy + private lateinit var usageViewModel: BasePermissionUsageDetailsViewModel private lateinit var filterGroup: String - private lateinit var roleManager: RoleManager - private var appPermissionUsages: List = listOf() private var showSystem = false - private var finishedInitialLoad = false private var hasSystemApps = false /** Unique Id of a request */ @@ -116,7 +100,7 @@ class AutoPermissionUsageDetailsFragment : !requireArguments().containsKey(Intent.EXTRA_PERMISSION_GROUP_NAME) or (requireArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME) == null) ) { - DumpableLog.e(LOG_TAG, "Missing argument ${Intent.EXTRA_USER}") + DumpableLog.e(LOG_TAG, "Missing argument ${Intent.EXTRA_PERMISSION_GROUP_NAME}") activity?.finish() return } @@ -130,28 +114,21 @@ class AutoPermissionUsageDetailsFragment : headerLabel = resources.getString( R.string.permission_group_usage_title, - getPermGroupLabel(requireContext(), filterGroup) + getPermGroupLabel(requireContext(), filterGroup), ) - - val context = preferenceManager.getContext() - permissionUsages = PermissionUsages(context) - roleManager = Utils.getSystemServiceSafe(context, RoleManager::class.java) - val usageViewModelFactory = - PermissionUsageDetailsViewModelFactoryLegacy( + val factory = + PermissionUsageDetailsViewModel.PermissionUsageDetailsViewModelFactory( PermissionControllerApplication.get(), - roleManager, + this, filterGroup, - sessionId ) usageViewModel = - ViewModelProvider(this, usageViewModelFactory)[ - PermissionUsageDetailsViewModelLegacy::class.java] - - reloadData() + ViewModelProvider(this, factory)[BasePermissionUsageDetailsViewModel::class.java] + usageViewModel.getPermissionUsagesDetailsInfoUiLiveData().observe(this, this::updateUI) } override fun onCreatePreferences(bundlle: Bundle?, s: String?) { - preferenceScreen = preferenceManager.createPreferenceScreen(context!!) + preferenceScreen = preferenceManager.createPreferenceScreen(requireContext()) } private fun setupHeaderPreferences() { @@ -161,38 +138,16 @@ class AutoPermissionUsageDetailsFragment : preferenceScreen.addPreference(AutoDividerPreference(context)) } - /** Reloads the data to show. */ - private fun reloadData() { - usageViewModel.loadPermissionUsages( - requireActivity().getLoaderManager(), - permissionUsages, - this, - FILTER_24_HOURS - ) - if (finishedInitialLoad) { - setLoading(true) - } - } - - override fun onPermissionUsagesChanged() { - if (permissionUsages.usages.isEmpty()) { - return - } - appPermissionUsages = ArrayList(permissionUsages.usages) - updateUI() - } - private fun updateSystemToggle() { if (!showSystem) { PermissionControllerStatsLog.write( PERMISSION_USAGE_FRAGMENT_INTERACTION, sessionId, - PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED + PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED, ) } showSystem = !showSystem updateAction() - updateUI() } private fun updateAction() { @@ -206,47 +161,36 @@ class AutoPermissionUsageDetailsFragment : } else { getString(R.string.menu_show_system) } - setAction(label) { updateSystemToggle() } + setAction(label) { + usageViewModel.updateShowSystemAppsToggle(!showSystem) + updateSystemToggle() + } } - private fun updateUI() { - if (appPermissionUsages.isEmpty()) { + private fun updateUI(uiInfo: PermissionUsageDetailsViewModel.PermissionUsageDetailsUiState) { + if ( + activity == null || + uiInfo is PermissionUsageDetailsViewModel.PermissionUsageDetailsUiState.Loading + ) { return } preferenceScreen.removeAll() setupHeaderPreferences() - - val uiData = - usageViewModel.buildPermissionUsageDetailsUiData( - appPermissionUsages, - showSystem, - SHOW_7_DAYS - ) - - if (hasSystemApps != uiData.shouldDisplayShowSystemToggle) { - hasSystemApps = uiData.shouldDisplayShowSystemToggle + val uiData = uiInfo as PermissionUsageDetailsViewModel.PermissionUsageDetailsUiState.Success + if (hasSystemApps != uiData.containsSystemAppUsage) { + hasSystemApps = uiData.containsSystemAppUsage updateAction() } - val category = AtomicReference(PreferenceCategory(requireContext())) preferenceScreen.addPreference(category.get()) - AppDataLoader(context) { - renderHistoryPreferences( - uiData.getHistoryPreferenceDataList(), - category, - preferenceScreen - ) + renderHistoryPreferences(uiData.appPermissionAccessUiInfoList, category, preferenceScreen) - setLoading(false) - finishedInitialLoad = true - permissionUsages.stopLoader(requireActivity().getLoaderManager()) - } - .execute(*uiData.permissionApps.toTypedArray()) + setLoading(false) } fun createPermissionHistoryPreference( - historyPreferenceData: PermissionUsageDetailsViewModelLegacy.HistoryPreferenceData + historyPreferenceData: AppPermissionAccessUiInfo ): Preference { return AutoPermissionHistoryPreference(requireContext(), historyPreferenceData) } @@ -257,7 +201,7 @@ class AutoPermissionUsageDetailsFragment : summary = getString( R.string.permission_group_usage_subtitle_24h, - getPermGroupLabel(requireContext(), filterGroup) + getPermGroupLabel(requireContext(), filterGroup), ) isSelectable = false } @@ -271,7 +215,7 @@ class AutoPermissionUsageDetailsFragment : summary = getString( R.string.manage_permission_summary, - getPermGroupLabel(requireContext(), filterGroup) + getPermGroupLabel(requireContext(), filterGroup), ) onPreferenceClickListener = Preference.OnPreferenceClickListener { @@ -287,9 +231,8 @@ class AutoPermissionUsageDetailsFragment : } /** Render the provided [historyPreferenceDataList] into the [preferenceScreen] UI. */ - fun renderHistoryPreferences( - historyPreferenceDataList: - List, + private fun renderHistoryPreferences( + historyPreferenceDataList: List, category: AtomicReference, preferenceScreen: PreferenceScreen, ) { @@ -299,7 +242,7 @@ class AutoPermissionUsageDetailsFragment : val currentDateMs = ZonedDateTime.ofInstant( Instant.ofEpochMilli(usageTimestamp), - Clock.system(ZoneId.systemDefault()).zone + Clock.system(ZoneId.systemDefault()).zone, ) .truncatedTo(ChronoUnit.DAYS) .toEpochSecond() * 1000L diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt index 9f39bd785..36d867b11 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt @@ -46,7 +46,7 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment() { Manifest.permission_group.CAMERA, 1, Manifest.permission_group.MICROPHONE, - 2 + 2, ) private const val DEFAULT_ORDER: Int = 3 } @@ -54,7 +54,6 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment() { private val SESSION_ID_KEY = (AutoPermissionUsageFragment::class.java.name + KEY_SESSION_ID) private var showSystem = false - private var finishedInitialLoad = false private var hasSystemApps = false /** Unique Id of a request */ @@ -89,7 +88,7 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment() { PermissionControllerStatsLog.write( PERMISSION_USAGE_FRAGMENT_INTERACTION, sessionId, - PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED + PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED, ) } showSystem = !showSystem @@ -133,13 +132,13 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment() { Comparator.comparing { permissionGroupWithUsageCount: Map.Entry -> PERMISSION_GROUP_ORDER.getOrDefault( permissionGroupWithUsageCount.key, - DEFAULT_ORDER + DEFAULT_ORDER, ) } .thenComparing { permissionGroupWithUsageCount: Map.Entry -> mViewModel.getPermissionGroupLabel( requireContext(), - permissionGroupWithUsageCount.key + permissionGroupWithUsageCount.key, ) } ) @@ -153,11 +152,10 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment() { permissionGroupWithUsageCountsEntries[i].value, showSystem, sessionId, - false + false, ) getPreferenceScreen().addPreference(permissionUsagePreference) } - finishedInitialLoad = true setLoading(false) } } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/PermissionUsageDetailsViewModelLegacy.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/PermissionUsageDetailsViewModelLegacy.kt deleted file mode 100644 index 1369bfdaa..000000000 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/PermissionUsageDetailsViewModelLegacy.kt +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -@file:Suppress("DEPRECATION") - -package com.android.permissioncontroller.permission.ui.legacy - -import android.Manifest -import android.app.AppOpsManager -import android.app.Application -import android.app.LoaderManager -import android.app.role.RoleManager -import android.content.Context -import android.content.pm.ApplicationInfo -import android.content.res.Resources -import android.graphics.drawable.Drawable -import android.os.Build -import android.os.UserHandle -import androidx.annotation.RequiresApi -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import com.android.permissioncontroller.PermissionControllerApplication -import com.android.permissioncontroller.R -import com.android.permissioncontroller.permission.model.AppPermissionGroup -import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp -import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage -import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage.TimelineUsage -import com.android.permissioncontroller.permission.model.v31.PermissionUsages -import com.android.permissioncontroller.permission.ui.handheld.v31.getDurationUsedStr -import com.android.permissioncontroller.permission.ui.handheld.v31.shouldShowSubattributionInPermissionsDashboard -import com.android.permissioncontroller.permission.utils.KotlinUtils.getPackageLabel -import com.android.permissioncontroller.permission.utils.PermissionMapping -import com.android.permissioncontroller.permission.utils.StringUtils -import com.android.permissioncontroller.permission.utils.Utils -import com.android.permissioncontroller.permission.utils.v31.SubattributionUtils -import java.time.Instant -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeUnit.DAYS -import kotlin.math.max - -/** View model for the permission details fragment. */ -@RequiresApi(Build.VERSION_CODES.S) -class PermissionUsageDetailsViewModelLegacy( - val application: Application, - val roleManager: RoleManager, - private val permissionGroup: String, - val sessionId: Long -) : ViewModel() { - - companion object { - private const val ONE_HOUR_MS = 3_600_000 - private const val ONE_MINUTE_MS = 60_000 - private const val CLUSTER_SPACING_MINUTES: Long = 1L - private val TIME_7_DAYS_DURATION: Long = DAYS.toMillis(7) - private val TIME_24_HOURS_DURATION: Long = DAYS.toMillis(1) - } - - private val mTimeFilterItemMs = mutableListOf() - - init { - initializeTimeFilterItems(application) - } - - /** Loads permission usages using [PermissionUsages]. Response is returned to the [callback]. */ - fun loadPermissionUsages( - loaderManager: LoaderManager, - permissionUsages: PermissionUsages, - callback: PermissionUsages.PermissionsUsagesChangeCallback, - filterTimesIndex: Int - ) { - val timeFilterItemMs: TimeFilterItemMs = mTimeFilterItemMs[filterTimesIndex] - val filterTimeBeginMillis = max(System.currentTimeMillis() - timeFilterItemMs.timeMs, 0) - permissionUsages.load( - /* filterPackageName= */ null, - /* filterPermissionGroups= */ null, - filterTimeBeginMillis, - Long.MAX_VALUE, - PermissionUsages.USAGE_FLAG_LAST or PermissionUsages.USAGE_FLAG_HISTORICAL, - loaderManager, - /* getUiInfo= */ false, - /* getNonPlatformPermissions= */ false, - /* callback= */ callback, - /* sync= */ false - ) - } - - /** - * Create a [PermissionUsageDetailsUiData] based on the provided data. - * - * @param appPermissionUsages data about app permission usages - * @param showSystem whether system apps should be shown - * @param show7Days whether the last 7 days of history should be shown - */ - fun buildPermissionUsageDetailsUiData( - appPermissionUsages: List, - showSystem: Boolean, - show7Days: Boolean - ): PermissionUsageDetailsUiData { - val showPermissionUsagesDuration = - if (show7Days) { - TIME_7_DAYS_DURATION - } else { - TIME_24_HOURS_DURATION - } - val startTime = - (System.currentTimeMillis() - showPermissionUsagesDuration).coerceAtLeast( - Instant.EPOCH.toEpochMilli() - ) - val appPermissionTimelineUsages: List = - extractAppPermissionTimelineUsagesForGroup(appPermissionUsages, permissionGroup) - val shouldDisplayShowSystemToggle = - shouldDisplayShowSystemToggle(appPermissionTimelineUsages) - val permissionApps: List = - getPermissionAppsWithRecentDiscreteUsage( - appPermissionTimelineUsages, - showSystem, - startTime - ) - val appPermissionUsageEntries = - buildDiscreteAccessClusterData(appPermissionTimelineUsages, showSystem, startTime) - - return PermissionUsageDetailsUiData( - permissionApps, - shouldDisplayShowSystemToggle, - appPermissionUsageEntries - ) - } - - private fun getHistoryPreferenceData( - discreteAccessClusterData: DiscreteAccessClusterData, - ): HistoryPreferenceData { - val context = application - val accessTimeList = - discreteAccessClusterData.discreteAccessDataList.map { p -> p.accessTimeMs } - val durationSummaryLabel = - getDurationSummary(discreteAccessClusterData, accessTimeList, context) - val proxyLabel = getProxyPackageLabel(discreteAccessClusterData) - val subattributionLabel = getSubattributionLabel(discreteAccessClusterData) - val showingSubattribution = subattributionLabel != null && subattributionLabel.isNotEmpty() - val summary = - buildUsageSummary(durationSummaryLabel, proxyLabel, subattributionLabel, context) - - return HistoryPreferenceData( - UserHandle.getUserHandleForUid( - discreteAccessClusterData.appPermissionTimelineUsage.permissionApp.uid - ), - discreteAccessClusterData.appPermissionTimelineUsage.permissionApp.packageName, - discreteAccessClusterData.appPermissionTimelineUsage.permissionApp.icon, - discreteAccessClusterData.appPermissionTimelineUsage.permissionApp.label, - permissionGroup, - discreteAccessClusterData.discreteAccessDataList.last().accessTimeMs, - discreteAccessClusterData.discreteAccessDataList.first().accessTimeMs, - summary, - showingSubattribution, - discreteAccessClusterData.appPermissionTimelineUsage.attributionTags, - sessionId - ) - } - - /** - * Returns whether the provided [AppPermissionUsage] instances contains the provided platform - * permission group. - */ - fun containsPlatformAppPermissionGroup( - appPermissionUsages: List, - groupName: String, - ) = appPermissionUsages.extractAllPlatformAppPermissionGroups().any { it.name == groupName } - - /** Extracts a list of [AppPermissionTimelineUsage] for a particular permission group. */ - private fun extractAppPermissionTimelineUsagesForGroup( - appPermissionUsages: List, - group: String - ): List { - val exemptedPackages = Utils.getExemptedPackages(roleManager) - return appPermissionUsages - .filter { !exemptedPackages.contains(it.packageName) } - .map { appPermissionUsage -> - getAppPermissionTimelineUsages( - appPermissionUsage.app, - appPermissionUsage.groupUsages.firstOrNull { it.group.name == group } - ) - } - .flatten() - } - - /** Returns whether the show/hide system toggle should be displayed in the UI. */ - private fun shouldDisplayShowSystemToggle( - appPermissionTimelineUsages: List, - ): Boolean = - appPermissionTimelineUsages - .map { it.timelineUsage } - .filter { it.hasDiscreteData() } - .any { it.group.isSystem() } - - /** - * Returns a list of [PermissionApp] instances which had recent discrete permission usage - * (recent here refers to usages occurring after the provided start time). - */ - private fun getPermissionAppsWithRecentDiscreteUsage( - appPermissionTimelineUsageList: List, - showSystem: Boolean, - startTime: Long, - ): List = - appPermissionTimelineUsageList - .filter { it.timelineUsage.hasDiscreteData() } - .filter { showSystem || !it.timelineUsage.group.isSystem() } - .filter { it.timelineUsage.allDiscreteAccessTime.any { it.first >= startTime } } - .map { it.permissionApp } - - /** - * Builds a list of [DiscreteAccessClusterData] from the provided list of - * [AppPermissionTimelineUsage]. - */ - private fun buildDiscreteAccessClusterData( - appPermissionTimelineUsageList: List, - showSystem: Boolean, - startTime: Long, - ): List = - appPermissionTimelineUsageList - .map { appPermissionTimelineUsages -> - val accessDataList = - extractRecentDiscreteAccessData( - appPermissionTimelineUsages.timelineUsage, - showSystem, - startTime - ) - - if (accessDataList.size <= 1) { - return@map accessDataList.map { - DiscreteAccessClusterData(appPermissionTimelineUsages, listOf(it)) - } - } - - clusterDiscreteAccessData(appPermissionTimelineUsages, accessDataList) - } - .flatten() - .sortedWith( - compareBy( - { -it.discreteAccessDataList.first().accessTimeMs }, - { it.appPermissionTimelineUsage.permissionApp.label } - ) - ) - .toList() - - /** - * Clusters a list of [DiscreteAccessData] into a list of [DiscreteAccessClusterData] instances. - * - * [DiscreteAccessData] which have accesses sufficiently close together in time will be places - * in the same cluster. - */ - private fun clusterDiscreteAccessData( - appPermissionTimelineUsage: AppPermissionTimelineUsage, - discreteAccessDataList: List - ): List { - val clusterDataList = mutableListOf() - val currentDiscreteAccessDataList: MutableList = mutableListOf() - for (discreteAccessData in discreteAccessDataList) { - if (currentDiscreteAccessDataList.isEmpty()) { - currentDiscreteAccessDataList.add(discreteAccessData) - } else if ( - !canAccessBeAddedToCluster(discreteAccessData, currentDiscreteAccessDataList) - ) { - clusterDataList.add( - DiscreteAccessClusterData( - appPermissionTimelineUsage, - currentDiscreteAccessDataList.toMutableList() - ) - ) - currentDiscreteAccessDataList.clear() - currentDiscreteAccessDataList.add(discreteAccessData) - } else { - currentDiscreteAccessDataList.add(discreteAccessData) - } - } - if (currentDiscreteAccessDataList.isNotEmpty()) { - clusterDataList.add( - DiscreteAccessClusterData(appPermissionTimelineUsage, currentDiscreteAccessDataList) - ) - } - return clusterDataList - } - - /** - * Extract recent [DiscreteAccessData] from a list of [TimelineUsage] instances, and return them - * ordered descending by access time (recent here refers to accesses occurring after the - * provided start time). - */ - private fun extractRecentDiscreteAccessData( - timelineUsages: TimelineUsage, - showSystem: Boolean, - startTime: Long - ): List { - return if ( - timelineUsages.hasDiscreteData() && (showSystem || !timelineUsages.group.isSystem()) - ) { - getRecentDiscreteAccessData(timelineUsages, startTime) - .sortedWith(compareBy { -it.accessTimeMs }) - .toList() - } else { - listOf() - } - } - - /** - * Extract recent [DiscreteAccessData] from a [TimelineUsage]. (recent here refers to accesses - * occurring after the provided start time). - */ - private fun getRecentDiscreteAccessData( - timelineUsage: TimelineUsage, - startTime: Long - ): List { - return timelineUsage.allDiscreteAccessTime - .filter { it.first >= startTime } - .map { - DiscreteAccessData( - it.first, - it.second, - it.third, - ) - } - } - - /** - * Returns whether the provided [DiscreteAccessData] occurred close enough to those in the - * clustered list that it can be added to the cluster - */ - private fun canAccessBeAddedToCluster( - accessData: DiscreteAccessData, - clusteredAccessDataList: List - ): Boolean = - accessData.accessTimeMs / ONE_HOUR_MS == - clusteredAccessDataList.first().accessTimeMs / ONE_HOUR_MS && - clusteredAccessDataList.last().accessTimeMs / ONE_MINUTE_MS - - accessData.accessTimeMs / ONE_MINUTE_MS > CLUSTER_SPACING_MINUTES - - /** - * Returns whether the provided [AppPermissionGroup] is considered a system group. - * - * For the purpose of Permissions Hub UI, non user-sensitive [AppPermissionGroup]s are - * considered "system" and should be hidden from the main page unless requested by the user - * through the "show/hide system" toggle. - */ - private fun AppPermissionGroup.isSystem() = !Utils.isGroupOrBgGroupUserSensitive(this) - - /** Returns whether app subattribution should be shown. */ - private fun shouldShowSubattributionForApp(appInfo: ApplicationInfo): Boolean { - return shouldShowSubattributionInPermissionsDashboard() && - SubattributionUtils.isSubattributionSupported(application, appInfo) - } - - /** Returns a summary of the duration the permission was accessed for. */ - private fun getDurationSummary( - usage: DiscreteAccessClusterData, - accessTimeList: List, - context: Context - ): String? { - if (accessTimeList.isEmpty()) { - return null - } - - var durationMs: Long - - // Since Location accesses are atomic, we manually calculate the access duration - // by comparing the first and last access within the cluster. - if (permissionGroup == Manifest.permission_group.LOCATION) { - durationMs = accessTimeList[0] - accessTimeList[accessTimeList.size - 1] - } else { - durationMs = - usage.discreteAccessDataList.map { it.accessDurationMs }.filter { it > 0 }.sum() - } - // Only show the duration summary if it is at least (CLUSTER_SPACING_MINUTES + 1) minutes. - // Displaying a time that is shorter than the cluster granularity - // (CLUSTER_SPACING_MINUTES) will not convey useful information. - if (durationMs >= TimeUnit.MINUTES.toMillis(CLUSTER_SPACING_MINUTES + 1)) { - return getDurationUsedStr(context, durationMs) - } - - return null - } - - /** Returns the proxied package label if the permission access was proxied. */ - private fun getProxyPackageLabel(usage: DiscreteAccessClusterData): String? = - usage.discreteAccessDataList - .firstOrNull { it.proxy?.packageName != null } - ?.let { - getPackageLabel( - PermissionControllerApplication.get(), - it.proxy!!.packageName!!, - UserHandle.getUserHandleForUid(it.proxy.uid) - ) - } - - /** Returns the attribution label for the permission access, if any. */ - private fun getSubattributionLabel(usage: DiscreteAccessClusterData): String? = - if (usage.appPermissionTimelineUsage.label == Resources.ID_NULL) null - else - usage.appPermissionTimelineUsage.permissionApp.attributionLabels?.let { - it[usage.appPermissionTimelineUsage.label] - } - - /** Builds a summary of the permission access. */ - private fun buildUsageSummary( - subattributionLabel: String?, - proxyPackageLabel: String?, - durationSummary: String?, - context: Context - ): String? { - val subTextStrings: MutableList = mutableListOf() - - subattributionLabel?.let { subTextStrings.add(subattributionLabel) } - proxyPackageLabel?.let { subTextStrings.add(it) } - durationSummary?.let { subTextStrings.add(it) } - return when (subTextStrings.size) { - 3 -> - context.getString( - R.string.history_preference_subtext_3, - subTextStrings[0], - subTextStrings[1], - subTextStrings[2] - ) - 2 -> - context.getString( - R.string.history_preference_subtext_2, - subTextStrings[0], - subTextStrings[1] - ) - 1 -> subTextStrings[0] - else -> null - } - } - - /** - * Builds a list of [AppPermissionTimelineUsage] from the provided - * [AppPermissionUsage.GroupUsage]. - */ - private fun getAppPermissionTimelineUsages( - app: PermissionApp, - groupUsage: AppPermissionUsage.GroupUsage? - ): List { - if (groupUsage == null) { - return listOf() - } - - if (shouldShowSubattributionForApp(app.appInfo)) { - return groupUsage.attributionLabelledGroupUsages.map { - AppPermissionTimelineUsage(permissionGroup, app, it, it.label) - } - } - - return listOf( - AppPermissionTimelineUsage(permissionGroup, app, groupUsage, Resources.ID_NULL) - ) - } - - /** Extracts to a set all the permission groups declared by the platform. */ - private fun List.extractAllPlatformAppPermissionGroups(): - Set = - this.flatMap { it.groupUsages } - .map { it.group } - .filter { PermissionMapping.isPlatformPermissionGroup(it.name) } - .toSet() - - /** Initialize all relevant [TimeFilterItemMs] values. */ - private fun initializeTimeFilterItems(context: Context) { - mTimeFilterItemMs.add( - TimeFilterItemMs(Long.MAX_VALUE, context.getString(R.string.permission_usage_any_time)) - ) - mTimeFilterItemMs.add( - TimeFilterItemMs( - DAYS.toMillis(7), - StringUtils.getIcuPluralsString(context, R.string.permission_usage_last_n_days, 7) - ) - ) - mTimeFilterItemMs.add( - TimeFilterItemMs( - DAYS.toMillis(1), - StringUtils.getIcuPluralsString(context, R.string.permission_usage_last_n_days, 1) - ) - ) - - // TODO: theianchen add code for filtering by time here. - } - - /** Data used to create a preference for an app's permission usage. */ - data class HistoryPreferenceData( - val userHandle: UserHandle, - val pkgName: String, - val appIcon: Drawable?, - val preferenceTitle: String, - val permissionGroup: String, - val accessStartTime: Long, - val accessEndTime: Long, - val summaryText: CharSequence?, - val showingAttribution: Boolean, - val attributionTags: ArrayList, - val sessionId: Long - ) - - /** - * A class representing a given time, e.g., "in the last hour". - * - * @param timeMs the time represented by this object in milliseconds. - * @param label the label to describe the timeframe - */ - data class TimeFilterItemMs(val timeMs: Long, val label: String) - - /** - * Class containing all the information needed by the permission usage details fragments to - * render UI. - */ - inner class PermissionUsageDetailsUiData( - /** List of [PermissionApp] instances */ - // Note that these are used only to cache app data for the permission usage details - // fragment, and have no bearing on the UI on the main permission usage page. - val permissionApps: List, - /** Whether to show the "show/hide system" toggle. */ - val shouldDisplayShowSystemToggle: Boolean, - /** [DiscreteAccessClusterData] instances ordered for display in UI */ - private val discreteAccessClusterDataList: List, - ) { - // Note that the HistoryPreferenceData are not initialized within the - // PermissionUsageDetailsUiData instance as the need to be constructed only after the - // calling fragment loads the necessary PermissionApp instances. We will attempt to remove - // this dependency in b/240978905. - /** Builds a list of [HistoryPreferenceData] to be displayed in the UI. */ - fun getHistoryPreferenceDataList(): List { - return discreteAccessClusterDataList.map { - this@PermissionUsageDetailsViewModelLegacy.getHistoryPreferenceData(it) - } - } - } - - /** - * Data class representing a cluster of accesses, to be represented as a single entry in the UI. - */ - data class DiscreteAccessClusterData( - val appPermissionTimelineUsage: AppPermissionTimelineUsage, - val discreteAccessDataList: List - ) - - /** Data class representing a discrete permission access. */ - data class DiscreteAccessData( - val accessTimeMs: Long, - val accessDurationMs: Long, - val proxy: AppOpsManager.OpEventProxyInfo? - ) - - /** Data class representing an app's permissions usages for a particular permission group. */ - data class AppPermissionTimelineUsage( - /** Permission group whose usage is being tracked. */ - val permissionGroup: String, - // we need a PermissionApp because the loader takes the PermissionApp - // object and loads the icon and label information asynchronously - /** App whose permissions are being tracked. */ - val permissionApp: PermissionApp, - /** Timeline usage for the given app and permission. */ - val timelineUsage: TimelineUsage, - val label: Int - ) { - val attributionTags: java.util.ArrayList - get() = ArrayList(timelineUsage.attributionTags) - } -} - -/** Factory for an [PermissionUsageDetailsViewModelLegacy] */ -@RequiresApi(Build.VERSION_CODES.S) -class PermissionUsageDetailsViewModelFactoryLegacy( - private val application: Application, - private val roleManager: RoleManager, - private val filterGroup: String, - private val sessionId: Long -) : ViewModelProvider.Factory { - - override fun create(modelClass: Class): T { - @Suppress("UNCHECKED_CAST") - return PermissionUsageDetailsViewModelLegacy( - application, - roleManager, - filterGroup, - sessionId - ) - as T - } -} -- cgit v1.2.3-59-g8ed1b From 624e540b673c08665ebc18ea19be625538500535 Mon Sep 17 00:00:00 2001 From: Kevin Han Date: Wed, 18 Sep 2024 23:26:58 +0000 Subject: Exempt caller apps from hibernation Exempt caller apps that need to receive notifications in order to receive calls for their calling functionality. See go/hibernation-caller-app-exemption LOW_COVERAGE_REASON=b/369899444 Bug: 369445204 Test: manual (set up Meet, ran hibernation job, confirmed Meet was not hibernated) Relnote: Exempt caller type apps from hibernation Flag: EXEMPT bugfix + low risk Change-Id: Ib5bb9f197d9c9851541ad38005373d40f4d439e8 --- .../hibernation/HibernationPolicy.kt | 31 +++++++++++ .../mocking/hibernation/HibernationPolicyTest.kt | 60 +++++++++++++++++++++- 2 files changed, 90 insertions(+), 1 deletion(-) (limited to 'PermissionController/src') diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt index d23225ed3..8b11036e8 100644 --- a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt +++ b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt @@ -73,6 +73,7 @@ import android.service.dreams.DreamService import android.service.notification.NotificationListenerService import android.service.voice.VoiceInteractionService import android.service.wallpaper.WallpaperService +import android.telecom.TelecomManager import android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS import android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS import android.util.Log @@ -658,6 +659,36 @@ suspend fun isPackageHibernationExemptBySystem( return true } + // Note that it's fine to check permissions instead of app ops as all these permissions were + // introduced before auto-revoke / hibernation in R. + val hasCallRelatedPermissions = + context.checkPermission(Manifest.permission.MANAGE_OWN_CALLS, -1 /* pid */, pkg.uid) == + PERMISSION_GRANTED + && context.checkPermission(Manifest.permission.RECORD_AUDIO, -1 /* pid */, pkg.uid) == + PERMISSION_GRANTED + && context.checkPermission(Manifest.permission.WRITE_CALL_LOG, -1 /* pid */, pkg.uid) == + PERMISSION_GRANTED + if (hasCallRelatedPermissions) { + val phoneAccounts = context.getSystemService(TelecomManager::class.java)!! + .selfManagedPhoneAccounts + var hasRegisteredPhoneAccount = false + for (phoneAccount in phoneAccounts) { + if (pkg.packageName == phoneAccount.componentName.packageName) { + hasRegisteredPhoneAccount = true + break + } + } + if (hasRegisteredPhoneAccount) { + if (DEBUG_HIBERNATION_POLICY) { + DumpableLog.i( + LOG_TAG, + "Exempted ${pkg.packageName} - caller app" + ) + } + return true + } + } + if (SdkLevel.isAtLeastS()) { val hasInstallOrUpdatePermissions = context.checkPermission(Manifest.permission.INSTALL_PACKAGES, -1 /* pid */, pkg.uid) == diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationPolicyTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationPolicyTest.kt index 99aa4baa9..0d89e0501 100644 --- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationPolicyTest.kt +++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationPolicyTest.kt @@ -16,19 +16,27 @@ package com.android.permissioncontroller.tests.mocking.hibernation +import android.Manifest import android.app.job.JobScheduler +import android.content.ComponentName import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.content.pm.PackageManager.PERMISSION_GRANTED import android.database.ContentObserver import android.net.Uri +import android.os.Binder import android.os.Build import android.os.SystemClock +import android.os.UserHandle import android.os.UserManager import android.preference.PreferenceManager import android.provider.DeviceConfig import android.provider.Settings +import android.telecom.PhoneAccountHandle +import android.telecom.TelecomManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress @@ -37,12 +45,16 @@ import com.android.permissioncontroller.Constants import com.android.permissioncontroller.PermissionControllerApplication import com.android.permissioncontroller.hibernation.HibernationBroadcastReceiver import com.android.permissioncontroller.hibernation.ONE_DAY_MS -import com.android.permissioncontroller.hibernation.PREF_KEY_SYSTEM_TIME_SNAPSHOT import com.android.permissioncontroller.hibernation.PREF_KEY_ELAPSED_REALTIME_SNAPSHOT import com.android.permissioncontroller.hibernation.PREF_KEY_START_TIME_OF_UNUSED_APP_TRACKING +import com.android.permissioncontroller.hibernation.PREF_KEY_SYSTEM_TIME_SNAPSHOT import com.android.permissioncontroller.hibernation.SNAPSHOT_UNINITIALIZED import com.android.permissioncontroller.hibernation.getStartTimeOfUnusedAppTracking +import com.android.permissioncontroller.hibernation.isPackageHibernationExemptBySystem +import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo +import com.android.permissioncontroller.permission.utils.ContextCompat import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before import org.junit.Test @@ -71,11 +83,14 @@ class HibernationPolicyTest { private val application = Mockito.mock(PermissionControllerApplication::class.java) private const val USER_SETUP_INCOMPLETE = 0 private const val USER_SETUP_COMPLETE = 1 + private const val TEST_PKG_NAME = "test.package" } @Mock lateinit var jobScheduler: JobScheduler @Mock lateinit var context: Context @Mock lateinit var userManager: UserManager + @Mock lateinit var packageManager: PackageManager + @Mock lateinit var telecomManager: TelecomManager @Mock lateinit var contentResolver: ContentResolver private lateinit var realContext: Context @@ -83,6 +98,7 @@ class HibernationPolicyTest { private lateinit var sharedPreferences: SharedPreferences private lateinit var mockitoSession: MockitoSession private lateinit var filesDir: File + private lateinit var userHandle: UserHandle @Before fun setup() { @@ -100,11 +116,15 @@ class HibernationPolicyTest { `when`(Settings.Secure.getUriFor(any())).thenReturn(Mockito.mock(Uri::class.java)) realContext = ApplicationProvider.getApplicationContext() + userHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid()) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(realContext.applicationContext) `when`(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPreferences) `when`(context.getSystemService(UserManager::class.java)).thenReturn(userManager) + `when`(application.getSystemService(TelecomManager::class.java)).thenReturn(telecomManager) + `when`(application.packageManager).thenReturn(packageManager) + `when`(application.applicationContext).thenReturn(context) filesDir = realContext.cacheDir `when`(application.filesDir).thenReturn(filesDir) @@ -207,6 +227,25 @@ class HibernationPolicyTest { .isNotEqualTo(systemTimeSnapshot) } + @Test + fun isPackageExemptBySystem_isCallingApp_returnsTrue() = runBlocking { + val pkgInfo = makePackageInfo(TEST_PKG_NAME) + + `when`(context.checkPermission( + eq(Manifest.permission.MANAGE_OWN_CALLS), anyInt(), eq(pkgInfo.uid))) + .thenReturn(PERMISSION_GRANTED) + `when`(context.checkPermission( + eq(Manifest.permission.RECORD_AUDIO), anyInt(), eq(pkgInfo.uid))) + .thenReturn(PERMISSION_GRANTED) + `when`(context.checkPermission( + eq(Manifest.permission.WRITE_CALL_LOG), anyInt(), eq(pkgInfo.uid))) + .thenReturn(PERMISSION_GRANTED) + `when`(telecomManager.selfManagedPhoneAccounts).thenReturn( + listOf(PhoneAccountHandle(ComponentName(TEST_PKG_NAME, "Service"), "id"))) + + assertThat(isPackageHibernationExemptBySystem(pkgInfo, userHandle)).isTrue() + } + private fun assertAdjustedTime(systemTimeSnapshot: Long, realtimeSnapshot: Long) { val newStartTimeOfUnusedAppTracking = sharedPreferences.getLong( @@ -223,4 +262,23 @@ class HibernationPolicyTest { assertThat(newRealtimeSnapshot).isNotEqualTo(SNAPSHOT_UNINITIALIZED) assertThat(newRealtimeSnapshot).isAtLeast(realtimeSnapshot) } + + private fun makePackageInfo(packageName: String): LightPackageInfo { + return LightPackageInfo( + packageName, + emptyList(), + emptyList(), + emptyList(), + 0 /* uid */, + Build.VERSION_CODES.CUR_DEVELOPMENT, + false /* isInstantApp */, + true /* enabled */, + 0 /* appFlags */, + 0 /* firstInstallTime */, + 0 /* lastUpdateTime */, + false /* areAttributionsUserVisible */, + emptyMap() /* attributionTagsToLabels */, + ContextCompat.DEVICE_ID_DEFAULT + ) + } } -- cgit v1.2.3-59-g8ed1b From 27884097f7a2c40efb6a3f385d2550bb349f2b83 Mon Sep 17 00:00:00 2001 From: mrulhania Date: Thu, 26 Sep 2024 05:30:44 -0700 Subject: Show attribution label from location provider only on permission timeline page Bug: 369606734 Test: atest PermissionHistoryTest Test: atest PermissionControllerMockingTests FLAG: com.android.permission.flags.permission_timeline_attribution_label_fix Relnote: bug fix Change-Id: Ic3f9256d3fef0c19f557142f332364fbe0aadab3 --- .../v31/GetPermissionGroupUsageDetailsUseCase.kt | 88 +++++---- .../GetPermissionGroupUsageDetailsUseCaseTest.kt | 206 +++++++++++++-------- flags/Android.bp | 1 + flags/flags.aconfig | 9 + 4 files changed, 192 insertions(+), 112 deletions(-) (limited to 'PermissionController/src') diff --git a/PermissionController/src/com/android/permissioncontroller/permission/domain/usecase/v31/GetPermissionGroupUsageDetailsUseCase.kt b/PermissionController/src/com/android/permissioncontroller/permission/domain/usecase/v31/GetPermissionGroupUsageDetailsUseCase.kt index 16eaaf6f2..edf6e22e5 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/domain/usecase/v31/GetPermissionGroupUsageDetailsUseCase.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/domain/usecase/v31/GetPermissionGroupUsageDetailsUseCase.kt @@ -21,6 +21,7 @@ import android.app.AppOpsManager import android.os.UserHandle import android.permission.flags.Flags import com.android.modules.utils.build.SdkLevel +import com.android.permissioncontroller.PermissionControllerApplication import com.android.permissioncontroller.appops.data.model.v31.DiscretePackageOpsModel import com.android.permissioncontroller.appops.data.model.v31.DiscretePackageOpsModel.DiscreteOpModel import com.android.permissioncontroller.appops.data.repository.v31.AppOpRepository @@ -29,6 +30,7 @@ import com.android.permissioncontroller.permission.domain.model.v31.PermissionTi import com.android.permissioncontroller.permission.domain.model.v31.PermissionTimelineUsageModelWrapper import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.Companion.CLUSTER_SPACING_MINUTES import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.Companion.ONE_MINUTE_MS +import com.android.permissioncontroller.permission.utils.LocationUtils import com.android.permissioncontroller.permission.utils.PermissionMapping import com.android.permissioncontroller.pm.data.repository.v31.PackageRepository import com.android.permissioncontroller.role.data.repository.v31.RoleRepository @@ -72,7 +74,7 @@ class GetPermissionGroupUsageDetailsUseCase( permissionGroup, packageOps.userId, permissionRepository, - packageRepository + packageRepository, ) packageOps } @@ -86,43 +88,61 @@ class GetPermissionGroupUsageDetailsUseCase( } } + // show attribution on T+ for location provider only.. + private fun shouldShowAttributionLabel(packageName: String): Boolean { + return if (com.android.permission.flags.Flags.permissionTimelineAttributionLabelFix()) { + SdkLevel.isAtLeastT() && + LocationUtils.isLocationProvider(PermissionControllerApplication.get(), packageName) + } else true + } + /** Group app op accesses by attribution label if it is available and user visible. */ private suspend fun List.groupByAttributionLabelIfNeeded() = map { packageOps -> - val attributionInfo = - packageRepository.getPackageAttributionInfo( - packageOps.packageName, - UserHandle.of(packageOps.userId) - ) - if (attributionInfo != null) { - if (attributionInfo.areUserVisible && attributionInfo.tagResourceMap != null) { - val attributionLabelOpsMap: Map> = - packageOps.appOpAccesses - .map { appOpEntry -> - val resourceId = - attributionInfo.tagResourceMap[appOpEntry.attributionTag] - val label = attributionInfo.resourceLabelMap?.get(resourceId) - label to appOpEntry - } - .groupBy { labelAppOpEntryPair -> labelAppOpEntryPair.first } - .mapValues { (_, values) -> - values.map { labelAppOpEntryPair -> labelAppOpEntryPair.second } - } + if (!shouldShowAttributionLabel(packageOps.packageName)) { + listOf(packageOps) + } else { + val attributionInfo = + packageRepository.getPackageAttributionInfo( + packageOps.packageName, + UserHandle.of(packageOps.userId), + ) + if (attributionInfo != null) { + if ( + attributionInfo.areUserVisible && attributionInfo.tagResourceMap != null + ) { + val attributionLabelOpsMap: Map> = + packageOps.appOpAccesses + .map { appOpEntry -> + val resourceId = + attributionInfo.tagResourceMap[ + appOpEntry.attributionTag] + val label = + attributionInfo.resourceLabelMap?.get(resourceId) + label to appOpEntry + } + .groupBy { labelAppOpEntryPair -> labelAppOpEntryPair.first } + .mapValues { (_, values) -> + values.map { labelAppOpEntryPair -> + labelAppOpEntryPair.second + } + } - attributionLabelOpsMap.map { labelAppOpsEntry -> - DiscretePackageOpsModel( - packageOps.packageName, - packageOps.userId, - appOpAccesses = labelAppOpsEntry.value, - attributionLabel = labelAppOpsEntry.key, - isUserSensitive = packageOps.isUserSensitive, - ) + attributionLabelOpsMap.map { labelAppOpsEntry -> + DiscretePackageOpsModel( + packageOps.packageName, + packageOps.userId, + appOpAccesses = labelAppOpsEntry.value, + attributionLabel = labelAppOpsEntry.key, + isUserSensitive = packageOps.isUserSensitive, + ) + } + } else { + listOf(packageOps) } } else { listOf(packageOps) } - } else { - listOf(packageOps) } } .flatten() @@ -147,7 +167,7 @@ class GetPermissionGroupUsageDetailsUseCase( packageOps.userId, currentCluster.toMutableList(), packageOps.attributionLabel, - packageOps.isUserSensitive + packageOps.isUserSensitive, ) ) currentCluster.clear() @@ -164,7 +184,7 @@ class GetPermissionGroupUsageDetailsUseCase( packageOps.userId, currentCluster.toMutableList(), packageOps.attributionLabel, - packageOps.isUserSensitive + packageOps.isUserSensitive, ) ) } @@ -220,7 +240,7 @@ class GetPermissionGroupUsageDetailsUseCase( private fun canAccessBeAddedToCluster( currentAccess: DiscreteOpModel, - clusteredAccesses: List + clusteredAccesses: List, ): Boolean { val clusterOp = clusteredAccesses.last().opName if ( @@ -282,7 +302,7 @@ class GetPermissionGroupUsageDetailsUseCase( listOf( Manifest.permission_group.CAMERA, Manifest.permission_group.LOCATION, - Manifest.permission_group.MICROPHONE + Manifest.permission_group.MICROPHONE, ) permissionGroups.forEach { permissionGroup -> val opNames = diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/domain/usecase/GetPermissionGroupUsageDetailsUseCaseTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/domain/usecase/GetPermissionGroupUsageDetailsUseCaseTest.kt index da2d05f63..c2040f76b 100644 --- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/domain/usecase/GetPermissionGroupUsageDetailsUseCaseTest.kt +++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/domain/usecase/GetPermissionGroupUsageDetailsUseCaseTest.kt @@ -34,6 +34,7 @@ import com.android.permissioncontroller.permission.domain.model.v31.PermissionTi import com.android.permissioncontroller.permission.domain.model.v31.PermissionTimelineUsageModelWrapper import com.android.permissioncontroller.permission.domain.usecase.v31.GetPermissionGroupUsageDetailsUseCase import com.android.permissioncontroller.permission.domain.usecase.v31.TELECOM_PACKAGE +import com.android.permissioncontroller.permission.utils.LocationUtils import com.android.permissioncontroller.pm.data.model.v31.PackageAttributionModel import com.android.permissioncontroller.pm.data.model.v31.PackageInfoModel import com.android.permissioncontroller.pm.data.repository.v31.PackageRepository @@ -56,9 +57,11 @@ import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations import org.mockito.MockitoSession @@ -88,11 +91,13 @@ class GetPermissionGroupUsageDetailsUseCaseTest { mockitoSession = ExtendedMockito.mockitoSession() .mockStatic(PermissionControllerApplication::class.java) + .mockStatic(LocationUtils::class.java) .strictness(Strictness.LENIENT) .startMocking() whenever(PermissionControllerApplication.get()).thenReturn(application) whenever(application.applicationContext).thenReturn(context) + whenever(LocationUtils.isLocationProvider(Mockito.any(), Mockito.any())).thenReturn(false) packageInfos = mapOf( @@ -102,7 +107,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { systemPackageName to getPackageInfoModel( systemPackageName, - applicationFlags = ApplicationInfo.FLAG_SYSTEM + applicationFlags = ApplicationInfo.FLAG_SYSTEM, ), ) .toMutableMap() @@ -124,7 +129,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { emit( listOf( DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), - DiscretePackageOpsModel(guestUserPkgName, guestUser.identifier, appOpEvents) + DiscretePackageOpsModel(guestUserPkgName, guestUser.identifier, appOpEvents), ) ) } @@ -150,11 +155,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { emit( listOf( DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), - DiscretePackageOpsModel( - testPackageName, - privateProfile.identifier, - appOpEvents - ), + DiscretePackageOpsModel(testPackageName, privateProfile.identifier, appOpEvents), ) ) } @@ -168,8 +169,8 @@ class GetPermissionGroupUsageDetailsUseCaseTest { currentUserProfiles = listOf(currentUser.identifier, privateProfile.identifier), quietUserProfiles = listOf(privateProfile.identifier), - showInQuiteModeProfiles = listOf(privateProfile.identifier) - ) + showInQuiteModeProfiles = listOf(privateProfile.identifier), + ), ) val permissionTimelineUsages = getResult(underTest, this) @@ -190,11 +191,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { emit( listOf( DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), - DiscretePackageOpsModel( - testPackageName, - privateProfile.identifier, - appOpEvents - ), + DiscretePackageOpsModel(testPackageName, privateProfile.identifier, appOpEvents), ) ) } @@ -208,7 +205,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { currentUserProfiles = listOf(currentUser.identifier, privateProfile.identifier), quietUserProfiles = listOf(privateProfile.identifier), - ) + ), ) val permissionTimelineUsages = getResult(underTest, this) @@ -254,7 +251,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -280,7 +277,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -303,23 +300,23 @@ class GetPermissionGroupUsageDetailsUseCaseTest { DiscreteOpModel( AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(1), - MINUTES.toMillis(1) + MINUTES.toMillis(1), ), DiscreteOpModel( AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(2), - MINUTES.toMillis(1) + MINUTES.toMillis(1), ), DiscreteOpModel( AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(3), - MINUTES.toMillis(2) + MINUTES.toMillis(2), ), ) val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -344,23 +341,19 @@ class GetPermissionGroupUsageDetailsUseCaseTest { DiscreteOpModel( AppOpsManager.OPSTR_FINE_LOCATION, hours3 + MINUTES.toMillis(59), - -1 + -1, ), DiscreteOpModel( AppOpsManager.OPSTR_FINE_LOCATION, hours4 + MINUTES.toMillis(0), - -1 - ), - DiscreteOpModel( - AppOpsManager.OPSTR_FINE_LOCATION, - hours4 + MINUTES.toMillis(1), - -1 + -1, ), + DiscreteOpModel(AppOpsManager.OPSTR_FINE_LOCATION, hours4 + MINUTES.toMillis(1), -1), ) val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -383,18 +376,18 @@ class GetPermissionGroupUsageDetailsUseCaseTest { DiscreteOpModel( AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(1), - MINUTES.toMillis(3) + MINUTES.toMillis(3), ), DiscreteOpModel( AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(3), - MINUTES.toMillis(2) + MINUTES.toMillis(2), ), ) val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -420,7 +413,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -448,18 +441,18 @@ class GetPermissionGroupUsageDetailsUseCaseTest { DiscreteOpModel( AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(1), - MINUTES.toMillis(3) + MINUTES.toMillis(3), ), DiscreteOpModel( AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(5), - MINUTES.toMillis(5) + MINUTES.toMillis(5), ), ) val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -483,13 +476,11 @@ class GetPermissionGroupUsageDetailsUseCaseTest { @Test fun singleDiscreteAccess() = runTest { val appOpEvents = - listOf( - DiscreteOpModel(AppOpsManager.OPSTR_FINE_LOCATION, MINUTES.toMillis(1), -1), - ) + listOf(DiscreteOpModel(AppOpsManager.OPSTR_FINE_LOCATION, MINUTES.toMillis(1), -1)) val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -512,13 +503,13 @@ class GetPermissionGroupUsageDetailsUseCaseTest { DiscreteOpModel( AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(1), - MINUTES.toMillis(3) - ), + MINUTES.toMillis(3), + ) ) val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -541,29 +532,29 @@ class GetPermissionGroupUsageDetailsUseCaseTest { DiscreteOpModel( AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(1), - MINUTES.toMillis(2) + MINUTES.toMillis(2), ), // This entry says the camera was accessed for 15 minutes starting at minute 3 DiscreteOpModel( AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(3), - MINUTES.toMillis(15) + MINUTES.toMillis(15), ), DiscreteOpModel( AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(4), - MINUTES.toMillis(1) + MINUTES.toMillis(1), ), DiscreteOpModel( AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(6), - MINUTES.toMillis(1) + MINUTES.toMillis(1), ), ) val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -582,9 +573,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { @Test fun verifyUserSensitiveFlags() = runTest { val appOpEvents = - listOf( - DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(1), -1), - ) + listOf(DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(1), -1)) val discretePackageOps = flow { emit( listOf( @@ -620,13 +609,11 @@ class GetPermissionGroupUsageDetailsUseCaseTest { @Test fun verifyNotUserSensitiveFlagsForSystemPackage() = runTest { val appOpEvents = - listOf( - DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(1), -1), - ) + listOf(DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(1), -1)) val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(systemPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(systemPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -651,14 +638,12 @@ class GetPermissionGroupUsageDetailsUseCaseTest { @Test fun verifyCameraUserSensitiveFlagsForTelecomPackage() = runTest { val appOpEvents = - listOf( - DiscreteOpModel(AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(1), -1), - ) + listOf(DiscreteOpModel(AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(1), -1)) packageInfos[TELECOM_PACKAGE] = getPackageInfoModel(TELECOM_PACKAGE) val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(TELECOM_PACKAGE, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(TELECOM_PACKAGE, currentUser.identifier, appOpEvents) ) ) } @@ -683,14 +668,12 @@ class GetPermissionGroupUsageDetailsUseCaseTest { @Test fun verifyLocationUserSensitiveFlagsForTelecomPackage() = runTest { val appOpEvents = - listOf( - DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(1), -1), - ) + listOf(DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(1), -1)) packageInfos[TELECOM_PACKAGE] = getPackageInfoModel(TELECOM_PACKAGE) val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(TELECOM_PACKAGE, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(TELECOM_PACKAGE, currentUser.identifier, appOpEvents) ) ) } @@ -714,7 +697,74 @@ class GetPermissionGroupUsageDetailsUseCaseTest { } @Test - fun verifyAttributionTagsAreGroupedAndClustered() = runTest { + @RequiresFlagsEnabled( + com.android.permission.flags.Flags.FLAG_PERMISSION_TIMELINE_ATTRIBUTION_LABEL_FIX + ) + @Ignore("b/365004787") + fun verifyAccessIsNotGroupedByAttributionLabelAndClustered() = runTest { + // The package is not a location provider. + val appOpEvents = + listOf( + // These entries should be clustered even though they have + // different attribution labels. + DiscreteOpModel( + AppOpsManager.OPSTR_COARSE_LOCATION, + MINUTES.toMillis(1), + -1, + "tag1", + ), + DiscreteOpModel( + AppOpsManager.OPSTR_COARSE_LOCATION, + MINUTES.toMillis(2), + -1, + "tag1", + ), + DiscreteOpModel( + AppOpsManager.OPSTR_COARSE_LOCATION, + MINUTES.toMillis(3), + -1, + "tag3", + ), + DiscreteOpModel( + AppOpsManager.OPSTR_COARSE_LOCATION, + MINUTES.toMillis(4), + -1, + "tag2", + ), + ) + val discretePackageOps = flow { + emit( + listOf( + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) + ) + ) + } + val packageAttributions = mutableMapOf() + // tag1 and tag3 refers to same attribution label. + val attributionTagToLabelRes = mapOf("tag1" to 100, "tag2" to 200, "tag3" to 100) + val attributionsMap = mapOf(100 to "Tag1 Label", 200 to "Tag2 Label") + packageAttributions[testPackageName] = + PackageAttributionModel( + testPackageName, + true, + attributionTagToLabelRes, + attributionsMap, + ) + + val underTest = + getPermissionGroupUsageDetailsUseCase( + LOCATION_PERMISSION_GROUP, + discretePackageOps, + packageRepository = FakePackageRepository(packageInfos, packageAttributions), + ) + val permissionTimelineUsages = getResult(underTest, this) + + Truth.assertThat(permissionTimelineUsages.size).isEqualTo(1) + } + + @Test + fun verifyAccessIsGroupedByAttributionLabelAndClustered() = runTest { + whenever(LocationUtils.isLocationProvider(Mockito.any(), Mockito.any())).thenReturn(true) val appOpEvents = listOf( // These 3 entries should be grouped and clustered. @@ -722,38 +772,38 @@ class GetPermissionGroupUsageDetailsUseCaseTest { AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(1), -1, - "tag1" + "tag1", ), DiscreteOpModel( AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(2), -1, - "tag1" + "tag1", ), DiscreteOpModel( AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(3), -1, - "tag3" + "tag3", ), // The access at minute 4 should not be grouped or clustered. DiscreteOpModel( AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(4), -1, - "tag2" + "tag2", ), DiscreteOpModel( AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(8), -1, - "tag2" + "tag2", ), ) val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -766,14 +816,14 @@ class GetPermissionGroupUsageDetailsUseCaseTest { testPackageName, true, attributionTagToLabelRes, - attributionsMap + attributionsMap, ) val underTest = getPermissionGroupUsageDetailsUseCase( LOCATION_PERMISSION_GROUP, discretePackageOps, - packageRepository = FakePackageRepository(packageInfos, packageAttributions) + packageRepository = FakePackageRepository(packageInfos, packageAttributions), ) val permissionTimelineUsages = getResult(underTest, this) @@ -805,7 +855,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -829,7 +879,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { val discretePackageOps = flow { emit( listOf( - DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents), + DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents) ) ) } @@ -843,7 +893,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { private fun TestScope.getResult( useCase: GetPermissionGroupUsageDetailsUseCase, - coroutineScope: CoroutineScope + coroutineScope: CoroutineScope, ): List { val usages by collectLastValue(useCase(coroutineScope)) return (usages as PermissionTimelineUsageModelWrapper.Success).timelineUsageModels @@ -854,7 +904,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { discreteUsageFlow: Flow>, permissionFlags: Map = emptyMap(), userRepository: UserRepository = FakeUserRepository(listOf(currentUser.identifier)), - packageRepository: PackageRepository = FakePackageRepository(packageInfos) + packageRepository: PackageRepository = FakePackageRepository(packageInfos), ): GetPermissionGroupUsageDetailsUseCase { val permissionRepository = FakePermissionRepository(permissionFlags) val appOpUsageRepository = FakeAppOpRepository(emptyFlow(), discreteUsageFlow) @@ -865,7 +915,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { permissionRepository, appOpUsageRepository, roleRepository, - userRepository + userRepository, ) } @@ -877,7 +927,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest { listOf( PackageInfo.REQUESTED_PERMISSION_GRANTED, PackageInfo.REQUESTED_PERMISSION_GRANTED, - PackageInfo.REQUESTED_PERMISSION_GRANTED + PackageInfo.REQUESTED_PERMISSION_GRANTED, ), applicationFlags: Int = 0, ) = PackageInfoModel(packageName, requestedPermissions, permissionsFlags, applicationFlags) diff --git a/flags/Android.bp b/flags/Android.bp index aba1e44a9..d22da26c3 100644 --- a/flags/Android.bp +++ b/flags/Android.bp @@ -36,6 +36,7 @@ java_aconfig_library { libs: ["framework-configinfrastructure.stubs.module_lib"], visibility: [ "//packages/modules/Permission:__subpackages__", + "//vendor:__subpackages__", ], apex_available: [ "com.android.permission", diff --git a/flags/flags.aconfig b/flags/flags.aconfig index c94614654..5f3d1cc93 100644 --- a/flags/flags.aconfig +++ b/flags/flags.aconfig @@ -108,3 +108,12 @@ flag { bug: "349675008" is_fixed_read_only: true } + +flag { + name: "permission_timeline_attribution_label_fix" + is_exported: true + namespace: "permissions" + description: "This flag is used for the attribution label fix on permission timeline page" + bug: "369606734" + is_fixed_read_only: true +} -- cgit v1.2.3-59-g8ed1b From f9014b933cd9907d33d460a046588e93e4cd9994 Mon Sep 17 00:00:00 2001 From: vignesh ramanathan Date: Thu, 5 Sep 2024 15:59:23 +0000 Subject: Reinstating capitalization of the subtitle text in permission controller screens Original CL ag/27365424 is overwritten by ag/27344529 Relnote: "Capitalizing subtitles" Fixes: b/298106886 Flag: EXEMPT bugfix After Capitalization : https://screenshot.googleplex.com/6N98SzhgYfXvp4L Test: Manually tested. Existing test passes. LOW_COVERAGE_REASON=OTHER_FORM_FACTOR Change-Id: Ib1703b6b8256d5f061bc93c5c377ca20854be0be --- .../permission/ui/wear/elements/AnnotatedText.kt | 57 +++++++++++++++------- .../ui/wear/elements/ScrollableScreen.kt | 1 + 2 files changed, 41 insertions(+), 17 deletions(-) (limited to 'PermissionController/src') diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AnnotatedText.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AnnotatedText.kt index bcdf3b661..34c7cee9a 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AnnotatedText.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AnnotatedText.kt @@ -32,47 +32,70 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextDecoration import androidx.wear.compose.material.MaterialTheme +import com.android.permissioncontroller.permission.ui.wear.WearUtils.capitalize const val CLICKABLE_SPAN_TAG = "CLICKABLE_SPAN_TAG" @Composable -fun AnnotatedText(text: CharSequence, style: TextStyle, modifier: Modifier = Modifier) { +fun AnnotatedText( + text: CharSequence, + style: TextStyle, + modifier: Modifier = Modifier, + shouldCapitalize: Boolean +) { val onClickCallbacks = mutableMapOf Unit>() val context = LocalContext.current val listener = LinkInteractionListener { if (it is LinkAnnotation.Clickable) { - onClickCallbacks.get(it.tag)?.invoke(View(context)) + onClickCallbacks[it.tag]?.invoke(View(context)) } } val annotatedString = - spannableStringToAnnotatedString(text, onClickCallbacks, listener = listener) + spannableStringToAnnotatedString( + text, + shouldCapitalize, + onClickCallbacks, + listener = listener + ) BasicText(text = annotatedString, style = style, modifier = modifier) } @Composable private fun spannableStringToAnnotatedString( text: CharSequence, + shouldCapitalize: Boolean, onClickCallbacks: MutableMap Unit>, spanColor: Color = MaterialTheme.colors.primary, listener: LinkInteractionListener -) = - if (text is Spanned) { - buildAnnotatedString { - append((text.toString())) - for (span in text.getSpans(0, text.length, Any::class.java)) { - val start = text.getSpanStart(span) - val end = text.getSpanEnd(span) - when (span) { - is ClickableSpan -> - addClickableSpan(span, spanColor, start, end, onClickCallbacks, listener) - else -> addStyle(SpanStyle(), start, end) +): AnnotatedString { + val finalString = if (shouldCapitalize) text.toString().capitalize() else text.toString() + val annotatedString = + if (text is Spanned) { + buildAnnotatedString { + append(finalString) + for (span in text.getSpans(0, text.length, Any::class.java)) { + val start = text.getSpanStart(span) + val end = text.getSpanEnd(span) + when (span) { + is ClickableSpan -> + addClickableSpan( + span, + spanColor, + start, + end, + onClickCallbacks, + listener + ) + else -> addStyle(SpanStyle(), start, end) + } } } + } else { + AnnotatedString(text.toString()) } - } else { - AnnotatedString(text.toString()) - } + return annotatedString +} private fun AnnotatedString.Builder.addClickableSpan( span: ClickableSpan, diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt index 53013def7..d8f340a7b 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt @@ -282,6 +282,7 @@ internal fun Scaffold( color = MaterialTheme.colors.onSurfaceVariant ), modifier = modifier, + shouldCapitalize = true ) } } -- cgit v1.2.3-59-g8ed1b From ed4b834dabf0a1e88fdb610c67ba7a4900cd6a27 Mon Sep 17 00:00:00 2001 From: Kevin Han Date: Fri, 27 Sep 2024 00:10:39 +0000 Subject: Add mocking tests to hibernation TEST_MAPPING postsubmit Add hibernation unit tests to the test mapping for hibernation files in postsubmit. If SLOs are met, it will be upgrades to presubmit which should help for incremental coverage detection. LOW_COVERAGE_REASON=NON_CODE_ONLY Bug: 369899444 Test: presubmit Flag: EXEMPT only changing test mapping Relnote: N/A Change-Id: Ifbf7f65d15a1d55c554de873fc29edab7ee3ecc7 --- .../com/android/permissioncontroller/hibernation/TEST_MAPPING | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'PermissionController/src') diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING index 69a8f74be..038b2f992 100644 --- a/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING +++ b/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING @@ -11,5 +11,15 @@ } ] } + ], + "postsubmit": [ + { + "name": "PermissionControllerMockingTests", + "options": [ + { + "include-filter": "com.android.permissioncontroller.tests.mocking.hibernation" + } + ] + } ] } -- cgit v1.2.3-59-g8ed1b From fb61df7a52acf1a33d3d5e3a4f56740d594a7a75 Mon Sep 17 00:00:00 2001 From: Jay Thomas Sullivan Date: Thu, 19 Sep 2024 16:42:40 -0700 Subject: [BC25] Add overlay styles for two preferences Add overlay styles for the following preferences: - PermissionSelectorWithWidgetPreference - PermissionTwoTargetPreference We're currently using these preferences from SettingsLib, but we have no way to overlay its styles in a mainline module. So, copy the layouts into our mainline module and give them overlayable styles. Also, create a new preference: - AppPermissionFooterLinkPreference This is a clone of PermissionPreference (which is a styleable clone of Preference), with the elements given new, distinct styles, to allow OEMs to customize these separately. Bug: 349675008 Test: atest CtsPermissionUiTestCases Flag: com.android.permission.flags.app_permission_fragment_uses_preferences Relnote: N/A Change-Id: I3588fa3ad9121b0cbc3b2d37296aef0b45acacbe NO_IFTTT=this only affects the new fragment --- .../app_permission_footer_link_preference.xml | 34 ++++ .../permission_preference_selector_with_widget.xml | 69 +++++++ .../permission_preference_two_target.xml | 60 ++++++ .../permission_preference_widget_radiobutton.xml | 24 +++ PermissionController/res/values-v35/styles.xml | 216 +++++++++++++++++++++ PermissionController/res/values/bools.xml | 1 + PermissionController/res/values/overlayable.xml | 29 +++ .../res/xml-v35/app_permission.xml | 102 ++++++++++ PermissionController/res/xml/app_permission.xml | 104 ---------- .../compat/AppPermissionFragmentCompat.java | 8 +- .../ui/handheld/AppPermissionWrapperFragment.java | 2 +- .../ui/handheld/PermissionFooterPreference.kt | 9 +- .../permission/ui/handheld/PermissionPreference.kt | 19 +- .../ui/handheld/PermissionPreferenceCategory.kt | 19 +- .../PermissionSelectorWithWidgetPreference.kt | 97 --------- .../ui/handheld/PermissionTwoTargetPreference.kt | 101 ---------- .../handheld/v35/SectionPreferenceGroupAdapter.kt | 6 +- .../v36/AppPermissionFooterLinkPreference.kt | 50 +++++ .../ui/handheld/v36/AppPermissionFragment.java | 13 +- .../v36/PermissionSelectorWithWidgetPreference.kt | 103 ++++++++++ .../handheld/v36/PermissionTwoTargetPreference.kt | 105 ++++++++++ 21 files changed, 852 insertions(+), 319 deletions(-) create mode 100644 PermissionController/res/layout-v35/app_permission_footer_link_preference.xml create mode 100644 PermissionController/res/layout-v35/permission_preference_selector_with_widget.xml create mode 100644 PermissionController/res/layout-v35/permission_preference_two_target.xml create mode 100644 PermissionController/res/layout-v35/permission_preference_widget_radiobutton.xml create mode 100644 PermissionController/res/xml-v35/app_permission.xml delete mode 100644 PermissionController/res/xml/app_permission.xml delete mode 100644 PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionSelectorWithWidgetPreference.kt delete mode 100644 PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionTwoTargetPreference.kt create mode 100644 PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/AppPermissionFooterLinkPreference.kt create mode 100644 PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/PermissionSelectorWithWidgetPreference.kt create mode 100644 PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/PermissionTwoTargetPreference.kt (limited to 'PermissionController/src') diff --git a/PermissionController/res/layout-v35/app_permission_footer_link_preference.xml b/PermissionController/res/layout-v35/app_permission_footer_link_preference.xml new file mode 100644 index 000000000..75381e762 --- /dev/null +++ b/PermissionController/res/layout-v35/app_permission_footer_link_preference.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + diff --git a/PermissionController/res/layout-v35/permission_preference_selector_with_widget.xml b/PermissionController/res/layout-v35/permission_preference_selector_with_widget.xml new file mode 100644 index 000000000..230e51fc3 --- /dev/null +++ b/PermissionController/res/layout-v35/permission_preference_selector_with_widget.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PermissionController/res/layout-v35/permission_preference_two_target.xml b/PermissionController/res/layout-v35/permission_preference_two_target.xml new file mode 100644 index 000000000..906393b3c --- /dev/null +++ b/PermissionController/res/layout-v35/permission_preference_two_target.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PermissionController/res/layout-v35/permission_preference_widget_radiobutton.xml b/PermissionController/res/layout-v35/permission_preference_widget_radiobutton.xml new file mode 100644 index 000000000..10e311110 --- /dev/null +++ b/PermissionController/res/layout-v35/permission_preference_widget_radiobutton.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/PermissionController/res/values-v35/styles.xml b/PermissionController/res/values-v35/styles.xml index 612125fb1..513754bb8 100644 --- a/PermissionController/res/values-v35/styles.xml +++ b/PermissionController/res/values-v35/styles.xml @@ -197,5 +197,221 @@ gone + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PermissionController/res/values-v33/themes.xml b/PermissionController/res/values-v33/themes.xml index 82a8ef5b6..a3d1b16c3 100644 --- a/PermissionController/res/values-v33/themes.xml +++ b/PermissionController/res/values-v33/themes.xml @@ -57,6 +57,8 @@ @style/SecondarySafetyCenterActionButton.Fixed + @dimen/sc_spacing_large + @color/sc_primary_action_button_text ?android:attr/textColorPrimary @@ -69,6 +71,10 @@ + \ No newline at end of file diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java index 3ab4faa66..54cb86ff3 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java @@ -26,7 +26,7 @@ import android.view.ViewGroup; import androidx.annotation.RequiresApi; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceViewHolder; -import androidx.preference.SwitchPreference; +import androidx.preference.SwitchPreferenceCompat; import com.android.permissioncontroller.R; import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsViewModel; @@ -38,7 +38,7 @@ import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsVie * method will not register any changes while it appears disabled. */ @RequiresApi(TIRAMISU) -public class ClickableDisabledSwitchPreference extends SwitchPreference { +public class ClickableDisabledSwitchPreference extends SwitchPreferenceCompat { private boolean mAppearDisabled; diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java index 7622270b9..88759797e 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java @@ -52,6 +52,7 @@ import androidx.preference.PreferenceViewHolder; import com.android.modules.utils.build.SdkLevel; import com.android.permissioncontroller.R; import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel; +import com.android.settingslib.widget.GroupSectionDividerMixin; import com.google.android.material.button.MaterialButton; import com.google.android.material.shape.AbsoluteCornerSize; @@ -62,7 +63,8 @@ import java.util.Objects; /** A preference that displays a card representing a {@link SafetyCenterIssue}. */ @RequiresApi(TIRAMISU) -public class IssueCardPreference extends Preference implements ComparablePreference { +public class IssueCardPreference extends Preference + implements ComparablePreference, GroupSectionDividerMixin { public static final String TAG = IssueCardPreference.class.getSimpleName(); @@ -101,12 +103,13 @@ public class IssueCardPreference extends Preference implements ComparablePrefere public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); - holder.itemView.setBackgroundResource(mPositionInCardList.getBackgroundDrawableResId()); + View issueCardView = holder.itemView.requireViewById(R.id.issue_card); + issueCardView.setBackgroundResource(mPositionInCardList.getBackgroundDrawableResId()); int topMargin = getTopMargin(mPositionInCardList, getContext()); - MarginLayoutParams layoutParams = (MarginLayoutParams) holder.itemView.getLayoutParams(); + MarginLayoutParams layoutParams = (MarginLayoutParams) issueCardView.getLayoutParams(); if (layoutParams.topMargin != topMargin) { layoutParams.topMargin = topMargin; - holder.itemView.setLayoutParams(layoutParams); + issueCardView.setLayoutParams(layoutParams); } // Set default group visibility in case view is being reused @@ -202,19 +205,20 @@ public class IssueCardPreference extends Preference implements ComparablePrefere private void configureSafetyProtectionView(PreferenceViewHolder holder) { View safetyProtectionSectionView = holder.findViewById(R.id.issue_card_protected_by_android); + View issueCard = holder.findViewById(R.id.issue_card); if (safetyProtectionSectionView.getVisibility() == View.GONE) { - holder.itemView.setPaddingRelative( - holder.itemView.getPaddingStart(), - holder.itemView.getPaddingTop(), - holder.itemView.getPaddingEnd(), + issueCard.setPaddingRelative( + issueCard.getPaddingStart(), + issueCard.getPaddingTop(), + issueCard.getPaddingEnd(), /* bottom= */ getContext() .getResources() .getDimensionPixelSize(R.dimen.sc_card_margin_bottom)); } else { - holder.itemView.setPaddingRelative( - holder.itemView.getPaddingStart(), - holder.itemView.getPaddingTop(), - holder.itemView.getPaddingEnd(), + issueCard.setPaddingRelative( + issueCard.getPaddingStart(), + issueCard.getPaddingTop(), + issueCard.getPaddingEnd(), /* bottom= */ 0); } } @@ -427,9 +431,7 @@ public class IssueCardPreference extends Preference implements ComparablePrefere TypedValue buttonThemeValue = new TypedValue(); mContext.getTheme() .resolveAttribute( - R.attr.scActionButtonTheme, - buttonThemeValue, - /* resolveRefs= */ false); + R.attr.scActionButtonTheme, buttonThemeValue, /* resolveRefs= */ false); mContextThemeWrapper = new ContextThemeWrapper(context, buttonThemeValue.data); } diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt index a63e19984..5c86b4515 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt @@ -24,6 +24,7 @@ import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import com.android.permissioncontroller.R import com.android.permissioncontroller.safetycenter.ui.view.MoreIssuesHeaderView +import com.android.settingslib.widget.GroupSectionDividerMixin /** A preference that displays a card linking to a list of more {@link SafetyCenterIssue}. */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) @@ -34,8 +35,8 @@ internal class MoreIssuesCardPreference( private var newMoreIssuesCardData: MoreIssuesCardData, private val dismissedOnly: Boolean, val isStaticHeader: Boolean, - private val onClickListener: () -> Unit -) : Preference(context), ComparablePreference { + private val onClickListener: () -> Unit, +) : Preference(context), ComparablePreference, GroupSectionDividerMixin { init { layoutResource = R.layout.preference_more_issues_card @@ -44,11 +45,12 @@ internal class MoreIssuesCardPreference( override fun onBindViewHolder(holder: PreferenceViewHolder) { super.onBindViewHolder(holder) - val issueHeaderView = holder.itemView as MoreIssuesHeaderView + val issueHeaderView = + holder.itemView.requireViewById(R.id.more_issues_card) if (isStaticHeader) { issueHeaderView.showStaticHeader( context.getString(R.string.safety_center_dismissed_issues_card_title), - newMoreIssuesCardData.severityLevel + newMoreIssuesCardData.severityLevel, ) } else { issueHeaderView.showExpandableHeader( @@ -62,7 +64,7 @@ internal class MoreIssuesCardPreference( } ), overrideChevronIconResId, - onClickListener + onClickListener, ) } } @@ -94,5 +96,5 @@ internal class MoreIssuesCardPreference( internal data class MoreIssuesCardData( val severityLevel: Int, val hiddenIssueCount: Int, - val isExpanded: Boolean + val isExpanded: Boolean, ) diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt index 57e4175ca..c5287af53 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt @@ -28,11 +28,12 @@ import androidx.preference.PreferenceViewHolder import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID import com.android.permissioncontroller.R import com.android.permissioncontroller.safetycenter.SafetyCenterConstants +import com.android.settingslib.widget.GroupSectionDividerMixin /** A preference that displays the Security and Privacy brand name on a Safety Center subpage. */ @RequiresApi(UPSIDE_DOWN_CAKE) internal class SafetyBrandChipPreference(context: Context, attrs: AttributeSet) : - Preference(context, attrs) { + Preference(context, attrs), GroupSectionDividerMixin { init { setLayoutResource(R.layout.preference_brand_chip) @@ -67,7 +68,7 @@ internal class SafetyBrandChipPreference(context: Context, attrs: AttributeSet) fun closeSubpage( fragmentActivity: FragmentActivity, fragmentContext: Context, - sessionId: Long + sessionId: Long, ) { val openedFromHomepage = fragmentActivity diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java index c6f2d146f..04206479f 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java @@ -29,6 +29,8 @@ import static com.android.permissioncontroller.safetycenter.SafetyCenterConstant import static com.android.permissioncontroller.safetycenter.SafetyCenterConstants.PRIVATE_PROFILE_SUFFIX; import static com.android.permissioncontroller.safetycenter.SafetyCenterConstants.WORK_PROFILE_SUFFIX; +import static java.util.Objects.requireNonNull; + import android.app.ActionBar; import android.content.Intent; import android.content.res.Configuration; @@ -60,6 +62,7 @@ import com.android.permissioncontroller.permission.utils.Utils; import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsViewModel.Pref; import com.android.settingslib.activityembedding.ActivityEmbeddingUtils; import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; +import com.android.settingslib.widget.SettingsThemeHelper; import java.util.List; import java.util.Objects; @@ -78,10 +81,10 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity { private static final String EXTRA_PREVENT_TRAMPOLINE_TO_SETTINGS = "com.android.permissioncontroller.safetycenter.extra.PREVENT_TRAMPOLINE_TO_SETTINGS"; - private SafetyCenterManager mSafetyCenterManager; + @Nullable private SafetyCenterManager mSafetyCenterManager; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSafetyCenterManager = getSystemService(SafetyCenterManager.class); @@ -93,18 +96,25 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity { return; } + if (SettingsThemeHelper.isExpressiveTheme(this)) { + // Setting a theme programmatically causes standard preferences to display weirdly. + // See b/377519324. + setTheme(R.style.Theme_SafetyCenterExpressive); + } + Fragment frag; + Intent intent = getIntent(); final boolean maybeOpenSubpage = SafetyCenterUiFlags.getShowSubpages() - && getIntent().getAction().equals(ACTION_SAFETY_CENTER); - if (maybeOpenSubpage && getIntent().hasExtra(EXTRA_SAFETY_SOURCES_GROUP_ID)) { - String groupId = getIntent().getStringExtra(EXTRA_SAFETY_SOURCES_GROUP_ID); + && Objects.equals(intent.getAction(), ACTION_SAFETY_CENTER); + if (maybeOpenSubpage && intent.hasExtra(EXTRA_SAFETY_SOURCES_GROUP_ID)) { + String groupId = intent.getStringExtra(EXTRA_SAFETY_SOURCES_GROUP_ID); frag = openRelevantSubpage(groupId); - } else if (maybeOpenSubpage && getIntent().hasExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY)) { - String preferenceKey = getIntent().getStringExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY); + } else if (maybeOpenSubpage && intent.hasExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY)) { + String preferenceKey = intent.getStringExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY); String groupId = getParentGroupId(preferenceKey); frag = openRelevantSubpage(groupId); - } else if (getIntent().getAction().equals(PRIVACY_CONTROLS_ACTION)) { + } else if (Objects.equals(intent.getAction(), PRIVACY_CONTROLS_ACTION)) { setTitle(R.string.privacy_controls_title); frag = PrivacyControlsFragment.newInstance(); } else { @@ -306,7 +316,8 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity { return PRIVACY_SOURCES_GROUP_ID; } - SafetyCenterConfig safetyCenterConfig = mSafetyCenterManager.getSafetyCenterConfig(); + SafetyCenterConfig safetyCenterConfig = + requireNonNull(mSafetyCenterManager).getSafetyCenterConfig(); String[] splitKey; if (preferenceKey.endsWith(PERSONAL_PROFILE_SUFFIX)) { splitKey = preferenceKey.split("_" + PERSONAL_PROFILE_SUFFIX); diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java index efbd57080..ed6bc382c 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java @@ -56,6 +56,7 @@ import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData; import com.android.safetycenter.internaldata.SafetyCenterBundles; import com.android.safetycenter.resources.SafetyCenterResourcesApk; +import com.android.settingslib.widget.SettingsThemeHelper; import kotlin.Unit; @@ -121,12 +122,16 @@ public final class SafetyCenterDashboardFragment extends SafetyCenterFragment { mEntriesGroup = getPreferenceScreen().findPreference(ENTRIES_GROUP_KEY); mStaticEntriesGroup = getPreferenceScreen().findPreference(STATIC_ENTRIES_GROUP_KEY); + Preference spacerPreference = getPreferenceScreen().findPreference(SPACER_KEY); + if (SettingsThemeHelper.isExpressiveTheme(requireContext())) { + getPreferenceScreen().removePreference(spacerPreference); + } + if (mIsQuickSettingsFragment) { getPreferenceScreen().removePreference(mEntriesGroup); mEntriesGroup = null; getPreferenceScreen().removePreference(mStaticEntriesGroup); mStaticEntriesGroup = null; - Preference spacerPreference = getPreferenceScreen().findPreference(SPACER_KEY); getPreferenceScreen().removePreference(spacerPreference); } getSafetyCenterViewModel().getStatusUiLiveData().observe(this, this::updateStatus); diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt index 9feecf5d4..04503de5e 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt @@ -22,7 +22,6 @@ import android.safetycenter.SafetyCenterErrorDetails import android.widget.Toast import androidx.annotation.RequiresApi import androidx.lifecycle.ViewModelProvider -import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceScreen import androidx.recyclerview.widget.RecyclerView import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID @@ -33,10 +32,12 @@ import com.android.permissioncontroller.safetycenter.ui.model.LiveSafetyCenterVi import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel import com.android.safetycenter.resources.SafetyCenterResourcesApk +import com.android.settingslib.widget.SettingsBasePreferenceFragment +import com.android.settingslib.widget.SettingsThemeHelper /** A base fragment that represents a page in Safety Center. */ @RequiresApi(TIRAMISU) -abstract class SafetyCenterFragment : PreferenceFragmentCompat() { +abstract class SafetyCenterFragment : SettingsBasePreferenceFragment() { lateinit var safetyCenterViewModel: SafetyCenterViewModel lateinit var sameTaskSourceIds: List @@ -51,12 +52,17 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() { override fun onCreateAdapter( preferenceScreen: PreferenceScreen - ): RecyclerView.Adapter { + ): RecyclerView.Adapter { /* The scroll-to-result functionality for settings search is currently implemented only for * subpages i.e. non expand-and-collapse type entries. Hence, we check that the flag is * enabled before using an adapter that does the highlighting and scrolling. */ - val adapter: RecyclerView.Adapter = - if (SafetyCenterUiFlags.getShowSubpages()) { + val adapter: RecyclerView.Adapter = + if ( + SafetyCenterUiFlags.getShowSubpages() && + !SettingsThemeHelper.isExpressiveTheme(requireContext()) + ) { + // TODO: b/378433878 - Create highlight adapter for settings expressive theme, which + // has a different base class. highlightManager.createAdapter(preferenceScreen) } else { super.onCreateAdapter(preferenceScreen) @@ -80,7 +86,7 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() { safetyCenterViewModel = ViewModelProvider( requireActivity(), - LiveSafetyCenterViewModelFactory(requireActivity().getApplication()) + LiveSafetyCenterViewModelFactory(requireActivity().getApplication()), ) .get(SafetyCenterViewModel::class.java) safetyCenterViewModel.safetyCenterUiLiveData.observe(this) { uiData: SafetyCenterUiData? -> @@ -177,7 +183,7 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() { safetyCenterViewModel.interactionLogger.recordForIssue( Action.SAFETY_CENTER_VIEWED, maybeIssue, - isDismissed = false + isDismissed = false, ) } } diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java index 2ad282449..d9f45cc08 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java @@ -24,7 +24,9 @@ import android.permission.PermissionManager; import androidx.fragment.app.FragmentActivity; import com.android.modules.utils.build.SdkLevel; +import com.android.permissioncontroller.R; import com.android.permissioncontroller.permission.utils.Utils; +import com.android.settingslib.widget.SettingsThemeHelper; /** Activity for the Safety Center Quick Settings Activity */ public class SafetyCenterQsActivity extends FragmentActivity { @@ -39,6 +41,12 @@ public class SafetyCenterQsActivity extends FragmentActivity { return; } + if (SettingsThemeHelper.isExpressiveTheme(this)) { + // Safe to set expressive theme here since QS doesn't display vanilla preferences. + // See b/377519324. + setTheme(R.style.Theme_SafetyCenterQsExpressive); + } + configureFragment(); } diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt index fdade2189..2e5ba49ae 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt @@ -28,6 +28,7 @@ import com.android.permissioncontroller.safetycenter.ui.SafetyBrandChipPreferenc import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData import com.android.safetycenter.resources.SafetyCenterResourcesApk import com.android.settingslib.widget.FooterPreference +import com.android.settingslib.widget.SettingsThemeHelper /** A fragment that represents a generic subpage in Safety Center. */ @RequiresApi(UPSIDE_DOWN_CAKE) @@ -45,15 +46,16 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() { setPreferencesFromResource(R.xml.safety_center_subpage, rootKey) sourceGroupId = requireArguments().getString(SOURCE_GROUP_ID_KEY)!! - subpageBrandChip = getPreferenceScreen().findPreference(BRAND_CHIP_KEY)!! - subpageIllustration = getPreferenceScreen().findPreference(ILLUSTRATION_KEY)!! - subpageIssueGroup = getPreferenceScreen().findPreference(ISSUE_GROUP_KEY)!! - subpageEntryGroup = getPreferenceScreen().findPreference(ENTRY_GROUP_KEY)!! - subpageFooter = getPreferenceScreen().findPreference(FOOTER_KEY)!! + subpageBrandChip = preferenceScreen.findPreference(BRAND_CHIP_KEY)!! + subpageIllustration = preferenceScreen.findPreference(ILLUSTRATION_KEY)!! + subpageIssueGroup = preferenceScreen.findPreference(ISSUE_GROUP_KEY)!! + subpageEntryGroup = preferenceScreen.findPreference(ENTRY_GROUP_KEY)!! + subpageFooter = preferenceScreen.findPreference(FOOTER_KEY)!! subpageBrandChip.setupListener(requireActivity(), safetyCenterSessionId) setupIllustration() setupFooter() + maybeRemoveSpacer() prerenderCurrentSafetyCenterData() } @@ -80,7 +82,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() { return } - requireActivity().setTitle(entryGroup.title) + requireActivity().title = entryGroup.title updateSafetyCenterIssues(uiData) updateSafetyCenterEntries(entryGroup) } @@ -91,7 +93,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() { val drawable = SafetyCenterResourcesApk(context).getDrawableByName(resName, context.theme) if (drawable == null) { Log.w(TAG, "$sourceGroupId doesn't have any matching illustration") - subpageIllustration.setVisible(false) + subpageIllustration.isVisible = false } subpageIllustration.illustrationDrawable = drawable @@ -102,12 +104,19 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() { val footerText = SafetyCenterResourcesApk(requireContext()).getStringByName(resName) if (footerText.isEmpty()) { Log.w(TAG, "$sourceGroupId doesn't have any matching footer") - subpageFooter.setVisible(false) + subpageFooter.isVisible = false } // footer is ordered last by default // in order to keep a spacer after the footer, footer needs to be the second from last - subpageFooter.setOrder(Int.MAX_VALUE - 2) - subpageFooter.setSummary(footerText) + subpageFooter.order = Int.MAX_VALUE - 2 + subpageFooter.summary = footerText + } + + private fun maybeRemoveSpacer() { + if (SettingsThemeHelper.isExpressiveTheme(requireContext())) { + val spacerPreference = preferenceScreen.findPreference(SPACER_KEY)!! + preferenceScreen.removePreference(spacerPreference) + } } private fun updateSafetyCenterIssues(uiData: SafetyCenterUiData?) { @@ -131,7 +140,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() { subpageIssues, subpageDismissedIssues, uiData.resolvedIssues, - requireActivity().getTaskId() + requireActivity().taskId, ) } @@ -145,23 +154,24 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() { PendingIntentSender.getTaskIdForEntry( entry.id, sameTaskSourceIds, - requireActivity() + requireActivity(), ), entry, - safetyCenterViewModel + safetyCenterViewModel, ) ) } } companion object { - private val TAG: String = SafetyCenterSubpageFragment::class.java.simpleName - private const val BRAND_CHIP_KEY: String = "subpage_brand_chip" - private const val ILLUSTRATION_KEY: String = "subpage_illustration" - private const val ISSUE_GROUP_KEY: String = "subpage_issue_group" - private const val ENTRY_GROUP_KEY: String = "subpage_entry_group" - private const val FOOTER_KEY: String = "subpage_footer" - private const val SOURCE_GROUP_ID_KEY: String = "source_group_id" + private val TAG = SafetyCenterSubpageFragment::class.java.simpleName + private const val BRAND_CHIP_KEY = "subpage_brand_chip" + private const val ILLUSTRATION_KEY = "subpage_illustration" + private const val ISSUE_GROUP_KEY = "subpage_issue_group" + private const val ENTRY_GROUP_KEY = "subpage_entry_group" + private const val FOOTER_KEY = "subpage_footer" + private const val SPACER_KEY = "subpage_spacer" + private const val SOURCE_GROUP_ID_KEY = "source_group_id" /** Creates an instance of SafetyCenterSubpageFragment with the arguments set */ @JvmStatic diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt index 5acb27131..3976fa7e3 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt @@ -25,11 +25,12 @@ import androidx.annotation.RequiresApi import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import com.android.permissioncontroller.R +import com.android.settingslib.widget.GroupSectionDividerMixin /** A preference that displays the illustration on a Safety Center subpage. */ @RequiresApi(UPSIDE_DOWN_CAKE) internal class SafetyIllustrationPreference(context: Context, attrs: AttributeSet) : - Preference(context, attrs) { + Preference(context, attrs), GroupSectionDividerMixin { init { layoutResource = R.layout.preference_illustration diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java index 811841845..abf159955 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java @@ -40,6 +40,7 @@ import com.android.permissioncontroller.R; import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel; import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData; import com.android.permissioncontroller.safetycenter.ui.view.StatusCardView; +import com.android.settingslib.widget.GroupSectionDividerMixin; import kotlin.Pair; @@ -48,7 +49,8 @@ import java.util.Objects; /** Preference which displays a visual representation of {@link SafetyCenterStatus}. */ @RequiresApi(TIRAMISU) -public class SafetyStatusPreference extends Preference implements ComparablePreference { +public class SafetyStatusPreference extends Preference + implements ComparablePreference, GroupSectionDividerMixin { private static final String TAG = "SafetyStatusPreference"; @@ -82,7 +84,8 @@ public class SafetyStatusPreference extends Preference implements ComparablePref } Context context = getContext(); - StatusCardView statusCardView = (StatusCardView) holder.itemView; + StatusCardView statusCardView = holder.itemView.requireViewById(R.id.status_card); + configureButtons(context, statusCardView); statusCardView .getTitleAndSummaryContainerView() diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt index 030b67be9..7f619d1ca 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt @@ -55,6 +55,7 @@ internal class SpacerPreference(context: Context, attrs: AttributeSet) : } private var maxKnownToolbarHeight = 0 + override fun onBindViewHolder(holder: PreferenceViewHolder) { super.onBindViewHolder(holder) val spacer = holder.itemView @@ -74,7 +75,7 @@ internal class SpacerPreference(context: Context, attrs: AttributeSet) : oldLeft: Int, oldTop: Int, oldRight: Int, - oldBottom: Int + oldBottom: Int, ) { adjustHeight(spacer) } -- cgit v1.2.3-59-g8ed1b From 56274716ae402172a9b372867c3ac6962c3f6fbf Mon Sep 17 00:00:00 2001 From: Kasia Krejszeff Date: Thu, 14 Nov 2024 17:32:35 +0000 Subject: Use profile labels as strings for default app sections for private and managed profiles Test: manually before http://screen/5pPsvPmPrkggHFc.png, after http://screen/7Ui5ca2cs23LtA9.png Bug: 358369931 Flag: android.permission.flags.use_profile_labels_for_default_app_section_titles Change-Id: I91b2bc34bca37bf4d06105d5f301e7ad39afaa43 Relnote: Use profile labels as strings for default app sections for private and managed profiles --- .../permission/utils/Utils.java | 33 ++++++++++++++++++---- .../role/ui/DefaultAppListChildFragment.java | 18 ++++++++++-- 2 files changed, 43 insertions(+), 8 deletions(-) (limited to 'PermissionController/src') diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java index e5de63f32..3d3b47272 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java @@ -126,6 +126,7 @@ import java.util.List; import java.util.Locale; import java.util.Random; import java.util.Set; +import java.util.function.Supplier; public final class Utils { @@ -1566,18 +1567,40 @@ public final class Utils { public static String getEnterpriseString(@NonNull Context context, @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs) { return SdkLevel.isAtLeastT() - ? getUpdatableEnterpriseString( - context, updatableStringId, defaultStringId, formatArgs) + ? getUpdatableEnterpriseString(context, updatableStringId, + () -> context.getString(defaultStringId, formatArgs), formatArgs) : context.getString(defaultStringId, formatArgs); } + /** + * Selects the appropriate enterprise string for the provided resource ID and a fallback string + */ + @NonNull + public static String getEnterpriseString(@NonNull Context context, + @NonNull String updatableStringId, @NonNull String defaultString) { + return SdkLevel.isAtLeastT() + ? getUpdatableEnterpriseString(context, updatableStringId, () -> defaultString) + : defaultString; + } + @RequiresApi(Build.VERSION_CODES.TIRAMISU) @NonNull private static String getUpdatableEnterpriseString(@NonNull Context context, - @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs) { + @NonNull String updatableStringId, @NonNull Supplier defaultStringLoader, + @NonNull Object... formatArgs) { DevicePolicyManager dpm = getSystemServiceSafe(context, DevicePolicyManager.class); - return dpm.getResources().getString(updatableStringId, () -> context.getString( - defaultStringId, formatArgs), formatArgs); + return dpm.getResources().getString(updatableStringId, defaultStringLoader, formatArgs); + } + + /** + * Returns the profile label from the {@link UserManager} for the provided profile + */ + @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) + @NonNull + public static String getProfileLabel(@NonNull UserHandle profile, @NonNull Context context) { + Context profileContext = context.createContextAsUser(profile, 0); + UserManager profileUserManager = profileContext.getSystemService(UserManager.class); + return profileUserManager.getProfileLabel(); } /** diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java index 0b96eb8ba..48472bc5e 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java @@ -22,6 +22,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.os.Bundle; import android.os.UserHandle; +import android.permission.flags.Flags; import android.provider.Settings; import android.util.ArrayMap; @@ -36,6 +37,7 @@ import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; +import com.android.modules.utils.build.SdkLevel; import com.android.permissioncontroller.R; import com.android.permissioncontroller.permission.utils.Utils; import com.android.permissioncontroller.role.utils.PackageUtils; @@ -145,15 +147,25 @@ public class DefaultAppListChildFragment Date: Tue, 3 Dec 2024 15:48:29 -0800 Subject: Add in-call version of ECM dialog, remove new ECM api This dialog looks similar to the existing ECM dialog, but has a different message, about being blocked while in a phone call Also removes the ecm "isUnknownCallOngoing", in favor of using "isRestricted" Test: atest EnhancedConfirmationInCallTest Flag: android.permission.flags.unknown_call_package_install_blocking_enabled Relnote: android 25Q2 feature LOW_COVERAGE_REASON=FLAG_NOT_ENABLED Change-Id: I113114c15df3df483e290f0bab00f5cecb2b44f8 --- PermissionController/res/values/strings.xml | 7 +++ .../ecm/EnhancedConfirmationDialogActivity.kt | 59 +++++++++++++++------- framework-s/api/system-current.txt | 2 - .../app/ecm/EnhancedConfirmationManager.java | 34 +++---------- .../app/ecm/IEnhancedConfirmationManager.aidl | 3 -- .../android/ecm/EnhancedConfirmationService.java | 37 +++++++++++--- .../cts/EnhancedConfirmationInCallTest.kt | 54 +++++--------------- 7 files changed, 98 insertions(+), 98 deletions(-) (limited to 'PermissionController/src') diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml index 9fa361b57..82c7889c6 100644 --- a/PermissionController/res/values/strings.xml +++ b/PermissionController/res/values/strings.xml @@ -2016,6 +2016,13 @@ Allow %4$s to upload a bug repo For your security, this setting is currently unavailable. + + Action not available while on a phone call + + Allowing apps to install other apps is not allowed during a phone call.\n\n + Scammers often request this type of action during phone call conversations, so it\u2019s blocked to protect you. If you are being guided to take this action + by someone you don\u2019t know, it might be a scam. + App was denied access to %1$s diff --git a/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt b/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt index bc6f774ad..e6cf094e3 100644 --- a/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt +++ b/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt @@ -18,6 +18,7 @@ package com.android.permissioncontroller.ecm import android.annotation.SuppressLint import android.app.AlertDialog +import android.app.AppOpsManager import android.app.Dialog import android.app.ecm.EnhancedConfirmationManager import android.content.Context @@ -65,6 +66,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { finish() return } + if (savedInstanceState != null) { wasClearRestrictionAllowed = savedInstanceState.getBoolean(KEY_WAS_CLEAR_RESTRICTION_ALLOWED) @@ -79,11 +81,19 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { require(uid != Process.INVALID_UID) { "EXTRA_UID cannot be null or invalid" } require(!packageName.isNullOrEmpty()) { "EXTRA_PACKAGE_NAME cannot be null or empty" } require(!settingIdentifier.isNullOrEmpty()) { "EXTRA_SUBJECT cannot be null or empty" } - wasClearRestrictionAllowed = setClearRestrictionAllowed(packageName, UserHandle.getUserHandleForUid(uid)) val setting = Setting.fromIdentifier(this, settingIdentifier, isEcmInApp) + if ( + SettingType.fromIdentifier(this, settingIdentifier, isEcmInApp) == + SettingType.BLOCKED_DUE_TO_PHONE_STATE && + !Flags.unknownCallPackageInstallBlockingEnabled() + ) { + finish() + return + } + if (DeviceUtils.isWear(this)) { WearEnhancedConfirmationDialogFragment.newInstance(setting.title, setting.message) .show(supportFragmentManager, WearEnhancedConfirmationDialogFragment.TAG) @@ -116,7 +126,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { fun fromIdentifier( context: Context, settingIdentifier: String, - isEcmInApp: Boolean + isEcmInApp: Boolean, ): Setting { val settingType = SettingType.fromIdentifier(context, settingIdentifier, isEcmInApp) val label = @@ -124,7 +134,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { SettingType.PLATFORM_PERMISSION -> KotlinUtils.getPermGroupLabel( context, - PermissionMapping.getGroupOfPlatformPermission(settingIdentifier)!! + PermissionMapping.getGroupOfPlatformPermission(settingIdentifier)!!, ) SettingType.PLATFORM_PERMISSION_GROUP -> KotlinUtils.getPermGroupLabel(context, settingIdentifier) @@ -132,15 +142,22 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { context.getString( Roles.get(context)[settingIdentifier]!!.shortLabelResource ) + SettingType.BLOCKED_DUE_TO_PHONE_STATE, SettingType.OTHER -> null } - val url = - context.getString(R.string.help_url_action_disabled_by_restricted_settings) - return Setting( - title = settingType.titleRes?.let { context.getString(it, label) }, + var title: String? + var message: CharSequence? + if (settingType == SettingType.BLOCKED_DUE_TO_PHONE_STATE) { + title = settingType.titleRes?.let { context.getString(it) } + message = settingType.messageRes?.let { context.getString(it) } + } else { + val url = + context.getString(R.string.help_url_action_disabled_by_restricted_settings) + title = (settingType.titleRes?.let { context.getString(it, label) }) message = settingType.messageRes?.let { Html.fromHtml(context.getString(it, url), 0) } - ) + } + return Setting(title, message) } } } @@ -148,29 +165,35 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { private enum class SettingType(val titleRes: Int?, val messageRes: Int?) { PLATFORM_PERMISSION( R.string.enhanced_confirmation_dialog_title_permission, - R.string.enhanced_confirmation_dialog_desc_permission + R.string.enhanced_confirmation_dialog_desc_permission, ), PLATFORM_PERMISSION_GROUP( R.string.enhanced_confirmation_dialog_title_permission, - R.string.enhanced_confirmation_dialog_desc_permission + R.string.enhanced_confirmation_dialog_desc_permission, ), ROLE( R.string.enhanced_confirmation_dialog_title_role, - R.string.enhanced_confirmation_dialog_desc_role + R.string.enhanced_confirmation_dialog_desc_role, ), OTHER( R.string.enhanced_confirmation_dialog_title_settings_default, - R.string.enhanced_confirmation_dialog_desc_settings_default + R.string.enhanced_confirmation_dialog_desc_settings_default, + ), + BLOCKED_DUE_TO_PHONE_STATE( + R.string.enhanced_confirmation_phone_state_dialog_title, + R.string.enhanced_confirmation_phone_state_dialog_desc, ); companion object { fun fromIdentifier( context: Context, settingIdentifier: String, - isEcmInApp: Boolean + isEcmInApp: Boolean, ): SettingType { - if (!isEcmInApp) return SettingType.OTHER return when { + settingIdentifier == AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES -> + BLOCKED_DUE_TO_PHONE_STATE + !isEcmInApp -> OTHER PermissionMapping.isRuntimePlatformPermission(settingIdentifier) && PermissionMapping.getGroupOfPlatformPermission(settingIdentifier) != null -> PLATFORM_PERMISSION @@ -178,7 +201,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { PLATFORM_PERMISSION_GROUP settingIdentifier.startsWith("android.app.role.") && Roles.get(context).containsKey(settingIdentifier) -> ROLE - else -> SettingType.OTHER + else -> OTHER } } } @@ -188,7 +211,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { this.dialogResult = dialogResult setResult( RESULT_OK, - Intent().apply { putExtra(Intent.EXTRA_RETURN_RESULT, dialogResult.statsLogValue) } + Intent().apply { putExtra(Intent.EXTRA_RETURN_RESULT, dialogResult.statsLogValue) }, ) finish() } @@ -200,7 +223,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID), settingIdentifier = intent.getStringExtra(Intent.EXTRA_SUBJECT)!!, firstShowForApp = !wasClearRestrictionAllowed, - dialogResult = dialogResult + dialogResult = dialogResult, ) } } @@ -249,7 +272,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { private fun createDialogView( context: Context, title: String?, - message: CharSequence? + message: CharSequence?, ): View = LayoutInflater.from(context) .inflate(R.layout.enhanced_confirmation_dialog, null) diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt index efab125bd..fc06d6927 100644 --- a/framework-s/api/system-current.txt +++ b/framework-s/api/system-current.txt @@ -6,9 +6,7 @@ package android.app.ecm { method @NonNull public android.content.Intent createRestrictedSettingDialogIntent(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public boolean isClearRestrictionAllowed(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public boolean isRestricted(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - method @FlaggedApi("android.permission.flags.enhanced_confirmation_in_call_apis_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public boolean isUnknownCallOngoing(); method @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public void setClearRestrictionAllowed(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - field @FlaggedApi("android.permission.flags.enhanced_confirmation_in_call_apis_enabled") public static final String ACTION_SHOW_ECM_PHONE_STATE_BLOCKED_SETTING_DIALOG = "android.app.ecm.action.SHOW_ECM_PHONE_STATE_BLOCKED_SETTING_DIALOG"; field public static final String ACTION_SHOW_ECM_RESTRICTED_SETTING_DIALOG = "android.app.ecm.action.SHOW_ECM_RESTRICTED_SETTING_DIALOG"; } diff --git a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java index e497ba5f4..db05a0af6 100644 --- a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java +++ b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java @@ -42,7 +42,7 @@ import java.lang.annotation.Retention; * This class provides the core API for ECM (Enhanced Confirmation Mode). ECM is a feature that * restricts access to protected **settings** (i.e., sensitive resources) by restricted **apps** * (apps from from dangerous sources, such as sideloaded packages or packages downloaded from a web - * browser). + * browser), or restricts settings globally based on device state. * *

Specifically, this class provides the ability to: * @@ -70,6 +70,9 @@ import java.lang.annotation.Retention; * particular app restricted is an implementation detail of ECM. However, the user is able to * clear any restricted app's restriction status (i.e, un-restrict it), after which ECM will * consider the app **not restricted**. + *

  • A setting may be globally restricted based on device state. In this case, any app may be + * automatically considered *restricted*, regardless of the app's restriction state. Users + * cannot un-restrict the app, in these cases. * * * Why is ECM needed? Consider the following (pre-ECM) scenario: @@ -199,15 +202,6 @@ public final class EnhancedConfirmationManager { public static final String ACTION_SHOW_ECM_RESTRICTED_SETTING_DIALOG = "android.app.ecm.action.SHOW_ECM_RESTRICTED_SETTING_DIALOG"; - /** - * Shows a dialog indicating a setting has been blocked due to the phone state (such as being - * on a call with an unknown number). Opened when a setting is blocked. - */ - @SdkConstant(BROADCAST_INTENT_ACTION) - @FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED) - public static final String ACTION_SHOW_ECM_PHONE_STATE_BLOCKED_SETTING_DIALOG = - "android.app.ecm.action.SHOW_ECM_PHONE_STATE_BLOCKED_SETTING_DIALOG"; - /** A map of ECM states to their corresponding app op states */ @Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef(prefix = {"ECM_STATE_"}, value = {EcmState.ECM_STATE_NOT_GUARDED, @@ -321,6 +315,9 @@ public final class EnhancedConfirmationManager { *

    This should be called from the "Restricted setting" dialog (which {@link * #createRestrictedSettingDialogIntent} directs to) upon being presented to the user. * + *

    This restriction clearing does not apply to any settings that are restricted based on + * global device state + * * @param packageName package name of the application which should be considered acknowledged * @throws NameNotFoundException if the provided package was not found */ @@ -336,23 +333,6 @@ public final class EnhancedConfirmationManager { } } - /** - * Returns whether the enhanced confirmation system thinks a call with an unknown party is - * occurring - * - * @hide - */ - @SystemApi - @FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED) - @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) - public boolean isUnknownCallOngoing() { - try { - return mService.isUntrustedCallOngoing(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** * Gets an intent that will open the "Restricted setting" dialog for the specified package * and setting. diff --git a/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl b/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl index 833485890..5149daa49 100644 --- a/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl +++ b/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl @@ -30,7 +30,4 @@ interface IEnhancedConfirmationManager { boolean isClearRestrictionAllowed(in String packageName, int userId); void setClearRestrictionAllowed(in String packageName, int userId); - - boolean isUntrustedCallOngoing(); - } diff --git a/service/java/com/android/ecm/EnhancedConfirmationService.java b/service/java/com/android/ecm/EnhancedConfirmationService.java index dde9fe2fd..af2e81133 100644 --- a/service/java/com/android/ecm/EnhancedConfirmationService.java +++ b/service/java/com/android/ecm/EnhancedConfirmationService.java @@ -239,6 +239,10 @@ public class EnhancedConfirmationService extends SystemService { private static final ArraySet PROTECTED_SETTINGS = new ArraySet<>(); + // Settings restricted when an untrusted call is ongoing. These must also be added to + // PROTECTED_SETTINGS + private static final ArraySet UNTRUSTED_CALL_RESTRICTED_SETTINGS = new ArraySet<>(); + static { // Runtime permissions PROTECTED_SETTINGS.add(Manifest.permission.SEND_SMS); @@ -259,6 +263,13 @@ public class EnhancedConfirmationService extends SystemService { // Default application roles. PROTECTED_SETTINGS.add(RoleManager.ROLE_DIALER); PROTECTED_SETTINGS.add(RoleManager.ROLE_SMS); + + if (Flags.unknownCallPackageInstallBlockingEnabled()) { + // Requesting package installs, limited during phone calls + PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES); + UNTRUSTED_CALL_RESTRICTED_SETTINGS.add( + AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES); + } } private final @NonNull Context mContext; @@ -287,8 +298,13 @@ public class EnhancedConfirmationService extends SystemService { "settingIdentifier cannot be null or empty"); try { - return isSettingEcmProtected(settingIdentifier) && isPackageEcmGuarded(packageName, - userId); + if (!isSettingEcmProtected(settingIdentifier)) { + return false; + } + if (isSettingProtectedGlobally(settingIdentifier)) { + return true; + } + return isPackageEcmGuarded(packageName, userId); } catch (NameNotFoundException e) { throw new IllegalArgumentException(e); } @@ -351,10 +367,11 @@ public class EnhancedConfirmationService extends SystemService { } } - @Override - public boolean isUntrustedCallOngoing() { - enforcePermissions("isUntrustedCallOngoing", - UserHandle.getUserHandleForUid(Binder.getCallingUid()).getIdentifier()); + private boolean isUntrustedCallOngoing() { + if (!Flags.unknownCallPackageInstallBlockingEnabled()) { + return false; + } + if (hasCallOfType(CALL_TYPE_EMERGENCY)) { // If we have an emergency call, return false always. return false; @@ -496,6 +513,14 @@ public class EnhancedConfirmationService extends SystemService { return false; } + private boolean isSettingProtectedGlobally(@NonNull String settingIdentifier) { + if (UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)) { + return isUntrustedCallOngoing(); + } + + return false; + } + @Nullable private ApplicationInfo getApplicationInfoAsUser(@Nullable String packageName, @UserIdInt int userId) { diff --git a/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt index cbf4734d5..c410f9c9c 100644 --- a/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt +++ b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt @@ -16,7 +16,7 @@ package android.permissionui.cts -import android.Manifest +import android.app.AppOpsManager import android.app.Instrumentation import android.app.ecm.EnhancedConfirmationManager import android.content.ContentProviderOperation @@ -37,7 +37,6 @@ import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity -import java.util.concurrent.Callable import org.junit.After import org.junit.AfterClass import org.junit.Assert @@ -152,47 +151,18 @@ class EnhancedConfirmationInCallTest { addedContacts.keys.forEach { removeContact(it) } } - private fun getInUnknownCallState(): Boolean { - return callWithShellPermissionIdentity { ecm.isUnknownCallOngoing } - } - - @Test - fun testCannotReadOngoingState_WithoutPermission() { - try { - ecm.isUnknownCallOngoing - Assert.fail() - } catch (expected: SecurityException) { - Assert.assertTrue( - expected.message?.contains( - Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES - ) == true - ) + private fun isSettingRestricted(): Boolean { + return callWithShellPermissionIdentity { + ecm.isRestricted(context.packageName, AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES) } - - val unexpectedException = - callWithShellPermissionIdentity( - Callable { - try { - ecm.isUnknownCallOngoing - null - } catch (unexpected: SecurityException) { - // Catching the exception, because exceptions thrown inside - // run/callWithShellPermissionIdentity are obscured by the rethrow - // from run/call. - unexpected - } - }, - Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES, - ) - Assert.assertNull(unexpectedException) } @Test fun testIncomingCall_NonContact() { voipService.createCallAndWaitForActive(NON_CONTACT_DISPLAY_NAME, NON_CONTACT_PHONE_NUMBER) - Assert.assertTrue(getInUnknownCallState()) + Assert.assertTrue(isSettingRestricted()) voipService.endCallAndWaitForInactive() - Assert.assertFalse(getInUnknownCallState()) + Assert.assertFalse(isSettingRestricted()) } @Test @@ -200,9 +170,9 @@ class EnhancedConfirmationInCallTest { addContact(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER) // If no phone number is given, the display name will be checked voipService.createCallAndWaitForActive(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER) - Assert.assertFalse(getInUnknownCallState()) + Assert.assertFalse(isSettingRestricted()) voipService.endCallAndWaitForInactive() - Assert.assertFalse(getInUnknownCallState()) + Assert.assertFalse(isSettingRestricted()) } @Test @@ -210,9 +180,9 @@ class EnhancedConfirmationInCallTest { addContact(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER) // If the phone number matches, the display name is not checked voipService.createCallAndWaitForActive(NON_CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER) - Assert.assertFalse(getInUnknownCallState()) + Assert.assertFalse(isSettingRestricted()) voipService.endCallAndWaitForInactive() - Assert.assertFalse(getInUnknownCallState()) + Assert.assertFalse(isSettingRestricted()) } @Test @@ -222,10 +192,10 @@ class EnhancedConfirmationInCallTest { voipService.createCallAndWaitForActive(tempContactDisplay, tempContactPhone) addContact(tempContactDisplay, tempContactPhone) // State should not be recomputed just because the contact is newly added - Assert.assertTrue(getInUnknownCallState()) + Assert.assertTrue(isSettingRestricted()) voipService.endCallAndWaitForInactive() voipService.createCallAndWaitForActive(tempContactDisplay, tempContactPhone) // A new call should recognize our contact, and mark the call as trusted - Assert.assertFalse(getInUnknownCallState()) + Assert.assertFalse(isSettingRestricted()) } } -- cgit v1.2.3-59-g8ed1b