diff options
67 files changed, 1730 insertions, 819 deletions
@@ -1,3 +1,5 @@ +#Bug component: 137825 + include platform/frameworks/base:/core/java/android/permission/OWNERS include platform/packages/modules/common:/MODULES_OWNERS # see go/mainline-owners-policy diff --git a/PermissionController/Android.bp b/PermissionController/Android.bp index 892f12f2b..f02022bbb 100644 --- a/PermissionController/Android.bp +++ b/PermissionController/Android.bp @@ -152,6 +152,14 @@ android_app { "lottie", "safety-label", "role-controller", + "permissions-flags-lib", + "androidx.compose.runtime_runtime", + "androidx.annotation_annotation", + "androidx.compose.ui_ui", + "androidx.compose.foundation_foundation", + "androidx.wear.compose_compose-foundation", + "androidx.compose.material3_material3", + "androidx.activity_activity-compose", ], proto: { @@ -161,9 +169,11 @@ android_app { lint: { strict_updatability_linting: true, + error_checks: ["Recycle"], }, optimize: { + proguard_compatibility: false, // TODO(b/215530220): remove when this is default behavior proguard_flags_files: ["proguard.flags"], }, diff --git a/PermissionController/AndroidManifest.xml b/PermissionController/AndroidManifest.xml index 874ba35d6..794d111da 100644 --- a/PermissionController/AndroidManifest.xml +++ b/PermissionController/AndroidManifest.xml @@ -399,6 +399,7 @@ <activity android:name="com.android.permissioncontroller.role.ui.RequestRoleActivity" android:excludeFromRecents="true" android:exported="true" + android:launchMode="singleTop" android:theme="@style/RequestRole.FilterTouches"> <intent-filter android:priority="1"> <action android:name="android.app.role.action.REQUEST_ROLE" /> @@ -606,8 +607,6 @@ </intent-filter> </activity> - <!-- Unexported empty activity for in-process tests --> - <activity android:name="android.app.Activity" /> </application> </manifest> diff --git a/PermissionController/TEST_MAPPING b/PermissionController/TEST_MAPPING index 0ae3818fd..869bb2020 100644 --- a/PermissionController/TEST_MAPPING +++ b/PermissionController/TEST_MAPPING @@ -8,6 +8,9 @@ "options": [ { "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" } ], "file_patterns": ["res/xml/roles\\.xml"] @@ -23,7 +26,7 @@ ], "presubmit-large": [ { - "name": "CtsPermission3TestCases", + "name": "CtsPermissionUiTestCases", "options": [ { "exclude-annotation": "android.platform.test.annotations.FlakyTest" @@ -44,6 +47,9 @@ }, { "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" } ], "file_patterns": ["res/xml/roles\\.xml"] @@ -73,7 +79,7 @@ ] }, { - "name": "CtsPermission3TestCases[com.google.android.permission.apex]", + "name": "CtsPermissionUiTestCases[com.google.android.permission.apex]", "options": [ { "exclude-annotation": "android.platform.test.annotations.FlakyTest" @@ -81,6 +87,57 @@ ] } ], + "postsubmit": [ + { + "name": "CtsRoleTestCases", + "file_patterns": ["res/xml/roles\\.xml"] + }, + { + "name": "PermissionUiTestCases" + }, + { + "name": "CtsPermissionUiTestCases" + } + ], + "mainline-postsubmit": [ + { + "name": "CtsRoleTestCases[com.google.android.permission.apex]", + "options": [ + // TODO(b/238677748): These two tests currently fails on R base image + { + "exclude-filter": "android.app.role.cts.RoleManagerTest#openDefaultAppListThenIsNotDefaultAppInList" + }, + { + "exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked" + } + ], + "file_patterns": ["res/xml/roles\\.xml"] + }, + { + "name": "PermissionUiTestCases[com.google.android.permission.apex]", + "options": [ + // TODO(b/238773220): These tests currently fails on R base image + { + "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageCustomPermissionsFragmentTest#groupSummaryGetsUpdatedWhenPermissionGetsGranted" + }, + { + "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageCustomPermissionsFragmentTest#groupSummaryGetsUpdatedWhenPermissionGetsRevoked" + }, + { + "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageStandardPermissionsFragmentTest#additionalPermissionSummaryGetUpdateWhenAppGetsInstalled" + }, + { + "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageStandardPermissionsFragmentTest#additionalPermissionSummaryGetUpdateWhenDefinerGetsUninstalled" + }, + { + "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageStandardPermissionsFragmentTest#additionalPermissionSummaryGetUpdateWhenUserGetsUninstalled" + } + ] + }, + { + "name": "CtsPermissionUiTestCases[com.google.android.permission.apex]" + } + ], "imports": [ { "path": "vendor/xts/gts-tests/hostsidetests/permissioncontroller" diff --git a/PermissionController/lint-baseline.xml b/PermissionController/lint-baseline.xml index 05a307234..546ed596d 100644 --- a/PermissionController/lint-baseline.xml +++ b/PermissionController/lint-baseline.xml @@ -3,61 +3,6 @@ <issue id="NewApi" - message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessAt`" - errorLine1=" val attributedOpEntry: AttributedOpEntry = it.getDiscreteAccessAt(i)" - errorLine2=" ~~~~~~~~~~~~~~~~~~~"> - <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt" - line="191" - column="67"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessAt`" - errorLine1=" val opEntry: AttributedOpEntry = it.getDiscreteAccessAt(i)" - errorLine2=" ~~~~~~~~~~~~~~~~~~~"> - <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt" - line="156" - column="57"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessCount`" - errorLine1=" for (i in 0 until it.discreteAccessCount) {" - errorLine2=" ~~~~~~~~~~~~~~~~~~~"> - <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt" - line="155" - column="38"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessCount`" - errorLine1=" for (i in 0 until it.discreteAccessCount) {" - errorLine2=" ~~~~~~~~~~~~~~~~~~~"> - <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt" - line="190" - column="38"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOpsRequest.Builder#setHistoryFlags`" - errorLine1=" .setHistoryFlags(HISTORY_FLAG_DISCRETE or HISTORY_FLAG_GET_ATTRIBUTION_CHAINS)" - errorLine2=" ~~~~~~~~~~~~~~~"> - <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt" - line="101" - column="18"/> - </issue> - - <issue - id="NewApi" message="Call requires API level 31 (current min is 30): `android.apphibernation.AppHibernationManager#isHibernatingForUser`" errorLine1=" if (hibernationManager.isHibernatingForUser(pkg.packageName)) {" errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -180,17 +125,6 @@ <issue id="NewApi" message="Call requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterData#getIssues`" - errorLine1=" ) : this(safetyCenterData.status, hasIssues = safetyCenterData.issues.size > 0)" - errorLine2=" ~~~~~~"> - <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt" - line="18" - column="68"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterData#getIssues`" errorLine1=" issues" errorLine2=" ~~~~~~"> <location @@ -213,17 +147,6 @@ <issue id="NewApi" message="Call requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterData#getStatus`" - errorLine1=" ) : this(safetyCenterData.status, hasIssues = safetyCenterData.issues.size > 0)" - errorLine2=" ~~~~~~"> - <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt" - line="18" - column="31"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterData#getStatus`" errorLine1=" status.refreshStatus == SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS" errorLine2=" ~~~~~~"> <location @@ -356,17 +279,6 @@ <issue id="NewApi" message="Call requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterStatus#getRefreshStatus`" - errorLine1=" when (status.refreshStatus) {" - errorLine2=" ~~~~~~~~~~~~~"> - <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt" - line="65" - column="26"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterStatus#getRefreshStatus`" errorLine1=" status.refreshStatus == SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS" errorLine2=" ~~~~~~~~~~~~~"> <location @@ -421,39 +333,6 @@ <issue id="NewApi" - message="Call requires API level 34 (current min is 33): `getParentGroupId`" - errorLine1=" String groupId = getParentGroupId(preferenceKey);" - errorLine2=" ~~~~~~~~~~~~~~~~"> - <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java" - line="89" - column="30"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level 34 (current min is 33): `openRelevantSubpage`" - errorLine1=" frag = openRelevantSubpage(groupId);" - errorLine2=" ~~~~~~~~~~~~~~~~~~~"> - <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java" - line="86" - column="20"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level 34 (current min is 33): `openRelevantSubpage`" - errorLine1=" frag = openRelevantSubpage(groupId);" - errorLine2=" ~~~~~~~~~~~~~~~~~~~"> - <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java" - line="90" - column="20"/> - </issue> - - <issue - id="NewApi" message="Class requires API level 31 (current min is 30): `android.apphibernation.AppHibernationManager`" errorLine1=" userContext.getSystemService(APP_HIBERNATION_SERVICE) as AppHibernationManager" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -498,68 +377,93 @@ <issue id="NewApi" - message="Class requires API level 34 (current min is 30): `android.app.AppOpsManager.OnOpNotedListener`" - errorLine1=" AppOpsManager.OnOpNotedListener," - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + message="Field requires API level 33 (current min is 30): `getTAG`" + errorLine1=" MoreIssuesCardPreference.TAG," + errorLine2=" ~~~"> + <location + file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardAnimator.kt" + line="107" + column="46"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessAt`"> + <location + file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt" + line="153"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessAt`"> + <location + file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt" + line="188"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessCount`"> + <location + file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt" + line="152"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOp#getDiscreteAccessCount`"> + <location + file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt" + line="187"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 31 (current min is 30): `android.app.AppOpsManager.HistoricalOpsRequest.Builder#setHistoryFlags`"> <location file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt" - line="45" - column="5"/> + line="104"/> </issue> <issue id="NewApi" - message="Class requires API level 34 (current min is 30): `android.app.AppOpsManager.OnOpNotedListener`" - errorLine1=" AppOpsManager.OnOpNotedListener," - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + message="Call requires API level 34 (current min is 33): `getParentGroupId`"> <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightPackageOpsLiveData.kt" - line="38" - column="5"/> + file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java" + line="91"/> </issue> <issue id="NewApi" - message="Field requires API level 33 (current min is 30): `getTAG`" - errorLine1=" MoreIssuesCardPreference.TAG," - errorLine2=" ~~~"> + message="Call requires API level 34 (current min is 33): `openRelevantSubpage`"> <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardAnimator.kt" - line="107" - column="46"/> + file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java" + line="88"/> </issue> <issue id="NewApi" - message="Method reference requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterStatus::severityLevel`" - errorLine1=" val severityLevel: Int by status::severityLevel" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + message="Call requires API level 34 (current min is 33): `openRelevantSubpage`"> <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt" - line="30" - column="31"/> + file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java" + line="92"/> </issue> <issue id="NewApi" - message="Method reference requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterStatus::summary`" - errorLine1=" val originalSummary: CharSequence by status::summary" - errorLine2=" ~~~~~~~~~~~~~~~"> + message="Class requires API level 34 (current min is 30): `android.app.AppOpsManager.OnOpNotedListener`"> <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt" - line="29" - column="42"/> + file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt" + line="46"/> </issue> <issue id="NewApi" - message="Method reference requires API level 33 (current min is 30): `android.safetycenter.SafetyCenterStatus::title`" - errorLine1=" val title: CharSequence by status::title" - errorLine2=" ~~~~~~~~~~~~~"> + message="Class requires API level 34 (current min is 30): `android.app.AppOpsManager.OnOpNotedListener`"> <location - file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt" - line="28" - column="32"/> + file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightPackageOpsLiveData.kt" + line="43"/> </issue> </issues>
\ No newline at end of file diff --git a/PermissionController/res/values/styles.xml b/PermissionController/res/values/styles.xml index a859b3ce9..a03b71e9d 100644 --- a/PermissionController/res/values/styles.xml +++ b/PermissionController/res/values/styles.xml @@ -51,7 +51,7 @@ <item name="android:layout_width">match_parent</item> <item name="android:layout_height">wrap_content</item> <item name="android:orientation">vertical</item> - <item name="android:paddingTop">18dp</item> + <item name="android:paddingTop">24dp</item> <item name="android:paddingBottom">24dp</item> <item name="android:paddingLeft">24dp</item> <item name="android:paddingRight">24dp</item> diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml index 2f8f5a291..ca664475b 100644 --- a/PermissionController/res/xml/roles.xml +++ b/PermissionController/res/xml/roles.xml @@ -1101,7 +1101,6 @@ --> <role name="android.app.role.SYSTEM_UI" - behavior="SystemUiRoleBehavior" defaultHolders="config_systemUi" exclusive="true" minSdkVersion="31" @@ -1610,4 +1609,29 @@ </service> </required-components> </role> + + <role + name="android.app.role.RETAIL_DEMO" + behavior="RetailDemoRoleBehavior" + defaultHolders="config_defaultRetailDemo" + exclusive="true" + minSdkVersion="35" + static="true" + visible="false"> + <permissions> + <permission name="android.permission.ACCESS_BLOBS_ACROSS_USERS" /> + <permission name="android.permission.CHANGE_CONFIGURATION" /> + <permission name="android.permission.MODIFY_DAY_NIGHT_MODE" /> + <permission name="android.permission.MODIFY_PHONE_STATE" /> + <permission name="android.permission.OBSERVE_APP_USAGE" /> + <permission name="android.permission.QUERY_USERS" /> + <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> + <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" /> + <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" /> + <permission name="android.permission.WRITE_SETTINGS" /> + </permissions> + <app-op-permissions> + <app-op-permission name="android.permission.PACKAGE_USAGE_STATS" /> + </app-op-permissions> + </role> </roles> diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java index 12710cfd3..eb1ceb3fa 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java +++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import android.app.role.RoleManager; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; @@ -54,10 +55,6 @@ public class AssistantRoleBehavior implements RoleBehavior { private static final String LOG_TAG = AssistantRoleBehavior.class.getSimpleName(); - private static final Intent ASSIST_SERVICE_PROBE = - new Intent(VoiceInteractionService.SERVICE_INTERFACE); - private static final Intent ASSIST_ACTIVITY_PROBE = new Intent(Intent.ACTION_ASSIST); - @Override public void onRoleAdded(@NonNull Role role, @NonNull Context context) { PackageManager packageManager = context.getPackageManager(); @@ -82,81 +79,72 @@ public class AssistantRoleBehavior implements RoleBehavior { @Override public List<String> getQualifyingPackagesAsUser(@NonNull Role role, @NonNull UserHandle user, @NonNull Context context) { + return getQualifyingPackagesInternal(null, user, context); + } + + @Nullable + @Override + public Boolean isPackageQualified(@NonNull Role role, @NonNull String packageName, + @NonNull Context context) { + return !getQualifyingPackagesInternal(packageName, Process.myUserHandle(), context) + .isEmpty(); + } + + @NonNull + private List<String> getQualifyingPackagesInternal(@Nullable String filterPackageName, + @NonNull UserHandle user, @NonNull Context context) { Context userContext = UserUtils.getUserContext(context, user); ActivityManager userActivityManager = userContext.getSystemService(ActivityManager.class); PackageManager userPackageManager = userContext.getPackageManager(); - Set<String> availableAssistants = new ArraySet<>(); + Set<String> packageNames = new ArraySet<>(); if (!userActivityManager.isLowRamDevice()) { - List<ResolveInfo> services = userPackageManager.queryIntentServices( - ASSIST_SERVICE_PROBE, PackageManager.GET_META_DATA + Intent serviceIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE); + if (filterPackageName != null) { + serviceIntent.setPackage(filterPackageName); + } + List<ResolveInfo> serviceResolveInfos = userPackageManager.queryIntentServices( + serviceIntent, PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); - int numServices = services.size(); - for (int i = 0; i < numServices; i++) { - ResolveInfo service = services.get(i); - - if (isAssistantVoiceInteractionService(userPackageManager, service.serviceInfo)) { - availableAssistants.add(service.serviceInfo.packageName); + int serviceResolveInfosSize = serviceResolveInfos.size(); + for (int i = 0; i < serviceResolveInfosSize; i++) { + ResolveInfo serviceResolveInfo = serviceResolveInfos.get(i); + + ServiceInfo serviceInfo = serviceResolveInfo.serviceInfo; + if (!isAssistantVoiceInteractionService(userPackageManager, serviceInfo)) { + if (filterPackageName != null) { + Log.w(LOG_TAG, "Package " + filterPackageName + + " has an unqualified voice interaction service"); + } + continue; } + + packageNames.add(serviceInfo.packageName); } } - List<ResolveInfo> activities = userPackageManager.queryIntentActivities( - ASSIST_ACTIVITY_PROBE, PackageManager.MATCH_DEFAULT_ONLY + Intent activityIntent = new Intent(Intent.ACTION_ASSIST); + if (filterPackageName != null) { + activityIntent.setPackage(filterPackageName); + } + List<ResolveInfo> activityResolveInfos = userPackageManager.queryIntentActivities( + activityIntent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); - int numActivities = activities.size(); - for (int i = 0; i < numActivities; i++) { - availableAssistants.add(activities.get(i).activityInfo.packageName); - } - - return new ArrayList<>(availableAssistants); - } + int activityResolveInfosSize = activityResolveInfos.size(); + for (int i = 0; i < activityResolveInfosSize; i++) { + ResolveInfo activityResolveInfo = activityResolveInfos.get(i); - @Nullable - @Override - public Boolean isPackageQualified(@NonNull Role role, @NonNull String packageName, - @NonNull Context context) { - ActivityManager activityManager = context.getSystemService(ActivityManager.class); - PackageManager packageManager = context.getPackageManager(); - - boolean hasAssistantService = false; - if (!activityManager.isLowRamDevice()) { - Intent pkgServiceProbe = new Intent(ASSIST_SERVICE_PROBE).setPackage(packageName); - List<ResolveInfo> services = packageManager.queryIntentServices(pkgServiceProbe, - PackageManager.GET_META_DATA | PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); - hasAssistantService = !services.isEmpty(); - int numServices = services.size(); - for (int i = 0; i < numServices; i++) { - ResolveInfo service = services.get(i); - - if (isAssistantVoiceInteractionService(packageManager, service.serviceInfo)) { - return true; - } + ActivityInfo activityInfo = activityResolveInfo.activityInfo; + if (!activityInfo.exported) { + continue; } - } - Intent pkgActivityProbe = new Intent(ASSIST_ACTIVITY_PROBE).setPackage(packageName); - boolean hasAssistantActivity = !packageManager.queryIntentActivities(pkgActivityProbe, - PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE).isEmpty(); - if (!hasAssistantActivity) { - Log.w(LOG_TAG, "Package " + packageName + " not qualified for " + role.getName() - + " due to " + (hasAssistantService ? "unqualified" : "missing") - + " service and missing activity"); + packageNames.add(activityInfo.packageName); } - return hasAssistantActivity; - } - - @Override - public void grant(@NonNull Role role, @NonNull String packageName, @NonNull Context context) { - } - - @Override - public void revoke(@NonNull Role role, @NonNull String packageName, @NonNull Context context) { + return new ArrayList<>(packageNames); } private boolean isAssistantVoiceInteractionService(@NonNull PackageManager pm, diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java index 3254bc6e4..0bcaa99cf 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java +++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java @@ -22,12 +22,16 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; +import android.os.Build; import android.os.UserHandle; import android.provider.Settings; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import com.android.modules.utils.build.SdkLevel; +import com.android.role.controller.model.AppOpPermissions; import com.android.role.controller.model.Permissions; import com.android.role.controller.model.Role; import com.android.role.controller.model.RoleBehavior; @@ -51,6 +55,14 @@ public class HomeRoleBehavior implements RoleBehavior { android.Manifest.permission.WRITE_CALL_LOG, android.Manifest.permission.READ_CONTACTS); + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + private static final List<String> WEAR_PERMISSIONS = Arrays.asList( + android.Manifest.permission.POST_NOTIFICATIONS, + android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY); + + private static final List<String> WEAR_APP_OP_PERMISSIONS = Arrays.asList( + android.Manifest.permission.SYSTEM_ALERT_WINDOW); + @Override public boolean isAvailableAsUser(@NonNull Role role, @NonNull UserHandle user, @NonNull Context context) { @@ -97,11 +109,12 @@ public class HomeRoleBehavior implements RoleBehavior { public static boolean isSettingsApplication(@NonNull ApplicationInfo applicationInfo, @NonNull Context context) { PackageManager packageManager = context.getPackageManager(); - ResolveInfo resolveInfo = packageManager.resolveActivity(new Intent( - Settings.ACTION_SETTINGS), PackageManager.MATCH_DEFAULT_ONLY + ResolveInfo resolveInfo = packageManager.resolveActivity( + new Intent(Settings.ACTION_SETTINGS), PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); - if (resolveInfo == null || resolveInfo.activityInfo == null) { + if (resolveInfo == null || resolveInfo.activityInfo == null + || !resolveInfo.activityInfo.exported) { return false; } return Objects.equals(applicationInfo.packageName, resolveInfo.activityInfo.packageName); @@ -131,6 +144,16 @@ public class HomeRoleBehavior implements RoleBehavior { Arrays.asList(android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES), true, false, true, false, false, context); } + + if (SdkLevel.isAtLeastT()) { + if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { + Permissions.grant(packageName, WEAR_PERMISSIONS, + true, false, true, false, false, context); + for (String permission : WEAR_APP_OP_PERMISSIONS) { + AppOpPermissions.grant(packageName, permission, true, context); + } + } + } } @Override @@ -145,6 +168,15 @@ public class HomeRoleBehavior implements RoleBehavior { Arrays.asList(android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES), true, false, false, context); } + + if (SdkLevel.isAtLeastT()) { + if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { + Permissions.revoke(packageName, WEAR_PERMISSIONS, true, false, false, context); + for (String permission : WEAR_APP_OP_PERMISSIONS) { + AppOpPermissions.revoke(packageName, permission, context); + } + } + } } /** diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/RetailDemoRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/RetailDemoRoleBehavior.java new file mode 100644 index 000000000..831714843 --- /dev/null +++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/RetailDemoRoleBehavior.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 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. + */ + +package com.android.role.controller.behavior; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.os.Build; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.android.role.controller.model.Role; +import com.android.role.controller.model.RoleBehavior; + +import java.util.Arrays; + +/** + * Class for behavior of the Retail Demo role. + */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public class RetailDemoRoleBehavior implements RoleBehavior { + + @Override + public Boolean isPackageQualified(@NonNull Role role, @NonNull String packageName, + @NonNull Context context) { + if (Settings.Global.getInt(context.getContentResolver(), + Settings.Global.DEVICE_DEMO_MODE, 0) == 0) { + return false; + } + DevicePolicyManager devicePolicyManager = + context.getSystemService(DevicePolicyManager.class); + if (!(devicePolicyManager.isDeviceOwnerApp(packageName) + || devicePolicyManager.isProfileOwnerApp(packageName))) { + return false; + } + // Fallback to do additional default check. + return null; + } + + @Override + public boolean isAvailableAsUser(@NonNull Role role, @NonNull UserHandle user, + @NonNull Context context) { + UserManager userManager = context.getSystemService(UserManager.class); + return userManager.isSystemUser() || userManager.isMainUser() || userManager.isDemoUser(); + } +} diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/SystemUiRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/SystemUiRoleBehavior.java deleted file mode 100644 index 210c2313f..000000000 --- a/PermissionController/role-controller/java/com/android/role/controller/behavior/SystemUiRoleBehavior.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2021 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. - */ - -package com.android.role.controller.behavior; - -import android.content.Context; -import android.content.pm.PackageManager; - -import androidx.annotation.NonNull; - -import com.android.modules.utils.build.SdkLevel; -import com.android.role.controller.model.AppOpPermissions; -import com.android.role.controller.model.Role; -import com.android.role.controller.model.RoleBehavior; - -import java.util.Arrays; -import java.util.List; - -/** The role behavior for system ui. */ -public class SystemUiRoleBehavior implements RoleBehavior { - - private static final List<String> WEAR_APP_OP_PERMISSIONS = - Arrays.asList(android.Manifest.permission.SYSTEM_ALERT_WINDOW); - - @Override - public void grant(@NonNull Role role, @NonNull String packageName, @NonNull Context context) { - if (SdkLevel.isAtLeastT()) { - if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { - for (String permission : WEAR_APP_OP_PERMISSIONS) { - AppOpPermissions.grant(packageName, permission, true, context); - } - } - } - } - - @Override - public void revoke(@NonNull Role role, @NonNull String packageName, @NonNull Context context) { - if (SdkLevel.isAtLeastT()) { - if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { - for (String permission : WEAR_APP_OP_PERMISSIONS) { - AppOpPermissions.revoke(packageName, permission, context); - } - } - } - } -} diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java b/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java index 0c4a14574..2d02d4204 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java @@ -60,9 +60,9 @@ public class Permission { * @return whether this permission is available */ public boolean isAvailable() { - // Workaround to match the value 34+ for U+ in roles.xml before SDK finalization. - if (mMinSdkVersion >= 34) { - return SdkLevel.isAtLeastU(); + // Workaround to match the value 35+ for V+ in roles.xml before SDK finalization. + if (mMinSdkVersion >= 35) { + return SdkLevel.isAtLeastV(); } else { return Build.VERSION.SDK_INT >= mMinSdkVersion; } diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredActivity.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredActivity.java index 58c878e56..25c97aefb 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredActivity.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredActivity.java @@ -16,12 +16,11 @@ package com.android.role.controller.model; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.os.Bundle; import android.os.UserHandle; import androidx.annotation.NonNull; @@ -52,11 +51,15 @@ public class RequiredActivity extends RequiredComponent { return userPackageManager.queryIntentActivities(intent, flags); } + @Override + protected boolean isComponentQualified(@NonNull ResolveInfo resolveInfo) { + return resolveInfo.activityInfo.exported; + } + @NonNull @Override - protected ComponentName getComponentComponentName(@NonNull ResolveInfo resolveInfo) { - return new ComponentName(resolveInfo.activityInfo.packageName, - resolveInfo.activityInfo.name); + protected ComponentInfo getComponentComponentInfo(@NonNull ResolveInfo resolveInfo) { + return resolveInfo.activityInfo; } @Override @@ -69,10 +72,4 @@ public class RequiredActivity extends RequiredComponent { protected String getComponentPermission(@NonNull ResolveInfo resolveInfo) { return resolveInfo.activityInfo.permission; } - - @Nullable - @Override - protected Bundle getComponentMetaData(@NonNull ResolveInfo resolveInfo) { - return resolveInfo.activityInfo.metaData; - } } diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredBroadcastReceiver.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredBroadcastReceiver.java index 945fda3c3..1e23af256 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredBroadcastReceiver.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredBroadcastReceiver.java @@ -16,12 +16,11 @@ package com.android.role.controller.model; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.os.Bundle; import android.os.UserHandle; import androidx.annotation.NonNull; @@ -53,9 +52,8 @@ public class RequiredBroadcastReceiver extends RequiredComponent { @NonNull @Override - protected ComponentName getComponentComponentName(@NonNull ResolveInfo resolveInfo) { - return new ComponentName(resolveInfo.activityInfo.packageName, - resolveInfo.activityInfo.name); + protected ComponentInfo getComponentComponentInfo(@NonNull ResolveInfo resolveInfo) { + return resolveInfo.activityInfo; } @Override @@ -68,10 +66,4 @@ public class RequiredBroadcastReceiver extends RequiredComponent { protected String getComponentPermission(@NonNull ResolveInfo resolveInfo) { return resolveInfo.activityInfo.permission; } - - @Nullable - @Override - protected Bundle getComponentMetaData(@NonNull ResolveInfo resolveInfo) { - return resolveInfo.activityInfo.metaData; - } } diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredComponent.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredComponent.java index ae6156e7f..f1ef50209 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredComponent.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredComponent.java @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Build; @@ -108,9 +109,9 @@ public abstract class RequiredComponent { * @return whether this required component is available */ public boolean isAvailable() { - // Workaround to match the value 34+ for U+ in roles.xml before SDK finalization. - if (mMinTargetSdkVersion >= 34) { - return SdkLevel.isAtLeastU(); + // Workaround to match the value 35+ for V+ in roles.xml before SDK finalization. + if (mMinTargetSdkVersion >= 35) { + return SdkLevel.isAtLeastV(); } else { return Build.VERSION.SDK_INT >= mMinTargetSdkVersion; } @@ -195,6 +196,10 @@ public abstract class RequiredComponent { for (int resolveInfosIndex = 0; resolveInfosIndex < resolveInfosSize; resolveInfosIndex++) { ResolveInfo resolveInfo = resolveInfos.get(resolveInfosIndex); + if (!isComponentQualified(resolveInfo)) { + continue; + } + if (mFlags != 0) { int componentFlags = getComponentFlags(resolveInfo); if ((componentFlags & mFlags) != mFlags) { @@ -209,8 +214,9 @@ public abstract class RequiredComponent { } } + ComponentInfo componentInfo = getComponentComponentInfo(resolveInfo); if (hasMetaData) { - Bundle componentMetaData = getComponentMetaData(resolveInfo); + Bundle componentMetaData = componentInfo.metaData; if (componentMetaData == null) { componentMetaData = Bundle.EMPTY; } @@ -229,13 +235,14 @@ public abstract class RequiredComponent { } } - ComponentName componentName = getComponentComponentName(resolveInfo); - String componentPackageName = componentName.getPackageName(); + String componentPackageName = componentInfo.packageName; if (componentPackageNames.contains(componentPackageName)) { continue; } - componentPackageNames.add(componentPackageName); + + ComponentName componentName = new ComponentName(componentPackageName, + componentInfo.name); componentNames.add(componentName); } return componentNames; @@ -256,15 +263,19 @@ public abstract class RequiredComponent { protected abstract List<ResolveInfo> queryIntentComponentsAsUser(@NonNull Intent intent, int flags, @NonNull UserHandle user, @NonNull Context context); + protected boolean isComponentQualified(@NonNull ResolveInfo resolveInfo) { + return true; + } + /** - * Get the {@code ComponentName} of a component. + * Get the {@code ComponentInfo} of a component. * * @param resolveInfo the {@code ResolveInfo} of the component * - * @return the {@code ComponentName} of the component + * @return the {@code ComponentInfo} of the component */ @NonNull - protected abstract ComponentName getComponentComponentName(@NonNull ResolveInfo resolveInfo); + protected abstract ComponentInfo getComponentComponentInfo(@NonNull ResolveInfo resolveInfo); /** * Get the flags that have been set on a component. @@ -285,16 +296,6 @@ public abstract class RequiredComponent { @Nullable protected abstract String getComponentPermission(@NonNull ResolveInfo resolveInfo); - /** - * Get the meta data associated with a component. - * - * @param resolveInfo the {@code ResolveInfo} of the component - * - * @return the meta data associated with a component - */ - @Nullable - protected abstract Bundle getComponentMetaData(@NonNull ResolveInfo resolveInfo); - @Override public String toString() { return "RequiredComponent{" diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredContentProvider.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredContentProvider.java index 7b53a25bb..b02062b11 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredContentProvider.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredContentProvider.java @@ -16,12 +16,11 @@ package com.android.role.controller.model; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.os.Bundle; import android.os.UserHandle; import androidx.annotation.NonNull; @@ -53,9 +52,8 @@ public class RequiredContentProvider extends RequiredComponent { @NonNull @Override - protected ComponentName getComponentComponentName(@NonNull ResolveInfo resolveInfo) { - return new ComponentName(resolveInfo.providerInfo.packageName, - resolveInfo.providerInfo.name); + protected ComponentInfo getComponentComponentInfo(@NonNull ResolveInfo resolveInfo) { + return resolveInfo.providerInfo; } @Override @@ -70,10 +68,4 @@ public class RequiredContentProvider extends RequiredComponent { //return resolveInfo.providerInfo.readPermission; throw new UnsupportedOperationException(); } - - @Nullable - @Override - protected Bundle getComponentMetaData(@NonNull ResolveInfo resolveInfo) { - return resolveInfo.providerInfo.metaData; - } } diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredService.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredService.java index f27aae013..0532e53b2 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/RequiredService.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredService.java @@ -16,12 +16,11 @@ package com.android.role.controller.model; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.os.Bundle; import android.os.UserHandle; import androidx.annotation.NonNull; @@ -53,8 +52,8 @@ public class RequiredService extends RequiredComponent { @NonNull @Override - protected ComponentName getComponentComponentName(@NonNull ResolveInfo resolveInfo) { - return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); + protected ComponentInfo getComponentComponentInfo(@NonNull ResolveInfo resolveInfo) { + return resolveInfo.serviceInfo; } @Override @@ -67,10 +66,4 @@ public class RequiredService extends RequiredComponent { protected String getComponentPermission(@NonNull ResolveInfo resolveInfo) { return resolveInfo.serviceInfo.permission; } - - @Nullable - @Override - protected Bundle getComponentMetaData(@NonNull ResolveInfo resolveInfo) { - return resolveInfo.serviceInfo.metaData; - } } diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java index aa6cba169..1d01fde58 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java @@ -392,9 +392,9 @@ public class Role { * @return whether this role is available based on SDK version */ boolean isAvailableBySdkVersion() { - // Workaround to match the value 34+ for U+ in roles.xml before SDK finalization. - if (mMinSdkVersion >= 34) { - return SdkLevel.isAtLeastU(); + // Workaround to match the value 35+ for V+ in roles.xml before SDK finalization. + if (mMinSdkVersion >= 35) { + return SdkLevel.isAtLeastV(); } else { return Build.VERSION.SDK_INT >= mMinSdkVersion && Build.VERSION.SDK_INT <= mMaxSdkVersion; diff --git a/PermissionController/src/com/android/permissioncontroller/Constants.java b/PermissionController/src/com/android/permissioncontroller/Constants.java index 58b62dc54..a063fb607 100644 --- a/PermissionController/src/com/android/permissioncontroller/Constants.java +++ b/PermissionController/src/com/android/permissioncontroller/Constants.java @@ -320,6 +320,17 @@ public class Constants { */ public static final String UNUSED_APPS_SAFETY_CENTER_SEE_UNUSED_APPS_ID = "see_unused_apps"; + /** + * Fallback Settings package name + */ + public static final String SETTINGS_PACKAGE_NAME_FALLBACK = "com.android.settings"; + + /** + * Extra launcher icon for notification + */ + public static final String NOTIFICATION_EXTRA_USE_LAUNCHER_ICON = + "com.android.car.notification.EXTRA_USE_LAUNCHER_ICON"; + // TODO(b/231624295) add to API @RequiresApi(Build.VERSION_CODES.TIRAMISU) public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = diff --git a/PermissionController/src/com/android/permissioncontroller/auto/AutoSettingsFrameFragment.java b/PermissionController/src/com/android/permissioncontroller/auto/AutoSettingsFrameFragment.java index 3b0e89b04..08e1b3560 100644 --- a/PermissionController/src/com/android/permissioncontroller/auto/AutoSettingsFrameFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/auto/AutoSettingsFrameFragment.java @@ -27,6 +27,9 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.car.ui.FocusArea; +import com.android.car.ui.R; +import com.android.car.ui.baselayout.Insets; import com.android.car.ui.preference.PreferenceFragment; import com.android.car.ui.toolbar.MenuItem; import com.android.car.ui.toolbar.ToolbarController; @@ -56,6 +59,20 @@ public abstract class AutoSettingsFrameFragment extends PreferenceFragment { return rootView; } + @Override + public void onCarUiInsetsChanged(Insets insets) { + // don't allow scrolling behind the toolbar to be consistent with the rest of Settings + // reference UI. Scrolling behind toolbar also leads to flakier tests due to UI being + // visible but clicks are intercepted and dropped by the toolbar. + FocusArea focusArea = getView().findViewById(R.id.car_ui_focus_area); + focusArea.setHighlightPadding( + /* left= */ 0, /* top= */ 0, /* right= */ 0, /* bottom= */ 0); + focusArea.setBoundsOffset(/* left= */ 0, /* top= */ 0, /* right= */ 0, /* bottom= */ 0); + getView().setPadding( + insets.getLeft(), insets.getTop(), insets.getRight(), insets.getBottom()); + getCarUiRecyclerView().setPadding( + /* left= */ 0, /* top= */ 0, /* right= */ 0, /* bottom= */ 0); + } /** Sets the header text of this fragment. */ public void setHeaderLabel(CharSequence label) { diff --git a/PermissionController/src/com/android/permissioncontroller/auto/DrivingDecisionReminderService.kt b/PermissionController/src/com/android/permissioncontroller/auto/DrivingDecisionReminderService.kt index b62f8d721..5fd780806 100644 --- a/PermissionController/src/com/android/permissioncontroller/auto/DrivingDecisionReminderService.kt +++ b/PermissionController/src/com/android/permissioncontroller/auto/DrivingDecisionReminderService.kt @@ -24,16 +24,13 @@ import android.app.PendingIntent import android.app.Service import android.car.Car import android.car.drivingstate.CarUxRestrictionsManager -import android.content.ComponentName import android.content.Context import android.content.Intent -import android.content.pm.PackageManager import android.os.Bundle import android.os.IBinder import android.os.Process import android.os.UserHandle import android.permission.PermissionManager -import android.provider.Settings import android.text.BidiFormatter import androidx.annotation.VisibleForTesting import com.android.permissioncontroller.Constants @@ -72,7 +69,6 @@ class DrivingDecisionReminderService : Service() { companion object { private const val LOG_TAG = "DrivingDecisionReminderService" - private const val SETTINGS_PACKAGE_NAME_FALLBACK = "com.android.settings" const val EXTRA_PACKAGE_NAME = "package_name" const val EXTRA_PERMISSION_GROUP = "permission_group" @@ -270,15 +266,9 @@ class DrivingDecisionReminderService : Service() { PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - val settingsDrawable = KotlinUtils.getBadgedPackageIcon( - application, - getSettingsPackageName(applicationContext.packageManager), - permissionReminders.first().user) - val settingsIcon = if (settingsDrawable != null) { - KotlinUtils.convertToBitmap(settingsDrawable) - } else { - null - } + val settingsIcon = KotlinUtils.getSettingsIcon(application, + permissionReminders.first().user, + applicationContext.packageManager) val b = Notification.Builder(this, Constants.PERMISSION_REMINDER_CHANNEL_ID) .setContentTitle(title) @@ -289,7 +279,7 @@ class DrivingDecisionReminderService : Service() { .setAutoCancel(true) .setContentIntent(pendingIntent) .addExtras(Bundle().apply { - putBoolean("com.android.car.notification.EXTRA_USE_LAUNCHER_ICON", false) + putBoolean(Constants.NOTIFICATION_EXTRA_USE_LAUNCHER_ICON, false) }) // Auto doesn't show icons for actions .addAction(Notification.Action.Builder(/* icon= */ null, @@ -302,12 +292,6 @@ class DrivingDecisionReminderService : Service() { return b.build() } - private fun getSettingsPackageName(pm: PackageManager): String { - val settingsIntent = Intent(Settings.ACTION_SETTINGS) - val settingsComponent: ComponentName? = settingsIntent.resolveActivity(pm) - return settingsComponent?.packageName ?: SETTINGS_PACKAGE_NAME_FALLBACK - } - private fun logNotificationPresented() { PermissionControllerStatsLog.write( PermissionControllerStatsLog.PERMISSION_REMINDER_NOTIFICATION_INTERACTED, diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt index 1f6b5272a..06f9aa7f0 100644 --- a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt +++ b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt @@ -80,6 +80,7 @@ import androidx.lifecycle.MutableLiveData import androidx.preference.PreferenceManager import com.android.modules.utils.build.SdkLevel import com.android.permissioncontroller.Constants +import com.android.permissioncontroller.DeviceUtils import com.android.permissioncontroller.DumpableLog import com.android.permissioncontroller.PermissionControllerApplication import com.android.permissioncontroller.R @@ -841,7 +842,9 @@ class HibernationJobService : JobService() { appsToHibernate, this@HibernationJobService, sessionId) val unusedApps: Set<Pair<String, UserHandle>> = hibernatedApps + revokedApps if (unusedApps.isNotEmpty()) { - showUnusedAppsNotification(unusedApps.size, sessionId) + showUnusedAppsNotification(unusedApps.size, + sessionId, + Process.myUserHandle()) if (SdkLevel.isAtLeastT() && revokedApps.isNotEmpty() && getSystemService(SafetyCenterManager::class.java)!!.isSafetyCenterEnabled) { @@ -861,7 +864,11 @@ class HibernationJobService : JobService() { return true } - private suspend fun showUnusedAppsNotification(numUnused: Int, sessionId: Long) { + private suspend fun showUnusedAppsNotification( + numUnused: Int, + sessionId: Long, + user: UserHandle + ) { val notificationManager = getSystemService(NotificationManager::class.java)!! val permissionReminderChannel = NotificationChannel( @@ -889,6 +896,13 @@ class HibernationJobService : JobService() { .setAutoCancel(true) .setContentIntent(makeUnusedAppsIntent(this, sessionId)) val extras = Bundle() + if (DeviceUtils.isAuto(this)) { + val settingsIcon = KotlinUtils.getSettingsIcon(application, + user, + applicationContext.packageManager) + extras.putBoolean(Constants.NOTIFICATION_EXTRA_USE_LAUNCHER_ICON, false) + b.setLargeIcon(settingsIcon) + } if (SdkLevel.isAtLeastT() && getSystemService(SafetyCenterManager::class.java)!!.isSafetyCenterEnabled) { val notificationResources = KotlinUtils.getSafetyCenterNotificationResources(this) diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING index 16ee9b47d..69a8f74be 100644 --- a/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING +++ b/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING @@ -5,6 +5,9 @@ "options": [ { "include-filter": "android.hibernation.cts.AppHibernationIntegrationTest" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING index 52f0fd1a5..fa3b7ef78 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING +++ b/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING @@ -30,7 +30,7 @@ "name": "CtsHibernationTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] } @@ -65,12 +65,8 @@ { "name": "CtsHibernationTestCases[com.google.android.permission.apex]", "options": [ - // TODO(b/238677038): This test currently fails on R base image - { - "exclude-filter": "android.hibernation.cts.AutoRevokeTest#testUnusedApp_uninstallApp" - }, { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] } @@ -78,6 +74,63 @@ "postsubmit": [ { "name": "CtsHibernationTestCases" + }, + { + "name": "CtsPermissionTestCases", + "options": [ + { + "include-filter": "android.permission.cts.BackgroundPermissionsTest" + }, + { + "include-filter": "android.permission.cts.LocationAccessCheckTest" + }, + { + "include-filter": "android.permission.cts.NotificationListenerCheckTest" + }, + { + "include-filter": "android.permission.cts.OneTimePermissionTest" + }, + { + "include-filter": "android.permission.cts.PermissionControllerTest" + }, + { + "include-filter": "android.permission.cts.PlatformPermissionGroupMappingTest" + } + ] + } + ], + "mainline-postsubmit": [ + { + "name": "CtsPermissionTestCases[com.google.android.permission.apex]", + "options": [ + { + "include-filter": "android.permission.cts.BackgroundPermissionsTest" + }, + { + "include-filter": "android.permission.cts.LocationAccessCheckTest" + }, + { + "include-filter": "android.permission.cts.NotificationListenerCheckTest" + }, + { + "include-filter": "android.permission.cts.OneTimePermissionTest" + }, + { + "include-filter": "android.permission.cts.PermissionControllerTest" + }, + { + "include-filter": "android.permission.cts.PlatformPermissionGroupMappingTest" + } + ] + }, + { + "name": "CtsHibernationTestCases[com.google.android.permission.apex]", + "options": [ + // TODO(b/238677038): This test currently fails on R base image + { + "exclude-filter": "android.hibernation.cts.AutoRevokeTest#testUnusedApp_uninstallApp" + } + ] } ] } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompat.java b/PermissionController/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompat.java deleted file mode 100644 index 637eb5fc4..000000000 --- a/PermissionController/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompat.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2023 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. - */ - -package com.android.permissioncontroller.permission.compat; - -import android.text.Layout; -import android.text.Selection; -import android.text.Spannable; -import android.text.method.LinkMovementMethod; -import android.text.method.Touch; -import android.view.MotionEvent; -import android.widget.TextView; - -import androidx.annotation.NonNull; - -/** - * Fixes the issue that links can be triggered for touches outside of line bounds for - * {@link LinkMovementMethod}. - * <p> - * This is based on the fix in ag/22301465. - */ -public class LinkMovementMethodCompat extends LinkMovementMethod { - @Override - public boolean onTouchEvent(@NonNull TextView widget, @NonNull Spannable buffer, - @NonNull MotionEvent event) { - int action = event.getAction(); - - if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { - int x = (int) event.getX(); - int y = (int) event.getY(); - - x -= widget.getTotalPaddingLeft(); - y -= widget.getTotalPaddingTop(); - - x += widget.getScrollX(); - y += widget.getScrollY(); - - Layout layout = widget.getLayout(); - boolean isOutOfLineBounds; - if (y < 0 || y > layout.getHeight()) { - isOutOfLineBounds = true; - } else { - int line = layout.getLineForVertical(y); - isOutOfLineBounds = x < layout.getLineLeft(line) || x > layout.getLineRight(line); - } - - if (isOutOfLineBounds) { - Selection.removeSelection(buffer); - - // return LinkMovementMethod.super.onTouchEvent(widget, buffer, event); - return Touch.onTouchEvent(widget, buffer, event); - } - } - - return super.onTouchEvent(widget, buffer, event); - } - - /** - * @return a {@link LinkMovementMethodCompat} instance - */ - @NonNull - public static LinkMovementMethodCompat getInstance() { - if (sInstance == null) { - sInstance = new LinkMovementMethodCompat(); - } - - return sInstance; - } - - private static LinkMovementMethodCompat sInstance; -} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt index 0f6b6c000..caa05d1fb 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt @@ -61,14 +61,14 @@ data class LightPackageInfo( pI.permissions?.map { perm -> LightPermInfo(perm) } ?: emptyList(), pI.requestedPermissions?.toList() ?: emptyList(), pI.requestedPermissionsFlags?.toList() ?: emptyList(), - pI.applicationInfo.uid, - pI.applicationInfo.targetSdkVersion, - pI.applicationInfo.isInstantApp, - pI.applicationInfo.enabled, - pI.applicationInfo.flags, + pI.applicationInfo!!.uid, + pI.applicationInfo!!.targetSdkVersion, + pI.applicationInfo!!.isInstantApp, + pI.applicationInfo!!.enabled, + pI.applicationInfo!!.flags, pI.firstInstallTime, pI.lastUpdateTime, - if (SdkLevel.isAtLeastS()) pI.applicationInfo.areAttributionsUserVisible() else false, + if (SdkLevel.isAtLeastS()) pI.applicationInfo!!.areAttributionsUserVisible() else false, if (SdkLevel.isAtLeastS()) buildAttributionTagsToLabelsMap(pI.attributions) else emptyMap()) /** Permissions which are granted according to the [requestedPermissionsFlags] */ diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt index 52e89e972..2fbf3be26 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt @@ -84,6 +84,9 @@ suspend fun revokeAppPermissions( // For each autorevoke-eligible app... userApps.forEachInParallel(Main) forEachInParallelOuter@ { pkg: LightPackageInfo -> if (pkg.grantedPermissions.isEmpty()) { + if (DEBUG_AUTO_REVOKE) { + DumpableLog.i(LOG_TAG, "${pkg.packageName}: no granted permissions") + } return@forEachInParallelOuter } val packageName = pkg.packageName @@ -97,9 +100,20 @@ suspend fun revokeAppPermissions( return@forEachInParallelOuter } val targetSdk = pkg.targetSdkVersion - val pkgPermGroups: Map<String, List<String>> = + val pkgPermGroups: Map<String, List<String>>? = PackagePermissionsLiveData[packageName, user] - .getInitializedValue() ?: return@forEachInParallelOuter + .getInitializedValue() + + if (pkgPermGroups == null || pkgPermGroups.isEmpty()) { + if (DEBUG_AUTO_REVOKE) { + DumpableLog.i(LOG_TAG, "$packageName: no permission groups found.") + } + return@forEachInParallelOuter + } + + if (DEBUG_AUTO_REVOKE) { + DumpableLog.i(LOG_TAG, "$packageName: perm groups: ${pkgPermGroups.keys}.") + } // Determine which permGroups are revocable val revocableGroups = mutableSetOf<String>() @@ -126,6 +140,10 @@ suspend fun revokeAppPermissions( } } + if (DEBUG_AUTO_REVOKE) { + DumpableLog.i(LOG_TAG, "$packageName: initial revocable groups: $revocableGroups") + } + // Mark any groups that split from an install-time permission as unrevocable for (fromPerm in pkgPermGroups[PackagePermissionsLiveData.NON_RUNTIME_NORMAL_PERMS] ?: emptyList()) { @@ -149,6 +167,9 @@ suspend fun revokeAppPermissions( } } + if (DEBUG_AUTO_REVOKE) { + DumpableLog.i(LOG_TAG, "$packageName: final revocable groups: $revocableGroups") + } // For each revocable group, revoke all of its permissions val anyPermsRevoked = AtomicBoolean(false) pkgPermGroups.entries @@ -161,6 +182,9 @@ suspend fun revokeAppPermissions( val revocablePermissions = group.permissions.keys.toList() if (revocablePermissions.isEmpty()) { + if (DEBUG_AUTO_REVOKE) { + DumpableLog.i(LOG_TAG, "$packageName: revocable permissions empty") + } return@forEachInParallelInner } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java b/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java index 57c828c20..937d92998 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java @@ -78,6 +78,7 @@ import static java.util.concurrent.TimeUnit.DAYS; import android.app.AppOpsManager; import android.app.AppOpsManager.OpEntry; import android.app.AppOpsManager.PackageOps; +import android.app.Application; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -126,6 +127,8 @@ import androidx.annotation.WorkerThread; import androidx.core.util.Preconditions; import com.android.modules.utils.build.SdkLevel; +import com.android.permissioncontroller.Constants; +import com.android.permissioncontroller.DeviceUtils; import com.android.permissioncontroller.PermissionControllerStatsLog; import com.android.permissioncontroller.R; import com.android.permissioncontroller.permission.model.AppPermissionGroup; @@ -435,7 +438,7 @@ public class LocationAccessCheck { } addLocationNotificationIfNeeded(mAppOpsManager.getPackagesForOps( - new String[]{OPSTR_FINE_LOCATION})); + new String[]{OPSTR_FINE_LOCATION}), service.getApplication()); service.jobFinished(params, false); } catch (Exception e) { Log.e(LOG_TAG, "Could not check for location access", e); @@ -449,7 +452,7 @@ public class LocationAccessCheck { } } - private void addLocationNotificationIfNeeded(@NonNull List<PackageOps> ops) + private void addLocationNotificationIfNeeded(@NonNull List<PackageOps> ops, Application app) throws InterruptedException { synchronized (sLock) { List<UserPackage> packages = getLocationUsersLocked(ops); @@ -506,7 +509,7 @@ public class LocationAccessCheck { } } createPermissionReminderChannel(getUserHandleForUid(pkgInfo.applicationInfo.uid)); - createNotificationForLocationUser(pkgInfo); + createNotificationForLocationUser(pkgInfo, app); } } @@ -642,7 +645,7 @@ public class LocationAccessCheck { * * @param pkg The {@link PackageInfo} for the package to to be changed */ - private void createNotificationForLocationUser(@NonNull PackageInfo pkg) { + private void createNotificationForLocationUser(@NonNull PackageInfo pkg, Application app) { CharSequence pkgLabel = mPackageManager.getApplicationLabel(pkg.applicationInfo); boolean safetyCenterBgLocationReminderEnabled = isSafetyCenterBgLocationReminderEnabled(); @@ -710,12 +713,18 @@ public class LocationAccessCheck { b.setLargeIcon(pkgIconBmp); } + Bundle extras = new Bundle(); + if (DeviceUtils.isAuto(mContext)) { + Bitmap settingsIcon = KotlinUtils.INSTANCE.getSettingsIcon(app, user, mPackageManager); + b.setLargeIcon(settingsIcon); + extras.putBoolean(Constants.NOTIFICATION_EXTRA_USE_LAUNCHER_ICON, false); + } + if (!TextUtils.isEmpty(appLabel)) { - Bundle extras = new Bundle(); String appNameSubstitute = appLabel.toString(); extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appNameSubstitute); - b.addExtras(extras); } + b.addExtras(extras); notificationManager.notify(pkgName, LOCATION_ACCESS_CHECK_NOTIFICATION_ID, b.build()); markAsNotified(pkgName, user, false); diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/permission/service/TEST_MAPPING index b8dd3d77a..b71a85187 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/service/TEST_MAPPING +++ b/PermissionController/src/com/android/permissioncontroller/permission/service/TEST_MAPPING @@ -4,21 +4,24 @@ "name": "CtsPermissionTestCases", "options": [ { - "include-filter": "android.permission.cts.PermissionControllerTest" + "exclude-annotation": "android.platform.test.annotations.FlakyTest" }, { - "include-filter": "android.permission.cts.OneTimePermissionTest" + "include-filter": "android.permission.cts.PermissionControllerTest" }, { - "exclude-annotation": "androidx.test.filters.FlakyTest" + "include-filter": "android.permission.cts.OneTimePermissionTest" } ] }, { - "name": "CtsPermission3TestCases", + "name": "CtsPermissionUiTestCases", "options": [ { - "include-filter": "android.permission3.cts.SafetyLabelChangesJobServiceTest" + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + }, + { + "include-filter": "android.permissionui.cts.SafetyLabelChangesJobServiceTest" } ] }, @@ -26,6 +29,9 @@ "name": "CtsPermissionTestCases", "options": [ { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + }, + { "include-filter": "android.permission.cts.LocationAccessCheckTest" } ], @@ -69,10 +75,43 @@ "name": "CtsPermissionTestCases", "options": [ { - "include-filter": "android.permission.cts.LocationAccessCheckTest" + "include-filter": "android.permission.cts.PermissionControllerTest" }, { - "exclude-annotation": "androidx.test.filters.FlakyTest" + "include-filter": "android.permission.cts.OneTimePermissionTest" + } + ] + }, + { + "name": "CtsPermissionUiTestCases", + "options": [ + { + "include-filter": "android.permissionui.cts.SafetyLabelChangesJobServiceTest" + } + ] + }, + { + "name": "CtsPermissionTestCases", + "options": [ + { + "include-filter": "android.permission.cts.LocationAccessCheckTest" + } + ], + "file_patterns": ["LocationAccessCheck\\.java"] + }, + { + "name": "CtsBackupTestCases", + "options": [ + { + "include-filter": "android.backup.cts.PermissionTest" + } + ] + }, + { + "name": "PermissionControllerOutOfProcessTests", + "options": [ + { + "include-filter": "com.android.permissioncontroller.tests.outofprocess.DumpTest" } ] } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/AutoGrantPermissionsNotifier.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/AutoGrantPermissionsNotifier.java index e9ed63b9a..f753a883d 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/AutoGrantPermissionsNotifier.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/AutoGrantPermissionsNotifier.java @@ -51,6 +51,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; +import android.service.notification.StatusBarNotification; import android.util.ArraySet; import androidx.annotation.NonNull; @@ -95,24 +96,23 @@ public class AutoGrantPermissionsNotifier { */ private final ArrayList<String> mGrantedPermissions = new ArrayList<>(); + private final NotificationManager mNotificationManager; + public AutoGrantPermissionsNotifier(@NonNull Context context, @NonNull PackageInfo packageInfo) { mPackageInfo = packageInfo; + mNotificationManager = getSystemServiceSafe(context, NotificationManager.class); UserHandle callingUser = getUserHandleForUid(mPackageInfo.applicationInfo.uid); mContext = context.createContextAsUser(callingUser, 0); } /** - * Create the channel to which the notification about auto-granted permission should be posted - * to. + * Create the channel to which the notification about auto-granted permission should be posted. * * @param user The user for which the permission was auto-granted. * @param shouldAlertUser */ private void createAutoGrantNotifierChannel(boolean shouldNotifySilently) { - NotificationManager notificationManager = getSystemServiceSafe(mContext, - NotificationManager.class); - NotificationChannel autoGrantedPermissionsChannel = new NotificationChannel( getNotificationChannelId(shouldNotifySilently), mContext.getString(R.string.auto_granted_permissions), @@ -121,7 +121,7 @@ public class AutoGrantPermissionsNotifier { autoGrantedPermissionsChannel.enableVibration(false); autoGrantedPermissionsChannel.setSound(Uri.EMPTY, null); } - notificationManager.createNotificationChannel(autoGrantedPermissionsChannel); + mNotificationManager.createNotificationChannel(autoGrantedPermissionsChannel); } /** @@ -160,12 +160,14 @@ public class AutoGrantPermissionsNotifier { String messageText = Utils.getEnterpriseString(mContext, LOCATION_AUTO_GRANTED_MESSAGE, R.string.auto_granted_permission_notification_body, pkgLabel); Notification.Builder notificationBuilder = (new Notification.Builder(mContext, - getNotificationChannelId(shouldNotifySilently))).setContentTitle(title) + getNotificationChannelId(shouldNotifySilently))) + .setContentTitle(title) .setContentText(messageText) .setStyle(new Notification.BigTextStyle().bigText(messageText).setBigContentTitle( title)) .setGroup(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_GROUP_ID) // NOTE: Different icons would be needed for different permissions. + .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY) .setSmallIcon(R.drawable.ic_pin_drop) .setLargeIcon(pkgIconBmp) .setColor(mContext.getColor(android.R.color.system_notification_accent_color)) @@ -180,29 +182,33 @@ public class AutoGrantPermissionsNotifier { notificationBuilder.addExtras(extras); } - String summaryTitle = mContext.getString(R.string.auto_granted_permissions); - - Notification.Builder summaryNotificationBuilder = new Notification.Builder(mContext, - getNotificationChannelId(shouldNotifySilently)) - .setContentTitle(summaryTitle) - .setSmallIcon(R.drawable.ic_pin_drop) - .setGroup(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_GROUP_ID) - .setGroupSummary(true); - - NotificationManager notificationManager = getSystemServiceSafe(mContext, - NotificationManager.class); // Cancel previous notifications for the same package to avoid redundant notifications. // This code currently only deals with location-related notifications, which would all lead // to the same Settings activity for managing location permissions. // If ever extended to cover multiple types of notifications, then only multiple // notifications of the same group should be canceled. - notificationManager.cancel( + mNotificationManager.cancel( mPackageInfo.packageName, PERMISSION_GRANTED_BY_ADMIN_NOTIFICATION_ID); - notificationManager.notify(mPackageInfo.packageName, + + mNotificationManager.notify(mPackageInfo.packageName, PERMISSION_GRANTED_BY_ADMIN_NOTIFICATION_ID, notificationBuilder.build()); - notificationManager.notify(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_SUMMARY_ID, - summaryNotificationBuilder.build()); + + // only show the summary notification if it is not already showing. Otherwise, this + // breaks the alerting behaviour. + if (!isNotificationActive(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_SUMMARY_ID)) { + String summaryTitle = mContext.getString(R.string.auto_granted_permissions); + + Notification.Builder summaryNotificationBuilder = new Notification.Builder(mContext, + getNotificationChannelId(shouldNotifySilently)) + .setContentTitle(summaryTitle) + .setSmallIcon(R.drawable.ic_pin_drop) + .setGroup(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_GROUP_ID) + .setGroupSummary(true); + + mNotificationManager.notify(ADMIN_AUTO_GRANTED_PERMISSIONS_NOTIFICATION_SUMMARY_ID, + summaryNotificationBuilder.build()); + } } /** @@ -248,5 +254,14 @@ public class AutoGrantPermissionsNotifier { return ADMIN_AUTO_GRANTED_PERMISSIONS_ALERTING_NOTIFICATION_CHANNEL_ID; } } + + private boolean isNotificationActive(int notificationId) { + for (StatusBarNotification notification : mNotificationManager.getActiveNotifications()) { + if (notification.getId() == notificationId) { + return true; + } + } + return false; + } } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java index c173146eb..78bd8f5ed 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission_group.LOCATION; import static android.Manifest.permission_group.READ_MEDIA_VISUAL; import static android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED; @@ -190,6 +191,10 @@ public class GrantPermissionsActivity extends SettingsActivity } getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + if (DeviceUtils.isWear(this)) { + // Do not grab input focus and hide keyboard. + getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM); + } int permissionsSdkLevel; if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(getIntent().getAction())) { @@ -427,7 +432,7 @@ public class GrantPermissionsActivity extends SettingsActivity return; } - CharSequence appLabel = KotlinUtils.INSTANCE.getPackageLabel(getApplication(), + String appLabel = KotlinUtils.INSTANCE.getPackageLabel(getApplication(), mTargetPackage, Process.myUserHandle()); Icon icon = null; @@ -641,7 +646,6 @@ public class GrantPermissionsActivity extends SettingsActivity logGrantPermissionActivityButtons(name, affectedForegroundPermissions, result); mViewModel.onPermissionGrantResult(name, affectedForegroundPermissions, result); - showNextRequest(); if (result == CANCELED) { setResultAndFinish(); } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java index 81af33294..cd1431eb5 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java @@ -31,6 +31,7 @@ import static com.android.permissioncontroller.PermissionControllerStatsLog.PERM import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__OPEN; import android.Manifest; +import android.annotation.TargetApi; import android.content.ComponentName; import android.content.Intent; import android.content.pm.PackageManager; @@ -137,11 +138,12 @@ public final class ManagePermissionsActivity extends SettingsActivity { */ private static final int PROXY_ACTIVITY_REQUEST_CODE = 5; + @TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) private static final String LAUNCH_PERMISSION_SETTINGS = - "android.permission.LAUNCH_PERMISSION_SETTINGS"; + Manifest.permission.LAUNCH_PERMISSION_SETTINGS; - private static final String APP_PERMISSIONS_SETTINGS = - "android.settings.APP_PERMISSIONS_SETTINGS"; + @TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) + private static final String APP_PERMISSIONS_SETTINGS = Settings.ACTION_APP_PERMISSIONS_SETTINGS; @Override public void onCreate(Bundle savedInstanceState) { @@ -304,7 +306,8 @@ public final class ManagePermissionsActivity extends SettingsActivity { case Intent.ACTION_MANAGE_APP_PERMISSIONS: case APP_PERMISSIONS_SETTINGS: { - if (Objects.equals(action, APP_PERMISSIONS_SETTINGS)) { + if (!SdkLevel.isAtLeastV() + && Objects.equals(action, APP_PERMISSIONS_SETTINGS)) { PermissionInfo permissionInfo; try { permissionInfo = getPackageManager() diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/permission/ui/TEST_MAPPING index de4c3a2b9..865ee6706 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/TEST_MAPPING +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/TEST_MAPPING @@ -5,6 +5,9 @@ "options": [ { "include-filter": "android.permission.cts.OneTimePermissionTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" } ] } @@ -24,7 +27,15 @@ ], "postsubmit": [ { - "name": "CtsPermission3TestCases" + "name": "CtsPermissionUiTestCases" + }, + { + "name": "CtsPermissionTestCases", + "options": [ + { + "include-filter": "android.permission.cts.OneTimePermissionTest" + } + ] } ] } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java index 6b09921cb..5a93a8e76 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java @@ -102,7 +102,7 @@ public class GrantPermissionsAutoViewHandler implements GrantPermissionsViewHand mGroupIcon = icon; mGroupMessage = message; mDetailMessage = detailMessage; - mButtonVisibilities = buttonVisibilities; + setButtonVisibilities(buttonVisibilities); update(); } @@ -193,11 +193,19 @@ public class GrantPermissionsAutoViewHandler implements GrantPermissionsViewHand mGroupCount = savedInstanceState.getInt(ARG_GROUP_COUNT); mGroupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX); mDetailMessage = savedInstanceState.getCharSequence(ARG_GROUP_DETAIL_MESSAGE); - mButtonVisibilities = savedInstanceState.getBooleanArray(ARG_BUTTON_VISIBILITIES); + setButtonVisibilities(savedInstanceState.getBooleanArray(ARG_BUTTON_VISIBILITIES)); update(); } + private void setButtonVisibilities(boolean[] visibilities) { + // If GrantPermissionsActivity sent the user directly to settings, button visibilities are + // not created. If the activity was then destroyed by the system, once the activity is + // recreated to perform onActivityResult, it will try to loadInstanceState in onCreate but + // the button visibilities were never set, so they will be null. + mButtonVisibilities = visibilities == null ? new boolean[0] : visibilities; + } + @Override public void onBackPressed() { if (mDialog != null) { diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt index 88b5ebe87..d74e745d4 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt @@ -22,10 +22,10 @@ import android.text.style.ClickableSpan import android.util.AttributeSet import android.view.View import android.widget.TextView +import androidx.core.text.method.LinkMovementMethodCompat import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import com.android.permissioncontroller.R -import com.android.permissioncontroller.permission.compat.LinkMovementMethodCompat /** A preference for a footer with an icon and a link. */ class AppDataSharingUpdatesFooterPreference : Preference { diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt index 3998ca141..6267ebad6 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt @@ -33,8 +33,8 @@ import android.widget.Button import android.widget.LinearLayout import android.widget.TextView import androidx.annotation.RequiresApi +import androidx.core.text.method.LinkMovementMethodCompat import com.android.permissioncontroller.R -import com.android.permissioncontroller.permission.compat.LinkMovementMethodCompat import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler.Result.Companion.CANCELLED diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt index 1b17041b6..31b7c0d7d 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt @@ -96,8 +96,7 @@ class PermissionAppsViewModel( val categorizedAppsLiveData = CategorizedAppsLiveData(groupName) @get:RequiresApi(Build.VERSION_CODES.S) - val sensorStatusLiveData: SensorStatusLiveData by lazy(LazyThreadSafetyMode.NONE) - @RequiresApi(Build.VERSION_CODES.S) { + val sensorStatusLiveData: SensorStatusLiveData by lazy(LazyThreadSafetyMode.NONE) { SensorStatusLiveData() } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/ReviewPermissionsWearFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/ReviewPermissionsWearFragment.java index c31aa5d63..9965a6b38 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/ReviewPermissionsWearFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/ReviewPermissionsWearFragment.java @@ -43,8 +43,8 @@ import com.android.permissioncontroller.permission.model.AppPermissionGroup; import com.android.permissioncontroller.permission.model.AppPermissions; import com.android.permissioncontroller.permission.utils.Utils; -import java.util.List; import java.util.ArrayList; +import java.util.List; public class ReviewPermissionsWearFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener { @@ -290,7 +290,7 @@ public class ReviewPermissionsWearFragment extends PreferenceFragmentCompat titlePref.setIcon(icon); // Set message - String appLabel = mAppPermissions.getAppLabel().toString(); + String appLabel = Html.escapeHtml(mAppPermissions.getAppLabel().toString()); final int labelTemplateResId = isPackageUpdated() ? R.string.permission_review_title_template_update : R.string.permission_review_title_template_install; diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt index 49a35cadf..f79e83f18 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt @@ -74,6 +74,7 @@ import androidx.navigation.NavController import androidx.preference.Preference import androidx.preference.PreferenceGroup import com.android.modules.utils.build.SdkLevel +import com.android.permissioncontroller.Constants import com.android.permissioncontroller.DeviceUtils import com.android.permissioncontroller.PermissionControllerApplication import com.android.permissioncontroller.R @@ -568,6 +569,25 @@ object KotlinUtils { } /** + * Get the settings icon + * + * @param app The current application + * @param user The user for whom we want the icon + * @param pm The PackageManager + * + * @return Bitmap of the setting's icon, or null + */ + fun getSettingsIcon( + app: Application, + user: UserHandle, + pm: PackageManager + ): Bitmap? { + val settingsPackageName = getPackageNameForIntent(pm, + Settings.ACTION_SETTINGS) ?: Constants.SETTINGS_PACKAGE_NAME_FALLBACK + return getBadgedPackageIconBitmap(app, user, settingsPackageName) + } + + /** * Gets a package's badged icon from the system. * * @param app The current application @@ -586,6 +606,33 @@ object KotlinUtils { } /** + * Get the icon of a package + * + * @param application The current application + * @param user The user for whom we want the icon + * @param packageName The name of the package whose icon we want + * + * @return Bitmap of the package icon, or null + */ + fun getBadgedPackageIconBitmap( + application: Application, + user: UserHandle, + packageName: String + ): Bitmap? { + val drawable = getBadgedPackageIcon( + application, + packageName, + user) + + val icon = if (drawable != null) { + convertToBitmap(drawable) + } else { + null + } + return icon + } + + /** * Gets a package's badged label from the system. * * @param app The current application @@ -618,6 +665,19 @@ object KotlinUtils { } /** + * Returns the name of the package that resolves the specified intent action + * + * @param pm The PackageManager + * @param intentAction The name of the intent action + * + * @return The package's name, or null + */ + fun getPackageNameForIntent(pm: PackageManager, intentAction: String): String? { + val intent = Intent(intentAction) + return intent.resolveActivity(pm)?.packageName + } + + /** * Gets a package's uid, using a cached liveData value, if the liveData is currently being * observed (and thus has an up-to-date value). * diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java index 6b3dc98ad..c9a05c087 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java @@ -700,11 +700,16 @@ public final class Utils { * @param groupName The name of the permission group * @param context A context to resolve resources * @param requestRes The resource id of the grant request message - * * @return The formatted message to be used as title when granting permissions */ - public static CharSequence getRequestMessage(CharSequence appLabel, String packageName, - String groupName, Context context, @StringRes int requestRes) { + @NonNull + public static CharSequence getRequestMessage( + @NonNull String appLabel, + @NonNull String packageName, + @NonNull String groupName, + @NonNull Context context, + @StringRes int requestRes) { + String escapedAppLabel = Html.escapeHtml(appLabel); boolean isIsolatedStorage; try { @@ -714,15 +719,21 @@ public final class Utils { } if (groupName.equals(STORAGE) && isIsolatedStorage) { return Html.fromHtml( - String.format(context.getResources().getConfiguration().getLocales().get(0), + String.format( + context.getResources().getConfiguration().getLocales().get(0), context.getString(R.string.permgrouprequest_storage_isolated), - appLabel), 0); + escapedAppLabel), + 0); } else if (requestRes != 0) { - return Html.fromHtml(context.getResources().getString(requestRes, appLabel), 0); + return Html.fromHtml(context.getResources().getString(requestRes, escapedAppLabel), 0); } - return Html.fromHtml(context.getString(R.string.permission_warning_template, appLabel, - loadGroupDescription(context, groupName, context.getPackageManager())), 0); + return Html.fromHtml( + context.getString( + R.string.permission_warning_template, + escapedAppLabel, + loadGroupDescription(context, groupName, context.getPackageManager())), + 0); } private static CharSequence loadGroupDescription(Context context, String groupName, diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt b/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt index 91a043a6a..2027581d5 100644 --- a/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt +++ b/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt @@ -504,8 +504,8 @@ internal class NotificationListenerCheckInternal( sessionId: Long ) { val pkgLabel = - Utils.getApplicationLabel(parentUserContext, pkg.applicationInfo) - val uid = pkg.applicationInfo.uid + Utils.getApplicationLabel(parentUserContext, pkg.applicationInfo!!) + val uid = pkg.applicationInfo!!.uid val deletePendingIntent = getNotificationDeletePendingIntent(parentUserContext, componentName, uid, sessionId) @@ -697,9 +697,9 @@ internal class NotificationListenerCheckInternal( } return null } - val pkgLabel = Utils.getApplicationLabel(parentUserContext, pkgInfo.applicationInfo) + val pkgLabel = Utils.getApplicationLabel(parentUserContext, pkgInfo.applicationInfo!!) val safetySourceIssueId = getSafetySourceIssueIdFromComponentName(componentName) - val uid = pkgInfo.applicationInfo.uid + val uid = pkgInfo.applicationInfo!!.uid val disableNlsPendingIntent = getDisableNlsPendingIntent( diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING index dc01ab3e2..d766ee080 100644 --- a/PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING +++ b/PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING @@ -33,8 +33,27 @@ }, { "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" } ] } + ], + "postsubmit": [ + { + "name": "CtsPermissionUiTestCases" + }, + { + "name": "CtsPermissionTestCases", + "options": [ + { + "include-filter": "android.permission.cts.NotificationListenerCheckTest" + }, + { + "include-filter": "android.permission.cts.AccessibilityPrivacySourceTest" + } + ] + } ] } diff --git a/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING index d7718a2f2..a95df2fd5 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING +++ b/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING @@ -4,7 +4,7 @@ "name": "CtsRoleTestCases", "options": [ { - "exclude-annotation": "androidx.test.filters.FlakyTest" + "exclude-annotation": "android.platform.test.annotations.FlakyTest" } ] } @@ -21,7 +21,26 @@ "exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked" }, { - "exclude-annotation": "androidx.test.filters.FlakyTest" + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + } + ] + } + ], + "postsubmit": [ + { + "name": "CtsRoleTestCases" + } + ], + "mainline-postsubmit": [ + { + "name": "CtsRoleTestCases[com.google.android.permission.apex]", + "options": [ + // TODO(b/238677748): These two tests currently fails on R base image + { + "exclude-filter": "android.app.role.cts.RoleManagerTest#openDefaultAppListThenIsNotDefaultAppInList" + }, + { + "exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked" } ] } diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java index 827d42643..eee30914d 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java @@ -302,4 +302,11 @@ public class RequestRoleActivity extends FragmentActivity { } return applicationInfo.uid; } + + @Override + protected void onNewIntent(@NonNull Intent intent) { + super.onNewIntent(intent); + + Log.w(LOG_TAG, "Ignoring new intent: " + intent); + } } diff --git a/PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING index b6e659353..23c1398da 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING +++ b/PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING @@ -11,7 +11,7 @@ ], "presubmit-large": [ { - "name": "CtsPermission3TestCases", + "name": "CtsPermissionUiTestCases", "options": [ { "exclude-annotation": "android.platform.test.annotations.FlakyTest" @@ -21,7 +21,30 @@ ], "mainline-presubmit": [ { - "name": "CtsPermission3TestCases[com.google.android.permission.apex]" + "name": "CtsPermissionUiTestCases[com.google.android.permission.apex]", + "options": [ + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + } + ] + } + ], + "postsubmit": [ + { + "name": "PermissionControllerMockingTests", + "options": [ + { + "include-filter": "com.android.permissioncontroller.tests.mocking.safetylabel" + } + ] + }, + { + "name": "CtsPermissionUiTestCases" + } + ], + "mainline-postsubmit": [ + { + "name": "CtsPermissionUiTestCases[com.google.android.permission.apex]" } ] }
\ No newline at end of file diff --git a/PermissionController/tests/inprocess/Android.bp b/PermissionController/tests/inprocess/Android.bp index 78c767f1d..7d55ff9cc 100644 --- a/PermissionController/tests/inprocess/Android.bp +++ b/PermissionController/tests/inprocess/Android.bp @@ -33,10 +33,7 @@ android_test { target_sdk_version: "30", min_sdk_version: "30", - srcs: [ - "src/**/*.kt", - "src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java", - ], + srcs: ["src/**/*.kt"], libs: [ "android.test.base", diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java deleted file mode 100644 index b4b18dbbe..000000000 --- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2023 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. - */ - -package com.android.permissioncontroller.permission.compat; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -import android.app.Activity; -import android.content.Context; -import android.os.SystemClock; -import android.text.Selection; -import android.text.Spannable; -import android.text.Spanned; -import android.text.method.MovementMethod; -import android.text.style.ClickableSpan; -import android.util.TypedValue; -import android.view.MotionEvent; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.test.annotation.UiThreadTest; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.rule.ActivityTestRule; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -/** - * Test for {@link LinkMovementMethodCompat} without using Mockito, which is unavailable for - * in-process tests. - * - * @see android.text.method.cts.LinkMovementMethodTest - */ -public class LinkMovementMethodCompatTest { - private static final String CONTENT = "clickable\nunclickable\nclickable"; - - private Activity mActivity; - private LinkMovementMethodCompat mMethod; - private TextView mView; - private Spannable mSpannable; - private MockClickableSpan mClickable0; - private MockClickableSpan mClickable1; - - @Rule - public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class); - - @Before - public void setup() throws Throwable { - mActivity = mActivityRule.getActivity(); - mMethod = new LinkMovementMethodCompat(); - - // Set the content view with a text view which contains 3 lines, - mActivityRule.runOnUiThread(() -> mView = new TextViewNoIme(mActivity)); - mView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10); - mView.setText(CONTENT, TextView.BufferType.SPANNABLE); - - mActivityRule.runOnUiThread(() -> mActivity.setContentView(mView)); - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - - mSpannable = (Spannable) mView.getText(); - // make first line clickable - mClickable0 = markClickable(0, CONTENT.indexOf('\n')); - // make last line clickable - mClickable1 = markClickable(CONTENT.lastIndexOf('\n'), CONTENT.length()); - } - - @Test - public void testConstructor() { - new LinkMovementMethodCompat(); - } - - @Test - public void testGetInstance() { - MovementMethod method0 = LinkMovementMethodCompat.getInstance(); - assertTrue(method0 instanceof LinkMovementMethodCompat); - - MovementMethod method1 = LinkMovementMethodCompat.getInstance(); - assertNotNull(method1); - assertSame(method0, method1); - } - - @UiThreadTest - @Test - public void testOnTouchEvent() { - assertSelection(mSpannable, -1); - - // press on first line (Clickable) - assertTrue(pressOnLine(0)); - assertSelectClickableLeftToRight(mSpannable, mClickable0); - - // release on first line - mClickable0.clearClickCount(); - assertTrue(releaseOnLine(0)); - mClickable0.assertClickCount(1); - - // press on second line (unclickable) - assertSelectClickableLeftToRight(mSpannable, mClickable0); - // just clear selection - pressOnLine(1); - assertSelection(mSpannable, -1); - - // press on last line (Clickable) - assertTrue(pressOnLine(2)); - assertSelectClickableLeftToRight(mSpannable, mClickable1); - - // release on last line - mClickable1.clearClickCount(); - assertTrue(releaseOnLine(2)); - mClickable1.assertClickCount(1); - - // release on second line (unclickable) - assertSelectClickableLeftToRight(mSpannable, mClickable1); - // just clear selection - releaseOnLine(1); - assertSelection(mSpannable, -1); - } - - @UiThreadTest - @Test - public void testOnTouchEvent_outsideLineBounds() { - assertSelection(mSpannable, -1); - - // press on first line (clickable) - assertTrue(pressOnLine(0)); - assertSelectClickableLeftToRight(mSpannable, mClickable0); - - // release above first line - mClickable0.clearClickCount(); - float x = (mView.getLayout().getLineLeft(0) + mView.getLayout().getLineRight(0)) / 2f; - float y = -1f; - assertFalse(performMotionAtPoint(x, y, MotionEvent.ACTION_UP)); - mClickable0.assertClickCount(0); - - // press on first line (clickable) - assertTrue(pressOnLine(0)); - assertSelectClickableLeftToRight(mSpannable, mClickable0); - - // release to left of first line - mClickable0.clearClickCount(); - x = mView.getLayout().getLineLeft(0) - 1f; - y = (mView.getLayout().getLineTop(0) + mView.getLayout().getLineBottom(0)) / 2f; - assertFalse(performMotionAtPoint(x, y, MotionEvent.ACTION_UP)); - mClickable0.assertClickCount(0); - - // press on first line (clickable) - assertTrue(pressOnLine(0)); - assertSelectClickableLeftToRight(mSpannable, mClickable0); - - // release to right of first line - mClickable0.clearClickCount(); - x = mView.getLayout().getLineRight(0) + 1f; - y = (mView.getLayout().getLineTop(0) + mView.getLayout().getLineBottom(0)) / 2f; - assertFalse(performMotionAtPoint(x, y, MotionEvent.ACTION_UP)); - mClickable0.assertClickCount(0); - - // press on last line (clickable) - assertTrue(pressOnLine(2)); - assertSelectClickableLeftToRight(mSpannable, mClickable1); - - // release below last line - mClickable1.clearClickCount(); - x = (mView.getLayout().getLineLeft(0) + mView.getLayout().getLineRight(0)) / 2f; - y = mView.getLayout().getHeight() + 1f; - assertFalse(performMotionAtPoint(x, y, MotionEvent.ACTION_UP)); - mClickable1.assertClickCount(0); - } - - private MockClickableSpan markClickable(final int start, final int end) throws Throwable { - final MockClickableSpan clickableSpan = new MockClickableSpan(); - mActivityRule.runOnUiThread(() -> mSpannable.setSpan(clickableSpan, start, end, - Spanned.SPAN_MARK_MARK)); - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); - return clickableSpan; - } - private boolean performMotionAtPoint(float x, float y, int action) { - long now = SystemClock.uptimeMillis(); - return mMethod.onTouchEvent(mView, mSpannable, - MotionEvent.obtain(now, now, action, x, y, 0)); - } - - private boolean performMotionOnLine(int line, int action) { - float x = (mView.getLayout().getLineLeft(line) + mView.getLayout().getLineRight(line)) / 2f; - float y = (mView.getLayout().getLineTop(line) + mView.getLayout().getLineBottom(line)) / 2f; - return performMotionAtPoint(x, y, action); - } - - private boolean pressOnLine(int line) { - return performMotionOnLine(line, MotionEvent.ACTION_DOWN); - } - - private boolean releaseOnLine(int line) { - return performMotionOnLine(line, MotionEvent.ACTION_UP); - } - - private void assertSelection(Spannable spannable, int start, int end) { - assertEquals(start, Selection.getSelectionStart(spannable)); - assertEquals(end, Selection.getSelectionEnd(spannable)); - } - - private void assertSelection(Spannable spannable, int position) { - assertSelection(spannable, position, position); - } - - private void assertSelectClickableLeftToRight(Spannable spannable, - ClickableSpan clickableSpan) { - assertSelection(spannable, spannable.getSpanStart(clickableSpan), - spannable.getSpanEnd(clickableSpan)); - } - - public static class TextViewNoIme extends TextView { - public TextViewNoIme(@NonNull Context context) { - super(context); - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - return null; - } - } - - public static class MockClickableSpan extends ClickableSpan { - private int mClickCount = 0; - - @Override - public void onClick(@NonNull View widget) { - ++mClickCount; - } - - public void assertClickCount(int expectedClickCount) { - assertEquals(expectedClickCount, mClickCount); - } - - public void clearClickCount() { - mClickCount = 0; - } - } -} diff --git a/PermissionController/tests/mocking/Android.bp b/PermissionController/tests/mocking/Android.bp index 64903d615..0c7c3e675 100644 --- a/PermissionController/tests/mocking/Android.bp +++ b/PermissionController/tests/mocking/Android.bp @@ -139,5 +139,5 @@ android_test { "mts-permission", ], - kotlincflags: ["-Xjvm-default=enable"], + kotlincflags: ["-Xjvm-default=all"], } diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/service/RuntimePermissionsUpgradeControllerTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/service/RuntimePermissionsUpgradeControllerTest.kt index 86aab9b60..694986312 100644 --- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/service/RuntimePermissionsUpgradeControllerTest.kt +++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/service/RuntimePermissionsUpgradeControllerTest.kt @@ -61,6 +61,7 @@ import org.junit.Assume import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.AdditionalMatchers import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq @@ -605,6 +606,14 @@ class RuntimePermissionsUpgradeControllerTest { verifyNotGranted(TEST_PKG_NAME, READ_MEDIA_VISUAL_USER_SELECTED) } + @Test + fun ensureDatabaseResetToLatestIfAboveLatest() { + setInitialDatabaseVersion(Int.MAX_VALUE) + upgradeIfNeeded() + verify(permissionManager).runtimePermissionsVersion = + AdditionalMatchers.not(eq(Int.MAX_VALUE)) + } + @After fun resetSystem() { // Send low memory notifications for all data repositories which will clear cached data diff --git a/PermissionController/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt b/PermissionController/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt index 4963a4683..8d82967e2 100644 --- a/PermissionController/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt +++ b/PermissionController/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt @@ -20,11 +20,14 @@ import android.os.ParcelFileDescriptor.AutoCloseInputStream import android.os.UserHandle.myUserId import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import com.android.modules.utils.build.SdkLevel import com.android.permissioncontroller.PermissionControllerProto.PermissionControllerDumpProto import com.google.common.truth.Truth.assertThat import com.google.protobuf.InvalidProtocolBufferException import org.junit.Assert.fail +import org.junit.Assume.assumeFalse import org.junit.Assume.assumeTrue +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import java.nio.charset.StandardCharsets.UTF_8 @@ -48,6 +51,12 @@ class DumpTest { } } + @Before + fun setUp() { + // We no longer dump auto revoke data since T. + assumeFalse(SdkLevel.isAtLeastT()) + } + @Test fun autoRevokeDumpHasCurrentUser() { val dump = getDump() diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/PermissionAppsFragmentTest.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/PermissionAppsFragmentTest.kt index a5453d9e7..1b29c3fc6 100644 --- a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/PermissionAppsFragmentTest.kt +++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/PermissionAppsFragmentTest.kt @@ -83,7 +83,6 @@ abstract class PermissionAppsFragmentTest( } } - // TODO(b/280652042) Slow tests aren't good @Test(timeout = 120000) fun appDisappearsWhenUninstalled() { assertNull(waitFindObjectOrNull(By.text(userPkg))) diff --git a/TEST_MAPPING b/TEST_MAPPING index aa594e81e..d2c722c63 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -6,7 +6,58 @@ ], "carpermission-presubmit" : [ { - "name" : "CtsPermission3TestCases" + "name" : "CtsPermissionUiTestCases", + "options": [ + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + } + ] + } + ], + "alltests" : [ + { + "name" : "PermissionControllerMockingTests" + }, + { + "name" : "CtsPermissionTestCases" + }, + { + "name" : "CtsPermissionUiTestCases" + }, + { + "name" : "CtsPermissionPolicyTestCases" + }, + { + "name" : "CtsAppOpsTestCases" + }, + { + "name" : "CtsAppOps2TestCases" + }, + { + "name": "PermissionControllerOutOfProcessTests" + }, + { + "name" : "CtsRoleTestCases" + }, + { + "name" : "CtsPermissionMultiUserTestCases" + }, + { + "name": "CtsBackupTestCases", + "options": [ + { + "include-filter": "android.backup.cts.PermissionTest" + } + ] + }, + { + "name": "CtsDevicePolicyManagerTestCases", + "options": [ + { + "include-annotation": "com.android.cts.devicepolicy.annotations.PermissionsTest" + } + ] } ] + // TODO: Do we need to create a carpermission-postsubmit test-group? } diff --git a/flags/Android.bp b/flags/Android.bp new file mode 100644 index 000000000..4f0241f91 --- /dev/null +++ b/flags/Android.bp @@ -0,0 +1,65 @@ +// +// Copyright (C) 2023 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. +// + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +aconfig_declarations { + name: "permissions-aconfig-flags", + package: "com.android.permission.flags", + srcs: ["flags.aconfig"], +} + +java_aconfig_library { + name: "permissions-aconfig-flags-lib", + aconfig_declarations: "permissions-aconfig-flags", + sdk_version: "system_current", + min_sdk_version: "30", + apex_available: [ + "com.android.permission", + "test_com.android.permission", + ], + installable: false, + visibility: [ + "//packages/modules/Permission:__subpackages__", + ], +} + +java_library { + name: "permissions-flags-lib", + sdk_version: "system_current", + min_sdk_version: "30", + target_sdk_version: "34", + srcs: [ + "java/**/*.java", + ], + static_libs: [ + "permissions-aconfig-flags-lib", + ], + libs: [ + "androidx.annotation_annotation", + "framework-annotations-lib", + ], + apex_available: [ + "com.android.permission", + "test_com.android.permission", + ], + installable: false, + visibility: [ + "//packages/modules/Permission:__subpackages__", + ], +}
\ No newline at end of file diff --git a/flags/flags.aconfig b/flags/flags.aconfig new file mode 100644 index 000000000..6a8cd5307 --- /dev/null +++ b/flags/flags.aconfig @@ -0,0 +1,7 @@ +package: "com.android.permission.flags" + +flag { + name: "voice_activation_op_enabled" + namespace: "permissions" + description: "This flag is used to support hotword activation events in privacy dashboard" +} diff --git a/flags/java/com/android/permission/flags/PermissionsFlags.java b/flags/java/com/android/permission/flags/PermissionsFlags.java new file mode 100644 index 000000000..afab3fae5 --- /dev/null +++ b/flags/java/com/android/permission/flags/PermissionsFlags.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2023 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. + */ + +package com.android.permission.flags; + +/** Class used for flags that do not work with aconfig tooling */ +public final class PermissionsFlags {} diff --git a/framework-s/java/android/app/role/RoleManager.java b/framework-s/java/android/app/role/RoleManager.java index de697f801..6b58da2d0 100644 --- a/framework-s/java/android/app/role/RoleManager.java +++ b/framework-s/java/android/app/role/RoleManager.java @@ -475,10 +475,10 @@ public final class RoleManager { } /** - * Get package names of the applications holding the role for a default application. + * Get package name of the application holding the role for a default application. * <p> - * <strong>Note:</strong> Using this API requires holding - * {@code android.permission.MANAGE_DEFAULT_APPLICATIONS}. + * Only roles describing default applications can be used with this method. They can have + * at most one holder. * * @param roleName the name of the default application role to get * @@ -506,8 +506,8 @@ public final class RoleManager { /** * Set a specific application as the default application. * <p> - * <strong>Note:</strong> Using this API requires holding - * {@code android.permission.MANAGE_DEFAULT_APPLICATIONS}. + * Only roles describing default applications can be used with this method. They can have + * at most one holder. * * @param roleName the name of the default application role to set the role holder for * @param packageName the package name of the application to set as the default application, diff --git a/service/java/com/android/role/RoleService.java b/service/java/com/android/role/RoleService.java index 485be4e72..f18a9e79e 100644 --- a/service/java/com/android/role/RoleService.java +++ b/service/java/com/android/role/RoleService.java @@ -24,6 +24,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.AppOpsManager; +import android.app.admin.DevicePolicyManager; import android.app.role.IOnRoleHoldersChangedListener; import android.app.role.IRoleManager; import android.app.role.RoleControllerManager; @@ -33,6 +34,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -41,6 +44,8 @@ import android.os.RemoteCallback; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; import android.util.IndentingPrintWriter; @@ -55,6 +60,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.Preconditions; import com.android.internal.util.dump.DualDumpOutputStream; +import com.android.modules.utils.build.SdkLevel; import com.android.permission.compat.UserHandleCompat; import com.android.permission.util.ArrayUtils; import com.android.permission.util.CollectionUtils; @@ -181,13 +187,14 @@ public class RoleService extends SystemService implements RoleUserState.Callback public void onStart() { publishBinderService(Context.ROLE_SERVICE, new Stub()); - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); - intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - intentFilter.addDataScheme("package"); - intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - getContext().registerReceiverForAllUsers(new BroadcastReceiver() { + Context context = getContext(); + IntentFilter packageIntentFilter = new IntentFilter(); + packageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + packageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageIntentFilter.addDataScheme("package"); + packageIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + context.registerReceiverForAllUsers(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int userId = UserHandleCompat.getUserId(intent.getIntExtra(Intent.EXTRA_UID, -1)); @@ -202,7 +209,44 @@ public class RoleService extends SystemService implements RoleUserState.Callback } maybeGrantDefaultRolesAsync(userId); } - }, intentFilter, null, null); + }, packageIntentFilter, null, null); + + if (SdkLevel.isAtLeastV()) { + IntentFilter devicePolicyIntentFilter = new IntentFilter(); + devicePolicyIntentFilter.addAction( + DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED); + devicePolicyIntentFilter.addAction( + DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED); + devicePolicyIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); + context.registerReceiverForAllUsers(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + int userId = getSendingUser().getIdentifier(); + if (DEBUG) { + Log.i(LOG_TAG, "Device policy changed (" + intent.getAction() + + ") - re-running initial grants for user " + userId); + } + maybeGrantDefaultRolesAsync(userId); + } + }, devicePolicyIntentFilter, null, null); + + context.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.DEVICE_DEMO_MODE), false, + new ContentObserver(ForegroundThread.getHandler()) { + public void onChange(boolean selfChange, Uri uri) { + if (DEBUG) { + Log.i(LOG_TAG, "Settings.Global.DEVICE_DEMO_MODE changed."); + } + UserManager userManager = + context.getSystemService(UserManager.class); + List<UserHandle> users = userManager.getUserHandles(true); + int usersSize = users.size(); + for (int i = 0; i < usersSize; i++) { + maybeGrantDefaultRolesAsync(users.get(i).getIdentifier()); + } + } + }); + } } @Override diff --git a/service/java/com/android/role/TEST_MAPPING b/service/java/com/android/role/TEST_MAPPING index 15173a9da..b3507f053 100644 --- a/service/java/com/android/role/TEST_MAPPING +++ b/service/java/com/android/role/TEST_MAPPING @@ -13,6 +13,9 @@ "options": [ { "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" } ] } @@ -30,6 +33,36 @@ }, { "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + } + ] + } + ], + "postsubmit": [ + { + "name": "CtsAppSecurityHostTestCases", + "options": [ + { + "include-filter": "android.appsecurity.cts.StatsdAppSecurityAtomTest#testRoleHolder" + } + ] + }, + { + "name": "CtsRoleTestCases" + } + ], + "mainline-postsubmit": [ + { + "name": "CtsRoleTestCases[com.google.android.permission.apex]", + "options": [ + // TODO(b/238677748): These two tests currently fails on R base image + { + "exclude-filter": "android.app.role.cts.RoleManagerTest#openDefaultAppListThenIsNotDefaultAppInList" + }, + { + "exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked" } ] } diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml index 90ea8d411..dd7e79ef5 100644 --- a/service/lint-baseline.xml +++ b/service/lint-baseline.xml @@ -19,8 +19,24 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> <location file="packages/modules/Permission/service/java/com/android/safetycenter/SafetyCenterService.java" - line="660" + line="651" column="40"/> </issue> + <issue + id="NewApi" + message="Call requires API level 34 (current min is 33): `getDeduplicationGroup`"> + <location + file="packages/modules/Permission/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java" + line="315"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 34 (current min is 33): `getDeduplicationId`"> + <location + file="packages/modules/Permission/service/java/com/android/safetycenter/data/SafetyCenterIssueDeduplicator.java" + line="316"/> + </issue> + </issues>
\ No newline at end of file diff --git a/tests/cts/permissionmultiuser/Android.bp b/tests/cts/permissionmultiuser/Android.bp new file mode 100644 index 000000000..f577c82e3 --- /dev/null +++ b/tests/cts/permissionmultiuser/Android.bp @@ -0,0 +1,47 @@ +// +// Copyright (C) 2023 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. +// + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test { + name: "CtsPermissionMultiUserTestCases", + defaults: ["mts-target-sdk-version-current"], + sdk_version: "test_current", + srcs: [ + "src/**/*.kt", + ], + min_sdk_version: "30", + static_libs: [ + "androidx.test.core", + "androidx.test.ext.junit", + "androidx.test.rules", + "compatibility-device-util-axt", + "ctstestrunner-axt", + "Harrier", + "modules-utils-build_system", + "Nene", + ], + data: [ + ":CtsRequestLocationApp", + ], + test_suites: [ + "cts", + "general-tests", + "mts-permission", + ], +} diff --git a/tests/cts/permissionmultiuser/AndroidManifest.xml b/tests/cts/permissionmultiuser/AndroidManifest.xml new file mode 100644 index 000000000..15bc3af34 --- /dev/null +++ b/tests/cts/permissionmultiuser/AndroidManifest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2023 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. + --> + +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="android.permissionmultiuser.cts"> + + <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.permissionmultiuser.cts" + android:label="CTS multi-user tests for permissions"> + </instrumentation> +</manifest> diff --git a/tests/cts/permissionmultiuser/AndroidTest.xml b/tests/cts/permissionmultiuser/AndroidTest.xml new file mode 100644 index 000000000..697901361 --- /dev/null +++ b/tests/cts/permissionmultiuser/AndroidTest.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2023 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. + --> + +<configuration description="Config for CTS permissionmultiuser test cases"> + + <option name="test-suite-tag" value="cts" /> + + <option name="config-descriptor:metadata" key="component" value="framework" /> + <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" /> + <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> + <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> + <option name="config-descriptor:metadata" key="parameter" value="multiuser" /> + <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" /> + <option name="config-descriptor:metadata" key="parameter" value="run_on_sdk_sandbox" /> + <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.permission.apex" /> + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" /> + + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device --> + <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" /> + <option name="restore-settings" value="true" /> + <option name="disable-device-config-sync" value="true" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="CtsPermissionMultiUserTestCases.apk" /> + </target_preparer> + + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <option name="push" value="CtsRequestLocationApp.apk->/data/local/tmp/cts/permissionmultiuser/CtsRequestLocationApp.apk" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="appops set android.permissionmultiuser.cts REQUEST_INSTALL_PACKAGES allow" /> + <!-- disable DeprecatedAbi warning --> + <option name="run-command" value="setprop debug.wm.disable_deprecated_abi_dialog 1" /> + <!-- disable DeprecatedTargetSdk warning --> + <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1" /> + <option name="run-command" value="am wait-for-broadcast-barrier" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.permissionmultiuser.cts" /> + <option name="runtime-hint" value="5m" /> + </test> + +</configuration> diff --git a/tests/cts/permissionmultiuser/OWNERS b/tests/cts/permissionmultiuser/OWNERS new file mode 100644 index 000000000..fb6099cf7 --- /dev/null +++ b/tests/cts/permissionmultiuser/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 137825 + +include platform/frameworks/base:/core/java/android/permission/OWNERS diff --git a/tests/cts/permissionmultiuser/RequestLocationApp/Android.bp b/tests/cts/permissionmultiuser/RequestLocationApp/Android.bp new file mode 100644 index 000000000..d645e15d7 --- /dev/null +++ b/tests/cts/permissionmultiuser/RequestLocationApp/Android.bp @@ -0,0 +1,25 @@ +// +// Copyright (C) 2023 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. +// + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test_helper_app { + name: "CtsRequestLocationApp", + target_sdk_version: "30", + min_sdk_version: "30", +} diff --git a/tests/cts/permissionmultiuser/RequestLocationApp/AndroidManifest.xml b/tests/cts/permissionmultiuser/RequestLocationApp/AndroidManifest.xml new file mode 100644 index 000000000..e5e7609eb --- /dev/null +++ b/tests/cts/permissionmultiuser/RequestLocationApp/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="android.permissionmultiuser.cts.requestlocation"> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <application> + </application> +</manifest> diff --git a/tests/cts/permissionmultiuser/TEST_MAPPING b/tests/cts/permissionmultiuser/TEST_MAPPING new file mode 100644 index 000000000..48f5ef537 --- /dev/null +++ b/tests/cts/permissionmultiuser/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "CtsPermissionMultiUserTestCases" + } + ] +} diff --git a/tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt b/tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt new file mode 100644 index 000000000..c0469291f --- /dev/null +++ b/tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2023 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. + */ + +package android.permissionmultiuser.cts + +import android.app.Instrumentation +import android.app.PendingIntent +import android.app.PendingIntent.FLAG_MUTABLE +import android.app.PendingIntent.FLAG_UPDATE_CURRENT +import android.app.UiAutomation +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Context.RECEIVER_EXPORTED +import android.content.Intent +import android.content.Intent.ACTION_REVIEW_APP_DATA_SHARING_UPDATES +import android.content.IntentFilter +import android.content.pm.PackageInstaller +import android.content.pm.PackageInstaller.EXTRA_STATUS +import android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE +import android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID +import android.content.pm.PackageInstaller.STATUS_SUCCESS +import android.content.pm.PackageInstaller.SessionParams +import android.content.pm.PackageManager +import android.content.pm.PackageManager.FEATURE_AUTOMOTIVE +import android.content.pm.PackageManager.FEATURE_LEANBACK +import android.os.Build +import android.os.PersistableBundle +import android.os.SystemClock +import android.os.UserHandle +import android.provider.DeviceConfig +import android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED +import android.support.test.uiautomator.By +import android.support.test.uiautomator.BySelector +import android.support.test.uiautomator.StaleObjectException +import android.support.test.uiautomator.UiDevice +import android.support.test.uiautomator.UiObject2 +import android.util.Log +import androidx.test.filters.SdkSuppress +import androidx.test.platform.app.InstrumentationRegistry +import com.android.bedstead.harrier.BedsteadJUnit4 +import com.android.bedstead.harrier.DeviceState +import com.android.bedstead.harrier.annotations.EnsureHasPermission +import com.android.bedstead.harrier.annotations.EnsureSecureSettingSet +import com.android.bedstead.harrier.annotations.RequireDoesNotHaveFeature +import com.android.bedstead.harrier.annotations.RequireNotWatch +import com.android.bedstead.harrier.annotations.RequireRunOnAdditionalUser +import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile +import com.android.bedstead.harrier.annotations.RequireSdkVersion +import com.android.bedstead.nene.permissions.CommonPermissions.INTERACT_ACROSS_USERS +import com.android.compatibility.common.util.ApiTest +import com.android.compatibility.common.util.DeviceConfigStateChangerRule +import com.android.compatibility.common.util.SystemUtil.runShellCommand +import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity +import com.android.compatibility.common.util.SystemUtil.waitForBroadcasts +import com.android.compatibility.common.util.UiAutomatorUtils +import com.google.common.truth.Truth.assertThat +import java.io.File +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.TimeUnit +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Tests the UI that displays information about apps' updates to their data sharing policies when + * device has multiple users. + */ +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake") +@RequireSdkVersion(min = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +@RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE) +@RequireDoesNotHaveFeature(FEATURE_LEANBACK) +@RequireNotWatch(reason = "Data sharing update page is unavailable on watch") +@RunWith(BedsteadJUnit4::class) +@EnsureSecureSettingSet(key = "user_setup_complete", value = "1") +class AppDataSharingUpdatesTest { + + @get:Rule + val deviceConfigSafetyLabelChangeNotificationsEnabled = + DeviceConfigStateChangerRule( + context, + DeviceConfig.NAMESPACE_PRIVACY, + SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, + true.toString()) + + @get:Rule + val deviceConfigDataSharingUpdatesPeriod = + DeviceConfigStateChangerRule( + context, + DeviceConfig.NAMESPACE_PRIVACY, + PROPERTY_DATA_SHARING_UPDATE_PERIOD_MILLIS, + "600000") + + /** + * This rule serves to limit the max number of safety labels that can be persisted, so that + * repeated tests don't overwhelm the disk storage on the device. + */ + @get:Rule + val deviceConfigMaxSafetyLabelsPersistedPerApp = + DeviceConfigStateChangerRule( + context, + DeviceConfig.NAMESPACE_PRIVACY, + PROPERTY_MAX_SAFETY_LABELS_PERSISTED_PER_APP, + "2") + + @Before + fun registerInstallSessionResultReceiver() { + context.registerReceiver( + installSessionResultReceiver, IntentFilter(INSTALL_ACTION_CALLBACK), RECEIVER_EXPORTED) + } + + @After + fun unregisterInstallSessionResultReceiver() { + try { + context.unregisterReceiver(installSessionResultReceiver) + } catch (ignored: IllegalArgumentException) {} + } + + @Test + @EnsureHasPermission(INTERACT_ACROSS_USERS) + @RequireRunOnWorkProfile + @ApiTest(apis = ["android.content.Intent#ACTION_REVIEW_APP_DATA_SHARING_UPDATES"]) + fun openDataSharingUpdatesPage_workProfile_whenAppHasUpdateAndLocationGranted_showUpdates() { + installPackageViaSession(LOCATION_PACKAGE_APK_PATH, createAppMetadataWithNoSharing()) + waitForBroadcasts() + installPackageViaSession( + LOCATION_PACKAGE_APK_PATH, createAppMetadataWithLocationSharingNoAds()) + waitForBroadcasts() + grantLocationPermission(LOCATION_PACKAGE_NAME) + + startAppDataSharingUpdatesActivityForUser(deviceState.initialUser().userHandle()) + + try { + assertUpdatesPresent() + findView(By.textContains(LOCATION_PACKAGE_NAME_SUBSTRING), true) + } finally { + pressBack() + uninstallPackage(LOCATION_PACKAGE_NAME) + } + } + + @Test + @EnsureHasPermission(INTERACT_ACROSS_USERS) + @RequireRunOnAdditionalUser + @ApiTest(apis = ["android.content.Intent#ACTION_REVIEW_APP_DATA_SHARING_UPDATES"]) + fun openDataSharingUpdatesPage_additionalUser_whenAppHasUpdateAndLocationGranted_showUpdates() { + installPackageViaSession(LOCATION_PACKAGE_APK_PATH, createAppMetadataWithNoSharing()) + waitForBroadcasts() + installPackageViaSession( + LOCATION_PACKAGE_APK_PATH, createAppMetadataWithLocationSharingNoAds()) + waitForBroadcasts() + grantLocationPermission(LOCATION_PACKAGE_NAME) + + startAppDataSharingUpdatesActivityForUser(deviceState.additionalUser().userHandle()) + + try { + assertUpdatesPresent() + findView(By.textContains(LOCATION_PACKAGE_NAME_SUBSTRING), true) + } finally { + pressBack() + uninstallPackage(LOCATION_PACKAGE_NAME) + } + + deviceState.initialUser().switchTo() + + startAppDataSharingUpdatesActivityForUser(deviceState.initialUser().userHandle()) + + try { + // Verify that state does not leak across users. + assertNoUpdatesPresent() + findView(By.textContains(LOCATION_PACKAGE_NAME_SUBSTRING), false) + } finally { + pressBack() + } + } + + /** Companion object for [AppDataSharingUpdatesTest]. */ + companion object { + @JvmField @ClassRule @Rule val deviceState: DeviceState = DeviceState() + + @JvmStatic + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val context: Context = instrumentation.context + @JvmStatic private val uiAutomation: UiAutomation = instrumentation.uiAutomation + @JvmStatic private val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) + @JvmStatic private val packageManager: PackageManager = context.packageManager + @JvmStatic private val packageInstaller = packageManager.packageInstaller + private data class SessionResult(val status: Int?) + private val TAG = AppDataSharingUpdatesTest::class.simpleName + + private const val APK_DIRECTORY = "/data/local/tmp/cts/permissionmultiuser" + private const val LOCATION_PACKAGE_NAME = "android.permissionmultiuser.cts.requestlocation" + private const val LOCATION_PACKAGE_APK_PATH = "CtsRequestLocationApp.apk" + private const val INSTALL_ACTION_CALLBACK = "AppDataSharingUpdatesTest.install_callback" + private const val PACKAGE_INSTALLER_TIMEOUT = 60000L + private const val IDLE_TIMEOUT_MILLIS: Long = 1000 + private const val TIMEOUT_MILLIS: Long = 20000 + + private const val KEY_VERSION = "version" + private const val KEY_SAFETY_LABELS = "safety_labels" + private const val KEY_DATA_SHARED = "data_shared" + private const val KEY_DATA_LABELS = "data_labels" + private const val KEY_PURPOSES = "purposes" + private const val INITIAL_SAFETY_LABELS_VERSION = 1L + private const val INITIAL_TOP_LEVEL_VERSION = 1L + private const val LOCATION_CATEGORY = "location" + private const val APPROX_LOCATION = "approx_location" + private const val PURPOSE_FRAUD_PREVENTION_SECURITY = 4 + + private const val DATA_SHARING_UPDATES = "Data sharing updates for location" + private const val DATA_SHARING_UPDATES_SUBTITLE = + "These apps have changed the way they may share your location data. They may not" + + " have shared it before, or may now share it for advertising or marketing" + + " purposes." + private const val DATA_SHARING_NO_UPDATES_MESSAGE = "No updates at this time" + private const val UPDATES_IN_LAST_30_DAYS = "Updated within 30 days" + private const val DATA_SHARING_UPDATES_FOOTER_MESSAGE = + "The developers of these apps provided info about their data sharing practices" + + " to an app store. They may update it over time.\n\nData sharing" + + " practices may vary based on your app version, use, region, and age." + private const val LOCATION_PACKAGE_NAME_SUBSTRING = "android.permissionmultiuser" + private const val PROPERTY_DATA_SHARING_UPDATE_PERIOD_MILLIS = + "data_sharing_update_period_millis" + private const val PROPERTY_MAX_SAFETY_LABELS_PERSISTED_PER_APP = + "max_safety_labels_persisted_per_app" + + private var installSessionResult = LinkedBlockingQueue<SessionResult>() + + private val installSessionResultReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID) + val msg = intent.getStringExtra(EXTRA_STATUS_MESSAGE) + Log.d(TAG, "status: $status, msg: $msg") + + installSessionResult.offer(SessionResult(status)) + } + } + + /** Installs an app with the provided [appMetadata] */ + private fun installPackageViaSession( + apkName: String, + appMetadata: PersistableBundle? = null, + packageSource: Int? = null + ) { + val session = createPackageInstallerSession(packageSource) + runWithShellPermissionIdentity { + writePackageInstallerSession(session, apkName) + if (appMetadata != null) { + setAppMetadata(session, appMetadata) + } + commitPackageInstallerSession(session) + + // No need to click installer UI here due to running in shell permission identity + // and not needing user interaction to complete install. + // Install should have succeeded. + val result = getInstallSessionResult() + assertThat(result.status).isEqualTo(STATUS_SUCCESS) + } + } + + private fun createPackageInstallerSession( + packageSource: Int? = null + ): PackageInstaller.Session { + val sessionParam = SessionParams(SessionParams.MODE_FULL_INSTALL) + if (packageSource != null) { + sessionParam.setPackageSource(packageSource) + } + + val sessionId = packageInstaller.createSession(sessionParam) + return packageInstaller.openSession(sessionId) + } + + private fun writePackageInstallerSession( + session: PackageInstaller.Session, + apkName: String + ) { + val apkFile = File(APK_DIRECTORY, apkName) + apkFile.inputStream().use { fileOnDisk -> + session + .openWrite(/* name= */ apkName, /* offsetBytes= */ 0, /* lengthBytes= */ -1) + .use { sessionFile -> fileOnDisk.copyTo(sessionFile) } + } + } + + private fun commitPackageInstallerSession(session: PackageInstaller.Session) { + // PendingIntent that triggers a INSTALL_ACTION_CALLBACK broadcast that gets received by + // installSessionResultReceiver when install actions occur with this session + val installActionPendingIntent = + PendingIntent.getBroadcast( + context, + 0, + Intent(INSTALL_ACTION_CALLBACK).setPackage(context.packageName), + FLAG_UPDATE_CURRENT or FLAG_MUTABLE) + session.commit(installActionPendingIntent.intentSender) + } + + private fun setAppMetadata(session: PackageInstaller.Session, data: PersistableBundle) { + try { + session.setAppMetadata(data) + } catch (e: Exception) { + session.abandon() + throw e + } + } + + private fun getInstallSessionResult( + timeout: Long = PACKAGE_INSTALLER_TIMEOUT + ): SessionResult { + return installSessionResult.poll(timeout, TimeUnit.MILLISECONDS) + ?: SessionResult(null /* status */) + } + + private fun uninstallPackage(packageName: String) { + runShellCommand("pm uninstall $packageName").trim() + } + + private fun pressBack() { + uiDevice.pressBack() + uiAutomation.waitForIdle(IDLE_TIMEOUT_MILLIS, TIMEOUT_MILLIS) + } + + /** Returns an App Metadata [PersistableBundle] representation where no data is shared. */ + private fun createAppMetadataWithNoSharing(): PersistableBundle { + return createMetadataWithDataShared(PersistableBundle()) + } + + /** + * Returns an App Metadata [PersistableBundle] representation where location data is shared, + * but not for advertising purpose. + */ + private fun createAppMetadataWithLocationSharingNoAds(): PersistableBundle { + val locationBundle = + PersistableBundle().apply { + putPersistableBundle( + APPROX_LOCATION, + PersistableBundle().apply { + putIntArray( + KEY_PURPOSES, + listOf(PURPOSE_FRAUD_PREVENTION_SECURITY).toIntArray()) + }) + } + + val dataSharedBundle = + PersistableBundle().apply { + putPersistableBundle(LOCATION_CATEGORY, locationBundle) + } + + return createMetadataWithDataShared(dataSharedBundle) + } + + /** + * Returns an App Metadata [PersistableBundle] representation where with the provided data + * shared. + */ + private fun createMetadataWithDataShared( + dataSharedBundle: PersistableBundle + ): PersistableBundle { + val dataLabelBundle = + PersistableBundle().apply { + putPersistableBundle(KEY_DATA_SHARED, dataSharedBundle) + } + + val safetyLabelBundle = + PersistableBundle().apply { + putLong(KEY_VERSION, INITIAL_SAFETY_LABELS_VERSION) + putPersistableBundle(KEY_DATA_LABELS, dataLabelBundle) + } + + return PersistableBundle().apply { + putLong(KEY_VERSION, INITIAL_TOP_LEVEL_VERSION) + putPersistableBundle(KEY_SAFETY_LABELS, safetyLabelBundle) + } + } + + /** + * Starts activity with intent [ACTION_REVIEW_APP_DATA_SHARING_UPDATES] for the provided + * user. + */ + fun startAppDataSharingUpdatesActivityForUser(userHandle: UserHandle) { + runWithShellPermissionIdentity { + context.startActivityAsUser( + Intent(ACTION_REVIEW_APP_DATA_SHARING_UPDATES).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + }, + userHandle) + } + } + + private fun assertUpdatesPresent() { + findView(By.descContains(DATA_SHARING_UPDATES), true) + findView(By.textContains(DATA_SHARING_UPDATES_SUBTITLE), true) + findView(By.textContains(UPDATES_IN_LAST_30_DAYS), true) + findView(By.textContains(DATA_SHARING_UPDATES_FOOTER_MESSAGE), true) + } + + private fun assertNoUpdatesPresent() { + findView(By.descContains(DATA_SHARING_UPDATES), true) + findView(By.textContains(DATA_SHARING_UPDATES_SUBTITLE), true) + findView(By.textContains(DATA_SHARING_NO_UPDATES_MESSAGE), true) + findView(By.textContains(LOCATION_PACKAGE_NAME_SUBSTRING), false) + findView(By.textContains(UPDATES_IN_LAST_30_DAYS), false) + findView(By.textContains(DATA_SHARING_UPDATES_FOOTER_MESSAGE), true) + } + + private fun grantLocationPermission(packageName: String) { + uiAutomation.grantRuntimePermission( + packageName, android.Manifest.permission.ACCESS_FINE_LOCATION) + } + + protected fun waitFindObject( + selector: BySelector, + timeoutMillis: Long = 20_000L + ): UiObject2 { + uiAutomation.waitForIdle(IDLE_TIMEOUT_MILLIS, TIMEOUT_MILLIS) + val startTime = SystemClock.elapsedRealtime() + return try { + UiAutomatorUtils.waitFindObject(selector, timeoutMillis) + } catch (e: StaleObjectException) { + val remainingTime = timeoutMillis - (SystemClock.elapsedRealtime() - startTime) + if (remainingTime <= 0) { + throw e + } + UiAutomatorUtils.waitFindObject(selector, remainingTime) + } + } + + private fun findView(selector: BySelector, expected: Boolean) { + val timeoutMillis = + if (expected) { + 10000L + } else { + 1000L + } + + val exception = + try { + uiAutomation.waitForIdle(IDLE_TIMEOUT_MILLIS, TIMEOUT_MILLIS) + val startTime = SystemClock.elapsedRealtime() + try { + UiAutomatorUtils.waitFindObject(selector, timeoutMillis) + } catch (e: StaleObjectException) { + val remainingTime = + timeoutMillis - (SystemClock.elapsedRealtime() - startTime) + if (remainingTime <= 0) { + throw e + } + UiAutomatorUtils.waitFindObject(selector, remainingTime) + } + null + } catch (e: Exception) { + e + } + Assert.assertTrue("Expected to find view: $expected", (exception == null) == expected) + } + } +} |