summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--OWNERS2
-rw-r--r--PermissionController/Android.bp8
-rw-r--r--PermissionController/AndroidManifest.xml4
-rw-r--r--PermissionController/TEST_MAPPING61
-rw-r--r--PermissionController/lint-baseline.xml216
-rw-r--r--PermissionController/res/navigation-watch/nav_graph.xml126
-rw-r--r--PermissionController/res/values-am/strings.xml46
-rw-r--r--PermissionController/res/values-ar-v33/strings.xml2
-rw-r--r--PermissionController/res/values-ar/strings.xml4
-rw-r--r--PermissionController/res/values-in/strings.xml2
-rw-r--r--PermissionController/res/values-pt-rPT/strings.xml34
-rw-r--r--PermissionController/res/values/styles.xml2
-rw-r--r--PermissionController/res/xml/roles.xml27
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java110
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java49
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/RetailDemoRoleBehavior.java63
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/SystemUiRoleBehavior.java59
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Permission.java6
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredActivity.java19
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredBroadcastReceiver.java14
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredComponent.java41
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredContentProvider.java14
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredService.java13
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Role.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/Constants.java11
-rw-r--r--PermissionController/src/com/android/permissioncontroller/auto/AutoSettingsFrameFragment.java17
-rw-r--r--PermissionController/src/com/android/permissioncontroller/auto/DrivingDecisionReminderService.kt54
-rw-r--r--PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING65
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompat.java84
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesLiveData.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesUiInfoLiveData.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt11
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java17
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/Permission.java12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt28
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceModel.kt10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/TEST_MAPPING53
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/AutoGrantPermissionsNotifier.java59
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java77
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java11
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/TEST_MAPPING13
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt32
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/NewGrantPermissionsViewModel.kt1292
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt20
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BackgroundGrantBehavior.kt207
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BasicGrantBehavior.kt55
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/GrantBehavior.kt85
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/HealthGrantBehavior.kt53
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/LocationGrantBehavior.kt106
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/NotificationGrantBehavior.kt58
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/StorageGrantBehavior.kt134
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/ReviewOngoingUsageViewModel.kt10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/ReviewPermissionsWearFragment.java4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionFragment.kt339
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt221
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsFragment.kt122
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt147
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionsAppHelper.kt185
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AdjustChipHeightToFontScale.kt30
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AlertDialog.kt126
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Button.kt137
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt223
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/DrawablePainter.kt172
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Icon.kt124
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListFooter.kt88
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt41
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt162
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt149
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/AppPermissionConfirmDialogViewModel.kt60
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt110
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java27
-rw-r--r--PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING19
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING23
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RoleListSortFunction.java14
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RoleSortFunction.java15
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING27
-rw-r--r--PermissionController/tests/inprocess/Android.bp5
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java258
-rw-r--r--PermissionController/tests/mocking/Android.bp7
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/service/RuntimePermissionsUpgradeControllerTest.kt9
-rw-r--r--PermissionController/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt9
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/PermissionAppsFragmentTest.kt1
-rw-r--r--SafetyCenter/Resources/shared_res/values-af/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-am/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ar/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-as/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-az/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-be/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-bg/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-bn/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-bs/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ca/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-cs/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-da/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-de/strings.xml3
-rw-r--r--SafetyCenter/Resources/shared_res/values-el/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml3
-rw-r--r--SafetyCenter/Resources/shared_res/values-es/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-et/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-eu/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-fa/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-fi/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-fr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-gl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-gu/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-hi/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-hr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-hu/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-hy/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-in/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-is/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-it/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-iw/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ja/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ka/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-kk/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-km/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-kn/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ko/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ky/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-lo/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-lt/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-lv/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-mk/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ml/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-mn/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-mr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ms/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-my/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-nb/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ne/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-nl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-or/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pa/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pt/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ro/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ru/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-si/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sk/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sq/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sv/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sw/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ta/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-te/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-th/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-tl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-tr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-uk/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ur/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-uz/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-vi/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-zu/strings.xml1
-rw-r--r--TEST_MAPPING62
-rw-r--r--flags/Android.bp65
-rw-r--r--flags/flags.aconfig8
-rw-r--r--flags/java/com/android/permission/flags/PermissionsFlags.java20
-rw-r--r--framework-s/java/android/app/role/RoleManager.java10
-rw-r--r--framework-s/java/android/app/role/TEST_MAPPING23
-rw-r--r--service/java/com/android/role/RoleService.java60
-rw-r--r--service/java/com/android/role/TEST_MAPPING33
-rw-r--r--service/lint-baseline.xml18
-rw-r--r--tests/cts/permission/Android.bp125
-rw-r--r--tests/cts/permission/AndroidManifest.xml100
-rw-r--r--tests/cts/permission/AndroidTest.xml136
-rw-r--r--tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp35
-rw-r--r--tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/AndroidManifest.xml40
-rw-r--r--tests/cts/permission/AppThatAccessesLocationOnCommand/Android.bp40
-rw-r--r--tests/cts/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java68
-rw-r--r--tests/cts/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl22
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionA/Android.bp32
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionA/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp32
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionADifferentCert/AndroidManifest.xml25
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp32
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp32
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionA/Android.bp32
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionA/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp31
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp31
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup30/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp31
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionWithPlatformGroup/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp32
-rw-r--r--tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/AndroidManifest.xml38
-rw-r--r--tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/src/android/permission/cts/appthatrequestpermission/RequestPermissions.kt39
-rw-r--r--tests/cts/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp36
-rw-r--r--tests/cts/permission/AppThatDoesNotHaveBgLocationAccess/AndroidManifest.xml24
-rw-r--r--tests/cts/permission/AppThatHasNotificationListener/Android.bp39
-rw-r--r--tests/cts/permission/AppThatHasNotificationListener/AndroidManifest.xml33
-rw-r--r--tests/cts/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java21
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermission30/Android.bp40
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermission30/AndroidManifest.xml35
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermission30/src/android/permission/cts/appthatrequestpermission/AccessBluetoothOnCommand.java161
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermission31/Android.bp33
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermission31/AndroidManifest.xml34
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocation31/Android.bp33
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocation31/AndroidManifest.xml40
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/Android.bp32
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/AndroidManifest.xml36
-rw-r--r--tests/cts/permission/AppThatRequestContactsAndCallLogPermission16/Android.bp31
-rw-r--r--tests/cts/permission/AppThatRequestContactsAndCallLogPermission16/AndroidManifest.xml29
-rw-r--r--tests/cts/permission/AppThatRequestContactsPermission15/Android.bp31
-rw-r--r--tests/cts/permission/AppThatRequestContactsPermission15/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatRequestContactsPermission16/Android.bp31
-rw-r--r--tests/cts/permission/AppThatRequestContactsPermission16/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatRequestCustomCameraPermission/Android.bp37
-rw-r--r--tests/cts/permission/AppThatRequestCustomCameraPermission/AndroidManifest.xml40
-rw-r--r--tests/cts/permission/AppThatRequestCustomCameraPermission/res/values/strings.xml20
-rw-r--r--tests/cts/permission/AppThatRequestCustomCameraPermission/src/android/permission/cts/appthatrequestcustomcamerapermission/RequestCameraPermission.java81
-rw-r--r--tests/cts/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp32
-rw-r--r--tests/cts/permission/AppThatRequestLocationAndBackgroundPermission28/AndroidManifest.xml32
-rw-r--r--tests/cts/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp33
-rw-r--r--tests/cts/permission/AppThatRequestLocationAndBackgroundPermission29/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission22/Android.bp31
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission22/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission28/Android.bp31
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission28/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission29/Android.bp32
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission29/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission29v4/Android.bp32
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission29v4/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatRequestMultiplePermissionsWithMinMaxSdk/Android.bp31
-rw-r--r--tests/cts/permission/AppThatRequestMultiplePermissionsWithMinMaxSdk/AndroidManifest.xml30
-rw-r--r--tests/cts/permission/AppThatRequestOneTimePermission/Android.bp35
-rw-r--r--tests/cts/permission/AppThatRequestOneTimePermission/AndroidManifest.xml36
-rw-r--r--tests/cts/permission/AppThatRequestOneTimePermission/src/android/permission/cts/appthatrequestpermission/KeepAliveForegroundService.java72
-rw-r--r--tests/cts/permission/AppThatRequestOneTimePermission/src/android/permission/cts/appthatrequestpermission/RequestPermission.java29
-rw-r--r--tests/cts/permission/AppThatRequestPermissionAandB/Android.bp33
-rw-r--r--tests/cts/permission/AppThatRequestPermissionAandB/AndroidManifest.xml35
-rw-r--r--tests/cts/permission/AppThatRequestPermissionAandB/res/values/strings.xml19
-rw-r--r--tests/cts/permission/AppThatRequestPermissionAandB/src/android/permission/cts/appthatrequestpermission/RequestPermission.java30
-rw-r--r--tests/cts/permission/AppThatRequestPermissionAandC/Android.bp33
-rw-r--r--tests/cts/permission/AppThatRequestPermissionAandC/AndroidManifest.xml35
-rw-r--r--tests/cts/permission/AppThatRequestPermissionAandC/res/values/strings.xml19
-rw-r--r--tests/cts/permission/AppThatRequestPermissionAandC/src/android/permission/cts/appthatrequestpermission/RequestPermission.java30
-rw-r--r--tests/cts/permission/AppThatRequestStoragePermission28/Android.bp31
-rw-r--r--tests/cts/permission/AppThatRequestStoragePermission28/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatRequestStoragePermission29/Android.bp31
-rw-r--r--tests/cts/permission/AppThatRequestStoragePermission29/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatRequestSystemAlertWindow22/Android.bp32
-rw-r--r--tests/cts/permission/AppThatRequestSystemAlertWindow22/AndroidManifest.xml24
-rw-r--r--tests/cts/permission/AppThatRequestSystemAlertWindow23/Android.bp32
-rw-r--r--tests/cts/permission/AppThatRequestSystemAlertWindow23/AndroidManifest.xml24
-rw-r--r--tests/cts/permission/AppThatRunsRationaleTests/Android.bp35
-rw-r--r--tests/cts/permission/AppThatRunsRationaleTests/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatRunsRationaleTests/src/android/permission/cts/appthatrunsrationaletests/TestActivity.java42
-rw-r--r--tests/cts/permission/AppToTestRevokeSelfPermission/Android.bp35
-rw-r--r--tests/cts/permission/AppToTestRevokeSelfPermission/AndroidManifest.xml31
-rw-r--r--tests/cts/permission/AppToTestRevokeSelfPermission/src/android/permission/cts/apptotestselfrevokepermission/RevokePermission.java40
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp33
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestLocationPermission28/AndroidManifest.xml29
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp33
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestLocationPermission29/AndroidManifest.xml30
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestsNoPermissions/Android.bp30
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestsNoPermissions/AndroidManifest.xml24
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestsPermissions/Android.bp30
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestsPermissions/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/OWNERS12
-rw-r--r--tests/cts/permission/README16
-rw-r--r--tests/cts/permission/StorageEscalationApp28/Android.bp30
-rw-r--r--tests/cts/permission/StorageEscalationApp28/AndroidManifest.xml30
-rw-r--r--tests/cts/permission/StorageEscalationApp29Full/Android.bp30
-rw-r--r--tests/cts/permission/StorageEscalationApp29Full/AndroidManifest.xml30
-rw-r--r--tests/cts/permission/StorageEscalationApp29Scoped/Android.bp30
-rw-r--r--tests/cts/permission/StorageEscalationApp29Scoped/AndroidManifest.xml30
-rw-r--r--tests/cts/permission/jni/Android.bp60
-rw-r--r--tests/cts/permission/jni/CtsPermissionsJniOnLoad.cpp34
-rw-r--r--tests/cts/permission/jni/PermissionManagerNativeJniTest.cpp50
-rw-r--r--tests/cts/permission/jni/android_permission_cts_FileUtils.cpp250
-rw-r--r--tests/cts/permission/nativeTests/Android.bp56
-rw-r--r--tests/cts/permission/nativeTests/AndroidTest.xml43
-rw-r--r--tests/cts/permission/nativeTests/src/PermissionManagerNativeTest.cpp63
-rw-r--r--tests/cts/permission/permissionTestUtilLib/Android.bp37
-rw-r--r--tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerHelperRule.kt58
-rw-r--r--tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerService.java72
-rw-r--r--tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerServiceUtils.kt119
-rw-r--r--tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/MtsIgnore.java40
-rw-r--r--tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java413
-rw-r--r--tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java205
-rw-r--r--tests/cts/permission/res/drawable/robot.pngbin0 -> 5634 bytes
-rw-r--r--tests/cts/permission/res/values/strings.xml22
-rw-r--r--tests/cts/permission/res/xml/test_accessibilityservice.xml21
-rw-r--r--tests/cts/permission/sdk28/Android.bp33
-rw-r--r--tests/cts/permission/sdk28/AndroidManifest.xml52
-rw-r--r--tests/cts/permission/sdk28/AndroidTest.xml32
-rw-r--r--tests/cts/permission/sdk28/OWNERS1
-rw-r--r--tests/cts/permission/sdk28/TEST_MAPPING7
-rw-r--r--tests/cts/permission/sdk28/res/values/strings.xml19
-rw-r--r--tests/cts/permission/sdk28/src/android/permission/cts/sdk28/RequestLocation.java70
-rw-r--r--tests/cts/permission/src/android/permission/cts/AccessibilityPrivacySourceTest.kt351
-rw-r--r--tests/cts/permission/src/android/permission/cts/AccessibilityTestService.kt24
-rw-r--r--tests/cts/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java134
-rw-r--r--tests/cts/permission/src/android/permission/cts/AppIdleStatePermissionTest.java92
-rw-r--r--tests/cts/permission/src/android/permission/cts/AppWidgetManagerPermissionTest.java61
-rw-r--r--tests/cts/permission/src/android/permission/cts/BackgroundPermissionButtonLabelTest.java55
-rw-r--r--tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java254
-rw-r--r--tests/cts/permission/src/android/permission/cts/BaseNotificationListenerCheckTest.java324
-rw-r--r--tests/cts/permission/src/android/permission/cts/Camera2PermissionTest.java191
-rw-r--r--tests/cts/permission/src/android/permission/cts/CameraPermissionTest.java94
-rw-r--r--tests/cts/permission/src/android/permission/cts/ConnectivityManagerPermissionTest.java120
-rw-r--r--tests/cts/permission/src/android/permission/cts/ContactsProviderTest.java157
-rw-r--r--tests/cts/permission/src/android/permission/cts/DebuggableTest.java50
-rw-r--r--tests/cts/permission/src/android/permission/cts/DuplicatePermissionDefinitionsTest.kt189
-rw-r--r--tests/cts/permission/src/android/permission/cts/EthernetManagerPermissionTest.java113
-rw-r--r--tests/cts/permission/src/android/permission/cts/FileSystemPermissionTest.java1279
-rw-r--r--tests/cts/permission/src/android/permission/cts/FileUtils.java128
-rw-r--r--tests/cts/permission/src/android/permission/cts/IgnoreAllTestsRule.java52
-rw-r--r--tests/cts/permission/src/android/permission/cts/LocationAccessCheckTest.java849
-rw-r--r--tests/cts/permission/src/android/permission/cts/MainlineNetworkStackPermissionTest.java68
-rw-r--r--tests/cts/permission/src/android/permission/cts/MinMaxSdkVersionTest.kt96
-rw-r--r--tests/cts/permission/src/android/permission/cts/NearbyDevicesPermissionTest.java328
-rw-r--r--tests/cts/permission/src/android/permission/cts/NearbyDevicesRenouncePermissionTest.java301
-rw-r--r--tests/cts/permission/src/android/permission/cts/NfcPermissionTest.java221
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java98
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoAudioPermissionTest.java123
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoBroadcastPackageRemovedPermissionTest.java103
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoCaptureVideoPermissionTest.java105
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoKeyPermissionTest.java99
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoNetworkStatePermissionTest.java98
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoReadLogsPermissionTest.java103
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoRollbackPermissionTest.java48
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java170
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoWakeLockPermissionTest.java118
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoWallpaperPermissionsTest.java189
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoWifiStatePermissionTest.java219
-rw-r--r--tests/cts/permission/src/android/permission/cts/NotificationListenerCheckTest.java226
-rw-r--r--tests/cts/permission/src/android/permission/cts/NotificationListenerCheckWithSafetyCenterUnsupportedTest.java108
-rw-r--r--tests/cts/permission/src/android/permission/cts/OneTimePermissionTest.java394
-rw-r--r--tests/cts/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java123
-rw-r--r--tests/cts/permission/src/android/permission/cts/PermissionControllerTest.java513
-rw-r--r--tests/cts/permission/src/android/permission/cts/PermissionFlagsTest.java252
-rw-r--r--tests/cts/permission/src/android/permission/cts/PermissionGroupChange.java207
-rw-r--r--tests/cts/permission/src/android/permission/cts/PermissionManagerNativeJniTest.java26
-rw-r--r--tests/cts/permission/src/android/permission/cts/PermissionManagerTest.java52
-rw-r--r--tests/cts/permission/src/android/permission/cts/PermissionStubActivity.java46
-rw-r--r--tests/cts/permission/src/android/permission/cts/PermissionUpdateListenerTest.java128
-rw-r--r--tests/cts/permission/src/android/permission/cts/PlatformPermissionGroupMappingTest.kt56
-rw-r--r--tests/cts/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java57
-rw-r--r--tests/cts/permission/src/android/permission/cts/ProviderPermissionTest.java295
-rw-r--r--tests/cts/permission/src/android/permission/cts/RebootPermissionTest.java48
-rw-r--r--tests/cts/permission/src/android/permission/cts/RemovePermissionTest.java247
-rw-r--r--tests/cts/permission/src/android/permission/cts/RevokePermissionTest.kt198
-rw-r--r--tests/cts/permission/src/android/permission/cts/RevokeSawPermissionTest.kt63
-rw-r--r--tests/cts/permission/src/android/permission/cts/RevokeSelfPermissionTest.java360
-rw-r--r--tests/cts/permission/src/android/permission/cts/RuntimePermissionPresentationInfoTest.java64
-rw-r--r--tests/cts/permission/src/android/permission/cts/SafetyCenterUtils.kt151
-rw-r--r--tests/cts/permission/src/android/permission/cts/SdkSandboxPermissionTest.java64
-rw-r--r--tests/cts/permission/src/android/permission/cts/SecureElementPermissionTest.java66
-rw-r--r--tests/cts/permission/src/android/permission/cts/ServicePermissionTest.java75
-rw-r--r--tests/cts/permission/src/android/permission/cts/ServicesInstantAppsCannotAccessTests.java92
-rw-r--r--tests/cts/permission/src/android/permission/cts/SharedUidPermissionsTest.java150
-rw-r--r--tests/cts/permission/src/android/permission/cts/ShellCommandPermissionTest.java75
-rw-r--r--tests/cts/permission/src/android/permission/cts/ShellPermissionTest.java95
-rw-r--r--tests/cts/permission/src/android/permission/cts/SmsManagerPermissionTest.java124
-rw-r--r--tests/cts/permission/src/android/permission/cts/SplitPermissionTest.java584
-rwxr-xr-xtests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java174
-rw-r--r--tests/cts/permission/src/android/permission/cts/StorageEscalationTest.kt152
-rw-r--r--tests/cts/permission/src/android/permission/cts/TvPermissionTest.java116
-rw-r--r--tests/cts/permission/src/android/permission/cts/UndefinedGroupPermissionTest.kt198
-rw-r--r--tests/cts/permission/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl22
-rw-r--r--tests/cts/permission/telephony/Android.bp38
-rw-r--r--tests/cts/permission/telephony/AndroidManifest.xml37
-rw-r--r--tests/cts/permission/telephony/AndroidTest.xml44
-rw-r--r--tests/cts/permission/telephony/OWNERS3
-rw-r--r--tests/cts/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java470
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp32
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/res/values/strings.xml19
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp32
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/AndroidManifest.xml25
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/Android.bp32
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/AndroidManifest.xml27
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/Android.bp32
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/Android.bp32
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/AndroidManifest.xml25
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionUserApp/AndroidManifest.xml25
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp32
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/res/values/strings.xml19
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp32
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/AndroidManifest.xml25
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp32
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/AndroidManifest.xml25
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/res/values/strings.xml19
-rw-r--r--tests/cts/permissionmultiuser/Android.bp47
-rw-r--r--tests/cts/permissionmultiuser/AndroidManifest.xml34
-rw-r--r--tests/cts/permissionmultiuser/AndroidTest.xml69
-rw-r--r--tests/cts/permissionmultiuser/OWNERS3
-rw-r--r--tests/cts/permissionmultiuser/RequestLocationApp/Android.bp25
-rw-r--r--tests/cts/permissionmultiuser/RequestLocationApp/AndroidManifest.xml23
-rw-r--r--tests/cts/permissionmultiuser/TEST_MAPPING7
-rw-r--r--tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt473
-rw-r--r--tests/cts/permissionpolicy/Android.bp66
-rwxr-xr-xtests/cts/permissionpolicy/AndroidManifest.xml74
-rw-r--r--tests/cts/permissionpolicy/AndroidTest.xml78
-rw-r--r--tests/cts/permissionpolicy/CtsLegacyStorageIsolatedWithSharedUid/Android.bp26
-rw-r--r--tests/cts/permissionpolicy/CtsLegacyStorageIsolatedWithSharedUid/AndroidManifest.xml26
-rw-r--r--tests/cts/permissionpolicy/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp26
-rw-r--r--tests/cts/permissionpolicy/CtsLegacyStorageNotIsolatedWithSharedUid/AndroidManifest.xml28
-rw-r--r--tests/cts/permissionpolicy/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp26
-rw-r--r--tests/cts/permissionpolicy/CtsLegacyStorageRestrictedSdk28WithSharedUid/AndroidManifest.xml28
-rw-r--r--tests/cts/permissionpolicy/CtsLegacyStorageRestrictedWithSharedUid/Android.bp26
-rw-r--r--tests/cts/permissionpolicy/CtsLegacyStorageRestrictedWithSharedUid/AndroidManifest.xml26
-rw-r--r--tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk22/Android.bp30
-rw-r--r--tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk22/AndroidManifest.xml29
-rw-r--r--tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk29/Android.bp30
-rw-r--r--tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk29/AndroidManifest.xml28
-rw-r--r--tests/cts/permissionpolicy/CtsProcessOutgoingCalls/Android.bp33
-rw-r--r--tests/cts/permissionpolicy/CtsProcessOutgoingCalls/AndroidManifest.xml33
-rw-r--r--tests/cts/permissionpolicy/CtsProcessOutgoingCalls/src/android/permissionpolicy/cts/receivecallbroadcast/ProcessOutgoingCallReceiver.kt35
-rw-r--r--tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk22/Android.bp30
-rw-r--r--tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk22/AndroidManifest.xml40
-rw-r--r--tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk29/Android.bp30
-rw-r--r--tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk29/AndroidManifest.xml38
-rw-r--r--tests/cts/permissionpolicy/CtsSMSNotRestrictedWithSharedUid/Android.bp26
-rw-r--r--tests/cts/permissionpolicy/CtsSMSNotRestrictedWithSharedUid/AndroidManifest.xml26
-rw-r--r--tests/cts/permissionpolicy/CtsSMSRestrictedWithSharedUid/Android.bp26
-rw-r--r--tests/cts/permissionpolicy/CtsSMSRestrictedWithSharedUid/AndroidManifest.xml26
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsPreservedUserOptOutSdk30/Android.bp26
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsPreservedUserOptOutSdk30/AndroidManifest.xml31
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk22/Android.bp24
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk22/AndroidManifest.xml30
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk28/Android.bp24
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk28/AndroidManifest.xml30
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk29/Android.bp26
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk29/AndroidManifest.xml30
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk30/Android.bp26
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk30/AndroidManifest.xml32
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk22/Android.bp24
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk22/AndroidManifest.xml31
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk28/Android.bp24
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk28/AndroidManifest.xml31
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserOptOutSdk29/Android.bp26
-rw-r--r--tests/cts/permissionpolicy/CtsStoragePermissionsUserOptOutSdk29/AndroidManifest.xml31
-rw-r--r--tests/cts/permissionpolicy/OWNERS7
-rw-r--r--tests/cts/permissionpolicy/res/raw/OWNERS7
-rw-r--r--tests/cts/permissionpolicy/res/raw/android_manifest.xml8033
-rw-r--r--tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml573
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/CommandBroadcastReceiver.java49
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ContactsProviderTest.java94
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/IntelligenceRolesPolicyTest.java99
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoCaptureAudioOutputPermissionTest.java69
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoProcessOutgoingCallPermissionTest.java141
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoReceiveSmsPermissionTest.java276
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoWriteSecureSettingsPermissionTest.java47
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionMaxSdkVersionTest.java58
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionPolicyTest.java533
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PrivappPermissionsTest.java251
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ProtectedBroadcastsTest.java121
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedPermissionsTest.java744
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedStoragePermissionSharedUidTest.java267
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedStoragePermissionTest.java754
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt172
-rw-r--r--tests/cts/permissionui/Android.bp79
-rw-r--r--tests/cts/permissionui/AndroidManifest.xml87
-rw-r--r--tests/cts/permissionui/AndroidTest.xml100
-rw-r--r--tests/cts/permissionui/AppThatAccessesCameraAndMic/Android.bp35
-rw-r--r--tests/cts/permissionui/AppThatAccessesCameraAndMic/AndroidManifest.xml34
-rw-r--r--tests/cts/permissionui/AppThatAccessesCameraAndMic/src/android/permissionui/cts/appthataccessescameraandmic/AccessCameraOrMicActivity.kt229
-rw-r--r--tests/cts/permissionui/CreateNotificationChannelsApp31/Android.bp33
-rw-r--r--tests/cts/permissionui/CreateNotificationChannelsApp31/AndroidManifest.xml43
-rw-r--r--tests/cts/permissionui/CreateNotificationChannelsApp31/src/android/permissionui/cts/usepermission/CreateNotificationChannelsActivity.kt160
-rw-r--r--tests/cts/permissionui/DifferentPkgNameApp/Android.bp33
-rw-r--r--tests/cts/permissionui/DifferentPkgNameApp/AndroidManifest.xml33
-rw-r--r--tests/cts/permissionui/DifferentPkgNameApp/src/android/permissionui/cts/usepermissionother/EmptyActivity.kt21
-rw-r--r--tests/cts/permissionui/HelperAppOverlay/Android.bp32
-rw-r--r--tests/cts/permissionui/HelperAppOverlay/AndroidManifest.xml28
-rw-r--r--tests/cts/permissionui/HelperAppOverlay/src/android/permissionui/cts/helper/overlay/OverlayActivity.kt26
-rw-r--r--tests/cts/permissionui/ImplicitUserSelectStorageApp/Android.bp33
-rw-r--r--tests/cts/permissionui/ImplicitUserSelectStorageApp/AndroidManifest.xml30
-rw-r--r--tests/cts/permissionui/MediaPermissionApp33WithStorage/Android.bp32
-rw-r--r--tests/cts/permissionui/MediaPermissionApp33WithStorage/AndroidManifest.xml29
-rw-r--r--tests/cts/permissionui/OWNERS4
-rw-r--r--tests/cts/permissionui/PermissionPolicyApp25/Android.bp31
-rw-r--r--tests/cts/permissionui/PermissionPolicyApp25/AndroidManifest.xml28
-rw-r--r--tests/cts/permissionui/PermissionPolicyApp25/src/android/permissionui/cts/permissionpolicy/TestProtectionFlagsActivity.kt108
-rw-r--r--tests/cts/permissionui/TEST_MAPPING32
-rw-r--r--tests/cts/permissionui/UsePermissionApp22/Android.bp32
-rw-r--r--tests/cts/permissionui/UsePermissionApp22/AndroidManifest.xml79
-rw-r--r--tests/cts/permissionui/UsePermissionApp22CalendarOnly/Android.bp33
-rw-r--r--tests/cts/permissionui/UsePermissionApp22CalendarOnly/AndroidManifest.xml36
-rw-r--r--tests/cts/permissionui/UsePermissionApp22CalendarOnly/src/android/permissionui/cts/usepermission/CheckPermissionService.kt42
-rw-r--r--tests/cts/permissionui/UsePermissionApp22CalendarOnly/src/android/permissionui/cts/usepermission/StartCheckPermissionServiceActivity.kt31
-rw-r--r--tests/cts/permissionui/UsePermissionApp22None/Android.bp32
-rw-r--r--tests/cts/permissionui/UsePermissionApp22None/AndroidManifest.xml30
-rw-r--r--tests/cts/permissionui/UsePermissionApp23/Android.bp32
-rw-r--r--tests/cts/permissionui/UsePermissionApp23/AndroidManifest.xml76
-rw-r--r--tests/cts/permissionui/UsePermissionApp25/Android.bp32
-rw-r--r--tests/cts/permissionui/UsePermissionApp25/AndroidManifest.xml76
-rw-r--r--tests/cts/permissionui/UsePermissionApp26/Android.bp32
-rw-r--r--tests/cts/permissionui/UsePermissionApp26/AndroidManifest.xml34
-rw-r--r--tests/cts/permissionui/UsePermissionApp28/Android.bp32
-rw-r--r--tests/cts/permissionui/UsePermissionApp28/AndroidManifest.xml32
-rw-r--r--tests/cts/permissionui/UsePermissionApp29/Android.bp32
-rw-r--r--tests/cts/permissionui/UsePermissionApp29/AndroidManifest.xml33
-rw-r--r--tests/cts/permissionui/UsePermissionApp30/Android.bp33
-rw-r--r--tests/cts/permissionui/UsePermissionApp30/AndroidManifest.xml43
-rw-r--r--tests/cts/permissionui/UsePermissionApp30WithBackground/Android.bp32
-rw-r--r--tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml31
-rw-r--r--tests/cts/permissionui/UsePermissionApp30WithBackground/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt44
-rw-r--r--tests/cts/permissionui/UsePermissionApp30WithBluetooth/Android.bp34
-rw-r--r--tests/cts/permissionui/UsePermissionApp30WithBluetooth/AndroidManifest.xml38
-rw-r--r--tests/cts/permissionui/UsePermissionApp30WithBluetooth/src/android/permissionui/cts/usepermission/AccessBluetoothOnCommand.kt145
-rw-r--r--tests/cts/permissionui/UsePermissionApp31/Android.bp33
-rw-r--r--tests/cts/permissionui/UsePermissionApp31/AndroidManifest.xml31
-rw-r--r--tests/cts/permissionui/UsePermissionApp32/Android.bp34
-rw-r--r--tests/cts/permissionui/UsePermissionApp32/AndroidManifest.xml26
-rw-r--r--tests/cts/permissionui/UsePermissionAppLatest/Android.bp40
-rw-r--r--tests/cts/permissionui/UsePermissionAppLatest/AndroidManifest.xml53
-rw-r--r--tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/CheckCalendarAccessActivity.kt69
-rw-r--r--tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/FinishOnCreateActivity.kt29
-rw-r--r--tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt85
-rw-r--r--tests/cts/permissionui/UsePermissionAppLatestNone/Android.bp33
-rw-r--r--tests/cts/permissionui/UsePermissionAppLatestNone/AndroidManifest.xml28
-rw-r--r--tests/cts/permissionui/UsePermissionAppLocationProvider/Android.bp31
-rw-r--r--tests/cts/permissionui/UsePermissionAppLocationProvider/AndroidManifest.xml37
-rw-r--r--tests/cts/permissionui/UsePermissionAppLocationProvider/res/values/strings.xml21
-rw-r--r--tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt40
-rw-r--r--tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt67
-rw-r--r--tests/cts/permissionui/UsePermissionAppWithOverlay/Android.bp35
-rw-r--r--tests/cts/permissionui/UsePermissionAppWithOverlay/AndroidManifest.xml29
-rw-r--r--tests/cts/permissionui/UsePermissionAppWithOverlay/res/drawable/border.xml25
-rw-r--r--tests/cts/permissionui/UsePermissionAppWithOverlay/res/layout/overlay_activity.xml34
-rw-r--r--tests/cts/permissionui/UsePermissionAppWithOverlay/res/values/strings.xml19
-rw-r--r--tests/cts/permissionui/UsePermissionAppWithOverlay/res/values/styles.xml22
-rw-r--r--tests/cts/permissionui/UsePermissionAppWithOverlay/src/android/permissionui/cts/usepermission/OverlayActivity.kt55
-rw-r--r--tests/cts/permissionui/UsePermissionAppWithOverlay/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt89
-rw-r--r--tests/cts/permissionui/res/raw/lg_g4_iso_800_jpg.jpgbin0 -> 107684 bytes
-rw-r--r--tests/cts/permissionui/res/raw/test_video.mp4bin0 -> 135632 bytes
-rwxr-xr-xtests/cts/permissionui/res/values-en-rGB/strings.xml21
-rwxr-xr-xtests/cts/permissionui/res/values/strings.xml28
-rw-r--r--tests/cts/permissionui/res/xml/test_accessibilityservice.xml21
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/AppDataSharingUpdatesTest.kt545
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/AppMetadata.kt210
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt210
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt467
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt1205
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt649
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt167
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/LocationProviderInterceptDialogTest.kt139
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionTest.kt180
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionUpgradeTest.kt60
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/MultiDevicePermissionTest.kt54
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/NoPermissionTest.kt46
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/NotificationPermissionTest.kt408
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionDecisionsTest.kt129
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionGroupTest.kt86
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionNoOpGtsTest.kt30
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionPolicyTest25.kt63
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt287
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionRationaleTest.kt396
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionReviewTapjackingTest.kt93
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionReviewTest.kt170
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt114
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt132
-rwxr-xr-xtests/cts/permissionui/src/android/permissionui/cts/PermissionTest22.kt66
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionTest23.kt371
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionTest29.kt200
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionTest30.kt82
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionTest30WithBluetooth.kt200
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionUpgradeTest.kt158
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionUsageInfoTest.kt56
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerPermissionTest.kt409
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerUtils.kt117
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/ReviewAccessibilityServicesTest.kt197
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/SafetyLabelChangesJobServiceTest.kt459
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/SafetyProtectionTest.kt123
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/SensorBlockedBannerTest.kt167
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/StartForFutureActivity.kt43
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/TestInstallerActivity.kt21
603 files changed, 51509 insertions, 980 deletions
diff --git a/OWNERS b/OWNERS
index 8f1443bc6..cd03f0db3 100644
--- a/OWNERS
+++ b/OWNERS
@@ -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..d0713a729 100644
--- a/PermissionController/Android.bp
+++ b/PermissionController/Android.bp
@@ -152,6 +152,12 @@ android_app {
"lottie",
"safety-label",
"role-controller",
+ "permissions-flags-lib",
+ "androidx.compose.foundation_foundation",
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.runtime_runtime-livedata",
+ "androidx.compose.ui_ui",
+ "androidx.wear.compose_compose-material",
],
proto: {
@@ -161,9 +167,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..d62f3d7b7 100644
--- a/PermissionController/AndroidManifest.xml
+++ b/PermissionController/AndroidManifest.xml
@@ -270,7 +270,6 @@
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:visibleToInstantApps="true"
android:inheritShowWhenLocked="true"
- android:hardwareAccelerated="false"
android:canDisplayOnRemoteDevices="false">
<intent-filter android:priority="1">
<action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />
@@ -399,6 +398,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 +606,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 &gt; 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 &gt; 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/navigation-watch/nav_graph.xml b/PermissionController/res/navigation-watch/nav_graph.xml
new file mode 100644
index 000000000..86c6458fa
--- /dev/null
+++ b/PermissionController/res/navigation-watch/nav_graph.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/nav_graph"
+ app:startDestination="@id/manage_standard">
+
+ <!-- For explanation of the navigation component, and this graph, see
+ https://developer.android.com/guide/navigation -->
+
+ <fragment
+ android:id="@+id/manage_standard"
+ android:name="com.android.permissioncontroller.permission.ui.handheld.ManageStandardPermissionsWrapperFragment"
+ android:label="ManageStandard">
+
+ <action
+ android:id="@+id/standard_to_custom"
+ app:destination="@id/manage_custom"
+ app:enterAnim="@anim/activity_open_enter"
+ app:popEnterAnim="@anim/activity_close_enter"
+ app:popExitAnim="@anim/activity_close_exit"/>
+
+ <action
+ android:id="@+id/manage_to_perm_apps"
+ app:destination="@id/permission_apps"
+ app:enterAnim="@anim/activity_open_enter"
+ app:popEnterAnim="@anim/activity_open_enter"
+ app:popExitAnim="@anim/activity_close_exit"/>
+
+ <action
+ android:id="@+id/manage_to_auto_revoke"
+ app:destination="@id/auto_revoke"
+ app:enterAnim="@anim/activity_open_enter"
+ app:popEnterAnim="@anim/activity_open_enter"
+ app:popExitAnim="@anim/activity_close_exit"/>
+
+ </fragment>
+
+ <fragment
+ android:id="@+id/manage_custom"
+ android:name="com.android.permissioncontroller.permission.ui.handheld.ManageCustomPermissionsWrapperFragment"
+ android:label="ManageCustom">
+
+ <action
+ android:id="@+id/manage_to_perm_apps"
+ app:destination="@id/permission_apps"
+ app:enterAnim="@anim/activity_open_enter"
+ app:popExitAnim="@anim/activity_close_exit"
+ app:popEnterAnim="@anim/activity_open_enter"/>
+
+ </fragment>
+
+ <fragment
+ android:id="@+id/auto_revoke"
+ android:name="com.android.permissioncontroller.permission.ui.handheld.HandheldUnusedAppsWrapperFragment"
+ android:label="AutoRevoke">
+
+ <action
+ android:id="@+id/auto_revoke_to_app_perms"
+ app:destination="@id/app_permission_groups"
+ app:enterAnim="@anim/activity_open_enter"
+ app:popExitAnim="@anim/activity_close_exit"
+ app:popEnterAnim="@anim/activity_open_enter"/>
+
+ </fragment>
+
+ <fragment
+ android:id="@+id/app_permission_groups"
+ android:name="com.android.permissioncontroller.permission.ui.handheld.AppPermissionGroupsWrapperFragment"
+ android:label="AppPermissionGroups">
+
+ <action
+ android:id="@+id/perm_groups_to_app"
+ app:destination="@id/app_permission"
+ app:enterAnim="@anim/activity_open_enter"
+ app:popExitAnim="@anim/activity_close_exit"
+ app:popEnterAnim="@anim/activity_open_enter"/>
+
+ <action
+ android:id="@+id/perm_groups_to_all_perms"
+ app:destination="@id/all_app_permissions"
+ app:enterAnim="@anim/activity_open_enter"
+ app:popExitAnim="@anim/activity_close_exit"
+ app:popEnterAnim="@anim/activity_open_enter"/>
+
+ <action
+ android:id="@+id/perm_groups_to_custom"
+ app:destination="@id/custom_app_permission_groups"
+ app:enterAnim="@anim/activity_open_enter"
+ app:popExitAnim="@anim/activity_close_exit"
+ app:popEnterAnim="@anim/activity_open_enter"/>
+ </fragment>
+
+ <fragment
+ android:id="@+id/app_permission"
+ android:name="com.android.permissioncontroller.permission.ui.wear.WearAppPermissionFragment"
+ android:label="AppPermission" />
+
+ <fragment
+ android:id="@+id/permission_apps"
+ android:name="com.android.permissioncontroller.permission.ui.wear.WearPermissionAppsFragment"
+ android:label="PermissionApps">
+
+ <action
+ android:id="@+id/perm_apps_to_app"
+ app:destination="@id/app_permission"
+ app:enterAnim="@anim/activity_open_enter"
+ app:popExitAnim="@anim/activity_close_exit"
+ app:popEnterAnim="@anim/activity_open_enter"/>
+ </fragment>
+</navigation>
diff --git a/PermissionController/res/values-am/strings.xml b/PermissionController/res/values-am/strings.xml
index aa5edc983..144de7866 100644
--- a/PermissionController/res/values-am/strings.xml
+++ b/PermissionController/res/values-am/strings.xml
@@ -161,9 +161,9 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"ባለፈው 1 ሰዓት የፈቃድ አጠቃቀም"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"ባለፉት 15 ደቂቃዎች ውስጥ የፈቃድ አጠቃቀም"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"ባለፈው 1 ደቂቃ የፈቃድ አጠቃቀም"</string>
- <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{ባለፈው # ቀን ውስጥ ሥራ ላይ አልዋለም}one{ባለፈው # ቀን ውስጥ ሥራ ላይ አልዋለም}other{ባለፉት # ቀናት ውስጥ ሥራ ላይ አልዋለም}}"</string>
- <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{ባለፈው # ሰዓት ውስጥ ሥራ ላይ አልዋለም}one{ባለፈው # ሰዓት ውስጥ ሥራ ላይ አልዋለም}other{ባለፉት # ሰዓታት ውስጥ ሥራ ላይ አልዋለም}}"</string>
- <string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{በ1 መተግበሪያ ሥራ ላይ ውሏል}one{በ# መተግበሪያዎች ሥራ ላይ ውለዋል}other{በ# መተግበሪያዎች ሥራ ላይ ውለዋል}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{ባለፈው # ቀን ውስጥ ስራ ላይ አልዋለም}one{ባለፈው # ቀን ውስጥ ስራ ላይ አልዋለም}other{ባለፉት # ቀናት ውስጥ ስራ ላይ አልዋለም}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{ባለፈው # ሰዓት ውስጥ ስራ ላይ አልዋለም}one{ባለፈው # ሰዓት ውስጥ ስራ ላይ አልዋለም}other{ባለፉት # ሰዓታት ውስጥ ስራ ላይ አልዋለም}}"</string>
+ <string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{በ1 መተግበሪያ ስራ ላይ ውሏል}one{በ# መተግበሪያዎች ስራ ላይ ውለዋል}other{በ# መተግበሪያዎች ስራ ላይ ውለዋል}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"ሁሉንም በዳሽ ቦርድ ውስጥ ይመልከቱ"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"የተጣራው በ፦ <xliff:g id="PERM">%1$s</xliff:g>"</string>
<string name="app_permission_usage_remove_filter" msgid="2926157607436428207">"ማጣሪያን አስወግድ"</string>
@@ -200,10 +200,10 @@
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"ሁሉንም <xliff:g id="APP">%1$s</xliff:g> ፈቃዶች ይመልከቱ"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"ከዚህ መተግበሪያ ጋር ሁሉንም መተግበሪያዎች ይመልከቱ"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"የረዳት ማይክሮፎን አጠቃቀምን አሳይ"</string>
- <string name="unused_apps_category_title" msgid="2988455616845243901">"ሥራ ላይ ያልዋሉ የመተግበሪያ ቅንብሮች"</string>
+ <string name="unused_apps_category_title" msgid="2988455616845243901">"ስራ ላይ ያልዋሉ የመተግበሪያ ቅንብሮች"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"መተግበሪያ ጥቅም ላይ ካልዋለ ፈቃዶችን አስወግድ"</string>
<string name="unused_apps_label" msgid="2595428768404901064">"ፈቃዶችን ያስወግዱ እና ቦታ ያስለቅቁ"</string>
- <string name="unused_apps_label_v2" msgid="7058776770056517980">"የመተግበሪያ እንቅስቃሴ ሥራ ላይ ካልዋለ ባለበት አቁም"</string>
+ <string name="unused_apps_label_v2" msgid="7058776770056517980">"የመተግበሪያ እንቅስቃሴ ስራ ላይ ካልዋለ ባለበት አቁም"</string>
<string name="unused_apps_summary" msgid="8839466950318403115">"ፈቃዶችን አስወግድ፣ ጊዜያዊ ፋይሎችን ሰርዝ እና ማሳወቂያዎችን አቁም"</string>
<string name="auto_revoke_summary" msgid="5867548789805911683">"ለእርስዎ ውሂብ ጥበቃ ለማድረግ፣ ለዚህ መተግበሪያ የተሰጡ ፈቃዶች መተግበሪያው ለጥቂት ወራት ጥቅም ላይ ካልዋለ ይህ መተግበሪያ ይወገዳል።"</string>
<string name="auto_revoke_summary_with_permissions" msgid="389712086597285013">"የእርስዎን ውሂብ ለመጠበቅ፣ መተግበሪያው ለጥቂት ወራት ጥቅም ላይ ካልዋለ፣ የሚከተሉት ፈቃዶች ይወገዳሉ፦ <xliff:g id="PERMS">%1$s</xliff:g>"</string>
@@ -218,8 +218,8 @@
<string name="auto_revoked_app_summary_one" msgid="7093213590301252970">"የ<xliff:g id="PERMISSION_NAME">%s</xliff:g> ፈቃድ ተወግዷል"</string>
<string name="auto_revoked_app_summary_two" msgid="1910545340763709389">"የ<xliff:g id="PERMISSION_NAME_0">%1$s</xliff:g> እና <xliff:g id="PERMISSION_NAME_1">%2$s</xliff:g> ፈቃዶች ተወግደዋል"</string>
<string name="auto_revoked_app_summary_many" msgid="5930976230827378798">"<xliff:g id="PERMISSION_NAME">%1$s</xliff:g> እና <xliff:g id="NUMBER">%2$s</xliff:g> ሌሎች ፈቃዶች ተወግደዋል"</string>
- <string name="unused_apps_page_title" msgid="6986983535677572559">"ሥራ ላይ ያልዋሉ መተግበሪያዎች"</string>
- <string name="unused_apps_page_summary" msgid="1867593913217272155">"አንድ መተግበሪያ ለጥቂት ወራት ሥራ ላይ ካልዋለ፦\n\n• ውሂብዎን ለመጠበቅ ፈቃዶች ይወገዳሉ\n• ባትሪን ለመቆጠብ ማሳወቂያዎች ይቆማሉ\n• ባዶ ቦታ ለማስለቀቅ ጊዜያዊ ፋይሎች ይወገዳሉ\n\nፈቃዶችን እና ማሳወቂያዎችን እንደገና ለመፍቀድ መተግበሪያውን ይክፈቱት።"</string>
+ <string name="unused_apps_page_title" msgid="6986983535677572559">"ስራ ላይ ያልዋሉ መተግበሪያዎች"</string>
+ <string name="unused_apps_page_summary" msgid="1867593913217272155">"አንድ መተግበሪያ ለጥቂት ወራት ስራ ላይ ካልዋለ፦\n\n• ውሂብዎን ለመጠበቅ ፈቃዶች ይወገዳሉ\n• ባትሪን ለመቆጠብ ማሳወቂያዎች ይቆማሉ\n• ባዶ ቦታ ለማስለቀቅ ጊዜያዊ ፋይሎች ይወገዳሉ\n\nፈቃዶችን እና ማሳወቂያዎችን እንደገና ለመፍቀድ መተግበሪያውን ይክፈቱት።"</string>
<string name="unused_apps_page_tv_summary" msgid="2624911608663778308">"መተግበሪያ ለአንድ ወር ጥቅም ላይ ካልዋለ፦\n\n• ውሂብዎን ለመጠበቅ ፈቃዶች ይወገዳሉ\n• ጊዜያዊ ፋይሎች ቦታ ለማስለቀቅ ይወገዳሉ\n\nፈቃዶችን ዳግም ለመፍቀድ መተግበሪያውን ይክፈቱ።"</string>
<string name="last_opened_category_title" msgid="8796557894614236128">"{count,plural, =1{መጨረሻ የተከፈተው ከ# ወር በፊት}one{መጨረሻ የተከፈተው ከ# ወር በፊት}other{መጨረሻ የተከፈተው ከ# ወራት በፊት}}"</string>
<string name="last_opened_summary" msgid="5248984030024968808">"መተግበሪያ ለመጨረሻ ጊዜ በ<xliff:g id="DATE">%s</xliff:g> ላይ ተከፍቷል"</string>
@@ -247,7 +247,7 @@
<string name="app_permission_never_accessed_denied_summary" msgid="6596000497490905146">"ተከልክሏል / በጭራሽ አልተደረሰበትም"</string>
<string name="allowed_header" msgid="7769277978004790414">"ይፈቀዳል"</string>
<string name="allowed_always_header" msgid="6455903312589013545">"ሁልጊዜ የተፈቀደ"</string>
- <string name="allowed_foreground_header" msgid="6845655788447833353">"ሥራ ላይ ሲውል ብቻ የሚፈቀድ"</string>
+ <string name="allowed_foreground_header" msgid="6845655788447833353">"ስራ ላይ ሲውል ብቻ የሚፈቀድ"</string>
<string name="allowed_storage_scoped" msgid="5383645873719086975">"ለሚዲያ ብቻ መዳረሻ ተፈቅዷል"</string>
<string name="allowed_storage_full" msgid="5356699280625693530">"ሁሉንም ፋይሎች ማስተዳደር ተፈቀዷል"</string>
<string name="ask_header" msgid="2633816846459944376">"ሁልጊዜ ጠይቅ"</string>
@@ -262,8 +262,8 @@
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ጥቅም ላይ ያልዋሉ መተግበሪያዎች"</string>
<string name="auto_revoke_permission_reminder_notification_content" msgid="4492228990462107487">"ፈቃዶች የእርስዎን ግላዊነት ለመጠበቅ ተወግደዋል። ለመገምገም መታ ያድርጉ"</string>
<string name="auto_revoke_permission_notification_title" msgid="2629844160853454657">"ጥቅም ላይ ላልዋሉ መተግበሪያዎች ፈቃዶች ተወግደዋል"</string>
- <string name="auto_revoke_permission_notification_content" msgid="5125990886047799375">"አንዳንድ መተግበሪያዎች ለጥቂት ወሮች ሥራ ላይ አልዋሉም። ለመገምገም መታ ያድርጉ።"</string>
- <string name="unused_apps_notification_title" msgid="4314832015894238019">"{count,plural, =1{# ሥራ ላይ ያልዋለ መተግበሪያ}one{# ሥራ ላይ ያልዋሉ መተግበሪያዎች}other{# ሥራ ላይ ያልዋሉ መተግበሪያዎች}}"</string>
+ <string name="auto_revoke_permission_notification_content" msgid="5125990886047799375">"አንዳንድ መተግበሪያዎች ለጥቂት ወሮች ስራ ላይ አልዋሉም። ለመገምገም መታ ያድርጉ።"</string>
+ <string name="unused_apps_notification_title" msgid="4314832015894238019">"{count,plural, =1{# ስራ ላይ ያልዋለ መተግበሪያ}one{# ስራ ላይ ያልዋሉ መተግበሪያዎች}other{# ስራ ላይ ያልዋሉ መተግበሪያዎች}}"</string>
<string name="unused_apps_notification_content" msgid="9195026773244581246">"ፈቃዶች እና ጊዜያዊ ፋይሎች ተወግደዋል እንዲሁም ማሳወቂያዎች ቆመዋል። ለመገምገም መታ ያድርጉ።"</string>
<string name="unused_apps_safety_center_card_title" msgid="5638409355530099149">"ፈቃዶቻቸው የተወገዱባቸው መተግበሪያዎችን ይገምግሙ"</string>
<string name="unused_apps_safety_center_card_content" msgid="1088557243627427820">"ለሆነ ያህል ጊዜ ላልተጠቀሙባቸው መተግበሪያዎች ፈቃዶች እና ጊዜያዊ ፋይሎች ተወግደዋል እና ማሳወቂያዎች ቆመዋል።"</string>
@@ -274,7 +274,7 @@
<string name="post_drive_permission_decision_reminder_summary_1_app_multi_permission" msgid="4080701771111456927">"እየነዱ ሳለ <xliff:g id="COUNT">%1$d</xliff:g> ፈቃዶችን ለ<xliff:g id="APP">%2$s</xliff:g> ሰጥተዋል"</string>
<string name="post_drive_permission_decision_reminder_summary_multi_apps" msgid="5253882771252863902">"{count,plural, =1{እየነዱ ሳለ የ<xliff:g id="APP_0">%1$s</xliff:g> &amp; # ሌላ መተግበሪያ መዳረሻ ሰጥተዋል}one{እየነዱ ሳለ የ<xliff:g id="APP_1">%1$s</xliff:g> &amp; # ሌላ መተግበሪያ መዳረሻ ሰጥተዋል}other{እየነዱ ሳለ የ<xliff:g id="APP_1">%1$s</xliff:g> &amp; # ሌላ መተግበሪያ መዳረሻ ሰጥተዋል}}"</string>
<string name="go_to_settings" msgid="1053735612211228335">"ወደ ቅንብሮች ሂድ"</string>
- <string name="auto_revoke_setting_subtitle" msgid="8631720570723050460">"አንዳንድ መተግበሪያዎች ለጥቂት ወሮች ሥራ ላይ አልዋሉም።"</string>
+ <string name="auto_revoke_setting_subtitle" msgid="8631720570723050460">"አንዳንድ መተግበሪያዎች ለጥቂት ወሮች ስራ ላይ አልዋሉም።"</string>
<string name="permissions_removed_category_title" msgid="1064754271178447643">"ፈቃዶች የተወገዱባቸው"</string>
<string name="permission_removed_page_title" msgid="2627436155091001209">"ፈቃዶች የተወገዱባቸው"</string>
<string name="all_unused_apps_category_title" msgid="755663524704745414">"ሁሉም ጥቅም ላይ ያልዋሉ መተግበሪያዎች"</string>
@@ -404,9 +404,9 @@
<string name="request_role_current_default" msgid="738722892438247184">"አሁን ያለ ነባሪ"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"ዳግም አትጠይቅ"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"እንደ ነባሪ አዘጋጅ"</string>
- <string name="phone_call_uses_microphone" msgid="233569591461187177">"ማይክሮፎን በ&lt;b&gt;ስልክ ጥሪ&lt;/b&gt; ላይ ሥራ ላይ ውሏል"</string>
- <string name="phone_call_uses_microphone_and_camera" msgid="6291898755681748189">"ካሜራ እና ማይክሮፎን በ&lt;b&gt;ቪዲዮ ጥሪ&lt;/b&gt; ላይ ሥራ ላይ ውለዋል"</string>
- <string name="phone_call_uses_camera" msgid="2048417022147857418">"ካሜራ በ&lt;b&gt;ቪዲዮ ጥሪ&lt;/b&gt; ላይ ሥራ ላይ ውሏል"</string>
+ <string name="phone_call_uses_microphone" msgid="233569591461187177">"ማይክሮፎን በ&lt;b&gt;ስልክ ጥሪ&lt;/b&gt; ላይ ስራ ላይ ውሏል"</string>
+ <string name="phone_call_uses_microphone_and_camera" msgid="6291898755681748189">"ካሜራ እና ማይክሮፎን በ&lt;b&gt;ቪዲዮ ጥሪ&lt;/b&gt; ላይ ስራ ላይ ውለዋል"</string>
+ <string name="phone_call_uses_camera" msgid="2048417022147857418">"ካሜራ በ&lt;b&gt;ቪዲዮ ጥሪ&lt;/b&gt; ላይ ስራ ላይ ውሏል"</string>
<string name="system_uses_microphone" msgid="576672130318877143">"ማይክሮፎን የሥርዓት አገልግሎትን በመጠቀም ተደርሶበታል"</string>
<string name="system_uses_microphone_and_camera" msgid="5124478304275138804">"ካሜራ እና ማይክሮፎን የሥርዓት አገልግሎትን በመጠቀም ተደርሶበታል"</string>
<string name="system_uses_camera" msgid="1911223105234441470">"ካሜራ የሥርዓት አገልግሎትን በመጠቀም ተደርሶበታል"</string>
@@ -495,7 +495,7 @@
<string name="permgroupupgraderequestdetail_sensors" msgid="6651914048792092835">"መተግበሪያውን በማይጠቀሙበት ጊዜም እንኳ ይህ መተግበሪያ የእርስዎን የመሠረታዊ ምልክቶች የዳሳሽ ውሂብን ሁልጊዜ መድረስ ይፈልጋል። ይህን ለውጥ ለማድረግ "<annotation id="link">"ወደ ቅንብሮች ይሂዱ።"</annotation></string>
<string name="permgroupbackgroundrequest_sensors" msgid="5661924322018503886">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; የሰውነትዎ መሠረታዊ ምልክቶች የዳሳሽ ውሂብ እንዲደርስ ይፈቀድለት?"</string>
<string name="permgroupbackgroundrequestdetail_sensors" msgid="7726767635834043501">"መተግበሪያውን በማይጠቀሙበት ጊዜ እንኳን ይህ መተግበሪያ የሰውነት ዳሳሽ ውሂብን ሁልጊዜ እንዲደርስ ለመፍቀድ "<annotation id="link">"ወደ ቅንብሮች ይሂዱ።"</annotation></string>
- <string name="permgroupupgraderequest_sensors" msgid="7576527638411370468">"መተግበሪያ ሥራ ላይ በሚውልበት ጊዜ የሰውነት ዳሳሽ ውሂብን እንዲደርስ ለ&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; መፍቀድ ይቀጥሉ?"</string>
+ <string name="permgroupupgraderequest_sensors" msgid="7576527638411370468">"መተግበሪያ ስራ ላይ በሚውልበት ጊዜ የሰውነት ዳሳሽ ውሂብን እንዲደርስ ለ&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; መፍቀድ ይቀጥሉ?"</string>
<string name="permgrouprequest_notifications" msgid="6396739062335106181">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ለእርስዎ ማሳወቂያዎች እንዲልክ ይፈቀድለት?"</string>
<string name="auto_granted_permissions" msgid="6009452264824455892">"ቁጥጥር የሚደረግባችድው ፈቃዶች"</string>
<string name="auto_granted_location_permission_notification_title" msgid="7570818224669050377">"<xliff:g id="APP_NAME">%1$s</xliff:g> የአካባቢ መዳረሻ አለው"</string>
@@ -544,14 +544,14 @@
<string name="remove_microphone_qs" msgid="1276551965129953198">"ለዚህ መተግበሪያ ፈቃድን አስወግድ"</string>
<string name="manage_service_qs" msgid="7862555549364153805">"አገልግሎት ያስተዳድሩ"</string>
<string name="manage_permissions_qs" msgid="3780541819763475434">"ፈቃዶችን ያስተዳድሩ"</string>
- <string name="active_call_usage_qs" msgid="8559974395932523391">"በስልክ ጥሪ ሥራ ላይ እየዋለ ነው"</string>
- <string name="recent_call_usage_qs" msgid="743044899599410935">"በቅርብ ጊዜ በስልክ ጥሪ ውስጥ ሥራ ላይ ውሏል"</string>
- <string name="active_app_usage_qs" msgid="4063912870936464727">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> ሥራ ላይ እየዋለ ነው"</string>
- <string name="recent_app_usage_qs" msgid="6650259601306212327">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> ሥራ ላይ ውሏል"</string>
- <string name="active_app_usage_1_qs" msgid="4325136375823357052">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ሥራ ላይ እየዋለ ነው"</string>
- <string name="recent_app_usage_1_qs" msgid="261450184773310741">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ሥራ ላይ ውሏል"</string>
- <string name="active_app_usage_2_qs" msgid="6107866785243565283">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ሥራ ላይ እየዋለ ነው"</string>
- <string name="recent_app_usage_2_qs" msgid="3591205954235694403">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ሥራ ላይ ውሏል"</string>
+ <string name="active_call_usage_qs" msgid="8559974395932523391">"በስልክ ጥሪ ስራ ላይ እየዋለ ነው"</string>
+ <string name="recent_call_usage_qs" msgid="743044899599410935">"በቅርብ ጊዜ በስልክ ጥሪ ውስጥ ስራ ላይ ውሏል"</string>
+ <string name="active_app_usage_qs" msgid="4063912870936464727">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> ስራ ላይ እየዋለ ነው"</string>
+ <string name="recent_app_usage_qs" msgid="6650259601306212327">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> ስራ ላይ ውሏል"</string>
+ <string name="active_app_usage_1_qs" msgid="4325136375823357052">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ስራ ላይ እየዋለ ነው"</string>
+ <string name="recent_app_usage_1_qs" msgid="261450184773310741">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ስራ ላይ ውሏል"</string>
+ <string name="active_app_usage_2_qs" msgid="6107866785243565283">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ስራ ላይ እየዋለ ነው"</string>
+ <string name="recent_app_usage_2_qs" msgid="3591205954235694403">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ስራ ላይ ውሏል"</string>
<string name="media_confirm_dialog_positive_button" msgid="9020793594051526399">"አረጋግጥ"</string>
<string name="media_confirm_dialog_negative_button" msgid="226987376924861785">"ተመለስ"</string>
<string name="media_confirm_dialog_title_a_to_p_aural_allow" msgid="8560601114044699903">"የሌሎች ፋይሎች መዳረሻም ይፈቀዳል"</string>
diff --git a/PermissionController/res/values-ar-v33/strings.xml b/PermissionController/res/values-ar-v33/strings.xml
index 6b881f064..9bfe9dc63 100644
--- a/PermissionController/res/values-ar-v33/strings.xml
+++ b/PermissionController/res/values-ar-v33/strings.xml
@@ -20,7 +20,7 @@
<string name="role_sms_request_description" msgid="1506966389698625395">"سيُسمح لهذا التطبيق بإرسال إشعارات إليك، وسيُمنح إذن الوصول إلى الكاميرا وجهات الاتصال والملفات والميكروفون والهاتف والرسائل القصيرة."</string>
<string name="permission_description_summary_storage" msgid="1917071243213043858">"يمكن للتطبيقات التي لديها هذا الإذن الوصول إلى جميع الملفات على هذا الجهاز."</string>
<string name="work_policy_title" msgid="832967780713677409">"معلومات سياسة العمل"</string>
- <string name="work_policy_summary" msgid="3886113358084963931">"يتولى مشرف تكنولوجيا المعلومات إدارة الإعدادات"</string>
+ <string name="work_policy_summary" msgid="3886113358084963931">"يتولى مشرف تكنولوجيا المعلومات إدارة الإعدادات."</string>
<string name="safety_center_entry_group_expand_action" msgid="5358289574941779652">"توسيع القائمة وعرضها"</string>
<string name="safety_center_entry_group_collapse_action" msgid="1525710152244405656">"تصغير القائمة وإخفاء الإعدادات"</string>
<string name="safety_center_entry_group_content_description" msgid="7048420958214443333">"قائمة <xliff:g id="ENTRY_TITLE">%1$s</xliff:g>. <xliff:g id="ENTRY_SUMMARY">%2$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-ar/strings.xml b/PermissionController/res/values-ar/strings.xml
index 92353fe85..dd77e27dc 100644
--- a/PermissionController/res/values-ar/strings.xml
+++ b/PermissionController/res/values-ar/strings.xml
@@ -246,7 +246,7 @@
<string name="app_permission_never_accessed_summary" msgid="401346181461975090">"لم يستخدم الإذن مطلقًا"</string>
<string name="app_permission_never_accessed_denied_summary" msgid="6596000497490905146">"تم الرفض / لم يسبق الحصول على الإذن"</string>
<string name="allowed_header" msgid="7769277978004790414">"التطبيقات المسموح لها"</string>
- <string name="allowed_always_header" msgid="6455903312589013545">"تطبيقات مسموح لها بالوصول طوال الوقت"</string>
+ <string name="allowed_always_header" msgid="6455903312589013545">"التطبيقات المسموح لها بالوصول طوال الوقت"</string>
<string name="allowed_foreground_header" msgid="6845655788447833353">"تطبيقات يمكنها الوصول عند استخدامها فقط"</string>
<string name="allowed_storage_scoped" msgid="5383645873719086975">"التطبيقات المسموح لها بالوصول إلى الوسائط فقط"</string>
<string name="allowed_storage_full" msgid="5356699280625693530">"التطبيقات المسموح لها بإدارة كل الملفات"</string>
@@ -340,7 +340,7 @@
<string name="no_apps_allowed" msgid="7718822655254468631">"لم يتم السماح لأي تطبيقات."</string>
<string name="no_apps_allowed_full" msgid="8011716991498934104">"ما من تطبيقات تم منحها إذن الوصول إلى جميع الملفات."</string>
<string name="no_apps_allowed_scoped" msgid="4908850477787659501">"ما من تطبيقات تم منحها إذن الوصول إلى الوسائط فقط."</string>
- <string name="no_apps_denied" msgid="7663435886986784743">"لم يتم رفض أي تطبيقات"</string>
+ <string name="no_apps_denied" msgid="7663435886986784743">"لم يتم رفض أي تطبيقات."</string>
<string name="car_permission_selected" msgid="180837028920791596">"مُختار"</string>
<string name="settings" msgid="5409109923158713323">"الإعدادات"</string>
<string name="accessibility_service_dialog_title_single" msgid="7956432823014102366">"تحظى خدمة <xliff:g id="SERVICE_NAME">%s</xliff:g> بوصول كامل إلى جهازك."</string>
diff --git a/PermissionController/res/values-in/strings.xml b/PermissionController/res/values-in/strings.xml
index 777933785..6bc772926 100644
--- a/PermissionController/res/values-in/strings.xml
+++ b/PermissionController/res/values-in/strings.xml
@@ -191,7 +191,7 @@
<string name="app_permission_button_always_allow_all" msgid="4905699259378428855">"Selalu izinkan semua"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Selalu tanya"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Jangan izinkan"</string>
- <string name="precise_image_description" msgid="6349638632303619872">"Lokasi presisi"</string>
+ <string name="precise_image_description" msgid="6349638632303619872">"Lokasi akurat"</string>
<string name="approximate_image_description" msgid="938803699637069884">"Perkiraan lokasi"</string>
<string name="app_permission_location_accuracy" msgid="7166912915040018669">"Gunakan lokasi presisi"</string>
<string name="app_permission_location_accuracy_subtitle" msgid="2654077606404987210">"Saat lokasi presisi dinonaktifkan, aplikasi dapat mengakses perkiraan lokasi"</string>
diff --git a/PermissionController/res/values-pt-rPT/strings.xml b/PermissionController/res/values-pt-rPT/strings.xml
index 012608eee..f2974b845 100644
--- a/PermissionController/res/values-pt-rPT/strings.xml
+++ b/PermissionController/res/values-pt-rPT/strings.xml
@@ -353,41 +353,41 @@
<string name="role_browser_label" msgid="2877796144554070207">"App navegador predefinida"</string>
<string name="role_browser_short_label" msgid="6745009127123292296">"App de navegador"</string>
<string name="role_browser_description" msgid="3465253637499842671">"Apps que lhe dão acesso à Internet e apresentam links em que pode tocar."</string>
- <string name="role_browser_request_title" msgid="2895200507835937192">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de navegador predefinida?"</string>
+ <string name="role_browser_request_title" msgid="2895200507835937192">"Pretende definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de navegador predefinida?"</string>
<string name="role_browser_request_description" msgid="5888803407905985941">"Não são necessárias autorizações."</string>
<string name="role_dialer_label" msgid="1100224146343237968">"App de telefone predefinida"</string>
<string name="role_dialer_short_label" msgid="7186888549465352489">"App Telefone"</string>
<string name="role_dialer_description" msgid="8768708633696539612">"Apps que permitem efetuar e receber chamadas no seu dispositivo."</string>
- <string name="role_dialer_request_title" msgid="5959618560705912058">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de telefone predefinida?"</string>
+ <string name="role_dialer_request_title" msgid="5959618560705912058">"Pretende definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de telefone predefinida?"</string>
<string name="role_dialer_request_description" msgid="6288839625724909320">"Esta app fica com acesso à sua Câmara, Contactos, Microfone, Telefone e SMS"</string>
<string name="role_dialer_search_keywords" msgid="3324448983559188087">"telefone"</string>
<string name="role_sms_label" msgid="8456999857547686640">"App de SMS predefinida"</string>
<string name="role_sms_short_label" msgid="4371444488034692243">"App de SMS"</string>
<string name="role_sms_description" msgid="3424020199148153513">"Apps que permitem utilizar o seu número de telefone para enviar e receber mensagens de texto, fotos, vídeos e muito mais."</string>
- <string name="role_sms_request_title" msgid="7953552109601185602">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como app SMS predefinida?"</string>
+ <string name="role_sms_request_title" msgid="7953552109601185602">"Pretende definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como app SMS predefinida?"</string>
<string name="role_sms_request_description" msgid="2691004766132144886">"Esta app fica com acesso à sua Câmara, Contactos, Ficheiros e multimédia, Microfone, Telefone e SMS"</string>
<string name="role_sms_search_keywords" msgid="8022048144395047352">"mensagem de texto, enviar mensagens de texto, mensagens"</string>
<string name="role_emergency_label" msgid="7028825857206842366">"Aplicação de emergência pred."</string>
<string name="role_emergency_short_label" msgid="2388431453335350348">"Aplicação de emergência"</string>
<string name="role_emergency_description" msgid="5051840234887686630">"Apps que permitem registar as suas informações médicas e disponibilizá-las aos contactos de resposta a emergências, receber alertas acerca de eventos atmosféricos e desastres graves, bem como notificar outras pessoas quando precisar de ajuda."</string>
- <string name="role_emergency_request_title" msgid="8469579020654348567">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de emergência predefinida?"</string>
+ <string name="role_emergency_request_title" msgid="8469579020654348567">"Pretende definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de emergência predefinida?"</string>
<string name="role_emergency_request_description" msgid="131645948770262850">"Não são necessárias autorizações."</string>
<string name="role_emergency_search_keywords" msgid="1920007722599213358">"em caso de emergência"</string>
<string name="role_home_label" msgid="3871847846649769412">"App página inicial predefinida"</string>
<string name="role_home_short_label" msgid="8544733747952272337">"App Página inicial"</string>
<string name="role_home_description" msgid="7997371519626556675">"Apps, frequentemente denominadas iniciadores, que substituem os ecrãs principais no dispositivo Android e dão acesso aos conteúdos e às funcionalidades do seu dispositivo."</string>
- <string name="role_home_request_title" msgid="738136983453341081">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app Página inicial predefinida?"</string>
+ <string name="role_home_request_title" msgid="738136983453341081">"Pretende definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app Página inicial predefinida?"</string>
<string name="role_home_request_description" msgid="2658833966716057673">"Não são necessárias autorizações."</string>
<string name="role_home_search_keywords" msgid="3830755001192666285">"iniciador"</string>
<string name="role_call_redirection_label" msgid="5785304207206147590">"Aplic. redirec. chamadas pred."</string>
<string name="role_call_redirection_short_label" msgid="7568143419571217757">"Aplic. de redirec. de chamadas"</string>
<string name="role_call_redirection_description" msgid="6091669882014664420">"Apps que permitem encaminhar chamadas efetuadas para outro número de telefone."</string>
- <string name="role_call_redirection_request_title" msgid="2816244455003562925">"Quer definir <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de redirecionamento de chamadas predefinida?"</string>
+ <string name="role_call_redirection_request_title" msgid="2816244455003562925">"Pretende definir <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de redirecionamento de chamadas predefinida?"</string>
<string name="role_call_redirection_request_description" msgid="3118895714178527164">"Não são necessárias autorizações."</string>
<string name="role_call_screening_label" msgid="883935222060878724">"App de filtro de chamadas e spam"</string>
<string name="role_call_screening_short_label" msgid="2048465565063130834">"App de ID de chamada e spam"</string>
<string name="role_call_screening_description" msgid="2349431420497468981">"Apps que lhe permitem identificar chamadas e bloquear spam, chamadas automáticas ou números indesejados."</string>
- <string name="role_call_screening_request_title" msgid="7358309224566977290">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de identificação de chamadas e spam predefinida?"</string>
+ <string name="role_call_screening_request_title" msgid="7358309224566977290">"Pretende definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de identificação de chamadas e spam predefinida?"</string>
<string name="role_call_screening_request_description" msgid="7338511921032446006">"Não são necessárias autorizações."</string>
<string name="role_automotive_navigation_label" msgid="2701890757955474751">"App de navegação predefinida"</string>
<string name="role_automotive_navigation_short_label" msgid="5165823092506922457">"App de navegação"</string>
@@ -437,11 +437,11 @@
<string name="special_app_access_no_apps" msgid="4102911722787886970">"Sem apps"</string>
<string name="home_missing_work_profile_support" msgid="1756855847669387977">"Não suporta o perfil de trabalho."</string>
<string name="encryption_unaware_confirmation_message" msgid="8274491794636402484">"Nota: se reiniciar o dispositivo e tiver um bloqueio de ecrã definido, só é possível iniciar esta app quando o dispositivo for desbloqueado."</string>
- <string name="assistant_confirmation_message" msgid="7476540402884416212">"O assistente pode ler informações sobre as apps em utilização no seu sistema, incluindo informações visíveis no ecrã ou acessíveis nas apps."</string>
+ <string name="assistant_confirmation_message" msgid="7476540402884416212">"O assistente pode ler informações sobre aplicações em utilização no seu sistema, incluindo informações visíveis no ecrã ou acessíveis nas aplicações."</string>
<string name="incident_report_channel_name" msgid="3144954065936288440">"Partilhar dados de depuração"</string>
- <string name="incident_report_notification_title" msgid="4635984625656519773">"Quer partilhar dados de depuração detalhados?"</string>
- <string name="incident_report_notification_text" msgid="3376480583513587923">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> quer carregar informações de depuração."</string>
- <string name="incident_report_dialog_title" msgid="669104389325204095">"Quer partilhar dados de depuração?"</string>
+ <string name="incident_report_notification_title" msgid="4635984625656519773">"Pretende partilhar dados de depuração detalhados?"</string>
+ <string name="incident_report_notification_text" msgid="3376480583513587923">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> pretende carregar informações de depuração."</string>
+ <string name="incident_report_dialog_title" msgid="669104389325204095">"Pretende partilhar dados de depuração?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"O sistema detetou um problema."</string>
<string name="incident_report_dialog_text" msgid="5675553296891757523">"A app <xliff:g id="APP_NAME_0">%1$s</xliff:g> está a solicitar o carregamento de um relatório de erro a partir deste dispositivo realizado a <xliff:g id="DATE">%2$s</xliff:g> à(s) <xliff:g id="TIME">%3$s</xliff:g>. Os relatórios de erros incluem informações pessoais acerca do seu dispositivo ou registadas por app, por exemplo, nomes de utilizador, dados de localização, identificadores do dispositivo e informações da rede. Apenas partilhe relatórios de erros com pessoas e apps nas quais confia. Permite que a app <xliff:g id="APP_NAME_1">%4$s</xliff:g> carregue um relatório de erro?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Ocorreu um erro ao processar o relatório de erro para a app <xliff:g id="APP_NAME">%1$s</xliff:g>. Como tal, a partilha dos dados de depuração detalhados foi negada. Pedimos desculpa pela interrupção."</string>
@@ -460,8 +460,8 @@
<string name="permgrouprequestdetail_location" msgid="2635935335778429894">"A app tem acesso à localização apenas enquanto a estiver a utilizar"</string>
<string name="permgroupbackgroundrequest_location" msgid="1085680897265734809">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aceda à localização deste dispositivo?"</string>
<string name="permgroupbackgroundrequestdetail_location" msgid="8021219324989662957">"Esta app poderá pretender aceder sempre à sua localização, mesmo quando não a estiver a utilizar. "<annotation id="link">"Permita-o nas definições."</annotation></string>
- <string name="permgroupupgraderequest_location" msgid="8328408946822691636">"Quer alterar o acesso à localização para a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgroupupgraderequestdetail_location" msgid="1550899076845189165">"Esta app quer aceder sempre à sua localização, mesmo quando não a estiver a utilizar. "<annotation id="link">"Permita-o nas definições."</annotation></string>
+ <string name="permgroupupgraderequest_location" msgid="8328408946822691636">"Pretende alterar o acesso à localização para a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
+ <string name="permgroupupgraderequestdetail_location" msgid="1550899076845189165">"Esta app pretende aceder sempre à sua localização, mesmo quando não a estiver a utilizar. "<annotation id="link">"Permita-o nas definições."</annotation></string>
<string name="permgrouprequest_nearby_devices" msgid="2272829282660436700">"Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; encontre, determine a posição relativa dos dispositivos próximos e se ligue aos mesmos?"</string>
<string name="permgroupupgraderequestdetail_nearby_devices" msgid="6877531270654738614">"Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; encontre, determine a posição relativa dos dispositivos próximos e se ligue aos mesmos? "<annotation id="link">"Permita nas Definições."</annotation></string>
<string name="permgrouprequest_fineupgrade" msgid="2334242928821697672">"Alterar o acesso à localização da app <xliff:g id="APP_NAME">&lt;b&gt;%1$s&lt;/b&gt;</xliff:g> de aproximada para exata?"</string>
@@ -480,15 +480,15 @@
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"A app apenas poderá gravar áudio enquanto a estiver a utilizar."</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; grave áudio?"</string>
<string name="permgroupbackgroundrequestdetail_microphone" msgid="553702902263681838">"Esta app pode pretender gravar áudio sempre, mesmo quando não a está a utilizar. "<annotation id="link">"Permita-o nas Definições."</annotation></string>
- <string name="permgroupupgraderequest_microphone" msgid="1362781696161233341">"Quer alterar o acesso ao microfone para a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgroupupgraderequestdetail_microphone" msgid="2870497719571464239">"Esta app quer gravar áudio sempre, mesmo quando não a está a utilizar. "<annotation id="link">"Permita-o nas Definições."</annotation></string>
+ <string name="permgroupupgraderequest_microphone" msgid="1362781696161233341">"Pretende alterar o acesso ao microfone para a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
+ <string name="permgroupupgraderequestdetail_microphone" msgid="2870497719571464239">"Esta app pretende gravar áudio sempre, mesmo quando não a está a utilizar. "<annotation id="link">"Permita-o nas Definições."</annotation></string>
<string name="permgrouprequest_activityRecognition" msgid="5415121592794230330">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aceda à sua atividade física?"</string>
<string name="permgrouprequest_camera" msgid="5123097035410002594">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tire fotos e grave vídeo?"</string>
<string name="permgrouprequestdetail_camera" msgid="9085323239764667883">"A app apenas poderá tirar fotos e gravar vídeos enquanto a estiver a utilizar."</string>
<string name="permgroupbackgroundrequest_camera" msgid="1274286575704213875">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tire fotos e grave vídeo?"</string>
<string name="permgroupbackgroundrequestdetail_camera" msgid="4458783509089859078">"Esta app pode pretender tirar fotos e gravar vídeos sempre, mesmo quando não a está a utilizar. "<annotation id="link">"Permita-o nas Definições."</annotation></string>
- <string name="permgroupupgraderequest_camera" msgid="640758449200241582">"Quer alterar o acesso à câmara para a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgroupupgraderequestdetail_camera" msgid="6642747548010962597">"Esta app quer tirar fotos e gravar vídeos sempre, mesmo quando não a está a utilizar. "<annotation id="link">"Permita-o nas Definições."</annotation></string>
+ <string name="permgroupupgraderequest_camera" msgid="640758449200241582">"Pretende alterar o acesso à câmara para a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
+ <string name="permgroupupgraderequestdetail_camera" msgid="6642747548010962597">"Esta app pretende tirar fotos e gravar vídeos sempre, mesmo quando não a está a utilizar. "<annotation id="link">"Permita-o nas Definições."</annotation></string>
<string name="permgrouprequest_calllog" msgid="2065327180175371397">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aceda aos registos de chamadas do seu telemóvel?"</string>
<string name="permgrouprequest_phone" msgid="1829234136997316752">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; faça e gira chamadas telefónicas?"</string>
<string name="permgrouprequest_sensors" msgid="4397358316850652235">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aceda aos dados de sensores acerca dos seus sinais vitais?"</string>
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..fcc76e7d1 100644
--- a/PermissionController/res/xml/roles.xml
+++ b/PermissionController/res/xml/roles.xml
@@ -667,6 +667,7 @@
<permission name="android.permission.SET_TIME" minSdkVersion="34" />
<permission name="android.permission.SET_TIME_ZONE" minSdkVersion="34" />
<permission name="android.permission.SATELLITE_COMMUNICATION" minSdkVersion="34" />
+ <permission name="android.permission.ALWAYS_UPDATE_WALLPAPER" minSdkVersion="35" />
</permissions>
</role>
@@ -1101,7 +1102,6 @@
-->
<role
name="android.app.role.SYSTEM_UI"
- behavior="SystemUiRoleBehavior"
defaultHolders="config_systemUi"
exclusive="true"
minSdkVersion="31"
@@ -1610,4 +1610,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..61fb7dae5 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,18 @@ 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_T = Arrays.asList(
+ android.Manifest.permission.POST_NOTIFICATIONS,
+ android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY);
+
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ private static final List<String> WEAR_PERMISSIONS_V = Arrays.asList(
+ android.Manifest.permission.ALWAYS_UPDATE_WALLPAPER);
+
+ 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 +113,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 +148,20 @@ public class HomeRoleBehavior implements RoleBehavior {
Arrays.asList(android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES),
true, false, true, false, false, context);
}
+
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ if (SdkLevel.isAtLeastT()) {
+ Permissions.grant(packageName, WEAR_PERMISSIONS_T,
+ true, false, true, false, false, context);
+ for (String permission : WEAR_APP_OP_PERMISSIONS) {
+ AppOpPermissions.grant(packageName, permission, true, context);
+ }
+ }
+ if (SdkLevel.isAtLeastV()) {
+ Permissions.grant(packageName, WEAR_PERMISSIONS_V,
+ true, false, true, false, false, context);
+ }
+ }
}
@Override
@@ -145,6 +176,18 @@ public class HomeRoleBehavior implements RoleBehavior {
Arrays.asList(android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES),
true, false, false, context);
}
+
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ if (SdkLevel.isAtLeastT()) {
+ Permissions.revoke(packageName, WEAR_PERMISSIONS_T, true, false, false, context);
+ for (String permission : WEAR_APP_OP_PERMISSIONS) {
+ AppOpPermissions.revoke(packageName, permission, context);
+ }
+ }
+ if (SdkLevel.isAtLeastV()) {
+ Permissions.revoke(packageName, WEAR_PERMISSIONS_V, true, false, false, 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..88d887327 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"
@@ -116,15 +112,25 @@ class DrivingDecisionReminderService : Service() {
// just give up if we can't connect to the car
if (ready) {
val restrictionsManager = car.getCarManager(
- Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager
- if (restrictionsManager.currentCarUxRestrictions
- .isRequiresDistractionOptimization) {
- context.startService(
- createIntent(
- context,
- packageName,
- permGroupName,
- Process.myUserHandle()))
+ Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager?
+ if (restrictionsManager != null) {
+ val currentCarUxRestrictions = restrictionsManager.currentCarUxRestrictions
+ if (currentCarUxRestrictions != null) {
+ if (currentCarUxRestrictions.isRequiresDistractionOptimization) {
+ context.startService(
+ createIntent(
+ context,
+ packageName,
+ permGroupName,
+ Process.myUserHandle()))
+ }
+ } else {
+ DumpableLog.e(LOG_TAG,
+ "Reminder service not created because CarUxRestrictions is null")
+ }
+ } else {
+ DumpableLog.e(LOG_TAG,
+ "Reminder service not created because CarUxRestrictionsManager is null")
}
}
car.disconnect()
@@ -270,15 +276,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 +289,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,15 +302,9 @@ 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,
sessionId, PERMISSION_REMINDER_NOTIFICATION_INTERACTED__RESULT__NOTIFICATION_PRESENTED)
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
index 6e901fa26..95c5158ac 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
@@ -842,7 +843,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) {
@@ -862,7 +865,11 @@ class HibernationJobService : JobService() {
return true
}
- private fun showUnusedAppsNotification(numUnused: Int, sessionId: Long) {
+ private fun showUnusedAppsNotification(
+ numUnused: Int,
+ sessionId: Long,
+ user: UserHandle
+ ) {
val notificationManager = getSystemService(NotificationManager::class.java)!!
val permissionReminderChannel = NotificationChannel(
@@ -890,6 +897,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/data/PermGroupsPackagesLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesLiveData.kt
index c44c2b473..e4fa75bd6 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesLiveData.kt
@@ -43,7 +43,7 @@ class PermGroupsPackagesLiveData private constructor(
init {
addSource(groupNamesLiveData) {
- groupNames = it ?: emptyList()
+ groupNames = it
val getLiveData = { groupName: String -> PermGroupLiveData[groupName] }
setSourcesToDifference(groupNames, permGroupLiveDatas, getLiveData) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesUiInfoLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesUiInfoLiveData.kt
index 5d91ebfda..37967840b 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesUiInfoLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesUiInfoLiveData.kt
@@ -65,7 +65,7 @@ class PermGroupsPackagesUiInfoLiveData(
init {
addSource(groupNamesLiveData) {
- groupNames = it ?: emptyList()
+ groupNames = it
update()
getPermGroupPackageLiveDatas()
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt
index d7fe4fb2e..7c3ebe0b3 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt
@@ -45,14 +45,6 @@ abstract class SmartUpdateMediatorLiveData<T>(private val isStaticVal: Boolean =
}
/**
- * Boolean, whether or not the value of this uiDataLiveData has been explicitly set yet.
- * Differentiates between "null value because liveData is new" and "null value because
- * liveData is invalid"
- */
- var isInitialized = false
- private set
-
- /**
* Boolean, whether or not this liveData has a stale value or not. Every time the liveData goes
* inactive, its data becomes stale, until it goes active again, and is explicitly set.
*/
@@ -66,7 +58,6 @@ abstract class SmartUpdateMediatorLiveData<T>(private val isStaticVal: Boolean =
ensureMainThread()
if (!isInitialized) {
- isInitialized = true
// If we have received an invalid value, and this is the first time we are set,
// notify observers.
if (newValue == null) {
@@ -247,6 +238,6 @@ abstract class SmartUpdateMediatorLiveData<T>(private val isStaticVal: Boolean =
update()
}
},
- isInitialized = { isInitialized && (staleOk || !isStale) })
+ isValueInitialized = { isInitialized && (staleOk || !isStale) })
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java b/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java
index e096a1a7e..bd5fdae8b 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java
@@ -936,8 +936,12 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
}
boolean wasGranted = permission.isGrantedIncludingAppOp();
+ boolean isPermissionSplitFromNonRuntime = KotlinUtils.isPermissionSplitFromNonRuntime(
+ mContext,
+ permission.getName(),
+ mPackageInfo.applicationInfo.targetSdkVersion);
- if (mAppSupportsRuntimePermissions) {
+ if (mAppSupportsRuntimePermissions && !isPermissionSplitFromNonRuntime) {
// Do not touch permissions fixed by the system.
if (permission.isSystemFixed()) {
wasAllGranted = false;
@@ -1127,7 +1131,14 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
boolean wasGranted = permission.isGrantedIncludingAppOp();
- if (mAppSupportsRuntimePermissions) {
+ boolean isPermissionSplitFromNonRuntime =
+ KotlinUtils.isPermissionSplitFromNonRuntime(
+ mContext,
+ permission.getName(),
+ mPackageInfo.applicationInfo.targetSdkVersion);
+
+ if (mAppSupportsRuntimePermissions && !isPermissionSplitFromNonRuntime) {
+
// Revoke the permission if needed.
if (permission.isGranted()) {
permission.setGranted(false);
@@ -1172,6 +1183,8 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
if (!permission.isRevokedCompat()) {
permission.setRevokedCompat(true);
}
+
+ permission.setRevokeWhenRequested(false);
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/Permission.java b/PermissionController/src/com/android/permissioncontroller/permission/model/Permission.java
index 5ddea4605..4daaeaec8 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/Permission.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/Permission.java
@@ -169,6 +169,18 @@ public final class Permission {
}
/**
+ * Sets the REVOKE_WHEN_REQUESTED permission flag
+ * @param revokeWhenRequested true to set the flag, false to unset it
+ */
+ public void setRevokeWhenRequested(boolean revokeWhenRequested) {
+ if (revokeWhenRequested) {
+ mFlags |= PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
+ } else {
+ mFlags &= ~PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
+ }
+ }
+
+ /**
* Sets the one-time permission flag
* @param oneTime true to set the flag, false to unset it
*/
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/PermissionControllerServiceModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceModel.kt
index 49a465898..b24b912f8 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceModel.kt
@@ -25,7 +25,7 @@ import androidx.core.util.Consumer
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
-import androidx.lifecycle.Transformations
+import androidx.lifecycle.map
import com.android.permissioncontroller.DumpableLog
import com.android.permissioncontroller.PermissionControllerProto.PermissionControllerDumpProto
import com.android.permissioncontroller.permission.utils.PermissionMapping
@@ -88,13 +88,13 @@ class PermissionControllerServiceModel(private val service: PermissionController
var updated = false
val observer = object : Observer<T> {
- override fun onChanged(data: T) {
+ override fun onChanged(value: T) {
if (updated) {
return
}
if ((liveData is SmartUpdateMediatorLiveData<T> && !liveData.isStale) ||
liveData !is SmartUpdateMediatorLiveData<T>) {
- onChangedFun(data)
+ onChangedFun(value)
liveData.removeObserver(this)
updated = true
}
@@ -288,9 +288,7 @@ class PermissionControllerServiceModel(private val service: PermissionController
fun onCountUnusedApps(
callback: IntConsumer
) {
- val unusedAppsCount = Transformations.map(getUnusedPackages()) {
- it?.size ?: 0
- }
+ val unusedAppsCount = getUnusedPackages().map { it?.size ?: 0 }
observeAndCheckForLifecycleState(unusedAppsCount) { count -> callback.accept(count ?: 0) }
}
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..4201969f3 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;
@@ -76,6 +77,8 @@ import com.android.permissioncontroller.permission.ui.auto.GrantPermissionsAutoV
import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel;
import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.RequestInfo;
import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModelFactory;
+import com.android.permissioncontroller.permission.ui.model.NewGrantPermissionsViewModel;
+import com.android.permissioncontroller.permission.ui.model.NewGrantPermissionsViewModelFactory;
import com.android.permissioncontroller.permission.ui.wear.GrantPermissionsWearViewHandler;
import com.android.permissioncontroller.permission.utils.KotlinUtils;
import com.android.permissioncontroller.permission.utils.Utils;
@@ -149,6 +152,11 @@ public class GrantPermissionsActivity extends SettingsActivity
/** The current list of permissions requested, across all current requests for this app */
private List<String> mRequestedPermissions = new ArrayList<>();
+
+ /** A list of permissions requested on an app's behalf by the system. Usually Implicitly
+ * requested, although this isn't necessarily always the case.
+ */
+ private List<String> mSystemRequestedPermissions = new ArrayList<>();
/** A copy of the list of permissions originally requested in the intent to this activity */
private String[] mOriginalRequestedPermissions = new String[0];
@@ -190,6 +198,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())) {
@@ -230,8 +242,17 @@ public class GrantPermissionsActivity extends SettingsActivity
return;
}
- mRequestedPermissions = GrantPermissionsViewModel.Companion.getSanitizedPermissionsList(
- requestedPermissionsArray, permissionsSdkLevel);
+ boolean useNewViewModel = KotlinUtils.INSTANCE.isNewGrantDialogBackendEnabled();
+ if (useNewViewModel) {
+ mRequestedPermissions = removeNullOrEmptyPermissions(requestedPermissionsArray);
+ if (mIsSystemTriggered) {
+ mSystemRequestedPermissions.addAll(mRequestedPermissions);
+ }
+ } else {
+ mRequestedPermissions = GrantPermissionsViewModel.Companion
+ .getSanitizedPermissionsList(requestedPermissionsArray, permissionsSdkLevel);
+ }
+
if (mRequestedPermissions.isEmpty()) {
setResultAndFinish();
return;
@@ -276,10 +297,19 @@ public class GrantPermissionsActivity extends SettingsActivity
.GrantPermissionsViewHandlerImpl(this, this);
}
- GrantPermissionsViewModelFactory factory = new GrantPermissionsViewModelFactory(
- getApplication(), mTargetPackage, mRequestedPermissions, mSessionId, icicle);
if (!mDelegated) {
- mViewModel = factory.create(GrantPermissionsViewModel.class);
+ if (useNewViewModel) {
+ NewGrantPermissionsViewModelFactory factory =
+ new NewGrantPermissionsViewModelFactory(getApplication(), mTargetPackage,
+ mRequestedPermissions, mSystemRequestedPermissions, mSessionId,
+ icicle);
+ mViewModel = factory.create(NewGrantPermissionsViewModel.class);
+ } else {
+ GrantPermissionsViewModelFactory factory = new GrantPermissionsViewModelFactory(
+ getApplication(), mTargetPackage, mRequestedPermissions, mSessionId,
+ icicle);
+ mViewModel = factory.create(GrantPermissionsViewModel.class);
+ }
mViewModel.getRequestInfosLiveData().observe(this, this::onRequestInfoLoad);
}
@@ -363,10 +393,17 @@ public class GrantPermissionsActivity extends SettingsActivity
Bundle oldState = new Bundle();
mViewModel.getRequestInfosLiveData().removeObservers(this);
mViewModel.saveInstanceState(oldState);
- GrantPermissionsViewModelFactory factory = new GrantPermissionsViewModelFactory(
- getApplication(), mTargetPackage, mRequestedPermissions,
- mSessionId, oldState);
- mViewModel = factory.create(GrantPermissionsViewModel.class);
+ if (KotlinUtils.INSTANCE.isNewGrantDialogBackendEnabled()) {
+ NewGrantPermissionsViewModelFactory factory = new NewGrantPermissionsViewModelFactory(
+ getApplication(), mTargetPackage, mRequestedPermissions,
+ mSystemRequestedPermissions, mSessionId, oldState);
+ mViewModel = factory.create(GrantPermissionsViewModel.class);
+ } else {
+ GrantPermissionsViewModelFactory factory = new GrantPermissionsViewModelFactory(
+ getApplication(), mTargetPackage, mRequestedPermissions,
+ mSessionId, oldState);
+ mViewModel = factory.create(GrantPermissionsViewModel.class);
+ }
mViewModel.getRequestInfosLiveData().observe(this, this::onRequestInfoLoad);
if (follower != null) {
follower.mViewModel = mViewModel;
@@ -419,6 +456,13 @@ public class GrantPermissionsActivity extends SettingsActivity
} else if (info.getOpenPhotoPicker()) {
mViewModel.openPhotoPicker(top, GRANTED_USER_SELECTED);
return;
+ } else if (!info.getFilteredPermissions().isEmpty()) {
+ // Filtered permissions should be removed from the requested permissions list entirely,
+ // and not have status returned to the app
+ mRequestedPermissions.removeAll(info.getFilteredPermissions());
+ mRequestInfos.remove(info);
+ onRequestInfoLoad(mRequestInfos);
+ return;
}
if (Utils.isHealthPermissionUiEnabled() && HEALTH_PERMISSION_GROUP.equals(
@@ -427,7 +471,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 +685,6 @@ public class GrantPermissionsActivity extends SettingsActivity
logGrantPermissionActivityButtons(name, affectedForegroundPermissions, result);
mViewModel.onPermissionGrantResult(name, affectedForegroundPermissions, result);
- showNextRequest();
if (result == CANCELED) {
setResultAndFinish();
}
@@ -872,6 +915,18 @@ public class GrantPermissionsActivity extends SettingsActivity
return mResultCode != Integer.MAX_VALUE;
}
+ // Remove null and empty permissions from an array, return a list
+ private List<String> removeNullOrEmptyPermissions(String[] perms) {
+ ArrayList<String> sanitized = new ArrayList<>();
+ for (String perm : perms) {
+ if (perm == null || perm.isEmpty()) {
+ continue;
+ }
+ sanitized.add(perm);
+ }
+ return sanitized;
+ }
+
/**
* If there is another system-shown dialog on another task, that is not being relied upon by an
* app-defined dialogs, these other dialogs should be finished.
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/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
index 8a2216469..2cd5d9343 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
@@ -144,7 +144,7 @@ import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils
* @param sessionId: A long to identify this session
* @param storedState: Previous state, if this activity was stopped and is being recreated
*/
-class GrantPermissionsViewModel(
+open class GrantPermissionsViewModel(
private val app: Application,
private val packageName: String,
private val requestedPermissions: List<String>,
@@ -187,6 +187,8 @@ class GrantPermissionsViewModel(
* A class which represents a correctly requested permission group, and the buttons and messages
* which should be shown with it.
*/
+ // TODO: 284183336, once the old viewModel is gone, this should be replaced with a Prompt and
+ // DenyButton
data class RequestInfo(
val groupInfo: LightPermGroupInfo,
val buttonVisibilities: List<Boolean> = List(NEXT_BUTTON) { false },
@@ -195,16 +197,18 @@ class GrantPermissionsViewModel(
val detailMessage: RequestMessage = RequestMessage.NO_MESSAGE,
val sendToSettingsImmediately: Boolean = false,
val openPhotoPicker: Boolean = false,
+ // Unused for now, will be included in the GrantPermissions refactor
+ val filteredPermissions: Collection<String> = emptyList()
) {
val groupName = groupInfo.name
}
- var activityResultCallback: Consumer<Intent>? = null
+ open var activityResultCallback: Consumer<Intent>? = null
/**
* A LiveData which holds a list of the currently pending RequestInfos
*/
- val requestInfosLiveData = object :
+ open val requestInfosLiveData = object :
SmartUpdateMediatorLiveData<List<RequestInfo>>() {
private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName
private val packagePermissionsLiveData = PackagePermissionsLiveData[packageName, user]
@@ -948,7 +952,7 @@ class GrantPermissionsViewModel(
* @param affectedForegroundPermissions The name of the foreground permission which was changed
* @param result The choice the user made regarding the group.
*/
- fun onPermissionGrantResult(
+ open fun onPermissionGrantResult(
groupName: String?,
affectedForegroundPermissions: List<String>?,
result: Int
@@ -1252,7 +1256,7 @@ class GrantPermissionsViewModel(
*
* @param outState The bundle in which to store state
*/
- fun saveInstanceState(outState: Bundle) {
+ open fun saveInstanceState(outState: Bundle) {
for ((groupKey, groupState) in groupStates) {
val (groupName, isBackground) = groupKey
outState.putInt(getInstanceStateKey(groupName, isBackground), groupState.state)
@@ -1265,7 +1269,7 @@ class GrantPermissionsViewModel(
* @return Whether or not state should be returned. False only if the package is pre-M, true
* otherwise.
*/
- fun shouldReturnPermissionState(): Boolean {
+ open fun shouldReturnPermissionState(): Boolean {
return if (packageInfoLiveData.value != null) {
packageInfoLiveData.value!!.targetSdkVersion >= Build.VERSION_CODES.M
} else {
@@ -1280,7 +1284,7 @@ class GrantPermissionsViewModel(
}
}
- fun handleHealthConnectPermissions(activity: Activity) {
+ open fun handleHealthConnectPermissions(activity: Activity) {
if (activityResultCallback == null) {
activityResultCallback = Consumer {
permGroupsToSkip.add(HEALTH_PERMISSION_GROUP)
@@ -1304,7 +1308,7 @@ class GrantPermissionsViewModel(
* @param activity The current activity
* @param groupName The name of the permission group whose fragment should be opened
*/
- fun sendDirectlyToSettings(activity: Activity, groupName: String) {
+ open fun sendDirectlyToSettings(activity: Activity, groupName: String) {
if (activityResultCallback == null) {
activityResultCallback = Consumer { data ->
if (data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED) == null) {
@@ -1328,7 +1332,7 @@ class GrantPermissionsViewModel(
}
}
- fun openPhotoPicker(activity: Activity, result: Int) {
+ open fun openPhotoPicker(activity: Activity, result: Int) {
if (activityResultCallback != null) {
return
}
@@ -1364,7 +1368,7 @@ class GrantPermissionsViewModel(
* @param activity The current activity
* @param groupName The name of the permission group whose fragment should be opened
*/
- fun sendToSettingsFromLink(activity: Activity, groupName: String) {
+ open fun sendToSettingsFromLink(activity: Activity, groupName: String) {
startAppPermissionFragment(activity, groupName)
activityResultCallback = Consumer { data ->
val returnGroupName = data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED)
@@ -1383,7 +1387,7 @@ class GrantPermissionsViewModel(
* @param activity The current activity
* @param groupName The name of the permission group whose fragment should be opened
*/
- fun showPermissionRationaleActivity(activity: Activity, groupName: String) {
+ open fun showPermissionRationaleActivity(activity: Activity, groupName: String) {
if (!SdkLevel.isAtLeastU()) {
return
}
@@ -1478,7 +1482,7 @@ class GrantPermissionsViewModel(
/**
* Log all permission groups which were requested
*/
- fun logRequestedPermissionGroups() {
+ open fun logRequestedPermissionGroups() {
if (groupStates.isEmpty()) {
return
}
@@ -1495,7 +1499,7 @@ class GrantPermissionsViewModel(
* @param clickedButton The button that was clicked by the user
* @param presentedButtons All buttons which were shown to the user
*/
- fun logClickedButtons(
+ open fun logClickedButtons(
groupName: String?,
selectedPrecision: Int,
clickedButton: Int,
@@ -1530,7 +1534,7 @@ class GrantPermissionsViewModel(
/**
* Use the autoGrantNotifier to notify of auto-granted permissions.
*/
- fun autoGrantNotify() {
+ open fun autoGrantNotify() {
autoGrantNotifier?.notifyOfAutoGrantPermissions(true)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
index e529f1cd5..e3ddab925 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
@@ -23,7 +23,7 @@ import android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.lifecycle.AndroidViewModel
-import androidx.lifecycle.Transformations
+import androidx.lifecycle.map
import androidx.navigation.fragment.findNavController
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.data.PermGroupsPackagesLiveData
@@ -48,9 +48,7 @@ class ManageStandardPermissionsViewModel(
val uiDataLiveData = PermGroupsPackagesUiInfoLiveData(app,
StandardPermGroupNamesLiveData)
val numCustomPermGroups = NumCustomPermGroupsWithPackagesLiveData()
- val numAutoRevoked = Transformations.map(unusedAutoRevokePackagesLiveData) {
- it?.size ?: 0
- }
+ val numAutoRevoked = unusedAutoRevokePackagesLiveData.map { it?.size ?: 0 }
/**
* Navigate to the Custom Permissions screen
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/NewGrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/NewGrantPermissionsViewModel.kt
new file mode 100644
index 000000000..1465ddfa6
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/NewGrantPermissionsViewModel.kt
@@ -0,0 +1,1292 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:Suppress("DEPRECATION")
+
+package com.android.permissioncontroller.permission.ui.model
+
+import android.Manifest.permission.ACCESS_COARSE_LOCATION
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+import android.Manifest.permission_group.LOCATION
+import android.Manifest.permission_group.NOTIFICATIONS
+import android.Manifest.permission_group.READ_MEDIA_AURAL
+import android.Manifest.permission_group.READ_MEDIA_VISUAL
+import android.Manifest.permission_group.STORAGE
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.Application
+import android.app.admin.DevicePolicyManager
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED
+import android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED
+import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET
+import android.health.connect.HealthConnectManager.ACTION_REQUEST_HEALTH_PERMISSIONS
+import android.health.connect.HealthConnectManager.isHealthPermission
+import android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP
+import android.os.Build
+import android.os.Bundle
+import android.os.Process
+import android.permission.PermissionManager
+import android.provider.MediaStore
+import android.util.Log
+import androidx.core.util.Consumer
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.modules.utils.build.SdkLevel
+import com.android.permission.safetylabel.SafetyLabel
+import com.android.permissioncontroller.Constants
+import com.android.permissioncontroller.DeviceUtils
+import com.android.permissioncontroller.PermissionControllerStatsLog
+import com.android.permissioncontroller.PermissionControllerStatsLog.GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__PHOTOS_SELECTED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE_IN_SETTINGS
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED
+import com.android.permissioncontroller.auto.DrivingDecisionReminderService
+import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
+import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
+import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData
+import com.android.permissioncontroller.permission.data.v34.SafetyLabelInfoLiveData
+import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
+import com.android.permissioncontroller.permission.data.get
+import com.android.permissioncontroller.permission.model.AppPermissionGroup
+import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
+import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
+import com.android.permissioncontroller.permission.service.PermissionChangeStorageImpl
+import com.android.permissioncontroller.permission.service.v33.PermissionDecisionStorageImpl
+import com.android.permissioncontroller.permission.ui.AutoGrantPermissionsNotifier
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALL_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_FOREGROUND_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ONE_TIME_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_SELECTED_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.COARSE_RADIO_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_AND_DONT_ASK_AGAIN_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_BOTH_LOCATIONS
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_COARSE_LOCATION_ONLY
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_FINE_LOCATION_ONLY
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DONT_ALLOW_MORE_SELECTED_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.FINE_RADIO_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.INTENT_PHOTOS_SELECTED
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LINK_TO_PERMISSION_RATIONALE
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LOCATION_ACCURACY_LAYOUT
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_LOCATION_DIALOG
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_MORE
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ONE_TIME
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_USER_SELECTED
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT
+import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.Companion.RequestMessage
+import com.android.permissioncontroller.permission.ui.model.grantPermissions.BackgroundGrantBehavior
+import com.android.permissioncontroller.permission.ui.model.grantPermissions.BasicGrantBehavior
+import com.android.permissioncontroller.permission.ui.model.grantPermissions.GrantBehavior
+import com.android.permissioncontroller.permission.ui.model.grantPermissions.HealthGrantBehavior
+import com.android.permissioncontroller.permission.ui.model.grantPermissions.LocationGrantBehavior
+import com.android.permissioncontroller.permission.ui.model.grantPermissions.NotificationGrantBehavior
+import com.android.permissioncontroller.permission.ui.model.grantPermissions.StorageGrantBehavior
+import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleActivity
+import com.android.permissioncontroller.permission.utils.v31.AdminRestrictedPermissionsUtils
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.KotlinUtils.grantBackgroundRuntimePermissions
+import com.android.permissioncontroller.permission.utils.KotlinUtils.grantForegroundRuntimePermissions
+import com.android.permissioncontroller.permission.utils.KotlinUtils.revokeBackgroundRuntimePermissions
+import com.android.permissioncontroller.permission.utils.KotlinUtils.revokeForegroundRuntimePermissions
+import com.android.permissioncontroller.permission.utils.PermissionMapping
+import com.android.permissioncontroller.permission.utils.PermissionMapping.getPartialStorageGrantPermissionsForGroup
+import com.android.permissioncontroller.permission.utils.SafetyNetLogger
+import com.android.permissioncontroller.permission.utils.Utils
+import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils
+
+/**
+ * ViewModel for the GrantPermissionsActivity. Tracks all permission groups that are affected by
+ * the permissions requested by the user, and generates a RequestInfo object for each group, if
+ * action is needed. It will not return any data if one of the requests is malformed.
+ *
+ * @param app: The current application
+ * @param packageName: The packageName permissions are being requested for
+ * @param requestedPermissions: The list of permissions requested
+ * @param systemRequestedPermissions: The list of permissions requested as a result of a system
+ * triggered dialog, not an app-triggered dialog
+ * @param sessionId: A long to identify this session
+ * @param storedState: Previous state, if this activity was stopped and is being recreated
+ */
+class NewGrantPermissionsViewModel(
+ private val app: Application,
+ private val packageName: String,
+ private val requestedPermissions: List<String>,
+ private val systemRequestedPermissions: List<String>,
+ private val sessionId: Long,
+ private val storedState: Bundle?
+) : GrantPermissionsViewModel(app, packageName, requestedPermissions, sessionId, storedState) {
+ private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName
+ private val user = Process.myUserHandle()
+ private val packageInfoLiveData = LightPackageInfoLiveData[packageName, user]
+ private val safetyLabelInfoLiveData =
+ if (SdkLevel.isAtLeastU() && requestedPermissions
+ .mapNotNull { PermissionMapping.getGroupOfPlatformPermission(it) }
+ .any { PermissionMapping.isSafetyLabelAwarePermissionGroup(it) }) {
+ SafetyLabelInfoLiveData[packageName, user]
+ } else {
+ null
+ }
+ private val dpm = app.getSystemService(DevicePolicyManager::class.java)!!
+ private val permissionPolicy = dpm.getPermissionPolicy(null)
+ private val groupStates = mutableMapOf<String, GroupState>()
+
+ private var autoGrantNotifier: AutoGrantPermissionsNotifier? = null
+ private fun getAutoGrantNotifier(): AutoGrantPermissionsNotifier {
+ autoGrantNotifier = AutoGrantPermissionsNotifier(app, packageInfo.toPackageInfo(app)!!)
+ return autoGrantNotifier!!
+ }
+
+ private lateinit var packageInfo: LightPackageInfo
+
+ // All permissions that could possibly be affected by the provided requested permissions, before
+ // filtering system fixed, auto grant, etc.
+ private var unfilteredAffectedPermissions = requestedPermissions
+
+ private var appPermGroupLiveDatas = mutableMapOf<String, LightAppPermGroupLiveData>()
+
+ override var activityResultCallback: Consumer<Intent>? = null
+
+ /**
+ * An internal class which represents the state of a current AppPermissionGroup grant request.
+ * It is made up of the following:
+ * @param group The LightAppPermGroup representing the current state of the permissions for
+ * this app
+ * @param affectedPermissions The permissions that should be affected by this
+ */
+ internal class GroupState(
+ internal val group: LightAppPermGroup,
+ internal val affectedPermissions: MutableSet<String> = mutableSetOf(),
+ internal var state: Int = STATE_UNKNOWN,
+ ) {
+ val fgPermissions = affectedPermissions - group.backgroundPermNames.toSet()
+ val bgPermissions = affectedPermissions - fgPermissions
+
+ override fun toString(): String {
+ val stateStr: String = when (state) {
+ STATE_UNKNOWN -> "unknown"
+ STATE_GRANTED -> "granted"
+ STATE_DENIED -> "denied"
+ STATE_FG_GRANTED_BG_UNKNOWN -> "foreground granted, background unknown"
+ else -> "skipped"
+ }
+ return "${group.permGroupName} $stateStr $affectedPermissions"
+ }
+ }
+
+ /**
+ * A LiveData which holds a list of the currently pending RequestInfos
+ * TODO 284183336: Once the old ViewModel is gone, remove RequestInfo, and replace it with
+ * just a prompt + denyButton
+ */
+ override val requestInfosLiveData = object :
+ SmartUpdateMediatorLiveData<List<RequestInfo>>() {
+ private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName
+ private val packagePermissionsLiveData = PackagePermissionsLiveData[packageName, user]
+
+ init {
+ addSource(packagePermissionsLiveData) { onPackageLoaded() }
+ addSource(packageInfoLiveData) { onPackageLoaded() }
+ if (safetyLabelInfoLiveData != null) {
+ addSource(safetyLabelInfoLiveData) { onPackageLoaded() }
+ }
+
+ // Load package state, if available
+ onPackageLoaded()
+ }
+
+ private fun onPackageLoaded() {
+ if (packageInfoLiveData.isStale ||
+ packagePermissionsLiveData.isStale ||
+ (safetyLabelInfoLiveData != null && safetyLabelInfoLiveData.isStale)) {
+ return
+ }
+
+ val groups = packagePermissionsLiveData.value
+ val pI = packageInfoLiveData.value
+ if (groups.isNullOrEmpty() || pI == null) {
+ Log.e(LOG_TAG, "Package $packageName not found")
+ value = null
+ return
+ }
+ packageInfo = pI
+
+ if (packageInfo.requestedPermissions.isEmpty() ||
+ packageInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+ Log.e(LOG_TAG, "Package $packageName has no requested permissions, or " +
+ "is a pre-M app")
+ value = null
+ return
+ }
+
+ val affectedPermissions = requestedPermissions.toMutableSet()
+ for (requestedPerm in requestedPermissions) {
+ affectedPermissions.addAll(getAffectedSplitPermissions(requestedPerm))
+ }
+ if (packageInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+ // For < O apps all permissions of the groups of the requested ones are affected
+ for (affectedPerm in affectedPermissions.toSet()) {
+ val otherGroupPerms =
+ groups.values.firstOrNull { affectedPerm in it } ?: emptyList()
+ affectedPermissions.addAll(otherGroupPerms)
+ }
+ }
+ unfilteredAffectedPermissions = affectedPermissions.toList()
+
+ setAppPermGroupsLiveDatas(groups.toMutableMap().apply {
+ remove(PackagePermissionsLiveData.NON_RUNTIME_NORMAL_PERMS)
+ })
+ }
+
+ private fun setAppPermGroupsLiveDatas(groups: Map<String, List<String>>) {
+ val requestedGroups = groups.filter { (_, perms) ->
+ perms.any { it in unfilteredAffectedPermissions }
+ }
+
+ if (requestedGroups.isEmpty()) {
+ Log.e(LOG_TAG, "None of " +
+ "$unfilteredAffectedPermissions in $groups")
+ value = null
+ return
+ }
+
+ val getLiveDataFun = { groupName: String ->
+ LightAppPermGroupLiveData[packageName, groupName, user]
+ }
+ setSourcesToDifference(requestedGroups.keys, appPermGroupLiveDatas, getLiveDataFun)
+ }
+
+ override fun onUpdate() {
+ if (appPermGroupLiveDatas.any { it.value.isStale }) {
+ return
+ }
+ var newGroups = false
+ for ((groupName, groupLiveData) in appPermGroupLiveDatas) {
+ val appPermGroup = groupLiveData.value
+ if (appPermGroup == null) {
+ Log.e(LOG_TAG, "Group $packageName $groupName invalid")
+ groupStates[groupName]?.state = STATE_SKIPPED
+ continue
+ }
+
+ packageInfo = appPermGroup.packageInfo
+
+ val state = groupStates[groupName]
+ if (state != null) {
+ val allAffectedGranted = state.affectedPermissions.all { perm ->
+ appPermGroup.permissions[perm]?.isGrantedIncludingAppOp == true &&
+ appPermGroup.permissions[perm]?.isRevokeWhenRequested == false
+ }
+ if (allAffectedGranted) {
+ groupStates[groupName]!!.state = STATE_GRANTED
+ }
+ } else {
+ newGroups = true
+ }
+ }
+
+ if (newGroups) {
+ addRequiredGroupStates(appPermGroupLiveDatas.mapNotNull { it.value.value })
+ }
+ setRequestInfosFromGroupStates()
+ }
+
+ private fun setRequestInfosFromGroupStates() {
+ val requestInfos = mutableListOf<RequestInfo>()
+ for (groupState in groupStates.values) {
+ if (!isStateUnknown(groupState.state)) {
+ continue
+ }
+ val behavior = getGrantBehavior(groupState.group)
+ val isSystemTriggered =
+ groupState.affectedPermissions.any { it in systemRequestedPermissions}
+ val prompt = behavior.getPrompt(groupState.group, groupState.affectedPermissions,
+ isSystemTriggered)
+ if (prompt == Prompt.NO_UI_REJECT_ALL_GROUPS) {
+ value = null
+ return
+ }
+ if (prompt == Prompt.NO_UI_REJECT_THIS_GROUP) {
+ reportRequestResult(groupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED)
+ continue
+ }
+
+ val denyBehavior = behavior.getDenyButton(groupState.group,
+ groupState.affectedPermissions, prompt)
+ requestInfos.add(convertPromptToRequestInfo(groupState, prompt, denyBehavior))
+ }
+ sortPermissionGroups(requestInfos)
+
+ value = if (requestInfos.any { it.sendToSettingsImmediately } &&
+ requestInfos.size > 1) {
+ Log.e(LOG_TAG, "For R+ apps, background permissions must be requested " +
+ "individually")
+ null
+ } else {
+ requestInfos
+ }
+ }
+ }
+
+ private fun shouldShowPermissionRationale(
+ safetyLabel: SafetyLabel?,
+ permissionGroupName: String?
+ ): Boolean {
+ if (safetyLabel == null || permissionGroupName == null) {
+ return false
+ }
+
+ val purposes = SafetyLabelUtils.getSafetyLabelSharingPurposesForGroup(safetyLabel,
+ permissionGroupName)
+ return purposes.isNotEmpty()
+ }
+
+ /**
+ * Converts a list of LightAppPermGroups into a list of GroupStates, and adds new GroupState
+ * objects to the tracked groupStates.
+ */
+ private fun addRequiredGroupStates(groups: List<LightAppPermGroup>) {
+ val filteredPermissions = unfilteredAffectedPermissions.filter { perm ->
+ val group = getGroupWithPerm(perm, groups)
+ group != null && isPermissionGrantableAndNotFixed(perm, group)
+ }
+ val newGroupStates = mutableMapOf<String, GroupState>()
+ for (perm in filteredPermissions) {
+ val group = getGroupWithPerm(perm, groups)!!
+
+ val oldGroupState = groupStates[group.permGroupName]
+ if (!isStateUnknown(oldGroupState?.state)) {
+ // we've already dealt with this group
+ continue
+ }
+
+ val groupState = newGroupStates.getOrPut(group.permGroupName) { GroupState(group) }
+
+ var currGroupState = groupState.state
+ if (storedState != null && !isStateUnknown(groupState.state)) {
+ currGroupState = storedState.getInt(group.permGroupName, STATE_UNKNOWN)
+ }
+
+ val otherAffectedPermissionsInGroup =
+ filteredPermissions.filter { it in group.permissions }.toSet()
+ val groupStateOfPerm = getGroupState(perm, group, otherAffectedPermissionsInGroup)
+ if (groupStateOfPerm != STATE_UNKNOWN) {
+ // update the state if it is allowed, denied, or granted in foreground
+ currGroupState = groupStateOfPerm
+ }
+
+ if (currGroupState != STATE_UNKNOWN) {
+ groupState.state = currGroupState
+ }
+
+ groupState.affectedPermissions.add(perm)
+ }
+ newGroupStates.forEach { (groupName, groupState) -> groupStates[groupName] = groupState }
+ }
+
+ /**
+ * Add additional permissions that should be granted in this request. For permissions that have
+ * split permissions, and apps that target an SDK before the split, this method automatically
+ * adds the split off permission.
+ *
+ * @param perm The requested permission
+ *
+ * @return The requested permissions plus any needed split permissions
+ */
+ private fun getAffectedSplitPermissions(
+ perm: String,
+ ): List<String> {
+ val requestingAppTargetSDK = packageInfo.targetSdkVersion
+
+ // If a permission is split, all permissions the original permission is split into are
+ // affected
+ val extendedBySplitPerms = mutableListOf(perm)
+
+ val splitPerms = app.getSystemService(PermissionManager::class.java)!!.splitPermissions
+ for (splitPerm in splitPerms) {
+
+ if (requestingAppTargetSDK < splitPerm.targetSdk && perm == splitPerm.splitPermission) {
+ extendedBySplitPerms.addAll(splitPerm.newPermissions)
+ }
+ }
+ return extendedBySplitPerms
+ }
+
+ private fun isPermissionGrantableAndNotFixed(perm: String, group: LightAppPermGroup): Boolean {
+ // If the permission is restricted it does not show in the UI and
+ // is not added to the group at all, so check that first.
+ if (perm in group.packageInfo.requestedPermissions && perm !in group.permissions) {
+ reportRequestResult(perm,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION)
+ return false
+ }
+
+ val subGroup = if (perm in group.backgroundPermNames) {
+ group.background
+ } else {
+ group.foreground
+ }
+
+ val lightPermission = group.permissions[perm] ?: return false
+
+ if (!subGroup.isGrantable) {
+ reportRequestResult(perm, PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED)
+ // Skip showing groups that we know cannot be granted.
+ return false
+ }
+
+ if (subGroup.isPolicyFixed && !subGroup.isGranted || lightPermission.isPolicyFixed) {
+ reportRequestResult(perm,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED)
+ return false
+ }
+
+ val behavior = getGrantBehavior(group)
+ if (behavior.isPermissionFixed(group, perm)) {
+ reportRequestResult(perm,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED)
+ return false
+ }
+
+ return true
+ }
+
+ private fun getGroupState(
+ perm: String,
+ group: LightAppPermGroup,
+ groupRequestedPermissions: Set<String>
+ ): Int {
+ val policyState = getStateFromPolicy(perm, group)
+ if (!isStateUnknown(policyState)) {
+ return policyState
+ }
+
+ val isBackground = perm in group.backgroundPermNames
+
+ val behavior = getGrantBehavior(group)
+ return if (behavior.isGroupFullyGranted(group, groupRequestedPermissions)) {
+ if (group.permissions[perm]?.isGrantedIncludingAppOp == false) {
+ if (isBackground) {
+ grantBackgroundRuntimePermissions(app, group, listOf(perm))
+ } else {
+ grantForegroundRuntimePermissions(app, group, listOf(perm), group.isOneTime)
+ }
+ KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_SET to false,
+ FLAG_PERMISSION_USER_FIXED to false, filterPermissions = listOf(perm))
+ reportRequestResult(perm,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED)
+ }
+ STATE_GRANTED
+ } else if (behavior.isForegroundFullyGranted(group, groupRequestedPermissions)) {
+ STATE_FG_GRANTED_BG_UNKNOWN
+ } else {
+ STATE_UNKNOWN
+ }
+ }
+
+ private fun getStateFromPolicy(perm: String, group: LightAppPermGroup): Int {
+ val isBackground = perm in group.backgroundPermNames
+ var state = STATE_UNKNOWN
+ when (permissionPolicy) {
+ DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT -> {
+ if (AdminRestrictedPermissionsUtils.mayAdminGrantPermission(
+ app, perm, user.identifier)) {
+ if (isBackground) {
+ grantBackgroundRuntimePermissions(app, group, listOf(perm))
+ } else {
+ grantForegroundRuntimePermissions(app, group, listOf(perm))
+ }
+ KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_POLICY_FIXED to true,
+ FLAG_PERMISSION_USER_SET to false, FLAG_PERMISSION_USER_FIXED to false,
+ filterPermissions = listOf(perm))
+ state = STATE_GRANTED
+ getAutoGrantNotifier().onPermissionAutoGranted(perm)
+ reportRequestResult(perm,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_GRANTED)
+ }
+ }
+
+ DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY -> {
+ if (group.permissions[perm]?.isPolicyFixed == false) {
+ KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_POLICY_FIXED to true,
+ FLAG_PERMISSION_USER_SET to false, FLAG_PERMISSION_USER_FIXED to false,
+ filterPermissions = listOf(perm))
+ }
+ state = STATE_DENIED
+ reportRequestResult(perm,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_DENIED)
+ }
+ }
+ return state
+ }
+
+ /**
+ * Upon the user clicking a button, grant permissions, if applicable.
+ *
+ * @param groupName The name of the permission group which was changed
+ * @param affectedForegroundPermissions The name of the foreground permission which was changed
+ * @param result The choice the user made regarding the group.
+ */
+ override fun onPermissionGrantResult(
+ groupName: String?,
+ affectedForegroundPermissions: List<String>?,
+ result: Int
+ ) {
+ onPermissionGrantResult(groupName, affectedForegroundPermissions, result, false)
+ }
+
+ private fun onPermissionGrantResult(
+ groupName: String?,
+ affectedForegroundPermissions: List<String>?,
+ result: Int,
+ alreadyRequestedStorageGroupsIfNeeded: Boolean
+ ) {
+ if (groupName == null) {
+ return
+ }
+
+ // If this is a legacy app, and a storage group is requested: request all storage groups
+ if (!alreadyRequestedStorageGroupsIfNeeded &&
+ groupName in PermissionMapping.STORAGE_SUPERGROUP_PERMISSIONS &&
+ packageInfo.targetSdkVersion <= Build.VERSION_CODES.S_V2) {
+ for (storageGroupName in PermissionMapping.STORAGE_SUPERGROUP_PERMISSIONS) {
+ val groupPerms = appPermGroupLiveDatas[storageGroupName]
+ ?.value?.allPermissions?.keys?.toList()
+ onPermissionGrantResult(storageGroupName, groupPerms, result, true)
+ }
+ return
+ }
+
+ val groupState = groupStates[groupName] ?: return
+ when (result) {
+ CANCELED -> {
+ reportRequestResult(groupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED)
+ groupState.state = STATE_SKIPPED
+ requestInfosLiveData.update()
+ return
+ }
+ GRANTED_ALWAYS -> {
+ onPermissionGrantResultSingleState(groupState,
+ affectedForegroundPermissions, granted = true, isOneTime = false,
+ foregroundOnly = false, doNotAskAgain = false)
+ }
+ GRANTED_FOREGROUND_ONLY -> {
+ onPermissionGrantResultSingleState(groupState,
+ affectedForegroundPermissions, granted = true, isOneTime = false,
+ foregroundOnly = true, doNotAskAgain = false)
+ }
+ GRANTED_ONE_TIME -> {
+ onPermissionGrantResultSingleState(groupState,
+ affectedForegroundPermissions, granted = true, isOneTime = true,
+ foregroundOnly = false, doNotAskAgain = false)
+ }
+ GRANTED_USER_SELECTED, DENIED_MORE -> {
+ grantUserSelectedVisualGroupPermissions(groupState)
+ }
+ DENIED -> {
+ onPermissionGrantResultSingleState(groupState,
+ affectedForegroundPermissions, granted = false, isOneTime = false,
+ foregroundOnly = false, doNotAskAgain = false)
+ }
+ DENIED_DO_NOT_ASK_AGAIN -> {
+ onPermissionGrantResultSingleState(groupState,
+ affectedForegroundPermissions, granted = false, isOneTime = false,
+ foregroundOnly = false, doNotAskAgain = true)
+ }
+ }
+ }
+
+ private fun grantUserSelectedVisualGroupPermissions(groupState: GroupState) {
+ val userSelectedPerm =
+ groupState.group.permissions[READ_MEDIA_VISUAL_USER_SELECTED] ?: return
+ if (userSelectedPerm.isImplicit) {
+ val nonSelectedPerms = groupState.group.permissions.keys
+ .filter { it != READ_MEDIA_VISUAL_USER_SELECTED }
+ // If the permission is implicit, grant USER_SELECTED as user set, and all other
+ // permissions as one time, and without app ops.
+ grantForegroundRuntimePermissions(app, groupState.group,
+ listOf(READ_MEDIA_VISUAL_USER_SELECTED))
+ grantForegroundRuntimePermissions(app, groupState.group,
+ nonSelectedPerms, isOneTime = true, userFixed = false, withoutAppOps = true)
+ val appPermGroup = AppPermissionGroup.create(app, packageName,
+ groupState.group.permGroupName, groupState.group.userHandle, false)
+ appPermGroup.setSelfRevoked()
+ appPermGroup.persistChanges(false, null, nonSelectedPerms.toSet())
+ } else {
+ val partialPerms = getPartialStorageGrantPermissionsForGroup(groupState.group).filter {
+ it in groupState.affectedPermissions
+ }
+ val nonSelectedPerms = groupState.affectedPermissions.filter { it !in partialPerms }
+ val setUserFixed = userSelectedPerm.isUserFixed || userSelectedPerm.isUserSet
+ grantForegroundRuntimePermissions(app, groupState.group,
+ partialPerms.toList(), userFixed = setUserFixed)
+ revokeForegroundRuntimePermissions(app, groupState.group,
+ userFixed = setUserFixed, oneTime = false, filterPermissions = nonSelectedPerms)
+ }
+ groupState.state = STATE_GRANTED
+ reportButtonClickResult(groupState, groupState.affectedPermissions, true,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__PHOTOS_SELECTED)
+ }
+
+ @SuppressLint("NewApi")
+ private fun onPermissionGrantResultSingleState(
+ groupState: GroupState,
+ affectedForegroundPermissions: List<String>?,
+ granted: Boolean,
+ foregroundOnly: Boolean,
+ isOneTime: Boolean,
+ doNotAskAgain: Boolean
+ ) {
+ if (!isStateUnknown(groupState.state)) {
+ // We already dealt with this group, don't re-grant/re-revoke
+ return
+ }
+ val shouldAffectBackgroundPermissions =
+ groupState.bgPermissions.isNotEmpty() && !foregroundOnly
+ val shouldAffectForegroundPermssions = groupState.state != STATE_FG_GRANTED_BG_UNKNOWN
+ val result: Int
+ if (granted) {
+ result = if (isOneTime) {
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME
+ } else {
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED
+ }
+ if (shouldAffectBackgroundPermissions) {
+ grantBackgroundRuntimePermissions(app, groupState.group,
+ groupState.affectedPermissions)
+ } else if (shouldAffectForegroundPermssions) {
+ if (affectedForegroundPermissions == null) {
+ grantForegroundRuntimePermissions(app, groupState.group,
+ groupState.affectedPermissions, isOneTime)
+ // This prevents weird flag state when app targetSDK switches from S+ to R-
+ if (groupState.affectedPermissions.contains(ACCESS_FINE_LOCATION)) {
+ KotlinUtils.setFlagsWhenLocationAccuracyChanged(
+ app, groupState.group, true)
+ }
+ } else {
+ val newGroup = grantForegroundRuntimePermissions(app,
+ groupState.group, affectedForegroundPermissions, isOneTime)
+ if (!isOneTime || newGroup.isOneTime) {
+ KotlinUtils.setFlagsWhenLocationAccuracyChanged(app, newGroup,
+ affectedForegroundPermissions.contains(ACCESS_FINE_LOCATION))
+ }
+ }
+ }
+ groupState.state = STATE_GRANTED
+ } else {
+ if (shouldAffectBackgroundPermissions) {
+ revokeBackgroundRuntimePermissions(app, groupState.group,
+ userFixed = doNotAskAgain, filterPermissions = groupState.affectedPermissions)
+ } else if (shouldAffectForegroundPermssions) {
+ if (affectedForegroundPermissions == null ||
+ affectedForegroundPermissions.contains(ACCESS_COARSE_LOCATION)) {
+ revokeForegroundRuntimePermissions(app, groupState.group,
+ userFixed = doNotAskAgain,
+ filterPermissions = groupState.affectedPermissions, oneTime = isOneTime)
+ } else {
+ revokeForegroundRuntimePermissions(app, groupState.group,
+ userFixed = doNotAskAgain,
+ filterPermissions = affectedForegroundPermissions, oneTime = isOneTime)
+ }
+ }
+ result = if (doNotAskAgain) {
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE
+ } else {
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED
+ }
+ groupState.state = STATE_DENIED
+ }
+ val permissionsChanged = if (foregroundOnly) {
+ groupState.fgPermissions
+ } else {
+ groupState.affectedPermissions
+ }
+ reportButtonClickResult(groupState, permissionsChanged, granted, result)
+ }
+
+ private fun reportButtonClickResult(
+ groupState: GroupState,
+ permissions: Set<String>,
+ granted: Boolean,
+ result: Int
+ ) {
+ reportRequestResult(permissions, result)
+ // group state has changed, reload liveData
+ requestInfosLiveData.update()
+ PermissionDecisionStorageImpl.recordPermissionDecision(app.applicationContext,
+ packageName, groupState.group.permGroupName, granted)
+ PermissionChangeStorageImpl.recordPermissionChange(packageName)
+ if (granted) {
+ startDrivingDecisionReminderServiceIfNecessary(groupState.group.permGroupName)
+ }
+ }
+
+ /**
+ * When distraction optimization is required (the vehicle is in motion), the user may want to
+ * review their permission grants when they are less distracted.
+ */
+ private fun startDrivingDecisionReminderServiceIfNecessary(permGroupName: String) {
+ if (!DeviceUtils.isAuto(app.applicationContext)) {
+ return
+ }
+ DrivingDecisionReminderService.startServiceIfCurrentlyRestricted(
+ Utils.getUserContext(app, user), packageName, permGroupName)
+ }
+
+ private fun getGroupWithPerm(
+ perm: String,
+ groups: List<LightAppPermGroup>
+ ): LightAppPermGroup? {
+ val groupsWithPerm = groups.filter { perm in it.permissions }
+ if (groupsWithPerm.isEmpty()) {
+ return null
+ }
+ return groupsWithPerm.first()
+ }
+
+
+ private fun reportRequestResult(permissions: Collection<String>, result: Int) {
+ permissions.forEach { reportRequestResult(it, result) }
+ }
+
+ /**
+ * Report the result of a grant of a permission.
+ *
+ * @param permission The permission that was granted or denied
+ * @param result The permission grant result
+ */
+ private fun reportRequestResult(permission: String, result: Int) {
+ val isImplicit = permission !in requestedPermissions
+ val isPermissionRationaleShown = shouldShowPermissionRationale(
+ safetyLabelInfoLiveData?.value?.safetyLabel,
+ PermissionMapping.getGroupOfPlatformPermission(permission))
+
+ Log.v(LOG_TAG, "Permission grant result requestId=$sessionId " +
+ "callingUid=${packageInfo.uid} callingPackage=$packageName permission=$permission " +
+ "isImplicit=$isImplicit result=$result " +
+ "isPermissionRationaleShown=$isPermissionRationaleShown")
+
+ PermissionControllerStatsLog.write(
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED, sessionId,
+ packageInfo.uid, packageName, permission, isImplicit, result,
+ isPermissionRationaleShown)
+ }
+
+ /**
+ * Save the group states of the view model, to allow for state restoration after lifecycle
+ * events
+ *
+ * @param outState The bundle in which to store state
+ */
+ override fun saveInstanceState(outState: Bundle) {
+ for ((groupName, groupState) in groupStates) {
+ outState.putInt(groupName, groupState.state)
+ }
+ }
+
+ /**
+ * Determine if the activity should return permission state to the caller
+ *
+ * @return Whether or not state should be returned. False only if the package is pre-M, true
+ * otherwise.
+ */
+ override fun shouldReturnPermissionState(): Boolean {
+ return if (packageInfoLiveData.value != null) {
+ packageInfoLiveData.value!!.targetSdkVersion >= Build.VERSION_CODES.M
+ } else {
+ // Should not be reached, as this method shouldn't be called before data is passed to
+ // the activity for the first time
+ try {
+ Utils.getUserContext(app, user).packageManager
+ .getApplicationInfo(packageName, 0).targetSdkVersion >= Build.VERSION_CODES.M
+ } catch (e: PackageManager.NameNotFoundException) {
+ true
+ }
+ }
+ }
+
+ override fun handleHealthConnectPermissions(activity: Activity) {
+ if (activityResultCallback == null) {
+ activityResultCallback = Consumer {
+ groupStates[HEALTH_PERMISSION_GROUP]?.state = STATE_SKIPPED
+ requestInfosLiveData.update()
+ }
+ val healthPermissions = unfilteredAffectedPermissions.filter { permission ->
+ isHealthPermission(activity, permission)
+ }.toTypedArray()
+ val intent: Intent = Intent(ACTION_REQUEST_HEALTH_PERMISSIONS)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ .putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, healthPermissions)
+ .putExtra(Intent.EXTRA_USER, Process.myUserHandle())
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE)
+ }
+ }
+
+ /**
+ * Send the user directly to the AppPermissionFragment. Used for R+ apps.
+ *
+ * @param activity The current activity
+ * @param groupName The name of the permission group whose fragment should be opened
+ */
+ override fun sendDirectlyToSettings(activity: Activity, groupName: String) {
+ if (activityResultCallback == null) {
+ activityResultCallback = Consumer { data ->
+ if (data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED) == null) {
+ // User didn't interact, count against rate limit
+ val group = groupStates[groupName]?.group ?: return@Consumer
+ if (group.background.isUserSet) {
+ KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_FIXED to true,
+ filterPermissions = group.backgroundPermNames)
+ } else {
+ KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_SET to true,
+ filterPermissions = group.backgroundPermNames)
+ }
+ }
+
+ groupStates[groupName]?.state = STATE_SKIPPED
+ // Update our liveData now that there is a new skipped group
+ requestInfosLiveData.update()
+ }
+ startAppPermissionFragment(activity, groupName)
+ }
+ }
+
+ override fun openPhotoPicker(activity: Activity, result: Int) {
+ if (activityResultCallback != null) {
+ return
+ }
+ if (groupStates[READ_MEDIA_VISUAL]?.affectedPermissions == null) {
+ return
+ }
+ activityResultCallback = Consumer { data ->
+ val anySelected = data?.getBooleanExtra(INTENT_PHOTOS_SELECTED, true) == true
+ if (anySelected) {
+ onPermissionGrantResult(READ_MEDIA_VISUAL, null, result)
+ } else {
+ onPermissionGrantResult(READ_MEDIA_VISUAL, null, CANCELED)
+ }
+ requestInfosLiveData.update()
+ }
+ activity.startActivityForResult(Intent(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP)
+ .putExtra(Intent.EXTRA_UID, packageInfo.uid)
+ .setType(KotlinUtils.getMimeTypeForPermissions(unfilteredAffectedPermissions)),
+ PHOTO_PICKER_REQUEST_CODE)
+ }
+
+ /**
+ * Send the user to the AppPermissionFragment from a link. Used for Q- apps
+ *
+ * @param activity The current activity
+ * @param groupName The name of the permission group whose fragment should be opened
+ */
+ override fun sendToSettingsFromLink(activity: Activity, groupName: String) {
+ startAppPermissionFragment(activity, groupName)
+ activityResultCallback = Consumer { data ->
+ val returnGroupName = data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED)
+ if (returnGroupName != null) {
+ groupStates[returnGroupName]?.state = STATE_SKIPPED
+ val result = data.getIntExtra(EXTRA_RESULT_PERMISSION_RESULT, -1)
+ logSettingsInteraction(returnGroupName, result)
+ requestInfosLiveData.update()
+ }
+ }
+ }
+
+ /**
+ * Shows the Permission Rationale Dialog. For use with U+ only, otherwise no-op.
+ *
+ * @param activity The current activity
+ * @param groupName The name of the permission group whose fragment should be opened
+ */
+ override fun showPermissionRationaleActivity(activity: Activity, groupName: String) {
+ if (!SdkLevel.isAtLeastU()) {
+ return
+ }
+
+ val intent = Intent(activity, PermissionRationaleActivity::class.java).apply {
+ putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName)
+ putExtra(Constants.EXTRA_SESSION_ID, sessionId)
+ }
+ activityResultCallback = Consumer { data ->
+ val returnGroupName = data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED)
+ if (returnGroupName != null) {
+ groupStates[returnGroupName]?.state = STATE_SKIPPED
+ val result = data.getIntExtra(EXTRA_RESULT_PERMISSION_RESULT, CANCELED)
+ logSettingsInteraction(returnGroupName, result)
+ requestInfosLiveData.update()
+ }
+ }
+ activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE)
+ }
+
+ private fun startAppPermissionFragment(activity: Activity, groupName: String) {
+ val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSION)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ .putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName)
+ .putExtra(Intent.EXTRA_USER, user)
+ .putExtra(ManagePermissionsActivity.EXTRA_CALLER_NAME,
+ GrantPermissionsActivity::class.java.name)
+ .putExtra(Constants.EXTRA_SESSION_ID, sessionId)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE)
+ }
+
+ private fun getGrantBehavior(group: LightAppPermGroup): GrantBehavior {
+ return when (group.permGroupName) {
+ LOCATION -> LocationGrantBehavior
+ HEALTH_PERMISSION_GROUP -> HealthGrantBehavior
+ NOTIFICATIONS -> NotificationGrantBehavior
+ STORAGE, READ_MEDIA_VISUAL, READ_MEDIA_AURAL -> StorageGrantBehavior
+ else -> {
+ if (Utils.hasPermWithBackgroundModeCompat(group)) {
+ BackgroundGrantBehavior
+ } else {
+ BasicGrantBehavior
+ }
+ }
+ }
+ }
+
+ private fun logSettingsInteraction(groupName: String, result: Int) {
+ val groupState = groupStates[groupName] ?: return
+ val backgroundPerms = groupState.affectedPermissions.filter {
+ it in groupState.group.backgroundPermNames
+ }
+ val foregroundPerms = groupState.affectedPermissions.filter {
+ it !in backgroundPerms }
+ val deniedPrejudiceInSettings =
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE_IN_SETTINGS
+ when (result) {
+ GRANTED_ALWAYS -> {
+ reportRequestResult(groupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS)
+ }
+ GRANTED_FOREGROUND_ONLY -> {
+ reportRequestResult(foregroundPerms,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_IN_SETTINGS)
+ if (backgroundPerms.isNotEmpty()) {
+ reportRequestResult(backgroundPerms,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS)
+ }
+ }
+ DENIED -> {
+ reportRequestResult(groupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS)
+ }
+ DENIED_DO_NOT_ASK_AGAIN -> {
+ reportRequestResult(groupState.affectedPermissions, deniedPrejudiceInSettings)
+ }
+ }
+ }
+
+ /**
+ * Log all permission groups which were requested
+ */
+ override fun logRequestedPermissionGroups() {
+ if (groupStates.isEmpty()) {
+ return
+ }
+ val groups = groupStates.map { it.value.group }
+ SafetyNetLogger.logPermissionsRequested(packageName, packageInfo.uid, groups)
+ }
+
+ /**
+ * Log information about the buttons which were shown and clicked by the user.
+ *
+ * @param groupName The name of the permission group which was interacted with
+ * @param selectedPrecision Selected precision of the location permission - bit flags indicate
+ * which locations were chosen
+ * @param clickedButton The button that was clicked by the user
+ * @param presentedButtons All buttons which were shown to the user
+ */
+ override fun logClickedButtons(
+ groupName: String?,
+ selectedPrecision: Int,
+ clickedButton: Int,
+ presentedButtons: Int,
+ isPermissionRationaleShown: Boolean
+ ) {
+ if (groupName == null) {
+ return
+ }
+
+ if (!requestInfosLiveData.isInitialized || !packageInfoLiveData.isInitialized) {
+ Log.wtf(LOG_TAG, "Logged buttons presented and clicked permissionGroupName=" +
+ "$groupName package=$packageName presentedButtons=$presentedButtons " +
+ "clickedButton=$clickedButton isPermissionRationaleShown=" +
+ "$isPermissionRationaleShown sessionId=$sessionId, but requests were not yet" +
+ "initialized", IllegalStateException())
+ return
+ }
+
+ PermissionControllerStatsLog.write(GRANT_PERMISSIONS_ACTIVITY_BUTTON_ACTIONS,
+ groupName, packageInfo.uid, packageName, presentedButtons, clickedButton, sessionId,
+ packageInfo.targetSdkVersion, selectedPrecision,
+ isPermissionRationaleShown)
+ Log.v(LOG_TAG, "Logged buttons presented and clicked permissionGroupName=" +
+ "$groupName uid=${packageInfo.uid} selectedPrecision=$selectedPrecision " +
+ "package=$packageName presentedButtons=$presentedButtons " +
+ "clickedButton=$clickedButton isPermissionRationaleShown=" +
+ "$isPermissionRationaleShown sessionId=$sessionId " +
+ "targetSdk=${packageInfo.targetSdkVersion}")
+ }
+
+ /**
+ * Use the autoGrantNotifier to notify of auto-granted permissions.
+ */
+ override fun autoGrantNotify() {
+ autoGrantNotifier?.notifyOfAutoGrantPermissions(true)
+ }
+
+ // TODO: 284183336, once the old viewModel is gone, this should be moved to the activity
+ private fun convertPromptToRequestInfo(
+ groupState: GroupState,
+ prompt: Prompt,
+ deny: DenyButton
+ ): RequestInfo {
+ val buttons = mutableSetOf<Int>()
+ val locationButtons = mutableSetOf<Int>()
+ var message = RequestMessage.FG_MESSAGE
+ var detailMessage = RequestMessage.NO_MESSAGE
+ when (prompt) {
+ Prompt.BASIC -> buttons.add(ALLOW_BUTTON)
+ Prompt.FG_ONLY -> buttons.add(ALLOW_FOREGROUND_BUTTON)
+ Prompt.ONE_TIME_FG ->
+ buttons.addAll(listOf(ALLOW_FOREGROUND_BUTTON, ALLOW_ONE_TIME_BUTTON))
+ Prompt.SETTINGS_LINK_FOR_BG -> {
+ buttons.add(ALLOW_FOREGROUND_BUTTON)
+ message = RequestMessage.BG_MESSAGE
+ detailMessage = RequestMessage.BG_MESSAGE
+ }
+ Prompt.SETTINGS_LINK_WITH_OT -> {
+ buttons.addAll(listOf(ALLOW_FOREGROUND_BUTTON, ALLOW_ONE_TIME_BUTTON))
+ message = RequestMessage.BG_MESSAGE
+ detailMessage = RequestMessage.BG_MESSAGE
+ }
+ Prompt.UPGRADE_SETTINGS_LINK -> {
+ message = RequestMessage.UPGRADE_MESSAGE
+ detailMessage = RequestMessage.UPGRADE_MESSAGE
+ }
+ Prompt.OT_UPGRADE_SETTINGS_LINK -> {
+ message = RequestMessage.UPGRADE_MESSAGE
+ detailMessage = RequestMessage.UPGRADE_MESSAGE
+ }
+ Prompt.LOCATION_TWO_BUTTON -> {
+ buttons.addAll(listOf(ALLOW_FOREGROUND_BUTTON, ALLOW_ONE_TIME_BUTTON))
+ locationButtons.addAll(listOf(LOCATION_ACCURACY_LAYOUT,
+ DIALOG_WITH_BOTH_LOCATIONS, getSelectedLocation(groupState.group)))
+ }
+ Prompt.LOCATION_COARSE_ONLY -> {
+ buttons.addAll(listOf(ALLOW_FOREGROUND_BUTTON, ALLOW_ONE_TIME_BUTTON))
+ locationButtons.addAll(listOf(LOCATION_ACCURACY_LAYOUT,
+ DIALOG_WITH_COARSE_LOCATION_ONLY))
+ message = RequestMessage.FG_COARSE_LOCATION_MESSAGE
+ }
+ Prompt.LOCATION_FINE_UPGRADE -> {
+ buttons.addAll(listOf(ALLOW_FOREGROUND_BUTTON, ALLOW_ONE_TIME_BUTTON))
+ locationButtons.addAll(listOf(LOCATION_ACCURACY_LAYOUT,
+ DIALOG_WITH_FINE_LOCATION_ONLY))
+ message = RequestMessage.FG_FINE_LOCATION_MESSAGE
+ }
+ Prompt.SELECT_PHOTOS -> {
+ buttons.addAll(listOf(ALLOW_ALL_BUTTON, ALLOW_SELECTED_BUTTON))
+ }
+ Prompt.SELECT_MORE_PHOTOS -> {
+ buttons.addAll(listOf(ALLOW_ALL_BUTTON, ALLOW_SELECTED_BUTTON))
+ message = GrantPermissionsViewModel.Companion.RequestMessage.MORE_PHOTOS_MESSAGE
+ }
+ Prompt.STORAGE_SUPERGROUP_PRE_Q -> {
+ buttons.add(ALLOW_BUTTON)
+ message = RequestMessage.STORAGE_SUPERGROUP_MESSAGE_PRE_Q
+ }
+ Prompt.STORAGE_SUPERGROUP_Q_TO_S -> {
+ buttons.add(ALLOW_BUTTON)
+ message = RequestMessage.STORAGE_SUPERGROUP_MESSAGE_Q_TO_S
+ }
+ Prompt.NO_UI_SETTINGS_REDIRECT -> {
+ return RequestInfo(groupState.group.permGroupInfo, sendToSettingsImmediately = true)
+ }
+ Prompt.NO_UI_PHOTO_PICKER_REDIRECT -> {
+ return RequestInfo(groupState.group.permGroupInfo, openPhotoPicker = true)
+ }
+ Prompt.NO_UI_HEALTH_REDIRECT -> {
+ return RequestInfo(groupState.group.permGroupInfo)
+ }
+ Prompt.NO_UI_FILTER_THIS_GROUP -> {
+ return RequestInfo(groupState.group.permGroupInfo,
+ filteredPermissions = groupState.affectedPermissions)
+ }
+ Prompt.NO_UI_REJECT_ALL_GROUPS, Prompt.NO_UI_REJECT_THIS_GROUP -> {
+ /* These have no buttons */
+ }
+ }
+
+ when (deny) {
+ DenyButton.DENY -> buttons.add(DENY_BUTTON)
+ DenyButton.DENY_DONT_ASK_AGAIN -> buttons.add(DENY_AND_DONT_ASK_AGAIN_BUTTON)
+ DenyButton.DONT_SELECT_MORE -> buttons.add(DONT_ALLOW_MORE_SELECTED_BUTTON)
+ DenyButton.NO_UPGRADE -> buttons.add(NO_UPGRADE_BUTTON)
+ DenyButton.NO_UPGRADE_OT -> buttons.add(NO_UPGRADE_OT_BUTTON)
+ DenyButton.NO_UPGRADE_AND_DONT_ASK_AGAIN ->
+ buttons.add(NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON)
+ DenyButton.NO_UPGRADE_AND_DONT_ASK_AGAIN_OT -> NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON
+ DenyButton.NONE -> {}
+ }
+
+ val safetyLabel = safetyLabelInfoLiveData?.value?.safetyLabel
+ if (shouldShowPermissionRationale(safetyLabel, groupState.group.permGroupName)) {
+ buttons.add(LINK_TO_PERMISSION_RATIONALE)
+ }
+ val buttonArray = convertSetToBoolList(buttons, NEXT_BUTTON)
+ val locationArray = convertSetToBoolList(locationButtons, NEXT_LOCATION_DIALOG)
+ return RequestInfo(groupState.group.permGroupInfo, buttonArray, locationArray,
+ message, detailMessage)
+ }
+
+ private fun getSelectedLocation(group: LightAppPermGroup): Int {
+ // Steps to decide location accuracy default state
+ // 1. If none of the FINE and COARSE isSelectedLocationAccuracy
+ // flags is set, then use default precision from device config.
+ // 2. Otherwise set to whichever isSelectedLocationAccuracy is true.
+ val coarseLocationPerm =
+ group.allPermissions[ACCESS_COARSE_LOCATION]
+ val fineLocationPerm =
+ group.allPermissions[ACCESS_FINE_LOCATION]
+ return if (coarseLocationPerm?.isSelectedLocationAccuracy == false &&
+ fineLocationPerm?.isSelectedLocationAccuracy == false) {
+ if (KotlinUtils.getDefaultPrecision()) {
+ FINE_RADIO_BUTTON
+ } else {
+ COARSE_RADIO_BUTTON
+ }
+ } else if (coarseLocationPerm?.isSelectedLocationAccuracy == true) {
+ COARSE_RADIO_BUTTON
+ } else {
+ FINE_RADIO_BUTTON
+ }
+ }
+
+ private fun convertSetToBoolList(buttonSet: Set<Int>, maxSize: Int): List<Boolean> {
+ val buttonArray = MutableList(maxSize) { false }
+ for (button in buttonSet) {
+ buttonArray[button] = true
+ }
+ return buttonArray
+ }
+
+ private fun isStateUnknown(state: Int?): Boolean {
+ return state == null || state == STATE_UNKNOWN || state == STATE_FG_GRANTED_BG_UNKNOWN
+ }
+
+ companion object {
+ const val APP_PERMISSION_REQUEST_CODE = 1
+ const val PHOTO_PICKER_REQUEST_CODE = 2
+ private const val STATE_UNKNOWN = 0
+ private const val STATE_GRANTED = 1
+ private const val STATE_DENIED = 2
+ private const val STATE_SKIPPED = 3
+ private const val STATE_FG_GRANTED_BG_UNKNOWN = 4
+ }
+}
+
+/**
+ * Factory for an AppPermissionViewModel
+ *
+ * @param app The current application
+ * @param packageName The name of the package this ViewModel represents
+ */
+class NewGrantPermissionsViewModelFactory(
+ private val app: Application,
+ private val packageName: String,
+ private val requestedPermissions: List<String>,
+ private val systemRequestedPermissions: List<String>,
+ private val sessionId: Long,
+ private val savedState: Bundle?
+) : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ @Suppress("UNCHECKED_CAST")
+ return NewGrantPermissionsViewModel(app, packageName, requestedPermissions,
+ systemRequestedPermissions, sessionId, savedState) as T
+ }
+}
+
+enum class Prompt {
+ BASIC, // Allow/Deny
+ ONE_TIME_FG, // Allow in foreground/one time/deny
+ FG_ONLY, // Allow in foreground/deny
+ SETTINGS_LINK_FOR_BG, // Allow in foreground/deny, with link to settings to change background
+ SETTINGS_LINK_WITH_OT, // Same as above, but with a one time button
+ UPGRADE_SETTINGS_LINK, // Keep foreground, with link to settings to grant background
+ OT_UPGRADE_SETTINGS_LINK, // Same as above, but the button is "keep one time"
+ LOCATION_TWO_BUTTON, // Choose coarse/fine, foreground/one time/deny
+ LOCATION_COARSE_ONLY, // Only coarse location, foreground/one time/deny
+ LOCATION_FINE_UPGRADE, // Upgrade coarse to fine, upgrade to fine/ one time/ keep coarse
+ SELECT_PHOTOS, // Select photos/allow all photos/deny
+ SELECT_MORE_PHOTOS, // Select more photos/allow all photos/don't allow more
+ // These next two are for T+ devices, and < T apps. They request the old "storage" group, and
+ // we "grant" it, while actually granting the new visual and audio groups
+ STORAGE_SUPERGROUP_Q_TO_S, // Allow/deny, special message
+ STORAGE_SUPERGROUP_PRE_Q, // Allow/deny, special message (different from above)
+ NO_UI_SETTINGS_REDIRECT, // Send the user directly to permission settings
+ NO_UI_PHOTO_PICKER_REDIRECT, // Send the user directly to the photo picker
+ NO_UI_HEALTH_REDIRECT, // Send the user directly to the Health Connect settings
+ NO_UI_REJECT_THIS_GROUP, // Auto deny this permission group
+ NO_UI_REJECT_ALL_GROUPS, // Auto deny all permission groups in this request
+ NO_UI_FILTER_THIS_GROUP, // Do not act on this permission group. Remove it from results.
+}
+
+enum class DenyButton {
+ DENY,
+ DENY_DONT_ASK_AGAIN,
+ NO_UPGRADE,
+ NO_UPGRADE_OT,
+ NO_UPGRADE_AND_DONT_ASK_AGAIN,
+ NO_UPGRADE_AND_DONT_ASK_AGAIN_OT,
+ DONT_SELECT_MORE, // used in the SELECT_MORE_PHOTOS dialog
+ NONE,
+}
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..7aedf5609 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()
}
@@ -464,13 +463,16 @@ class PermissionAppsViewModelFactory(
owner: SavedStateRegistryOwner,
defaultArgs: Bundle
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
-
- override fun <T : ViewModel> create(p0: String, p1: Class<T>, state: SavedStateHandle): T {
- state.set(SHOULD_SHOW_SYSTEM_KEY, state.get<Boolean>(SHOULD_SHOW_SYSTEM_KEY) ?: false)
- state.set(HAS_SYSTEM_APPS_KEY, state.get<Boolean>(HAS_SYSTEM_APPS_KEY) ?: true)
- state.set(SHOW_ALWAYS_ALLOWED, state.get<Boolean>(SHOW_ALWAYS_ALLOWED) ?: false)
- state.set(CREATION_LOGGED_KEY, state.get<Boolean>(CREATION_LOGGED_KEY) ?: false)
+ override fun <T : ViewModel> create(
+ key: String,
+ modelClass: Class<T>,
+ handle: SavedStateHandle
+ ): T {
+ handle.set(SHOULD_SHOW_SYSTEM_KEY, handle.get<Boolean>(SHOULD_SHOW_SYSTEM_KEY) ?: false)
+ handle.set(HAS_SYSTEM_APPS_KEY, handle.get<Boolean>(HAS_SYSTEM_APPS_KEY) ?: true)
+ handle.set(SHOW_ALWAYS_ALLOWED, handle.get<Boolean>(SHOW_ALWAYS_ALLOWED) ?: false)
+ handle.set(CREATION_LOGGED_KEY, handle.get<Boolean>(CREATION_LOGGED_KEY) ?: false)
@Suppress("UNCHECKED_CAST")
- return PermissionAppsViewModel(state, app, groupName) as T
+ return PermissionAppsViewModel(handle, app, groupName) as T
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BackgroundGrantBehavior.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BackgroundGrantBehavior.kt
new file mode 100644
index 000000000..071692d65
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BackgroundGrantBehavior.kt
@@ -0,0 +1,207 @@
+/*
+ * 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.ui.model.grantPermissions
+
+import android.os.Build
+import android.permission.PermissionManager
+import android.util.Log
+import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
+import com.android.permissioncontroller.permission.ui.model.DenyButton
+import com.android.permissioncontroller.permission.ui.model.Prompt
+import com.android.permissioncontroller.permission.utils.PermissionMapping
+
+/**
+ * This behavior handles groups that respect the difference between foreground and background
+ * access. At present, this includes all one-time permissions, in addition to those with explicit
+ * background permissions.
+ *
+ * The behavior is split based on the target SDK when the app split into foreground and background
+ * (or android R, whichever is newer). If the app targets prior to the split, we show a dialog with
+ * a link to permission settings. Once the app targets after the split, we no longer allow the app
+ * to request background and foreground at the same time. If they try, we reject it. An app has to
+ * request foreground, get it granted, then request background only, we send the user to settings.
+ */
+object BackgroundGrantBehavior : GrantBehavior() {
+
+ private val splitPermissionSdkMap = buildMap {
+ for (splitPerm in
+ PermissionControllerApplication.get()
+ .getSystemService(PermissionManager::class.java)!!
+ .splitPermissions) {
+ put(splitPerm.splitPermission, splitPerm.targetSdk)
+ }
+ }
+
+ // Redundant "if" conditions are suppressed, because the conditions are clearer
+ @Suppress("KotlinConstantConditions")
+ override fun getPrompt(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ isSystemTriggeredPrompt: Boolean
+ ): Prompt {
+ val requestsBg = hasBgPerms(group, requestedPerms)
+ val requestsFg = requestedPerms.any { it !in group.backgroundPermNames }
+ val isOneTimeGroup = PermissionMapping.supportsOneTimeGrant(group.permGroupName)
+ val isFgGranted = group.foreground.isGrantedExcludingRWROrAllRWR
+ val isFgOneTime = group.foreground.isOneTime
+ val splitSdk = getSdkGroupWasSplitToBg(requestedPerms)
+ val isAppIsOlderThanSplitToBg = group.packageInfo.targetSdkVersion < splitSdk
+
+ if (!requestsBg && !isOneTimeGroup) {
+ return Prompt.FG_ONLY
+ } else if (!requestsBg) {
+ return Prompt.ONE_TIME_FG
+ }
+
+ if (requestsBg && !requestsFg && !isFgGranted) {
+ Log.w(
+ LOG_TAG,
+ "Cannot grant ${group.permGroupName} as the foreground permissions" +
+ " are not requested or already granted.")
+ return Prompt.NO_UI_REJECT_THIS_GROUP
+ }
+
+ return if (isAppIsOlderThanSplitToBg) {
+ getPromptForOlderApp(isOneTimeGroup, requestsFg, isFgGranted, isFgOneTime)
+ } else {
+ getPromptForNewerApp(group.permGroupName, splitSdk, requestsFg, isFgGranted)
+ }
+ }
+
+ /**
+ * Get the prompt for an app that targets before the sdk level where the permission group was
+ * split into foreground and background.
+ */
+ private fun getPromptForOlderApp(
+ isOneTimeGroup: Boolean,
+ requestsFg: Boolean,
+ isFgGranted: Boolean,
+ isFgOneTime: Boolean
+ ): Prompt {
+ if (requestsFg && !isFgGranted) {
+ if (isOneTimeGroup) {
+ return Prompt.SETTINGS_LINK_WITH_OT
+ }
+ return Prompt.SETTINGS_LINK_FOR_BG
+ }
+
+ if (isFgGranted) {
+ if (isFgOneTime) {
+ return Prompt.OT_UPGRADE_SETTINGS_LINK
+ }
+ return Prompt.UPGRADE_SETTINGS_LINK
+ }
+ return Prompt.NO_UI_REJECT_ALL_GROUPS
+ }
+
+ /**
+ * Get the prompt for an app that targets at least the sdk level where the permission group was
+ * split into foreground and background.
+ */
+ private fun getPromptForNewerApp(
+ groupName: String,
+ splitSdk: Int,
+ requestsFg: Boolean,
+ isFgGranted: Boolean
+ ): Prompt {
+ if (!requestsFg && isFgGranted) {
+ return Prompt.NO_UI_SETTINGS_REDIRECT
+ }
+
+ if (requestsFg) {
+ Log.e(
+ LOG_TAG,
+ "For SDK $splitSdk+ apps requesting $groupName, " +
+ "background permissions must be requested alone after foreground permissions " +
+ "are already granted")
+ return Prompt.NO_UI_REJECT_ALL_GROUPS
+ } else if (!isFgGranted) {
+ Log.e(
+ LOG_TAG,
+ "For SDK $splitSdk+ apps requesting, $groupName, " +
+ "background permissions must be requested after foreground permissions are " +
+ "already granted")
+ Prompt.NO_UI_REJECT_THIS_GROUP
+ }
+ return Prompt.NO_UI_REJECT_ALL_GROUPS
+ }
+
+ override fun getDenyButton(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ prompt: Prompt
+ ): DenyButton {
+ val basicDenyBehavior = BasicGrantBehavior.getDenyButton(group, requestedPerms, prompt)
+ if (prompt == Prompt.UPGRADE_SETTINGS_LINK || prompt == Prompt.OT_UPGRADE_SETTINGS_LINK) {
+ if (basicDenyBehavior == DenyButton.DENY) {
+ return if (prompt == Prompt.UPGRADE_SETTINGS_LINK) {
+ DenyButton.NO_UPGRADE
+ } else {
+ DenyButton.NO_UPGRADE_OT
+ }
+ }
+ return if (prompt == Prompt.UPGRADE_SETTINGS_LINK) {
+ DenyButton.NO_UPGRADE_AND_DONT_ASK_AGAIN
+ } else {
+ DenyButton.NO_UPGRADE_AND_DONT_ASK_AGAIN_OT
+ }
+ }
+ return basicDenyBehavior
+ }
+
+ override fun isGroupFullyGranted(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>
+ ): Boolean {
+ return (!hasBgPerms(group, requestedPerms) ||
+ group.background.isGrantedExcludingRWROrAllRWR) &&
+ group.foreground.isGrantedExcludingRWROrAllRWR
+ }
+
+ override fun isForegroundFullyGranted(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>
+ ): Boolean {
+ return group.foreground.isGrantedExcludingRWROrAllRWR
+ }
+
+ override fun isPermissionFixed(group: LightAppPermGroup, perm: String): Boolean {
+ if (perm in group.backgroundPermNames) {
+ return group.background.isUserFixed
+ }
+ return group.foreground.isUserFixed
+ }
+
+ private fun hasBgPerms(group: LightAppPermGroup, requestedPerms: Set<String>): Boolean {
+ return requestedPerms.any { it in group.backgroundPermNames }
+ }
+
+ private fun getSdkGroupWasSplitToBg(requestedPerms: Set<String>): Int {
+ val splitSdks = requestedPerms.mapNotNull { perm -> splitPermissionSdkMap[perm] }
+
+ if (splitSdks.isEmpty()) {
+ // If there was no split found, assume the split happened in R. This applies to
+ // Mic and Camera, which technically split in S, but were treated as foreground-only
+ // in R
+ return Build.VERSION_CODES.R
+ }
+ // The background permission request UI changed in R, so if a split happened before R,
+ // treat it as if it happened in R
+ return maxOf(splitSdks.min(), Build.VERSION_CODES.R)
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BasicGrantBehavior.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BasicGrantBehavior.kt
new file mode 100644
index 000000000..b97212f94
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/BasicGrantBehavior.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.ui.model.grantPermissions
+
+import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
+import com.android.permissioncontroller.permission.ui.model.DenyButton
+import com.android.permissioncontroller.permission.ui.model.Prompt
+
+/**
+ * A basic group. Shows "allow" and "deny", does not allow fixed permissions to be re-requested
+ */
+object BasicGrantBehavior : GrantBehavior() {
+
+ override fun getPrompt(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ isSystemTriggeredPrompt: Boolean
+ ): Prompt {
+ return Prompt.BASIC
+ }
+
+ override fun getDenyButton(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ prompt: Prompt
+ ): DenyButton {
+ if (prompt in noDenyButtonPrompts) {
+ return DenyButton.NONE
+ }
+ if (group.isUserSet) {
+ return DenyButton.DENY_DONT_ASK_AGAIN
+ }
+ return DenyButton.DENY
+ }
+
+ // A list of prompts without any deny behavior
+ private val noDenyButtonPrompts = listOf(Prompt.NO_UI_SETTINGS_REDIRECT,
+ Prompt.NO_UI_PHOTO_PICKER_REDIRECT, Prompt.NO_UI_HEALTH_REDIRECT,
+ Prompt.NO_UI_REJECT_THIS_GROUP, Prompt.NO_UI_REJECT_ALL_GROUPS,
+ Prompt.NO_UI_FILTER_THIS_GROUP)
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/GrantBehavior.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/GrantBehavior.kt
new file mode 100644
index 000000000..b92281272
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/GrantBehavior.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.ui.model.grantPermissions
+
+import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
+import com.android.permissioncontroller.permission.ui.model.DenyButton
+import com.android.permissioncontroller.permission.ui.model.Prompt
+
+/**
+ * The base behavior all grant behavior objects inherit from.
+ */
+abstract class GrantBehavior {
+ val LOG_TAG = "GrantPermissionsViewModel"
+
+ /**
+ * Get the prompt type for the given set of requested permissions
+ * @param group The LightAppPermGroup representing the state of the app and its permissions
+ * @param requestedPerms The permissions requested by the app, after filtering
+ * @param isSystemTriggeredPrompt Whether the prompt was triggered by the system, instead of
+ * by the app.
+ */
+ abstract fun getPrompt(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ isSystemTriggeredPrompt: Boolean = false
+ ): Prompt
+
+ /**
+ * Get the deny button type for the given set of requested permissions. This is separate from
+ * the prompt, because the same prompt can have multiple deny behaviors, based on if the user
+ * has seen it before.
+ * @param group The LightAppPermGroup representing the state of the app and its permissions
+ * @param requestedPerms The permissions requested by the app, after filtering
+ * @param prompt The prompt determined by the behavior.
+ */
+ abstract fun getDenyButton(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ prompt: Prompt
+ ): DenyButton
+
+ /**
+ * Whether the group is considered "fully granted". If it is, any remaining permissions in the
+ * group not already granted will be granted.
+ */
+ open fun isGroupFullyGranted(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>
+ ): Boolean {
+ return group.foreground.isGrantedExcludingRWROrAllRWR
+ }
+
+ /**
+ * Whether or not all foreground permissions in the group are granted. Only different in groups
+ * that respect background and foreground permissions.
+ */
+ open fun isForegroundFullyGranted(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>
+ ): Boolean {
+ return isGroupFullyGranted(group, requestedPerms)
+ }
+
+ /**
+ * Whether or not the permission should be considered as "user fixed". If it is, it is removed
+ * from the request.
+ */
+ open fun isPermissionFixed(group: LightAppPermGroup, perm: String): Boolean {
+ return group.isUserFixed
+ }
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/HealthGrantBehavior.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/HealthGrantBehavior.kt
new file mode 100644
index 000000000..7bc520c22
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/HealthGrantBehavior.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.ui.model.grantPermissions
+
+import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
+import com.android.permissioncontroller.permission.ui.model.DenyButton
+import com.android.permissioncontroller.permission.ui.model.Prompt
+
+/**
+ * Health permissions always redirect to the health connect UI.
+ */
+object HealthGrantBehavior : GrantBehavior() {
+ override fun getPrompt(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ isSystemTriggeredPrompt: Boolean
+ ): Prompt {
+ return Prompt.NO_UI_HEALTH_REDIRECT
+ }
+
+ override fun getDenyButton(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ prompt: Prompt
+ ): DenyButton {
+ return DenyButton.NONE
+ }
+
+ override fun isGroupFullyGranted(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>
+ ): Boolean {
+ return requestedPerms.all { group.permissions[it]?.isGrantedIncludingAppOp != false }
+ }
+
+ override fun isPermissionFixed(group: LightAppPermGroup, perm: String): Boolean {
+ return group.permissions[perm]?.isUserFixed == true
+ }
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/LocationGrantBehavior.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/LocationGrantBehavior.kt
new file mode 100644
index 000000000..c541c4f4d
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/LocationGrantBehavior.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.ui.model.grantPermissions
+
+import android.Manifest.permission.ACCESS_COARSE_LOCATION
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.os.Build
+import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
+import com.android.permissioncontroller.permission.ui.model.DenyButton
+import com.android.permissioncontroller.permission.ui.model.Prompt
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+
+/**
+ * The Location Grant behavior is the same as the Background group behavior, up until S. After S,
+ * the fine and coarse location permissions were allowed to be granted separately, and this created
+ * a new set of grant dialogs.
+ */
+object LocationGrantBehavior : GrantBehavior() {
+ override fun getPrompt(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ isSystemTriggeredPrompt: Boolean
+ ): Prompt {
+ val backgroundPrompt = BackgroundGrantBehavior.getPrompt(group, requestedPerms)
+ val requestsBackground = requestedPerms.any { it in group.backgroundPermNames }
+ val coarseGranted =
+ group.permissions[ACCESS_COARSE_LOCATION]?.isGrantedIncludingAppOp == true
+ return if (!supportsLocationAccuracy(group) || requestsBackground) {
+ backgroundPrompt
+ } else if (requestedPerms.contains(ACCESS_FINE_LOCATION)) {
+ if (coarseGranted) {
+ Prompt.LOCATION_FINE_UPGRADE
+ } else {
+ Prompt.LOCATION_TWO_BUTTON
+ }
+ } else if (requestedPerms.contains(ACCESS_COARSE_LOCATION) && !coarseGranted) {
+ Prompt.LOCATION_COARSE_ONLY
+ } else {
+ backgroundPrompt
+ }
+ }
+
+ override fun getDenyButton(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ prompt: Prompt
+ ): DenyButton {
+ return BackgroundGrantBehavior.getDenyButton(group, requestedPerms, prompt)
+ }
+
+ override fun isGroupFullyGranted(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>
+ ): Boolean {
+ val requestsBackground = requestedPerms.any { it in group.backgroundPermNames }
+ if (!supportsLocationAccuracy(group) || requestsBackground) {
+ return BackgroundGrantBehavior.isGroupFullyGranted(group, requestedPerms)
+ }
+ return isForegroundFullyGranted(group, requestedPerms)
+ }
+
+ override fun isForegroundFullyGranted(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>
+ ): Boolean {
+ if (!supportsLocationAccuracy(group)) {
+ return BackgroundGrantBehavior.isForegroundFullyGranted(group, requestedPerms)
+ }
+
+ if (requestedPerms.contains(ACCESS_FINE_LOCATION)) {
+ return group.permissions[ACCESS_FINE_LOCATION]?.isGrantedIncludingAppOp == true
+ }
+
+ return group.foreground.isGrantedExcludingRWROrAllRWR
+ }
+
+ override fun isPermissionFixed(group: LightAppPermGroup, perm: String): Boolean {
+ if (!supportsLocationAccuracy(group) || perm != ACCESS_COARSE_LOCATION) {
+ return BackgroundGrantBehavior.isPermissionFixed(group, perm)
+ }
+
+ // If the location group is user fixed but ACCESS_COARSE_LOCATION is not, then
+ // ACCESS_FINE_LOCATION must be user fixed. In this case ACCESS_COARSE_LOCATION
+ // is still grantable.
+ return group.foreground.isUserFixed && group.permissions[perm]?.isUserFixed == true
+ }
+
+ private fun supportsLocationAccuracy(group: LightAppPermGroup): Boolean {
+ return KotlinUtils.isLocationAccuracyEnabled() &&
+ group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.S
+ }
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/NotificationGrantBehavior.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/NotificationGrantBehavior.kt
new file mode 100644
index 000000000..0245f7b8e
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/NotificationGrantBehavior.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.ui.model.grantPermissions
+
+import android.os.Build.VERSION_CODES.TIRAMISU
+import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
+import com.android.permissioncontroller.permission.ui.model.DenyButton
+import com.android.permissioncontroller.permission.ui.model.Prompt
+
+/**
+ * The Notification permission behavior is similar to basic, except:
+ *
+ * It can be triggered by the system. If it's system triggered we only show it until the user makes
+ * one decision. We don't make the user show it twice.
+ *
+ * It can't be explicitly requested from apps that don't yet target android T. If they try, we
+ * remove it entirely from the request, do not return a result, and take no action on it.
+ *
+ */
+object NotificationGrantBehavior : GrantBehavior() {
+ override fun getPrompt(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ isSystemTriggeredPrompt: Boolean
+ ): Prompt {
+ val isPreT = group.packageInfo.targetSdkVersion < TIRAMISU
+ if (!isSystemTriggeredPrompt && isPreT) {
+ // Apps targeting below T cannot manually request Notifications
+ return Prompt.NO_UI_FILTER_THIS_GROUP
+ } else if (isPreT && group.isUserSet) {
+ // If the user has seen the system-triggered prompt once, don't show it again
+ return Prompt.NO_UI_FILTER_THIS_GROUP
+ }
+ return BasicGrantBehavior.getPrompt(group, requestedPerms, isSystemTriggeredPrompt)
+ }
+
+ override fun getDenyButton(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ prompt: Prompt
+ ): DenyButton {
+ return BasicGrantBehavior.getDenyButton(group, requestedPerms, prompt)
+ }
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/StorageGrantBehavior.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/StorageGrantBehavior.kt
new file mode 100644
index 000000000..af6ac22cd
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/grantPermissions/StorageGrantBehavior.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.ui.model.grantPermissions
+
+import android.Manifest.permission.ACCESS_MEDIA_LOCATION
+import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+import android.Manifest.permission_group.READ_MEDIA_VISUAL
+import android.Manifest.permission_group.STORAGE
+import android.os.Build
+import com.android.modules.utils.build.SdkLevel
+import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
+import com.android.permissioncontroller.permission.ui.model.DenyButton
+import com.android.permissioncontroller.permission.ui.model.Prompt
+import com.android.permissioncontroller.permission.utils.KotlinUtils.isPhotoPickerPromptEnabled
+import com.android.permissioncontroller.permission.utils.KotlinUtils.isPhotoPickerPromptSupported
+
+/**
+ * Storage split from one group (STORAGE) into two (READ_MEDIA_VISUAL and READ_MEDIA_AURAL) in T.
+ * There are special dialogs to deal with a pre-T app requesting STORAGE on a post-T device. In U,
+ * the READ_MEDIA_VISUAL group was augmented with the option to select photos, which led to several
+ * new dialogs to handle that.
+ */
+object StorageGrantBehavior : GrantBehavior() {
+ override fun getPrompt(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ isSystemTriggeredPrompt: Boolean
+ ): Prompt {
+ val appSupportsSplitStoragePermissions = appSupportsSplitStoragePermissions(group)
+ if (!SdkLevel.isAtLeastT()) {
+ return Prompt.BASIC
+ } else if (appSupportsSplitStoragePermissions && group.permGroupName == STORAGE) {
+ return Prompt.NO_UI_REJECT_THIS_GROUP
+ }
+
+ if (appSupportsSplitStoragePermissions && !shouldShowPhotoPickerPrompt(group)) {
+ return Prompt.BASIC
+ }
+
+ if (!appSupportsSplitStoragePermissions) {
+ if (group.packageInfo.targetSdkVersion < Build.VERSION_CODES.Q) {
+ return Prompt.STORAGE_SUPERGROUP_PRE_Q
+ } else {
+ return Prompt.STORAGE_SUPERGROUP_Q_TO_S
+ }
+ }
+
+ // Else, app supports the new photo picker dialog
+ if (requestedPerms.all { it in getPartialGrantPermissions(group) }) {
+ // Do not allow apps to request only READ_MEDIA_VISUAL_USER_SELECTED
+ return Prompt.NO_UI_REJECT_THIS_GROUP
+ }
+
+ val userSelectedPerm = group.permissions[READ_MEDIA_VISUAL_USER_SELECTED]
+ if (userSelectedPerm?.isUserFixed == true && userSelectedPerm.isGrantedIncludingAppOp) {
+ return Prompt.NO_UI_PHOTO_PICKER_REDIRECT
+ }
+
+ if (userSelectedPerm?.isGrantedIncludingAppOp == true) {
+ return Prompt.SELECT_MORE_PHOTOS
+ } else {
+ return Prompt.SELECT_PHOTOS
+ }
+ }
+
+ override fun getDenyButton(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>,
+ prompt: Prompt
+ ): DenyButton {
+ if (prompt == Prompt.SELECT_MORE_PHOTOS) {
+ return DenyButton.DONT_SELECT_MORE
+ }
+ return BasicGrantBehavior.getDenyButton(group, requestedPerms, prompt)
+ }
+
+ override fun isGroupFullyGranted(
+ group: LightAppPermGroup,
+ requestedPerms: Set<String>
+ ): Boolean {
+ if (!isPhotoPickerPromptSupported() ||
+ group.permGroupName != READ_MEDIA_VISUAL) {
+ return super.isGroupFullyGranted(group, requestedPerms)
+ }
+
+ return group.permissions.values.any {
+ it.name !in getPartialGrantPermissions(group) && it.isGrantedIncludingAppOp
+ }
+ }
+
+ override fun isPermissionFixed(group: LightAppPermGroup, perm: String): Boolean {
+ val userSelectedPerm = group.permissions[READ_MEDIA_VISUAL_USER_SELECTED]
+ if (userSelectedPerm != null && userSelectedPerm.isGrantedIncludingAppOp &&
+ userSelectedPerm.isUserFixed) {
+ // If the user selected permission is fixed and granted, we immediately show the
+ // photo picker, rather than filtering
+ return false
+ }
+ return super.isPermissionFixed(group, perm)
+ }
+
+ private fun appSupportsSplitStoragePermissions(group: LightAppPermGroup) =
+ SdkLevel.isAtLeastT() && group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU
+
+ private fun shouldShowPhotoPickerPrompt(group: LightAppPermGroup) =
+ isPhotoPickerPromptEnabled() && group.permGroupName == READ_MEDIA_VISUAL &&
+ appSupportsSplitStoragePermissions(group)
+
+ private fun appSupportsPhotoPicker(group: LightAppPermGroup) =
+ shouldShowPhotoPickerPrompt(group) &&
+ group.permissions[READ_MEDIA_VISUAL_USER_SELECTED]?.isImplicit == false
+
+ private fun getPartialGrantPermissions(group: LightAppPermGroup): Set<String> {
+ return if (appSupportsPhotoPicker(group)) {
+ setOf(READ_MEDIA_VISUAL_USER_SELECTED, ACCESS_MEDIA_LOCATION)
+ } else {
+ setOf(READ_MEDIA_VISUAL_USER_SELECTED)
+ }
+ }
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/ReviewOngoingUsageViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/ReviewOngoingUsageViewModel.kt
index 9c9a55523..d6efd38a6 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/ReviewOngoingUsageViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/ReviewOngoingUsageViewModel.kt
@@ -551,10 +551,14 @@ class ReviewOngoingUsageViewModelFactory(
owner: SavedStateRegistryOwner,
defaultArgs: Bundle
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
- override fun <T : ViewModel> create(p0: String, p1: Class<T>, state: SavedStateHandle): T {
- state.set(FIRST_OPENED_KEY, state.get<Long>(FIRST_OPENED_KEY)
+ override fun <T : ViewModel> create(
+ key: String,
+ modelClass: Class<T>,
+ handle: SavedStateHandle
+ ): T {
+ handle.set(FIRST_OPENED_KEY, handle.get<Long>(FIRST_OPENED_KEY)
?: System.currentTimeMillis())
@Suppress("UNCHECKED_CAST")
- return ReviewOngoingUsageViewModel(state, extraDurationMillis) as T
+ return ReviewOngoingUsageViewModel(handle, extraDurationMillis) as T
}
}
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/ui/wear/WearAppPermissionFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionFragment.kt
new file mode 100644
index 000000000..76ba89559
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionFragment.kt
@@ -0,0 +1,339 @@
+/*
+ * 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.ui.wear
+
+import android.Manifest
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.StringRes
+import androidx.compose.ui.platform.ComposeView
+import androidx.core.os.BundleCompat
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import com.android.permissioncontroller.Constants
+import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ASK_EVERY_TIME
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY_FOREGROUND
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__GRANT_FINE_LOCATION
+import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__REVOKE_FINE_LOCATION
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME
+import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel
+import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType
+import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ChangeRequest
+import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ConfirmDialogShowingFragment
+import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModelFactory
+import com.android.permissioncontroller.permission.ui.v33.AdvancedConfirmDialogArgs
+import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionConfirmDialogViewModel
+import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionConfirmDialogViewModelFactory
+import com.android.permissioncontroller.permission.ui.wear.model.ConfirmDialogArgs
+import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel
+import com.android.settingslib.RestrictedLockUtils
+
+/**
+ * Show and manage a single permission group for an app.
+ *
+ * <p>Allows the user to control whether the app is granted the permission
+ * <p>
+ * Based on AppPermissionFragment in handheld code.
+ */
+class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
+
+ private lateinit var confirmDialogViewModel: AppPermissionConfirmDialogViewModel
+
+ /**
+ * Create a bundle with the arguments needed by this fragment
+ *
+ * @param packageName The name of the package
+ * @param permName The name of the permission whose group this fragment is for (optional)
+ * @param groupName The name of the permission group (required if permName not specified)
+ * @param userHandle The user of the app permission group
+ * @param caller The name of the fragment we called from
+ * @param sessionId The current session ID
+ * @param grantCategory The grant status of this app permission group. Used to initially set the
+ * button state
+ * @return A bundle with all of the args placed
+ */
+ fun createArgs(
+ packageName: String?,
+ permName: String?,
+ groupName: String?,
+ userHandle: UserHandle?,
+ caller: String?,
+ sessionId: Long,
+ grantCategory: String?
+ ): Bundle {
+ val arguments = Bundle()
+ arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName)
+ if (groupName == null) {
+ arguments.putString(Intent.EXTRA_PERMISSION_NAME, permName)
+ } else {
+ arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName)
+ }
+ arguments.putParcelable(Intent.EXTRA_USER, userHandle)
+ arguments.putString(EXTRA_CALLER_NAME, caller)
+ arguments.putLong(EXTRA_SESSION_ID, sessionId)
+ arguments.putString(GRANT_CATEGORY, grantCategory)
+ return arguments
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val activity = requireActivity()
+ val packageName = arguments?.getString(Intent.EXTRA_PACKAGE_NAME)
+ ?: throw RuntimeException("Package name must not be null.")
+ val permGroupName = arguments?.getString(Intent.EXTRA_PERMISSION_GROUP_NAME)
+ ?: arguments?.getString(Intent.EXTRA_PERMISSION_NAME)
+ ?: throw RuntimeException("Permission name must not be null.")
+
+ val isStorageGroup = permGroupName == Manifest.permission_group.STORAGE
+
+ val user = arguments?.let{
+ BundleCompat.getParcelable(it, Intent.EXTRA_USER, UserHandle::class.java)
+ } ?: UserHandle.SYSTEM
+ val permGroupLabel = getPermGroupLabel(
+ activity,
+ permGroupName
+ ).toString()
+
+ val sessionId = arguments?.getLong(EXTRA_SESSION_ID) ?: Constants.INVALID_SESSION_ID
+
+ val factory = AppPermissionViewModelFactory(
+ activity.getApplication(),
+ packageName,
+ permGroupName,
+ user,
+ sessionId
+ )
+ val viewModel = ViewModelProvider(this, factory).get(
+ AppPermissionViewModel::class.java
+ )
+ confirmDialogViewModel =
+ ViewModelProvider(this, AppPermissionConfirmDialogViewModelFactory()).get(
+ AppPermissionConfirmDialogViewModel::class.java
+ )
+
+ val onLocationSwitchChanged: (Boolean) -> Unit = { checked ->
+ run {
+ val changeRequest = if (checked) {
+ ChangeRequest.GRANT_FINE_LOCATION
+ } else {
+ ChangeRequest.REVOKE_FINE_LOCATION
+ }
+ val buttonClicked = if (checked) {
+ APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__GRANT_FINE_LOCATION
+ } else {
+ APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__REVOKE_FINE_LOCATION
+ }
+ viewModel.requestChange(false, this, this, changeRequest, buttonClicked)
+ }
+ }
+ val onGrantedStateChanged: (ButtonType, Boolean) -> Unit = { buttonType, checked ->
+ run {
+ if (!checked) {
+ return@run
+ }
+ val param = getGrantedStateChangeParam(buttonType)
+ if (!isStorageGroup || !param.requiresCustomStorageBehavior) {
+ viewModel.requestChange(
+ param.setOneTime,
+ this,
+ this,
+ param.request,
+ param.buttonClickAction
+ )
+ } else {
+ showConfirmDialog(
+ ChangeRequest.GRANT_All_FILE_ACCESS,
+ R.string.special_file_access_dialog,
+ -1,
+ false
+ )
+ }
+ setResult(param.result, permGroupName)
+ }
+ }
+ val onFooterClicked: (RestrictedLockUtils.EnforcedAdmin) -> Unit = { admin ->
+ run {
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(requireContext(), admin)
+ }
+ }
+ val onConfirmDialogOkButtonClick: (ConfirmDialogArgs) -> Unit =
+ { args ->
+ run {
+ if (args.changeRequest == ChangeRequest.GRANT_All_FILE_ACCESS) {
+ viewModel.setAllFilesAccess(true)
+ viewModel.requestChange(
+ false,
+ this,
+ this,
+ ChangeRequest.GRANT_BOTH,
+ APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW
+ )
+ } else {
+ viewModel.onDenyAnyWay(args.changeRequest, args.buttonPressed, args.oneTime)
+ }
+ confirmDialogViewModel.showConfirmDialogLiveData.value = false
+ }
+ }
+ val onConfirmDialogCancelButtonClick: () -> Unit = {
+ confirmDialogViewModel.showConfirmDialogLiveData.value = false
+ }
+ val onAdvancedConfirmDialogOkButtonClick: (AdvancedConfirmDialogArgs) -> Unit =
+ { args ->
+ run {
+ viewModel.requestChange(
+ args.setOneTime!!,
+ this,
+ this,
+ args.changeRequest!!,
+ args.buttonClicked!!
+ )
+ confirmDialogViewModel.showAdvancedConfirmDialogLiveData.value = false
+ }
+ }
+ val onAdvancedConfirmDialogCancelButtonClick: () -> Unit = {
+ confirmDialogViewModel.showAdvancedConfirmDialogLiveData.value = false
+ }
+
+ return ComposeView(activity).apply {
+ setContent {
+ WearAppPermissionScreen(
+ permGroupLabel,
+ viewModel,
+ confirmDialogViewModel,
+ onLocationSwitchChanged,
+ onGrantedStateChanged,
+ onFooterClicked,
+ onConfirmDialogOkButtonClick,
+ onConfirmDialogCancelButtonClick,
+ onAdvancedConfirmDialogOkButtonClick,
+ onAdvancedConfirmDialogCancelButtonClick
+ )
+ }
+ }
+ }
+
+ override fun showConfirmDialog(
+ changeRequest: ChangeRequest,
+ @StringRes messageId: Int,
+ buttonPressed: Int,
+ oneTime: Boolean
+ ) {
+ confirmDialogViewModel.confirmDialogArgs = ConfirmDialogArgs(
+ messageId = messageId,
+ changeRequest = changeRequest,
+ buttonPressed = buttonPressed,
+ oneTime = oneTime
+ )
+ confirmDialogViewModel.showConfirmDialogLiveData.value = true
+ }
+
+ override fun showAdvancedConfirmDialog(args: AdvancedConfirmDialogArgs) {
+ confirmDialogViewModel.advancedConfirmDialogArgs = args
+ confirmDialogViewModel.showAdvancedConfirmDialogLiveData.value = true
+ }
+
+ private fun setResult(@GrantPermissionsViewHandler.Result result: Int, permGroupName: String) {
+ val intent: Intent = Intent()
+ .putExtra(ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED, permGroupName)
+ .putExtra(ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT, result)
+ requireActivity().setResult(Activity.RESULT_OK, intent)
+ }
+
+ fun getGrantedStateChangeParam(buttonType: ButtonType) = when (buttonType) {
+ ButtonType.ALLOW -> GrantedStateChangeParam(
+ false,
+ ChangeRequest.GRANT_FOREGROUND,
+ APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW,
+ GRANTED_ALWAYS,
+ false
+ )
+
+ ButtonType.ALLOW_ALWAYS -> GrantedStateChangeParam(
+ false,
+ ChangeRequest.GRANT_BOTH,
+ APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS,
+ GRANTED_ALWAYS,
+ true
+ )
+
+ ButtonType.ALLOW_FOREGROUND -> GrantedStateChangeParam(
+ false,
+ ChangeRequest.GRANT_FOREGROUND_ONLY,
+ APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND,
+ GRANTED_FOREGROUND_ONLY,
+ true
+ )
+
+ ButtonType.ASK -> GrantedStateChangeParam(
+ true,
+ ChangeRequest.REVOKE_BOTH,
+ APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ASK_EVERY_TIME,
+ DENIED,
+ false
+ )
+
+ ButtonType.DENY -> GrantedStateChangeParam(
+ false,
+ ChangeRequest.REVOKE_BOTH,
+ APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY,
+ DENIED_DO_NOT_ASK_AGAIN,
+ false
+ )
+
+ ButtonType.DENY_FOREGROUND -> GrantedStateChangeParam(
+ false,
+ ChangeRequest.REVOKE_FOREGROUND,
+ APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY_FOREGROUND,
+ DENIED_DO_NOT_ASK_AGAIN,
+ false
+ )
+
+ else -> throw RuntimeException("Wrong button type: $buttonType")
+ }
+
+ companion object {
+ private const val GRANT_CATEGORY = "grant_category"
+ }
+}
+
+data class GrantedStateChangeParam(
+ val setOneTime: Boolean,
+ val request: ChangeRequest,
+ val buttonClickAction: Int,
+ val result: Int,
+ val requiresCustomStorageBehavior: Boolean
+)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt
new file mode 100644
index 000000000..30fec772a
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt
@@ -0,0 +1,221 @@
+/*
+ * 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.ui.wear
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.res.stringResource
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel
+import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonState
+import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType
+import com.android.permissioncontroller.permission.ui.v33.AdvancedConfirmDialogArgs
+import com.android.permissioncontroller.permission.ui.wear.elements.AlertDialog
+import com.android.permissioncontroller.permission.ui.wear.elements.ListFooter
+import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
+import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChip
+import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl
+import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionConfirmDialogViewModel
+import com.android.permissioncontroller.permission.ui.wear.model.ConfirmDialogArgs
+import com.android.settingslib.RestrictedLockUtils
+
+@Composable
+fun WearAppPermissionScreen(
+ title: String,
+ viewModel: AppPermissionViewModel,
+ confirmDialogViewModel: AppPermissionConfirmDialogViewModel,
+ onLocationSwitchChanged: (Boolean) -> Unit,
+ onGrantedStateChanged: (ButtonType, Boolean) -> Unit,
+ onFooterClicked: (RestrictedLockUtils.EnforcedAdmin) -> Unit,
+ onConfirmDialogOkButtonClick: (ConfirmDialogArgs) -> Unit,
+ onConfirmDialogCancelButtonClick: () -> Unit,
+ onAdvancedConfirmDialogOkButtonClick: (AdvancedConfirmDialogArgs) -> Unit,
+ onAdvancedConfirmDialogCancelButtonClick: () -> Unit
+) {
+ val buttonState = viewModel.buttonStateLiveData.observeAsState(emptyMap())
+ val detailResIds = viewModel.detailResIdLiveData.observeAsState(null)
+ val admin = viewModel.showAdminSupportLiveData.observeAsState(null)
+ var isLoading by remember { mutableStateOf(true) }
+ val showConfirmDialog = confirmDialogViewModel.showConfirmDialogLiveData.observeAsState(false)
+ val showAdvancedConfirmDialog =
+ confirmDialogViewModel.showAdvancedConfirmDialogLiveData.observeAsState(false)
+
+ Box {
+ WearAppPermissionContent(
+ title,
+ buttonState.value,
+ detailResIds.value,
+ admin.value,
+ isLoading,
+ onLocationSwitchChanged,
+ onGrantedStateChanged,
+ onFooterClicked,
+ )
+ ConfirmDialog(
+ showDialog = showConfirmDialog.value,
+ args = confirmDialogViewModel.confirmDialogArgs,
+ onOkButtonClick = onConfirmDialogOkButtonClick,
+ onCancelButtonClick = onConfirmDialogCancelButtonClick
+ )
+ AdvancedConfirmDialog(
+ showDialog = showAdvancedConfirmDialog.value,
+ args = confirmDialogViewModel.advancedConfirmDialogArgs,
+ onOkButtonClick = onAdvancedConfirmDialogOkButtonClick,
+ onCancelButtonClick = onAdvancedConfirmDialogCancelButtonClick
+ )
+ }
+ if (isLoading && buttonState.value.isNotEmpty()) {
+ isLoading = false
+ }
+}
+
+@Composable
+internal fun WearAppPermissionContent(
+ title: String,
+ buttonState: Map<ButtonType, ButtonState>,
+ detailResIds: Pair<Int, Int?>?,
+ admin: RestrictedLockUtils.EnforcedAdmin?,
+ isLoading: Boolean,
+ onLocationSwitchChanged: (Boolean) -> Unit,
+ onGrantedStateChanged: (ButtonType, Boolean) -> Unit,
+ onFooterClicked: (RestrictedLockUtils.EnforcedAdmin) -> Unit
+) {
+ ScrollableScreen(
+ title = title,
+ isLoading = isLoading
+ ) {
+ buttonState[ButtonType.LOCATION_ACCURACY]?.let {
+ if (it.isShown) {
+ item {
+ ToggleChip(
+ checked = it.isChecked,
+ enabled = it.isEnabled,
+ label = stringResource(R.string.app_permission_location_accuracy),
+ toggleControl = ToggleChipToggleControl.Switch,
+ onCheckedChanged = onLocationSwitchChanged
+ )
+ }
+ }
+ }
+ for (buttonType in buttonTypeOrder) {
+ buttonState[buttonType]?.let {
+ if (it.isShown) {
+ item {
+ ToggleChip(
+ checked = it.isChecked,
+ enabled = it.isEnabled,
+ label = labelsByButton(buttonType),
+ toggleControl = ToggleChipToggleControl.Radio,
+ onCheckedChanged = { checked ->
+ onGrantedStateChanged(buttonType, checked)
+ }
+ )
+ }
+ }
+ }
+ }
+ detailResIds?.let {
+ item {
+ ListFooter(
+ description = stringResource(detailResIds.first),
+ iconRes = R.drawable.ic_info,
+ onClick = if (admin != null) {
+ { onFooterClicked(admin) }
+ } else {
+ null
+ }
+ )
+ }
+ }
+ }
+}
+
+internal val buttonTypeOrder = listOf(
+ ButtonType.ALLOW,
+ ButtonType.ALLOW_ALWAYS,
+ ButtonType.ALLOW_FOREGROUND,
+ ButtonType.ASK_ONCE,
+ ButtonType.ASK,
+ ButtonType.DENY,
+ ButtonType.DENY_FOREGROUND
+)
+
+@Composable
+internal fun labelsByButton(buttonType: ButtonType) = when (buttonType) {
+ ButtonType.ALLOW -> stringResource(R.string.app_permission_button_allow)
+ ButtonType.ALLOW_ALWAYS -> stringResource(R.string.app_permission_button_allow_always)
+ ButtonType.ALLOW_FOREGROUND -> stringResource(R.string.app_permission_button_allow_foreground)
+ ButtonType.ASK_ONCE -> stringResource(R.string.app_permission_button_ask)
+ ButtonType.ASK -> stringResource(R.string.app_permission_button_ask)
+ ButtonType.DENY -> stringResource(R.string.app_permission_button_deny)
+ ButtonType.DENY_FOREGROUND -> stringResource(R.string.app_permission_button_deny)
+ else -> ""
+}
+
+@Composable
+internal fun ConfirmDialog(
+ showDialog: Boolean,
+ args: ConfirmDialogArgs?,
+ onOkButtonClick: (ConfirmDialogArgs) -> Unit,
+ onCancelButtonClick: () -> Unit
+) {
+ args?.let {
+ AlertDialog(
+ showDialog = showDialog,
+ message = stringResource(it.messageId),
+ onOKButtonClick = {
+ onOkButtonClick(it)
+ },
+ onCancelButtonClick = onCancelButtonClick,
+ scalingLazyListState = rememberScalingLazyListState()
+ )
+ }
+}
+
+@Composable
+internal fun AdvancedConfirmDialog(
+ showDialog: Boolean,
+ args: AdvancedConfirmDialogArgs?,
+ onOkButtonClick: (AdvancedConfirmDialogArgs) -> Unit,
+ onCancelButtonClick: () -> Unit
+) {
+ args?.let {
+ AlertDialog(
+ showDialog = showDialog,
+ title = if (it.titleId != 0) {
+ stringResource(it.titleId)
+ } else {
+ ""
+ },
+ iconRes = it.iconId,
+ message = stringResource(it.messageId),
+ okButtonContentDescription = stringResource(it.positiveButtonTextId),
+ cancelButtonContentDescription = stringResource(it.negativeButtonTextId),
+ onOKButtonClick = {
+ onOkButtonClick(it)
+ },
+ onCancelButtonClick = onCancelButtonClick,
+ scalingLazyListState = rememberScalingLazyListState()
+ )
+ }
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsFragment.kt
new file mode 100644
index 000000000..7b14fbe0f
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsFragment.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.ui.wear
+
+import android.Manifest
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import com.android.permissioncontroller.Constants
+import com.android.permissioncontroller.permission.ui.handheld.AppPermissionFragment
+import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel
+import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModelFactory
+
+/**
+ * This is a condensed version of
+ * [com.android.permissioncontroller.permission.ui.handheld.PermissionAppsFragment],
+ * tailored for Wear.
+ *
+ * Show and manage apps which request a single permission group.
+ *
+ * <p>Shows a list of apps which request at least on permission of this group.
+ */
+class WearPermissionAppsFragment : Fragment() {
+ private val LOG_TAG = "PermissionAppsFragment"
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val permGroupName = arguments?.getString(Intent.EXTRA_PERMISSION_GROUP_NAME)
+ ?: arguments?.getString(Intent.EXTRA_PERMISSION_NAME)
+ ?: throw RuntimeException("Permission group name must not be null.")
+ val sessionId: Long =
+ arguments?.getLong(Constants.EXTRA_SESSION_ID) ?: Constants.INVALID_SESSION_ID
+ val isStorage = permGroupName == Manifest.permission_group.STORAGE
+
+ val activity = requireActivity()
+ val factory = PermissionAppsViewModelFactory(
+ activity.getApplication(),
+ permGroupName,
+ this,
+ Bundle()
+ )
+ val viewModel =
+ ViewModelProvider(activity, factory).get(PermissionAppsViewModel::class.java)
+
+ val onAppClick: (String, UserHandle, String) -> Unit = {
+ packageName, user, category ->
+ run {
+ viewModel.navigateToAppPermission(
+ this,
+ packageName,
+ user,
+ AppPermissionFragment.createArgs(
+ packageName,
+ null,
+ permGroupName,
+ user,
+ this::class.java.name,
+ sessionId,
+ category
+ )
+ )
+ }
+ }
+
+ val onShowSystemClick: (Boolean) -> Unit = { showSystem ->
+ run {
+ viewModel.updateShowSystem(showSystem)
+ }
+ }
+
+ val logPermissionAppsFragmentCreated:
+ (String, UserHandle, Long, Boolean, Boolean, Boolean) -> Unit =
+ { packageName, user, viewId, isAllowed, isAllowedForeground, isDenied ->
+ run {
+ viewModel.logPermissionAppsFragmentCreated(
+ packageName, user, viewId, isAllowed,
+ isAllowedForeground, isDenied, sessionId, activity.getApplication(),
+ permGroupName, LOG_TAG
+ )
+ }
+ }
+
+ return ComposeView(requireContext()).apply {
+ setContent {
+ WearPermissionAppsScreen(
+ WearPermissionsAppHelper(
+ activity.getApplication(),
+ permGroupName,
+ viewModel,
+ isStorage,
+ onAppClick,
+ onShowSystemClick,
+ logPermissionAppsFragmentCreated
+ )
+ )
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt
new file mode 100644
index 000000000..2cf16e37f
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.ui.wear
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.Category
+import com.android.permissioncontroller.permission.ui.wear.elements.Chip
+import com.android.permissioncontroller.permission.ui.wear.elements.ListHeader
+import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
+
+/**
+ * Compose the screen associated to a [WearPermissionAppsFragment].
+ */
+@Composable
+fun WearPermissionAppsScreen(
+ helper: WearPermissionsAppHelper
+) {
+ val categorizedApps = helper.categorizedAppsLiveData().observeAsState(emptyMap())
+ val hasSystemApps = helper.hasSystemAppsLiveData().observeAsState(false)
+ val showSystem = helper.shouldShowSystemLiveData().observeAsState(false)
+ var isLoading by remember { mutableStateOf(true) }
+
+ val title = helper.getTitle()
+ val subTitle = helper.getSubTitle()
+ val showAlways = helper.showAlways()
+ val chipsByCategory = helper.getChipsByCategory(categorizedApps.value)
+
+ WearPermissionAppsContent(
+ chipsByCategory,
+ showSystem.value,
+ hasSystemApps.value,
+ title,
+ subTitle,
+ showAlways,
+ isLoading,
+ helper.onShowSystemClick
+ )
+
+ if (isLoading && categorizedApps.value.isNotEmpty()) {
+ isLoading = false
+ }
+ helper.setCreationLogged(true)
+}
+
+@Composable
+internal fun WearPermissionAppsContent(
+ chipsByCategory: Map<String, List<ChipInfo>>,
+ showSystem: Boolean,
+ hasSystemApps: Boolean,
+ title: String,
+ subtitle: String,
+ showAlways: Boolean,
+ isLoading: Boolean,
+ onShowSystemClick: (showSystem: Boolean) -> Unit
+) {
+ ScrollableScreen (
+ title = title,
+ subtitle = subtitle,
+ isLoading = isLoading
+ ) {
+ for (category in categoryOrder) {
+ val chips = chipsByCategory[category]
+ if (chips.isNullOrEmpty()) {
+ continue
+ }
+ item {
+ ListHeader(getCategoryString(category, showAlways))
+ }
+ chips.forEach {
+ item {
+ Chip(
+ label = it.title,
+ icon = it.icon,
+ onClick = {
+ it.onClick()
+ },
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+ }
+ }
+
+ if (hasSystemApps) {
+ item {
+ Chip(
+ label = if (showSystem) {
+ stringResource(R.string.menu_hide_system)
+ } else {
+ stringResource(R.string.menu_show_system)
+ },
+ onClick = {
+ onShowSystemClick(!showSystem)
+ },
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+ }
+ }
+}
+
+internal fun getCategoryString(category: String, showAlways: Boolean) =
+ when (category) {
+ "allowed_storage_full" -> R.string.allowed_storage_full
+ "allowed_storage_scoped" -> R.string.allowed_storage_scoped
+ Category.ALLOWED.categoryName ->
+ if (showAlways) {
+ R.string.allowed_always_header
+ } else {
+ R.string.allowed_header
+ }
+ Category.ALLOWED_FOREGROUND.categoryName -> R.string.allowed_foreground_header
+ Category.ASK.categoryName -> R.string.ask_header
+ Category.DENIED.categoryName -> R.string.denied_header
+ else -> throw IllegalArgumentException("Wrong category: $category")
+ }
+
+internal val categoryOrder = listOf(
+ "allowed_storage_full",
+ "allowed_storage_scoped",
+ Category.ALLOWED.categoryName,
+ Category.ALLOWED_FOREGROUND.categoryName,
+ Category.ASK.categoryName,
+ Category.DENIED.categoryName
+)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionsAppHelper.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionsAppHelper.kt
new file mode 100644
index 000000000..9f782f1fe
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionsAppHelper.kt
@@ -0,0 +1,185 @@
+/*
+ * 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.ui.wear
+
+import android.app.Application
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.Category
+import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupDescription
+import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel
+import com.android.settingslib.utils.applications.AppUtils
+import java.text.Collator
+import java.util.Random
+
+/**
+ * Helper class for WearPermissionsAppScreen.
+ */
+class WearPermissionsAppHelper(
+ val application: Application,
+ val permGroupName: String,
+ val viewModel: PermissionAppsViewModel,
+ private val isStorage: Boolean,
+ private val onAppClick: (String, UserHandle, String) -> Unit,
+ val onShowSystemClick: (Boolean) -> Unit,
+ val logFragmentCreated: (String, UserHandle, Long, Boolean, Boolean, Boolean) -> Unit
+) {
+ fun categorizedAppsLiveData() = viewModel.categorizedAppsLiveData
+ fun hasSystemAppsLiveData() = viewModel.hasSystemAppsLiveData
+ fun shouldShowSystemLiveData() = viewModel.shouldShowSystemLiveData
+ fun showAlways() = viewModel.showAllowAlwaysStringLiveData.value ?: false
+ fun getTitle() = getPermGroupLabel(application, permGroupName).toString()
+ fun getSubTitle() = getPermGroupDescription(application, permGroupName).toString()
+ fun getChipsByCategory(
+ categorizedApps: Map<Category, List<Pair<String, UserHandle>>>
+ ): Map<String, List<ChipInfo>> {
+ val chipsByCategory: MutableMap<String, MutableList<ChipInfo>> = HashMap()
+
+ val context = application
+ val collator = Collator.getInstance(
+ context.getResources().getConfiguration().getLocales().get(0)
+ )
+ val comparator = ChipComparator(collator)
+
+ val viewIdForLogging = Random().nextLong()
+ for (category in Category.values()) {
+ if (category == Category.ALLOWED && isStorage) {
+ val allowedList = categorizedApps[Category.ALLOWED]
+ if (!allowedList.isNullOrEmpty()) {
+ allowedList.partition { p ->
+ viewModel.packageHasFullStorage(
+ p.first,
+ p.second
+ )
+ }.let {
+ if (it.first.isNotEmpty()) {
+ chipsByCategory["allowed_storage_full"] =
+ convertToChips(category, it.first, viewIdForLogging, comparator)
+ }
+ if (it.second.isNotEmpty()) {
+ chipsByCategory["allowed_storage_scoped"] =
+ convertToChips(category, it.second, viewIdForLogging, comparator)
+ }
+ }
+ }
+ continue
+ }
+ val list = categorizedApps[category]
+ if (!list.isNullOrEmpty()) {
+ chipsByCategory[category.categoryName] =
+ convertToChips(category, list, viewIdForLogging, comparator)
+ }
+ }
+
+ // Add no_apps chips to allowed and denied if it doesn't have an app.
+ chipsByCategory[Category.ALLOWED.categoryName]?.let {
+ if (it.isEmpty()) {
+ it.add(
+ ChipInfo(
+ title = context.resources.getString(R.string.no_apps_allowed),
+ enabled = false
+ )
+ )
+ }
+ }
+ chipsByCategory[Category.DENIED.categoryName]?.let {
+ if (it.isEmpty()) {
+ it.add(
+ ChipInfo(
+ title = context.resources.getString(R.string.no_apps_denied),
+ enabled = false
+ )
+ )
+ }
+ }
+ return chipsByCategory
+ }
+
+ private fun convertToChips(
+ category: Category,
+ list: List<Pair<String, UserHandle>>,
+ viewIdForLogging: Long,
+ comparator: Comparator<ChipInfo>
+ ) =
+ list.map { p ->
+ createAppChipInfo(
+ application,
+ p.first,
+ p.second,
+ category,
+ onAppClick,
+ viewIdForLogging
+ )
+ }.sortedWith(comparator).toMutableList()
+
+ fun setCreationLogged(isLogged: Boolean) {
+ viewModel.creationLogged = isLogged
+ }
+
+ private fun createAppChipInfo(
+ application: Application,
+ packageName: String,
+ user: UserHandle,
+ category: Category,
+ onClick: (packageName: String, user: UserHandle, category: String) -> Unit,
+ viewIdForLogging: Long
+ ): ChipInfo {
+ if (!viewModel.creationLogged) {
+ logFragmentCreated(
+ packageName,
+ user,
+ viewIdForLogging,
+ category == Category.ALLOWED,
+ category == Category.ALLOWED_FOREGROUND,
+ category == Category.DENIED
+ )
+ }
+ return ChipInfo(
+ title = KotlinUtils.getPackageLabel(application, packageName, user),
+ contentDescription = AppUtils.getAppContentDescription(
+ application,
+ packageName,
+ user.getIdentifier()
+ ),
+ icon = KotlinUtils.getBadgedPackageIcon(application, packageName, user),
+ onClick = { onClick(packageName, user, category.categoryName) }
+ )
+ }
+}
+
+class ChipInfo(
+ val title: String,
+ val contentDescription: String? = null,
+ val onClick: () -> Unit = {},
+ val icon: Drawable? = null,
+ val enabled: Boolean = true
+)
+
+internal class ChipComparator(
+ val collator: Collator
+) : Comparator<ChipInfo> {
+ override fun compare(lhs: ChipInfo, rhs: ChipInfo): Int {
+ var result = collator.compare(lhs.title, rhs.title)
+ if (result == 0) {
+ result = lhs.title.compareTo(rhs.title)
+ }
+ return result
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AdjustChipHeightToFontScale.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AdjustChipHeightToFontScale.kt
new file mode 100644
index 000000000..c20959f7a
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AdjustChipHeightToFontScale.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.ui.wear.elements
+
+import androidx.compose.foundation.layout.height
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+/** Adjusts height of the chip as per the font scale. */
+public fun Modifier.adjustChipHeightToFontScale(fontScale: Float, padding: Dp = 0.dp): Modifier =
+ if (fontScale > 1) {
+ this.then(Modifier.height(60.dp + padding))
+ } else {
+ this
+ } \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AlertDialog.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AlertDialog.kt
new file mode 100644
index 000000000..55c4192ea
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AlertDialog.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.ui.wear.elements
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.material.ButtonDefaults
+import androidx.wear.compose.material.Icon
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.Text
+import androidx.wear.compose.material.dialog.Alert
+import androidx.wear.compose.material.dialog.Dialog
+
+/**
+ * This component is an alternative to [Alert], providing the following:
+ * - a convenient way of passing a title and a message;
+ * - default positive and negative buttons;
+ * - wrapped in a [Dialog];
+ */
+@Composable
+public fun AlertDialog(
+ message: String,
+ iconRes: Int? = null,
+ onCancelButtonClick: () -> Unit,
+ onOKButtonClick: () -> Unit,
+ showDialog: Boolean,
+ scalingLazyListState: ScalingLazyListState,
+ modifier: Modifier = Modifier,
+ title: String = "",
+ okButtonContentDescription: String = stringResource(android.R.string.ok),
+ cancelButtonContentDescription: String = stringResource(android.R.string.cancel)
+) {
+ Dialog(
+ showDialog = showDialog,
+ onDismissRequest = onCancelButtonClick,
+ scrollState = scalingLazyListState,
+ modifier = modifier
+ ) {
+ Alert(
+ title = title,
+ iconRes = iconRes,
+ body = message,
+ onCancelButtonClick = onCancelButtonClick,
+ onOKButtonClick = onOKButtonClick,
+ okButtonContentDescription = okButtonContentDescription,
+ cancelButtonContentDescription = cancelButtonContentDescription
+ )
+ }
+}
+
+@Composable
+internal fun Alert(
+ title: String,
+ iconRes: Int? = null,
+ body: String,
+ onCancelButtonClick: () -> Unit,
+ onOKButtonClick: () -> Unit,
+ okButtonContentDescription: String,
+ cancelButtonContentDescription: String
+) {
+ Alert(
+ title = {
+ Text(
+ text = title,
+ color = MaterialTheme.colors.onBackground,
+ textAlign = TextAlign.Center,
+ maxLines = 3,
+ style = MaterialTheme.typography.title3
+ )
+ },
+ icon = if (iconRes != null && iconRes != 0) {
+ {
+ Icon(
+ painter = painterResource(iconRes),
+ contentDescription = null
+ )
+ }
+ } else {
+ null
+ },
+ content = {
+ Text(
+ text = body,
+ color = MaterialTheme.colors.onBackground,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.body2
+ )
+ },
+ negativeButton = {
+ Button(
+ imageVector = Icons.Default.Close,
+ contentDescription = cancelButtonContentDescription,
+ onClick = onCancelButtonClick,
+ colors = ButtonDefaults.secondaryButtonColors()
+ )
+ },
+ positiveButton = {
+ Button(
+ imageVector = Icons.Default.Check,
+ contentDescription = okButtonContentDescription,
+ onClick = onOKButtonClick
+ )
+ }
+ )
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Button.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Button.kt
new file mode 100644
index 000000000..2e90f3356
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Button.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.ui.wear.elements
+
+import androidx.annotation.DrawableRes
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.Dp
+import androidx.wear.compose.material.Button
+import androidx.wear.compose.material.ButtonColors
+import androidx.wear.compose.material.ButtonDefaults
+import androidx.wear.compose.material.ButtonDefaults.DefaultButtonSize
+import androidx.wear.compose.material.ButtonDefaults.DefaultIconSize
+import androidx.wear.compose.material.ButtonDefaults.LargeButtonSize
+import androidx.wear.compose.material.ButtonDefaults.LargeIconSize
+import androidx.wear.compose.material.ButtonDefaults.SmallButtonSize
+import androidx.wear.compose.material.ButtonDefaults.SmallIconSize
+
+/**
+ * This component is an alternative to [Button], providing the following:
+ * - a convenient way of providing an icon and choosing its size from a range of sizes recommended
+ * by the Wear guidelines;
+ */
+@Composable
+public fun Button(
+ imageVector: ImageVector,
+ contentDescription: String,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ colors: ButtonColors = ButtonDefaults.primaryButtonColors(),
+ buttonSize: ButtonSize = ButtonSize.Default,
+ iconRtlMode: IconRtlMode = IconRtlMode.Default,
+ enabled: Boolean = true
+) {
+ Button(
+ icon = imageVector,
+ contentDescription = contentDescription,
+ onClick = onClick,
+ modifier = modifier,
+ colors = colors,
+ buttonSize = buttonSize,
+ iconRtlMode = iconRtlMode,
+ enabled = enabled
+ )
+}
+
+/**
+ * This component is an alternative to [Button], providing the following:
+ * - a convenient way of providing an icon and choosing its size from a range of sizes recommended
+ * by the Wear guidelines;
+ */
+@Composable
+public fun Button(
+ @DrawableRes id: Int,
+ contentDescription: String,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ colors: ButtonColors = ButtonDefaults.primaryButtonColors(),
+ buttonSize: ButtonSize = ButtonSize.Default,
+ iconRtlMode: IconRtlMode = IconRtlMode.Default,
+ enabled: Boolean = true
+) {
+ Button(
+ icon = id,
+ contentDescription = contentDescription,
+ onClick = onClick,
+ modifier = modifier,
+ colors = colors,
+ buttonSize = buttonSize,
+ iconRtlMode = iconRtlMode,
+ enabled = enabled
+ )
+}
+
+@Composable
+internal fun Button(
+ icon: Any,
+ contentDescription: String,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ colors: ButtonColors = ButtonDefaults.primaryButtonColors(),
+ buttonSize: ButtonSize = ButtonSize.Default,
+ iconRtlMode: IconRtlMode = IconRtlMode.Default,
+ enabled: Boolean = true
+) {
+ Button(
+ onClick = onClick,
+ modifier = modifier.size(buttonSize.tapTargetSize),
+ enabled = enabled,
+ colors = colors
+ ) {
+ val iconModifier = Modifier
+ .size(buttonSize.iconSize)
+ .align(Alignment.Center)
+
+ Icon(
+ icon = icon,
+ contentDescription = contentDescription,
+ modifier = iconModifier,
+ rtlMode = iconRtlMode
+ )
+ }
+}
+
+public sealed class ButtonSize(
+ public val iconSize: Dp,
+ public val tapTargetSize: Dp
+) {
+ public object Default :
+ ButtonSize(iconSize = DefaultIconSize, tapTargetSize = DefaultButtonSize)
+
+ public object Large : ButtonSize(iconSize = LargeIconSize, tapTargetSize = LargeButtonSize)
+ public object Small : ButtonSize(iconSize = SmallIconSize, tapTargetSize = SmallButtonSize)
+
+ /**
+ * Custom sizes should follow the [accessibility principles and guidance for touch targets](https://developer.android.com/training/wearables/accessibility#set-minimum).
+ */
+ public data class Custom(val customIconSize: Dp, val customTapTargetSize: Dp) :
+ ButtonSize(iconSize = customIconSize, tapTargetSize = customTapTargetSize)
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt
new file mode 100644
index 000000000..5dc8141fc
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt
@@ -0,0 +1,223 @@
+/*
+ * 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.ui.wear.elements
+
+import android.graphics.drawable.Drawable
+import androidx.annotation.StringRes
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.Chip
+import androidx.wear.compose.material.ChipColors
+import androidx.wear.compose.material.ChipDefaults
+import androidx.wear.compose.material.Icon
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.Text
+
+/**
+ * This component is an alternative to [Chip], providing the following:
+ * - a convenient way of providing a label and a secondary label;
+ * - a convenient way of providing an icon, and choosing their size based on the
+ * sizes recommended by the Wear guidelines;
+ */
+@Composable
+public fun Chip(
+ label: String,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ secondaryLabel: String? = null,
+ icon: Any? = null,
+ iconContentDescription: String? = null,
+ largeIcon: Boolean = false,
+ textColor: Color = MaterialTheme.colors.onSurface,
+ iconColor: Color = Color.Unspecified,
+ colors: ChipColors = ChipDefaults.secondaryChipColors(),
+ enabled: Boolean = true
+) {
+ val iconParam: (@Composable BoxScope.() -> Unit)? =
+ icon?.let {
+ {
+ val iconSize = if (largeIcon) {
+ ChipDefaults.LargeIconSize
+ } else {
+ ChipDefaults.IconSize
+ }
+
+ Row {
+ val iconModifier = Modifier
+ .size(iconSize)
+ .clip(CircleShape)
+ when (icon) {
+ is ImageVector ->
+ Icon(
+ imageVector = icon,
+ tint = iconColor,
+ contentDescription = iconContentDescription,
+ modifier = iconModifier
+ )
+
+ is Int ->
+ Icon(
+ painter = painterResource(id = icon),
+ tint = iconColor,
+ contentDescription = iconContentDescription,
+ modifier = iconModifier
+ )
+ is Drawable ->
+ Icon(
+ painter = rememberDrawablePainter(icon),
+ tint = iconColor,
+ contentDescription = iconContentDescription,
+ modifier = iconModifier
+ )
+ else -> {
+ }
+ }
+ }
+ }
+ }
+
+ Chip(
+ label = label,
+ onClick = onClick,
+ modifier = modifier,
+ secondaryLabel = secondaryLabel,
+ icon = iconParam,
+ largeIcon = largeIcon,
+ textColor = textColor,
+ colors = colors,
+ enabled = enabled
+ )
+}
+
+/**
+ * This component is an alternative to [Chip], providing the following:
+ * - a convenient way of providing a label and a secondary label;
+ * - a convenient way of providing an icon, and choosing their size based on the
+ * sizes recommended by the Wear guidelines;
+ */
+@Composable
+public fun Chip(
+ @StringRes labelId: Int,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ @StringRes secondaryLabel: Int? = null,
+ icon: Any? = null,
+ largeIcon: Boolean = false,
+ textColor: Color = MaterialTheme.colors.onSurface,
+ iconColor: Color = Color.Unspecified,
+ colors: ChipColors = ChipDefaults.secondaryChipColors(),
+ enabled: Boolean = true
+) {
+ Chip(
+ label = stringResource(id = labelId),
+ onClick = onClick,
+ modifier = modifier,
+ secondaryLabel = secondaryLabel?.let { stringResource(id = it) },
+ icon = icon,
+ largeIcon = largeIcon,
+ textColor = textColor,
+ iconColor = iconColor,
+ colors = colors,
+ enabled = enabled
+ )
+}
+
+/**
+ * This component is an alternative to [Chip], providing the following:
+ * - a convenient way of providing a label and a secondary label;
+ */
+@Composable
+public fun Chip(
+ label: String,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ secondaryLabel: String? = null,
+ icon: (@Composable BoxScope.() -> Unit)? = null,
+ largeIcon: Boolean = false,
+ textColor: Color = MaterialTheme.colors.onSurface,
+ colors: ChipColors = ChipDefaults.secondaryChipColors(),
+ enabled: Boolean = true
+) {
+ val hasSecondaryLabel = secondaryLabel != null
+ val hasIcon = icon != null
+
+ val labelParam: (@Composable RowScope.() -> Unit) =
+ {
+ Text(
+ text = label,
+ color = textColor,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = if (hasSecondaryLabel || hasIcon) TextAlign.Start else TextAlign.Center,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = if (hasSecondaryLabel) 1 else 2,
+ style = MaterialTheme.typography.button
+ )
+ }
+
+ val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
+ secondaryLabel?.let {
+ {
+ Text(
+ text = secondaryLabel,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ style = MaterialTheme.typography.caption2
+ )
+ }
+ }
+
+ val contentPadding = if (largeIcon) {
+ val verticalPadding = ChipDefaults.ChipVerticalPadding
+ PaddingValues(
+ start = 10.dp,
+ top = verticalPadding,
+ end = ChipDefaults.ChipHorizontalPadding,
+ bottom = verticalPadding
+ )
+ } else {
+ ChipDefaults.ContentPadding
+ }
+
+ Chip(
+ label = labelParam,
+ onClick = onClick,
+ modifier = modifier
+ .adjustChipHeightToFontScale(LocalConfiguration.current.fontScale)
+ .fillMaxWidth(),
+ secondaryLabel = secondaryLabelParam,
+ icon = icon,
+ colors = colors,
+ enabled = enabled,
+ contentPadding = contentPadding
+ )
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/DrawablePainter.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/DrawablePainter.kt
new file mode 100644
index 000000000..28e7be022
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/DrawablePainter.kt
@@ -0,0 +1,172 @@
+/*
+ * 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.ui.wear.elements
+
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.asAndroidColorFilter
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.withSave
+import androidx.compose.ui.unit.LayoutDirection
+import kotlin.math.roundToInt
+
+private val MAIN_HANDLER by lazy(LazyThreadSafetyMode.NONE) {
+ Handler(Looper.getMainLooper())
+}
+
+/**
+ * A [Painter] which draws an Android [Drawable] and supports [Animatable] drawables. Instances
+ * should be remembered to be able to start and stop [Animatable] animations.
+ *
+ * Instances are usually retrieved from [rememberDrawablePainter].
+ */
+class DrawablePainter(
+ val drawable: Drawable
+) : Painter(), RememberObserver {
+ private var drawInvalidateTick by mutableStateOf(0)
+ private var drawableIntrinsicSize by mutableStateOf(drawable.intrinsicSize)
+
+ private val callback: Drawable.Callback by lazy {
+ object : Drawable.Callback {
+ override fun invalidateDrawable(d: Drawable) {
+ // Update the tick so that we get re-drawn
+ drawInvalidateTick++
+ // Update our intrinsic size too
+ drawableIntrinsicSize = drawable.intrinsicSize
+ }
+
+ override fun scheduleDrawable(d: Drawable, what: Runnable, time: Long) {
+ MAIN_HANDLER.postAtTime(what, time)
+ }
+
+ override fun unscheduleDrawable(d: Drawable, what: Runnable) {
+ MAIN_HANDLER.removeCallbacks(what)
+ }
+ }
+ }
+
+ init {
+ if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) {
+ // Update the drawable's bounds to match the intrinsic size
+ drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
+ }
+ }
+
+ override fun onRemembered() {
+ drawable.callback = callback
+ drawable.setVisible(true, true)
+ if (drawable is Animatable) drawable.start()
+ }
+
+ override fun onAbandoned() = onForgotten()
+
+ override fun onForgotten() {
+ if (drawable is Animatable) drawable.stop()
+ drawable.setVisible(false, false)
+ drawable.callback = null
+ }
+
+ override fun applyAlpha(alpha: Float): Boolean {
+ drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255)
+ return true
+ }
+
+ override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
+ drawable.colorFilter = colorFilter?.asAndroidColorFilter()
+ return true
+ }
+
+ override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
+ if (Build.VERSION.SDK_INT >= 23) {
+ return drawable.setLayoutDirection(
+ when (layoutDirection) {
+ LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
+ LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
+ }
+ )
+ }
+ return false
+ }
+
+ override val intrinsicSize: Size get() = drawableIntrinsicSize
+
+ override fun DrawScope.onDraw() {
+ drawIntoCanvas { canvas ->
+ // Reading this ensures that we invalidate when invalidateDrawable() is called
+ drawInvalidateTick
+
+ // Update the Drawable's bounds
+ drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
+
+ canvas.withSave {
+ drawable.draw(canvas.nativeCanvas)
+ }
+ }
+ }
+}
+
+/**
+ * Remembers [Drawable] wrapped up as a [Painter]. This function attempts to un-wrap the
+ * drawable contents and use Compose primitives where possible.
+ *
+ * If the provided [drawable] is `null`, an empty no-op painter is returned.
+ *
+ * This function tries to dispatch lifecycle events to [drawable] as much as possible from
+ * within Compose.
+ */
+@Composable
+fun rememberDrawablePainter(drawable: Drawable?): Painter = remember(drawable) {
+ when (drawable) {
+ null -> EmptyPainter
+ is ColorDrawable -> ColorPainter(Color(drawable.color))
+ // Since the DrawablePainter will be remembered and it implements RememberObserver, it
+ // will receive the necessary events
+ else -> DrawablePainter(drawable.mutate())
+ }
+}
+
+private val Drawable.intrinsicSize: Size
+ get() = when {
+ // Only return a finite size if the drawable has an intrinsic size
+ intrinsicWidth >= 0 && intrinsicHeight >= 0 -> {
+ Size(width = intrinsicWidth.toFloat(), height = intrinsicHeight.toFloat())
+ }
+ else -> Size.Unspecified
+ }
+
+internal object EmptyPainter : Painter() {
+ override val intrinsicSize: Size get() = Size.Unspecified
+ override fun DrawScope.onDraw() {}
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Icon.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Icon.kt
new file mode 100644
index 000000000..b96c2ca51
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Icon.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.ui.wear.elements
+
+import androidx.annotation.DrawableRes
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.scale
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.wear.compose.material.Icon
+import androidx.wear.compose.material.LocalContentAlpha
+import androidx.wear.compose.material.LocalContentColor
+
+/**
+ * This component is an alternative to [Icon], providing the following:
+ * - a convenient way of setting the icon to be mirrored in RTL mode;
+ */
+@Composable
+public fun Icon(
+ imageVector: ImageVector,
+ contentDescription: String?,
+ modifier: Modifier = Modifier,
+ tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
+ rtlMode: IconRtlMode = IconRtlMode.Default
+) {
+ val shouldMirror =
+ rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl
+ Icon(
+ modifier = modifier.scale(
+ scaleX = if (shouldMirror) -1f else 1f,
+ scaleY = 1f
+ ),
+ imageVector = imageVector,
+ contentDescription = contentDescription,
+ tint = tint
+ )
+}
+
+/**
+ * This component is an alternative to [Icon], providing the following:
+ * - a convenient way of setting the icon to be mirrored in RTL mode;
+ */
+@Composable
+public fun Icon(
+ @DrawableRes id: Int,
+ contentDescription: String?,
+ modifier: Modifier = Modifier,
+ tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
+ rtlMode: IconRtlMode = IconRtlMode.Default
+) {
+ val shouldMirror =
+ rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl
+
+ Icon(
+ painter = painterResource(id = id),
+ contentDescription = contentDescription,
+ modifier = modifier.scale(
+ scaleX = if (shouldMirror) -1f else 1f,
+ scaleY = 1f
+ ),
+ tint = tint
+ )
+}
+
+@Composable
+internal fun Icon(
+ icon: Any,
+ contentDescription: String?,
+ modifier: Modifier = Modifier,
+ tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
+ rtlMode: IconRtlMode = IconRtlMode.Default
+) {
+ val shouldMirror =
+ rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl
+
+ val iconModifier = modifier.scale(
+ scaleX = if (shouldMirror) -1f else 1f,
+ scaleY = 1f
+ )
+ when (icon) {
+ is ImageVector -> {
+ Icon(
+ imageVector = icon,
+ modifier = iconModifier,
+ contentDescription = contentDescription,
+ tint = tint
+ )
+ }
+
+ is Int -> {
+ Icon(
+ painter = painterResource(id = icon),
+ contentDescription = contentDescription,
+ modifier = iconModifier,
+ tint = tint
+ )
+ }
+
+ else -> throw IllegalArgumentException("Type not supported.")
+ }
+}
+
+public enum class IconRtlMode {
+ Default,
+ Mirrored
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListFooter.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListFooter.kt
new file mode 100644
index 000000000..3d668ca02
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListFooter.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.ui.wear.elements
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.Icon
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.Text
+
+/**
+ * A slot based composable for creating a list footer item.
+ */
+@Composable
+fun ListFooter(
+ description: String,
+ iconRes: Int? = null,
+ onClick: (() -> Unit)? = null
+) {
+ val modifier = Modifier.fillMaxWidth()
+ Row(
+ modifier = if (onClick == null) {
+ modifier
+ } else {
+ modifier.clickable(onClick = onClick)
+ }
+ ) {
+ iconRes?.let {
+ Spacer(modifier = Modifier.width(LeadingIconStartSpacing))
+ Icon(
+ painter = painterResource(id = it),
+ contentDescription = null,
+ modifier = Modifier
+ .size(LeadingIconSize, LeadingIconSize)
+ .align(Alignment.CenterVertically)
+ )
+ Spacer(modifier = Modifier.width(LeadingIconEndSpacing))
+ }
+ Text(
+ text = description,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Start,
+ overflow = TextOverflow.Ellipsis,
+ color = MaterialTheme.colors.onSurfaceVariant,
+ style = MaterialTheme.typography.caption2
+ )
+ }
+}
+
+/**
+ * The size of the spacing before the leading icon when they used inside a list footer.
+ */
+private val LeadingIconStartSpacing = 4.dp
+
+/**
+ * The size of the spacing between the leading icon and a text inside a list footer.
+ */
+private val LeadingIconEndSpacing = 8.dp
+
+/**
+ * The size of the leading icon.
+ */
+private val LeadingIconSize = 24.dp
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt
new file mode 100644
index 000000000..b916b24fd
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.ui.wear.elements
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.Text
+
+/**
+ * A slot based composable for creating a list header item.
+ */
+@Composable
+public fun ListHeader(labelStringRes: Int) {
+ Text(
+ text = stringResource(labelStringRes),
+ style = MaterialTheme.typography.caption1,
+ modifier =
+ Modifier.padding(top = 16.dp, bottom = 12.dp, start = 8.dp, end = 8.dp).fillMaxWidth(),
+ textAlign = TextAlign.Start
+ )
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt
new file mode 100644
index 000000000..20b51ed61
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.ui.wear.elements
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.input.rotary.onRotaryScrollEvent
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.material.CircularProgressIndicator
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.PositionIndicator
+import androidx.wear.compose.material.Scaffold
+import androidx.wear.compose.material.Text
+import androidx.wear.compose.material.TimeText
+import androidx.wear.compose.material.Vignette
+import androidx.wear.compose.material.VignettePosition
+import androidx.wear.compose.material.scrollAway
+import kotlinx.coroutines.launch
+
+/**
+ * Screen that contains a list of items defined using the [content] parameter, adds the time text
+ * (if [showTimeText] is true), the tile (if [title] is not null), the vignette and the position
+ * indicator. It also manages the scaling animation and allows the user to scroll the content using
+ * the crown.
+ */
+@Composable
+fun ScrollableScreen(
+ showTimeText: Boolean = true,
+ title: String? = null,
+ subtitle: String? = null,
+ image: Int? = null,
+ isLoading: Boolean = false,
+ content: ScalingLazyListScope.() -> Unit,
+) {
+ val focusRequester = remember { FocusRequester() }
+ val listState = remember { ScalingLazyListState(initialCenterItemIndex = 0) }
+ val coroutineScope = rememberCoroutineScope()
+
+ MaterialTheme {
+ Scaffold(
+ modifier = Modifier.onRotaryScrollEvent {
+ coroutineScope.launch {
+ listState.scrollBy(it.verticalScrollPixels)
+ }
+ true
+ }
+ .focusRequester(focusRequester)
+ .focusable(),
+ timeText = {
+ if (showTimeText && !isLoading) {
+ TimeText(
+ modifier = Modifier.scrollAway(listState),
+ contentPadding = PaddingValues(15.dp)
+ )
+ }
+ },
+ vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) },
+ positionIndicator = { PositionIndicator(scalingLazyListState = listState) }
+ ) {
+ Box(modifier = Modifier.fillMaxSize()) {
+ if (isLoading) {
+ CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
+ } else {
+ ScalingLazyColumn(
+ state = listState,
+ // Set autoCentering to null to avoid adding extra padding based on the content.
+ autoCentering = null,
+ contentPadding = PaddingValues(
+ start = 10.dp,
+ end = 10.dp,
+ top = 32.dp,
+ bottom = 70.dp
+ )
+ ) {
+ if (image != null) {
+ item {
+ Image(
+ painter = painterResource(id = image),
+ modifier = Modifier.padding(bottom = 14.dp),
+ contentDescription = null,
+ )
+ }
+ }
+ if (title != null) {
+ item {
+ Text(
+ text = title,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.button,
+ modifier = Modifier.padding(
+ bottom = 12.dp,
+ start = 24.dp,
+ end = 24.dp
+ )
+ )
+ }
+ }
+ if (subtitle != null) {
+ item {
+ Text(
+ text = subtitle,
+ style = MaterialTheme.typography.body2,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ }
+ }
+
+ content()
+ }
+ RequestFocusOnResume(focusRequester = focusRequester)
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun RequestFocusOnResume(focusRequester: FocusRequester) {
+ val lifecycleOwner = LocalLifecycleOwner.current
+ LaunchedEffect(Unit) {
+ lifecycleOwner.repeatOnLifecycle(state = Lifecycle.State.RESUMED) {
+ focusRequester.requestFocus()
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt
new file mode 100644
index 000000000..29342658c
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.ui.wear.elements
+
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.wear.compose.material.ChipDefaults
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.Text
+import androidx.wear.compose.material.ToggleChip
+import androidx.wear.compose.material.ToggleChipColors
+import androidx.wear.compose.material.ToggleChipDefaults
+import com.android.permissioncontroller.R
+
+/**
+ * This component is an alternative to [ToggleChip], providing the following:
+ * - a convenient way of providing a label and a secondary label;
+ * - a convenient way of choosing the toggle control;
+ * - a convenient way of providing an icon and setting the icon to be mirrored in RTL mode;
+ */
+@Composable
+public fun ToggleChip(
+ checked: Boolean,
+ onCheckedChanged: (Boolean) -> Unit,
+ label: String,
+ toggleControl: ToggleChipToggleControl,
+ modifier: Modifier = Modifier,
+ icon: ImageVector? = null,
+ iconRtlMode: IconRtlMode = IconRtlMode.Default,
+ secondaryLabel: String? = null,
+ colors: ToggleChipColors = ToggleChipDefaults.toggleChipColors(),
+ enabled: Boolean = true,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
+) {
+ val hasSecondaryLabel = secondaryLabel != null
+
+ val labelParam: (@Composable RowScope.() -> Unit) =
+ {
+ Text(
+ text = label,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Start,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = if (hasSecondaryLabel) 1 else 2,
+ style = MaterialTheme.typography.button
+ )
+ }
+
+ val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
+ secondaryLabel?.let {
+ {
+ Text(
+ text = secondaryLabel,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ style = MaterialTheme.typography.caption2
+ )
+ }
+ }
+
+ val toggleControlParam: (@Composable () -> Unit) = {
+ Icon(
+ imageVector = when (toggleControl) {
+ ToggleChipToggleControl.Switch -> ToggleChipDefaults.switchIcon(checked)
+ ToggleChipToggleControl.Radio -> ToggleChipDefaults.radioIcon(checked)
+ ToggleChipToggleControl.Checkbox -> ToggleChipDefaults.checkboxIcon(checked)
+ },
+ contentDescription = null,
+ // This potentially be removed once this issue is addressed:
+ // https://issuetracker.google.com/issues/287087138
+ rtlMode = if (toggleControl == ToggleChipToggleControl.Switch) {
+ IconRtlMode.Mirrored
+ } else {
+ IconRtlMode.Default
+ }
+ )
+ }
+
+ val iconParam: (@Composable BoxScope.() -> Unit)? =
+ icon?.let {
+ {
+ Row {
+ Icon(
+ imageVector = icon,
+ contentDescription = null,
+ modifier = Modifier
+ .size(ChipDefaults.IconSize)
+ .clip(CircleShape),
+ rtlMode = iconRtlMode
+ )
+ }
+ }
+ }
+
+ val stateDescriptionSemantics = stringResource(
+ if (checked) {
+ R.string.on
+ } else {
+ R.string.off
+ }
+ )
+ ToggleChip(
+ checked = checked,
+ onCheckedChange = onCheckedChanged,
+ label = labelParam,
+ toggleControl = toggleControlParam,
+ modifier = modifier
+ .adjustChipHeightToFontScale(LocalConfiguration.current.fontScale)
+ .fillMaxWidth()
+ .semantics {
+ stateDescription = stateDescriptionSemantics
+ },
+ appIcon = iconParam,
+ secondaryLabel = secondaryLabelParam,
+ colors = colors,
+ enabled = enabled,
+ interactionSource = interactionSource
+ )
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt
new file mode 100644
index 000000000..e123ecb81
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.ui.wear.elements
+
+public enum class ToggleChipToggleControl {
+ Switch, Radio, Checkbox
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/AppPermissionConfirmDialogViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/AppPermissionConfirmDialogViewModel.kt
new file mode 100644
index 000000000..8da12d037
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/AppPermissionConfirmDialogViewModel.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.ui.wear.model
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel
+import com.android.permissioncontroller.permission.ui.v33.AdvancedConfirmDialogArgs
+
+class AppPermissionConfirmDialogViewModel : ViewModel() {
+ /** A livedata which stores whether confirmation dialog is visible. */
+ val showConfirmDialogLiveData = MutableLiveData<Boolean>()
+
+ /** Arguments for a confirmation dialog. */
+ var confirmDialogArgs: ConfirmDialogArgs? = null
+
+ /** A livedata which stores whether confirmation dialog is visible. */
+ val showAdvancedConfirmDialogLiveData = MutableLiveData<Boolean>()
+
+ /** Arguments for an advanced confirmation dialog. */
+ var advancedConfirmDialogArgs: AdvancedConfirmDialogArgs? = null
+
+ init {
+ showConfirmDialogLiveData.value = false
+ showAdvancedConfirmDialogLiveData.value = false
+ }
+}
+
+/**
+ * Factory for an AppPermissionConfirmDialogViewModel
+ */
+class AppPermissionConfirmDialogViewModelFactory : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ @Suppress("UNCHECKED_CAST")
+ return AppPermissionConfirmDialogViewModel() as T
+ }
+}
+
+/** */
+data class ConfirmDialogArgs(
+ val messageId: Int,
+ val changeRequest: AppPermissionViewModel.ChangeRequest,
+ val buttonPressed: Int,
+ val oneTime: Boolean
+) \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
index 358f86f4b..909af6eea 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
@@ -98,7 +99,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
-
/**
* A set of util functions designed to work with kotlin, though they can work with java, as well.
*/
@@ -172,6 +172,8 @@ object KotlinUtils {
private const val PROPERTY_SAFETY_LABEL_CHANGES_JOB_SERVICE_KILL_SWITCH =
"safety_label_changes_job_service_kill_switch"
+ private const val PROPERTY_NEW_GRANT_DIALOG_BACKEND = "new_grand_dialog_backend"
+
/**
* Whether the Permissions Hub 2 flag is enabled
*
@@ -375,6 +377,11 @@ object KotlinUtils {
)
}
+ fun isNewGrantDialogBackendEnabled(): Boolean {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_NEW_GRANT_DIALOG_BACKEND, true)
+ }
+
/**
* Given a Map, and a List, determines which elements are in the list, but not the map, and vice
* versa. Used primarily for determining which liveDatas are already being watched, and which
@@ -577,6 +584,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
@@ -595,6 +621,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
@@ -627,6 +680,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).
*
@@ -696,8 +762,9 @@ object KotlinUtils {
* Determine if the given permission should be treated as split from a non-runtime permission
* for an application targeting the given SDK level.
*/
- private fun isPermissionSplitFromNonRuntime(
- app: Application,
+ @JvmStatic
+ fun isPermissionSplitFromNonRuntime(
+ app: Context,
permName: String,
targetSdk: Int
): Boolean {
@@ -786,7 +853,7 @@ object KotlinUtils {
fun grantForegroundRuntimePermissions(
app: Application,
group: LightAppPermGroup,
- filterPermissions: List<String> = group.permissions.keys.toList(),
+ filterPermissions: Collection<String> = group.permissions.keys,
isOneTime: Boolean = false,
userFixed: Boolean = false,
withoutAppOps: Boolean = false,
@@ -817,9 +884,10 @@ object KotlinUtils {
fun grantBackgroundRuntimePermissions(
app: Application,
group: LightAppPermGroup,
- filterPermissions: List<String> = group.permissions.keys.toList()
+ filterPermissions: Collection<String> = group.permissions.keys
): LightAppPermGroup {
- return grantRuntimePermissions(app, group, true, false, false, false, filterPermissions)
+ return grantRuntimePermissions(app, group, grantBackground = true, isOneTime = false,
+ userFixed = false, withoutAppOps = false, filterPermissions = filterPermissions)
}
private fun grantRuntimePermissions(
@@ -829,7 +897,7 @@ object KotlinUtils {
isOneTime: Boolean = false,
userFixed: Boolean = false,
withoutAppOps: Boolean = false,
- filterPermissions: List<String> = group.permissions.keys.toList(),
+ filterPermissions: Collection<String> = group.permissions.keys
): LightAppPermGroup {
val newPerms = group.permissions.toMutableMap()
var shouldKillForAnyPermission = false
@@ -1052,7 +1120,7 @@ object KotlinUtils {
userFixed: Boolean = false,
oneTime: Boolean = false,
forceRemoveRevokedCompat: Boolean = false,
- filterPermissions: List<String> = group.permissions.keys.toList()
+ filterPermissions: Collection<String> = group.permissions.keys
): LightAppPermGroup {
return revokeRuntimePermissions(
app,
@@ -1084,7 +1152,7 @@ object KotlinUtils {
userFixed: Boolean = false,
oneTime: Boolean = false,
forceRemoveRevokedCompat: Boolean = false,
- filterPermissions: List<String> = group.permissions.keys.toList()
+ filterPermissions: Collection<String> = group.permissions.keys
): LightAppPermGroup {
return revokeRuntimePermissions(
app,
@@ -1104,7 +1172,7 @@ object KotlinUtils {
userFixed: Boolean,
oneTime: Boolean,
forceRemoveRevokedCompat: Boolean = false,
- filterPermissions: List<String>
+ filterPermissions: Collection<String>
): LightAppPermGroup {
val wasOneTime = group.isOneTime
val newPerms = group.permissions.toMutableMap()
@@ -1710,21 +1778,21 @@ object KotlinUtils {
/** Get the [value][LiveData.getValue], suspending until [isInitialized] if not yet so */
suspend fun <T, LD : LiveData<T>> LD.getInitializedValue(
observe: LD.(Observer<T>) -> Unit = { observeForever(it) },
- isInitialized: LD.() -> Boolean = { value != null }
+ isValueInitialized: LD.() -> Boolean = { value != null }
): T {
- return if (isInitialized()) {
- value!!
+ return if (isValueInitialized()) {
+ @Suppress("UNCHECKED_CAST")
+ value as T
} else {
suspendCoroutine { continuation: Continuation<T> ->
val observer = AtomicReference<Observer<T>>()
- observer.set(
- Observer { newValue ->
- if (isInitialized()) {
- GlobalScope.launch(Dispatchers.Main) {
- observer.getAndSet(null)?.let { observerSnapshot ->
- removeObserver(observerSnapshot)
- continuation.resume(newValue)
- }
+ observer.set(Observer { newValue ->
+ if (isValueInitialized()) {
+ GlobalScope.launch(Dispatchers.Main) {
+ observer.getAndSet(null)?.let { observerSnapshot ->
+ removeObserver(observerSnapshot)
+ continuation.resume(newValue)
+ }
}
}
}
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/AccessibilitySourceService.kt b/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt
index 33acd8285..0450b1bdd 100644
--- a/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt
+++ b/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt
@@ -147,9 +147,7 @@ class AccessibilitySourceService(
interruptJobIfCanceled(cancel)
val a11yServiceList = getEnabledAccessibilityServices()
if (a11yServiceList.isEmpty()) {
- if (DEBUG) {
- Log.v(LOG_TAG, "accessibility services not enabled, job completed.")
- }
+ Log.d(LOG_TAG, "accessibility services not enabled, job completed.")
jobService.jobFinished(params, false)
jobService.clearJob()
return
@@ -452,9 +450,7 @@ class AccessibilitySourceService(
val dataBuilder = SafetySourceData.Builder()
pendingIssues.forEach { dataBuilder.addIssue(it) }
val safetySourceData = dataBuilder.build()
- if (DEBUG) {
- Log.v(LOG_TAG, "sending ${pendingIssues.size} issue to sc, data: $safetySourceData")
- }
+ Log.d(LOG_TAG, "a11y source sending ${pendingIssues.size} issue to sc")
safetyCenterManager.setSafetySourceData(
SC_ACCESSIBILITY_SOURCE_ID,
safetySourceData,
@@ -506,8 +502,10 @@ class AccessibilitySourceService(
installedServices[it]
}
- return enabledServices.filterNotNull()
+ val enabled3rdPartyServices = enabledServices.filterNotNull()
.filter { !it.isAccessibilityTool }
+ Log.d(LOG_TAG, "enabled a11y services count ${enabledServices.size}")
+ return enabled3rdPartyServices
}
/**
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/role/ui/RoleListSortFunction.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListSortFunction.java
index b3e34236f..ca059aa32 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListSortFunction.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListSortFunction.java
@@ -20,17 +20,18 @@ import android.content.Context;
import android.icu.text.Collator;
import androidx.annotation.NonNull;
-import androidx.arch.core.util.Function;
+
+import kotlin.jvm.functions.Function1;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
- * A function for {@link androidx.lifecycle.Transformations#map(androidx.lifecycle.LiveData,
- * Function)} that sorts a live data for role list.
+ * A function for {@link androidx.lifecycle#map(androidx.lifecycle.LiveData, Function1)}
+ * that sorts a live data for role list.
*/
-public class RoleListSortFunction implements Function<List<RoleItem>, List<RoleItem>> {
+public class RoleListSortFunction implements Function1<List<RoleItem>, List<RoleItem>> {
@NonNull
private final Comparator<RoleItem> mComparator;
@@ -42,10 +43,9 @@ public class RoleListSortFunction implements Function<List<RoleItem>, List<RoleI
roleItem.getRole().getShortLabelResource()), collator);
}
- @NonNull
@Override
- public List<RoleItem> apply(@NonNull List<RoleItem> input) {
- List<RoleItem> sorted = new ArrayList<>(input);
+ public List<RoleItem> invoke(List<RoleItem> p1) {
+ List<RoleItem> sorted = new ArrayList<>(p1);
sorted.sort(mComparator);
return sorted;
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleSortFunction.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleSortFunction.java
index 5e74d5e89..10db9dbcd 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleSortFunction.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleSortFunction.java
@@ -23,19 +23,20 @@ import android.os.UserHandle;
import android.util.Pair;
import androidx.annotation.NonNull;
-import androidx.arch.core.util.Function;
import com.android.permissioncontroller.permission.utils.Utils;
+import kotlin.jvm.functions.Function1;
+
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
- * A function for {@link androidx.lifecycle.Transformations#map(androidx.lifecycle.LiveData,
- * Function)} that sorts a live data for role.
+ * A function for {@link androidx.lifecycle#map(androidx.lifecycle.LiveData, Function1)}
+ * that sorts a live data for role.
*/
-public class RoleSortFunction implements Function<List<Pair<ApplicationInfo, Boolean>>,
+public class RoleSortFunction implements Function1<List<Pair<ApplicationInfo, Boolean>>,
List<Pair<ApplicationInfo, Boolean>>> {
@NonNull
@@ -51,11 +52,9 @@ public class RoleSortFunction implements Function<List<Pair<ApplicationInfo, Boo
mComparator = labelComparator.thenComparing(userIdComparator);
}
- @NonNull
@Override
- public List<Pair<ApplicationInfo, Boolean>> apply(
- @NonNull List<Pair<ApplicationInfo, Boolean>> input) {
- List<Pair<ApplicationInfo, Boolean>> sorted = new ArrayList<>(input);
+ public List<Pair<ApplicationInfo, Boolean>> invoke(List<Pair<ApplicationInfo, Boolean>> p1) {
+ List<Pair<ApplicationInfo, Boolean>> sorted = new ArrayList<>(p1);
sorted.sort(mComparator);
return sorted;
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt
index 4714d42cb..4ddcf1c3d 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt
@@ -33,9 +33,9 @@ import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat.getMainExecutor
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.map
import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.safetycenter.ui.InteractionLogger
import com.android.permissioncontroller.safetycenter.ui.NavigationSource
@@ -47,7 +47,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
private val TAG: String = LiveSafetyCenterViewModel::class.java.simpleName
override val statusUiLiveData: LiveData<StatusUiData>
- get() = Transformations.map(safetyCenterUiLiveData) { StatusUiData(it.safetyCenterData) }
+ get() = safetyCenterUiLiveData.map { StatusUiData(it.safetyCenterData) }
override val safetyCenterUiLiveData: LiveData<SafetyCenterUiData> by this::_safetyCenterLiveData
override val errorLiveData: LiveData<SafetyCenterErrorDetails> by this::_errorLiveData
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..863c7baab 100644
--- a/PermissionController/tests/mocking/Android.bp
+++ b/PermissionController/tests/mocking/Android.bp
@@ -53,6 +53,10 @@ android_test {
"iconloader_sc_mainline_prod",
"com.google.android.material_material",
"androidx.transition_transition",
+ "androidx.compose.foundation_foundation",
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.runtime_runtime-livedata",
+ "androidx.compose.ui_ui",
"androidx-constraintlayout_constraintlayout",
"androidx.core_core",
"androidx.media_media",
@@ -67,6 +71,7 @@ android_test {
"androidx.leanback_leanback-preference",
"androidx.lifecycle_lifecycle-extensions",
"androidx.lifecycle_lifecycle-common-java8",
+ "androidx.wear.compose_compose-material",
"kotlin-stdlib",
"kotlinx-coroutines-android",
"androidx.navigation_navigation-common-ktx",
@@ -139,5 +144,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/SafetyCenter/Resources/shared_res/values-af/strings.xml b/SafetyCenter/Resources/shared_res/values-af/strings.xml
index 3c20c6428..9ab860ecf 100644
--- a/SafetyCenter/Resources/shared_res/values-af/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-af/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Sien waarskuwing}other{Sien waarskuwings}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Kon nie bladsy oopmaak nie"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Kon nie opletberig afhandel nie"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Kon nie instellings herlaai nie"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Kon nie instelling nagaan nie}other{Kon nie instellings nagaan nie}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Werkprofiel is onderbreek"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nog geen inligting nie"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-am/strings.xml b/SafetyCenter/Resources/shared_res/values-am/strings.xml
index d1080ebf7..093dda2f2 100644
--- a/SafetyCenter/Resources/shared_res/values-am/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-am/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ማንቂያ ይመልከቱ}one{ማንቂያ ይመልከቱ}other{ማንቂያዎች ይመልከቱ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ገጹን መከፈት አልተቻለም"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ማንቂያን መፍታት አልተቻለም"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"ቅንብሮችን ማደስ አልተቻለም"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ቅንብርን መፈተሽ አልተቻለም}one{ቅንብርን መፈተሽ አልተቻለም}other{ቅንብሮችን መፈተሽ አልተቻለም}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"የስራ መገለጫ ባለበት ቆሟል"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ገና ምንም መረጃ የለም"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ar/strings.xml b/SafetyCenter/Resources/shared_res/values-ar/strings.xml
index 49e16c39c..ce0b1c10d 100644
--- a/SafetyCenter/Resources/shared_res/values-ar/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ar/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{الاطّلاع على التنبيه}zero{الاطّلاع على التنبيهات}two{الاطّلاع على التنبيهَين}few{الاطّلاع على التنبيهات}many{الاطّلاع على التنبيهات}other{الاطّلاع على التنبيهات}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"تعذَّر فتح الصفحة"</string>
<string name="resolving_action_error" msgid="371968886143262375">"تعذَّر التعامل بشكل نهائي مع التنبيه"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"تعذّر تحديث الإعدادات"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{تعذّر التحقّق من الإعداد.}zero{تعذّر التحقّق من الإعدادات.}two{تعذّر التحقّق من الإعدادَين.}few{تعذّر التحقّق من الإعدادات.}many{تعذّر التحقّق من الإعدادات.}other{تعذّر التحقّق من الإعدادات.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"تم إيقاف الملف الشخصي للعمل مؤقتًا"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ما مِن معلومات بعد."</string>
diff --git a/SafetyCenter/Resources/shared_res/values-as/strings.xml b/SafetyCenter/Resources/shared_res/values-as/strings.xml
index 4105c26d0..473003f59 100644
--- a/SafetyCenter/Resources/shared_res/values-as/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-as/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{সতৰ্কবাৰ্তা চাওক}one{সতৰ্কবাৰ্তাসমূহ চাওক}other{সতৰ্কবাৰ্তাসমূহ চাওক}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"পৃষ্ঠাখন খুলিব পৰা নগ’ল"</string>
<string name="resolving_action_error" msgid="371968886143262375">"সতৰ্কবাৰ্তা সমাধান কৰিব পৰা নগ’ল"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"ছেটিং ৰিফ্ৰেশ্ব কৰিব পৰা নগ’ল"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ছেটিং পৰীক্ষা কৰিব পৰা নগ’ল}one{ছেটিং পৰীক্ষা কৰিব পৰা নগ’ল}other{ছেটিং পৰীক্ষা কৰিব পৰা নগ’ল}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"কৰ্মস্থানৰ প্ৰ’ফাইলটো পজ কৰা আছে"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"এতিয়ালৈকে কোনো তথ্য নাই"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-az/strings.xml b/SafetyCenter/Resources/shared_res/values-az/strings.xml
index 7e2e7c140..e3c31a178 100644
--- a/SafetyCenter/Resources/shared_res/values-az/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-az/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Xəbərdarlığa baxın}other{Xəbərdarlıqlara baxın}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Səhifəni açmaq mümkün olmadı"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Siqnalı həll etmək mümkün olmadı"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Ayarları yeniləmək mümkün olmadı"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ayarı yoxlamaq alınmadı}other{Ayarları yoxlamaq alınmadı}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"İş profili durdurulub"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Hələ ki, məlumat yoxdur"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml b/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml
index 19660cb06..5233edc91 100644
--- a/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Prikaži obaveštenje}one{Prikaži obaveštenja}few{Prikaži obaveštenja}other{Prikaži obaveštenja}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Otvaranje stranice nije uspelo"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Rešavanje obaveštenja nije uspelo"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Osvežavanje podešavanja nije uspelo"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Provera podešavanja nije uspela}one{Provera podešavanja nije uspela}few{Provera podešavanja nije uspela}other{Provera podešavanja nije uspela}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Poslovni profil je pauziran"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Još nema informacija"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-be/strings.xml b/SafetyCenter/Resources/shared_res/values-be/strings.xml
index 2c27670e4..2b386f3e6 100644
--- a/SafetyCenter/Resources/shared_res/values-be/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-be/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Паглядзець абвестку}one{Паглядзець абвесткі}few{Паглядзець абвесткі}many{Паглядзець абвесткі}other{Паглядзець абвесткі}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Не ўдалося адкрыць старонку"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Не ўдалося вырашыць праблему"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Не ўдалося абнавіць налады"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Не ўдалося праверыць наладу}one{Не ўдалося праверыць налады}few{Не ўдалося праверыць налады}many{Не ўдалося праверыць налады}other{Не ўдалося праверыць налады}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Працоўны профіль прыпынены"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Пакуль няма інфармацыі"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-bg/strings.xml b/SafetyCenter/Resources/shared_res/values-bg/strings.xml
index e3495d4d5..85833f145 100644
--- a/SafetyCenter/Resources/shared_res/values-bg/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-bg/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Преглед на сигнала}other{Преглед на сигналите}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Страницата не се отвори"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Сигналът не се отстрани"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Настройките не бяха опреснени"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Настройката не бе проверена}other{Настройките не бяха проверени}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Служебният потребителски профил е поставен на пауза"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Още няма информация"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-bn/strings.xml b/SafetyCenter/Resources/shared_res/values-bn/strings.xml
index 3f24f05a4..30904c97e 100644
--- a/SafetyCenter/Resources/shared_res/values-bn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-bn/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{বিজ্ঞপ্তি দেখুন}one{বিজ্ঞপ্তি দেখুন}other{বিজ্ঞপ্তি দেখুন}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"পৃষ্ঠা খোলা যায়নি"</string>
<string name="resolving_action_error" msgid="371968886143262375">"সতর্কতার সমাধান করা যায়নি"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"সেটিংস রিফ্রেশ করা যায়নি"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{সেটিং চেক করা যায়নি}one{সেটিংস চেক করা যায়নি}other{সেটিংস চেক করা যায়নি}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"অফিস প্রোফাইল পজ করা আছে"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"এখনও কোনও তথ্য নেই"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-bs/strings.xml b/SafetyCenter/Resources/shared_res/values-bs/strings.xml
index a877f8b36..93cfcf8fa 100644
--- a/SafetyCenter/Resources/shared_res/values-bs/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-bs/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Pogledajte upozorenje}one{Pogledajte upozorenja}few{Pogledajte upozorenja}other{Pogledajte upozorenja}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Otvaranje stranice nije uspjelo"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Rješavanje upozorenja nije uspjelo"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Osvježavanje postavki nije uspjelo"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Provjera postavke nije uspjela}one{Provjera postavki nije uspjela}few{Provjera postavki nije uspjela}other{Provjera postavki nije uspjela}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Radni profil je pauziran"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Još uvijek nema informacija"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ca/strings.xml b/SafetyCenter/Resources/shared_res/values-ca/strings.xml
index a2d8b0f35..dbec15679 100644
--- a/SafetyCenter/Resources/shared_res/values-ca/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ca/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Mostra l\'alerta}many{Mostra les alertes}other{Mostra les alertes}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"No s\'ha pogut obrir la pàgina"</string>
<string name="resolving_action_error" msgid="371968886143262375">"No s\'ha pogut resoldre l\'alerta"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"No s\'ha pogut actualitzar la configuració"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{No s\'ha pogut comprovar la configuració}many{No s\'ha pogut comprovar la configuració}other{No s\'ha pogut comprovar la configuració}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"El perfil de treball s\'ha posat en pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Encara no hi ha informació"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-cs/strings.xml b/SafetyCenter/Resources/shared_res/values-cs/strings.xml
index 28aabd4e8..9d90b05aa 100644
--- a/SafetyCenter/Resources/shared_res/values-cs/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-cs/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Zobrazit upozornění}few{Zobrazit upozornění}many{Zobrazit upozornění}other{Zobrazit upozornění}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Stránku nelze otevřít"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Upozornění se nepodařilo vyřešit"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Nastavení se nepodařilo obnovit"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nastavení nelze zkontrolovat}few{Nastavení nelze zkontrolovat}many{Nastavení nelze zkontrolovat}other{Nastavení nelze zkontrolovat}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Pracovní profil je pozastaven"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Zatím žádné údaje"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-da/strings.xml b/SafetyCenter/Resources/shared_res/values-da/strings.xml
index e4d28aaf1..bce5898fa 100644
--- a/SafetyCenter/Resources/shared_res/values-da/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-da/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Se underretning}one{Se underretning}other{Se underretninger}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Siden kunne ikke åbnes"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Underretningen kunne ikke behandles"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Indstillingerne kunne ikke opdateres"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Indstillingen kunne ikke tjekkes}one{Indstillingen kunne ikke tjekkes}other{Indstillingerne kunne ikke tjekkes}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Arbejdsprofilen er sat på pause"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Der er ingen oplysninger endnu"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-de/strings.xml b/SafetyCenter/Resources/shared_res/values-de/strings.xml
index a5b1758ad..cbaf373d1 100644
--- a/SafetyCenter/Resources/shared_res/values-de/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-de/strings.xml
@@ -36,10 +36,11 @@
<string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Mögliche Sicherheitsrisiken gefunden"</string>
<string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Sicherheitsrisiken gefunden"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Konto eventuell gefährdet"</string>
- <string name="overall_severity_level_critical_account_warning_title" msgid="1913235490583842004">"Konto ist gefährdet"</string>
+ <string name="overall_severity_level_critical_account_warning_title" msgid="1913235490583842004">"Konto gefährdet"</string>
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Benachrichtigung ansehen}other{Benachrichtigungen ansehen}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Seite konnte nicht geöffnet werden"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ursache konnte nicht behoben werden"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Einstellungen konnten nicht aktualisiert werden"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Einstellung konnte nicht überprüft werden}other{Einstellungen konnten nicht überprüft werden}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Arbeitsprofil pausiert"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Noch keine Angaben vorhanden"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-el/strings.xml b/SafetyCenter/Resources/shared_res/values-el/strings.xml
index c6f005d5f..ac93dedd8 100644
--- a/SafetyCenter/Resources/shared_res/values-el/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-el/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Εμφάνιση ειδοποίησης}other{Εμφάνιση ειδοποιήσεων}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Δεν ήταν δυνατό το άνοιγμα της σελίδας"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Δεν ήταν δυνατή η επίλυση της ειδοποίησης"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Δεν ήταν δυνατή η ανανέωση των ρυθμίσεων"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Δεν ήταν δυνατός ο έλεγχος της ρύθμισης}other{Δεν ήταν δυνατός ο έλεγχος των ρυθμίσεων}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Το προφίλ εργασίας έχει τεθεί σε παύση"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Δεν υπάρχουν ακόμα πληροφορίες"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml
index f389be966..7e0312660 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{See alert}other{See alerts}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Couldn\'t open page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Couldn\'t resolve alert"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Couldn\'t refresh settings"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Couldn\'t check setting}other{Couldn\'t check settings}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Work profile is paused"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"No info yet"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml
index 00234beca..f63228247 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{See alert}other{See alerts}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Couldnt open page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Couldnt resolve alert"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Couldnt refresh settings"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Couldnt check setting}other{Couldnt check settings}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Work profile is paused"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"No info yet"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml
index f389be966..7e0312660 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{See alert}other{See alerts}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Couldn\'t open page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Couldn\'t resolve alert"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Couldn\'t refresh settings"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Couldn\'t check setting}other{Couldn\'t check settings}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Work profile is paused"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"No info yet"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml
index f389be966..7e0312660 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{See alert}other{See alerts}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Couldn\'t open page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Couldn\'t resolve alert"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Couldn\'t refresh settings"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Couldn\'t check setting}other{Couldn\'t check settings}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Work profile is paused"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"No info yet"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml
index 811e6e7fd..d1cd45d43 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎See alert‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎See alerts‎‏‎‎‏‎}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎Couldnt open page‎‏‎‎‏‎"</string>
<string name="resolving_action_error" msgid="371968886143262375">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎Couldnt resolve alert‎‏‎‎‏‎"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎Couldnt refresh settings‎‏‎‎‏‎"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‎Couldnt check setting‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‎Couldnt check settings‎‏‎‎‏‎}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‏‏‎Work profile is paused‎‏‎‎‏‎"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎No info yet‎‏‎‎‏‎"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml b/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml
index 29613fc69..787838a20 100644
--- a/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml
@@ -36,10 +36,11 @@
<string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Se detectaron riesgos potenciales"</string>
<string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Se detectaron riesgos"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"La cuenta podría estar en riesgo"</string>
- <string name="overall_severity_level_critical_account_warning_title" msgid="1913235490583842004">"La cuenta está en riesgo"</string>
+ <string name="overall_severity_level_critical_account_warning_title" msgid="1913235490583842004">"Cuenta en peligro"</string>
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ver alerta}many{Ver alertas}other{Ver alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"No se pudo abrir la página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"No se pudo resolver la alerta"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"No se pudo actualizar la configuración"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{No se pudo revisar el parámetro de configuración}many{No se pudieron revisar los parámetros de configuración}other{No se pudieron revisar los parámetros de configuración}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"El perfil de trabajo está en pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aún no hay información"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-es/strings.xml b/SafetyCenter/Resources/shared_res/values-es/strings.xml
index a13a68d8f..245d3394c 100644
--- a/SafetyCenter/Resources/shared_res/values-es/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-es/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ver alerta}many{Ver alertas}other{Ver alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"No se ha podido abrir la página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"No se ha podido resolver la alerta"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"No se han podido actualizar los ajustes"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{No se ha podido comprobar el ajuste}many{No se han podido comprobar los ajustes}other{No se han podido comprobar los ajustes}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"El perfil de trabajo está en pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aún no hay información"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-et/strings.xml b/SafetyCenter/Resources/shared_res/values-et/strings.xml
index cfe0541d2..0aab37815 100644
--- a/SafetyCenter/Resources/shared_res/values-et/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-et/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Vaadake hoiatust}other{Vaadake hoiatusi}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Lehte ei saanud avada"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Hoiatusega seotud probleemi ei saanud lahendada"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Seadeid ei saanud värskendada"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Seadet ei õnnestunud kontrollida}other{Seadeid ei õnnestunud kontrollida}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Tööprofiil on peatatud"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Teavet ei ole veel"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-eu/strings.xml b/SafetyCenter/Resources/shared_res/values-eu/strings.xml
index c00cb5827..88942a47b 100644
--- a/SafetyCenter/Resources/shared_res/values-eu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-eu/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ikusi alerta}other{Ikusi alertak}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Ezin da ireki orria"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ezin izan da ebatzi alerta"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Ezin izan dira freskatu ezarpenak"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ezin izan da egiaztatu ezarpena}other{Ezin izan dira egiaztatu ezarpenak}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Laneko profila pausatuta dago"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ez dago informaziorik oraindik"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fa/strings.xml b/SafetyCenter/Resources/shared_res/values-fa/strings.xml
index 744ac950c..df11d470f 100644
--- a/SafetyCenter/Resources/shared_res/values-fa/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fa/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{دیدن هشدار}one{دیدن هشدار}other{دیدن هشدارها}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"صفحه باز نشد"</string>
<string name="resolving_action_error" msgid="371968886143262375">"هشدار رفع نشد"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"تنظیمات بازآوری نشد"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{تنظیم بررسی نشد}one{تنظیم بررسی نشد}other{تنظیمات بررسی نشدند}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"نمایه کاری موقتاً متوقف شده است"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"هنوز اطلاعاتی دردسترس نیست"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fi/strings.xml b/SafetyCenter/Resources/shared_res/values-fi/strings.xml
index 801b40081..bad542e6d 100644
--- a/SafetyCenter/Resources/shared_res/values-fi/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fi/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Näytä ilmoitus}other{Näytä ilmoitukset}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Sivun avaaminen epäonnistui"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Hälytyksen ratkaiseminen epäonnistui"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Asetuksia ei voitu päivittää"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Asetuksen tarkistaminen ei onnistunut}other{Asetusten tarkistaminen ei onnistunut}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Työprofiilin käyttö on keskeytetty"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ei vielä tietoa"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml b/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml
index 3956c61f5..c1530b2cb 100644
--- a/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Afficher l\'alerte}one{Afficher l\'alerte}many{Afficher les alertes}other{Afficher les alertes}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Impossible d\'ouvrir la page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Impossible de résoudre l\'alerte"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Impossible d\'actualiser les paramètres"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Impossible de vérifier le paramètre}one{Impossible de vérifier le paramètre}many{Impossible de vérifier les paramètres}other{Impossible de vérifier les paramètres}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Le profil professionnel est interrompu"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aucune donnée pour le moment"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fr/strings.xml b/SafetyCenter/Resources/shared_res/values-fr/strings.xml
index b05f99a15..d7bf215a0 100644
--- a/SafetyCenter/Resources/shared_res/values-fr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fr/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Voir l\'alerte}one{Voir l\'alerte}many{Voir les alertes}other{Voir les alertes}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Impossible d\'accéder à la page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Impossible de résoudre l\'alerte"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Impossible d\'actualiser les paramètres"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Impossible de vérifier le paramètre}one{Impossible de vérifier le paramètre}many{Impossible de vérifier les paramètres}other{Impossible de vérifier les paramètres}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profil professionnel en pause"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aucune info pour l\'instant"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-gl/strings.xml b/SafetyCenter/Resources/shared_res/values-gl/strings.xml
index 3a0251dd2..ea934a37b 100644
--- a/SafetyCenter/Resources/shared_res/values-gl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-gl/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Consulta a alerta}other{Consulta as alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Non se puido abrir a páxina"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Non se puido resolver a alerta"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Non se puido actualizar a configuración"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Non se puido comprobar a opción de configuración}other{Non se puideron comprobar as opcións de configuración}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"O perfil de traballo está en pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aínda non hai información"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-gu/strings.xml b/SafetyCenter/Resources/shared_res/values-gu/strings.xml
index ded82b3e5..0894b304b 100644
--- a/SafetyCenter/Resources/shared_res/values-gu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-gu/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{અલર્ટ જુઓ}one{અલર્ટ જુઓ}other{અલર્ટ જુઓ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"પેજ ખોલી શક્યા નથી"</string>
<string name="resolving_action_error" msgid="371968886143262375">"અલર્ટનું નિરાકરણ લાવી શક્યા નથી"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"સેટિંગ રિફ્રેશ કરી શકાયા નથી"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{સેટિંગ ચેક કરી શકાયું નથી}one{સેટિંગ ચેક કરી શકાયું નથી}other{સેટિંગ ચેક કરી શકાયા નથી}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ઑફિસની પ્રોફાઇલ થોભાવી છે"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"હજી સુધી કોઈ માહિતી નથી"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hi/strings.xml b/SafetyCenter/Resources/shared_res/values-hi/strings.xml
index 6fe62b414..c9a80e7f0 100644
--- a/SafetyCenter/Resources/shared_res/values-hi/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hi/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{चेतावनी देखें}one{चेतावनी देखें}other{चेतावनियां देखें}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"पेज को खोला नहीं जा सका"</string>
<string name="resolving_action_error" msgid="371968886143262375">"चेतावनी में बताई गई समस्या को ठीक नहीं किया जा सका"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"सेटिंग को रीफ़्रेश नहीं किया जा सका"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{सेटिंग की जांच नहीं की जा सकी}one{सेटिंग की जांच नहीं की जा सकी}other{सेटिंग की जांच नहीं की जा सकी}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"वर्क प्रोफ़ाइल रोक दी गई है"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"कोई जानकारी मौजूद नहीं है"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hr/strings.xml b/SafetyCenter/Resources/shared_res/values-hr/strings.xml
index a644d6126..ed02bc991 100644
--- a/SafetyCenter/Resources/shared_res/values-hr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hr/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Pogledajte upozorenje}one{Pogledajte upozorenja}few{Pogledajte upozorenja}other{Pogledajte upozorenja}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Otvaranje stranice nije uspjelo"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Razrješavanje upozorenja nije uspjelo"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Nije bilo moglo osvježiti postavke"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nije moguće provjeriti postavku}one{Nije moguće provjeriti postavke}few{Nije moguće provjeriti postavke}other{Nije moguće provjeriti postavke}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Poslovni profil je pauziran"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Još nema podataka"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hu/strings.xml b/SafetyCenter/Resources/shared_res/values-hu/strings.xml
index b8a8540ef..5f97845bc 100644
--- a/SafetyCenter/Resources/shared_res/values-hu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hu/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Értesítés megtekintése}other{Értesítések megtekintése}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Nem sikerült megnyitni az oldalt"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nem sikerült feloldani a figyelmeztetést"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Nem sikerült a beállítások frissítése"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nem sikerült a beállítás ellenőrzése}other{Nem sikerült a beállítások ellenőrzése}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"A munkaprofil használata szünetel"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Még nincsenek adatok"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hy/strings.xml b/SafetyCenter/Resources/shared_res/values-hy/strings.xml
index e6b05056b..befaeb8e5 100644
--- a/SafetyCenter/Resources/shared_res/values-hy/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hy/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Տեսնել ծանուցումը}one{Տեսնել ծանուցումները}other{Տեսնել ծանուցումները}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Չհաջողվեց բացել էջը"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Չհաջողվեց լուծել ծանուցումը"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Չհաջողվեց թարմացնել կարգավորումները"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Չհաջողվեց ստուգել կարգավորումը}one{Չհաջողվեց ստուգել կարգավորումը}other{Չհաջողվեց ստուգել կարգավորումները}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Աշխատանքային պրոֆիլը դադարեցված է"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Տեղեկություններ դեռ չկան"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-in/strings.xml b/SafetyCenter/Resources/shared_res/values-in/strings.xml
index 8d25d7340..cd584dbab 100644
--- a/SafetyCenter/Resources/shared_res/values-in/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-in/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Lihat peringatan}other{Lihat peringatan}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Tidak dapat membuka halaman"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Tidak dapat menyelesaikan peringatan"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Tidak dapat merefresh setelan"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Tidak dapat memeriksa setelan}other{Tidak dapat memeriksa setelan}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profil kerja dijeda"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Belum ada info"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-is/strings.xml b/SafetyCenter/Resources/shared_res/values-is/strings.xml
index faa4c2e1f..3e36f81f1 100644
--- a/SafetyCenter/Resources/shared_res/values-is/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-is/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Sjá viðvörun}one{Sjá viðvaranir}other{Sjá viðvaranir}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Ekki tókst að opna síðuna"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ekki tókst að leysa úr viðvöruninni"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Ekki tókst að endurnýja stillingar"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ekki tókst að athuga stillingu}one{Ekki tókst að athuga stillingar}other{Ekki tókst að athuga stillingar}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Hlé gert á vinnusniði"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Engar upplýsingar ennþá"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-it/strings.xml b/SafetyCenter/Resources/shared_res/values-it/strings.xml
index b2661e84a..68aa4eab3 100644
--- a/SafetyCenter/Resources/shared_res/values-it/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-it/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Visualizza l\'avviso}many{Visualizza gli avvisi}other{Visualizza gli avvisi}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Impossibile aprire la pagina"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Impossibile risolvere l\'avviso"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Impossibile aggiornare le impostazioni"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Impossibile controllare l\'impostazione}many{Impossibile controllare le impostazioni}other{Impossibile controllare le impostazioni}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profilo di lavoro in pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ancora nessuna informazione"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-iw/strings.xml b/SafetyCenter/Resources/shared_res/values-iw/strings.xml
index 8a0794632..05cb0cd18 100644
--- a/SafetyCenter/Resources/shared_res/values-iw/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-iw/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{הצגת ההתראה}one{הצגת ההתראות}two{הצגת ההתראות}other{הצגת ההתראות}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"לא ניתן היה לפתוח את הדף"</string>
<string name="resolving_action_error" msgid="371968886143262375">"לא ניתן היה לפתור את הבעיה בהתראה"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"לא ניתן היה לרענן את ההגדרות"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{לא ניתן היה לבדוק את ההגדרה}one{לא ניתן היה לבדוק את ההגדרות}two{לא ניתן היה לבדוק את ההגדרות}other{לא ניתן היה לבדוק את ההגדרות}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"פרופיל העבודה מושהה"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"אין עדיין פרטים"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ja/strings.xml b/SafetyCenter/Resources/shared_res/values-ja/strings.xml
index 393ca271c..aa760e5d7 100644
--- a/SafetyCenter/Resources/shared_res/values-ja/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ja/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{アラートを確認する}other{アラートを確認する}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ページを開けませんでした"</string>
<string name="resolving_action_error" msgid="371968886143262375">"アラートを解決できませんでした"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"設定を更新できませんでした"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{設定を確認できませんでした}other{設定を確認できませんでした}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"仕事用プロファイルが一時停止しています"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"まだ情報がありません"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ka/strings.xml b/SafetyCenter/Resources/shared_res/values-ka/strings.xml
index de8890dec..4dda15927 100644
--- a/SafetyCenter/Resources/shared_res/values-ka/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ka/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{გაფრთხილების ნახვა}other{გაფრთხილებების ნახვა}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"გვერდის გახსნა ვერ მოხერხდა"</string>
<string name="resolving_action_error" msgid="371968886143262375">"გაფრთხილება ვერ გადაიჭრა"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"პარამეტრები ვერ განახლდა"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{პარამეტრის შემოწმება ვერ მოხერხდა}other{პარამეტრების Შემოწმება ვერ მოხერხდა}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"სამსახურის პროფილი დაპაუზებულია"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ინფო ჯერ არ არის"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-kk/strings.xml b/SafetyCenter/Resources/shared_res/values-kk/strings.xml
index 9226d4fcb..5ea3fa3e1 100644
--- a/SafetyCenter/Resources/shared_res/values-kk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-kk/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Хабарландыруды көру}other{Хабарландыруларды көру}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Бет ашылмады."</string>
<string name="resolving_action_error" msgid="371968886143262375">"Хабарландыруда көрсетілген мәселе шешілмеді."</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Параметрлер жаңартылмады."</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Параметрді тексеру мүмкін болмады.}other{Параметрлерді тексеру мүмкін болмады.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Жұмыс профилі кідіртілді."</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Әзірге мәлімет жоқ."</string>
diff --git a/SafetyCenter/Resources/shared_res/values-km/strings.xml b/SafetyCenter/Resources/shared_res/values-km/strings.xml
index e1af9019d..d2bf8fc48 100644
--- a/SafetyCenter/Resources/shared_res/values-km/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-km/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{មើលការជូន​ដំណឹង}other{មើលការជូនដំណឹង}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"មិនអាច​បើកទំព័រ​បានទេ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"មិនអាច​ដោះស្រាយការជូនដំណឹងនេះ​បានទេ"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"មិនអាចផ្ទុកការកំណត់ឡើងវិញបានទេ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{មិន​អាច​ពិនិត្យ​មើល​ការកំណត់​បាន​ទេ}other{មិន​អាច​ពិនិត្យ​មើល​ការកំណត់​បាន​ទេ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"កម្រងព័ត៌មានការងារត្រូវបាន​ផ្អាក"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"មិន​ទាន់​មាន​ព័ត៌មាន​នៅ​ឡើយ​ទេ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-kn/strings.xml b/SafetyCenter/Resources/shared_res/values-kn/strings.xml
index 46eedaac5..9c3cb9dca 100644
--- a/SafetyCenter/Resources/shared_res/values-kn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-kn/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ಎಚ್ಚರಿಕೆಯನ್ನು ನೋಡಿ}one{ಎಚ್ಚರಿಕೆಗಳನ್ನು ನೋಡಿ}other{ಎಚ್ಚರಿಕೆಗಳನ್ನು ನೋಡಿ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ಪುಟವನ್ನು ತೆರೆಯಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ಅಲರ್ಟ್ ಅನ್ನು ಬಗೆಹರಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ರಿಫ್ರೆಶ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ಸೆಟ್ಟಿಂಗ್ ಅನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ}one{ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ}other{ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ಅನ್ನು ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ಇನ್ನೂ ಯಾವುದೇ ಮಾಹಿತಿ ಲಭ್ಯವಿಲ್ಲ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ko/strings.xml b/SafetyCenter/Resources/shared_res/values-ko/strings.xml
index 1a5938c9a..295fe4992 100644
--- a/SafetyCenter/Resources/shared_res/values-ko/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ko/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{알림 보기}other{알림 보기}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"페이지를 열 수 없습니다."</string>
<string name="resolving_action_error" msgid="371968886143262375">"알림을 해결할 수 없습니다."</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"설정을 새로고침할 수 없습니다."</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{설정을 확인할 수 없습니다.}other{설정을 확인할 수 없습니다.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"직장 프로필이 일시중지됨"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"아직 정보 없음"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ky/strings.xml b/SafetyCenter/Resources/shared_res/values-ky/strings.xml
index 474377b87..2da0e82e4 100644
--- a/SafetyCenter/Resources/shared_res/values-ky/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ky/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Эскетүүнү көрүү}other{Эскертүүлөрдү көрүү}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Барак ачылган жок"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Эскертүү чечилген жок"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Параметрлер жаңырган жок"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Параметр текшерилген жок}other{Параметрлер текшерилген жок}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Жумуш профили тындырылды"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Азырынча маалымат жок"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-lo/strings.xml b/SafetyCenter/Resources/shared_res/values-lo/strings.xml
index 0abf76b30..724411879 100644
--- a/SafetyCenter/Resources/shared_res/values-lo/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-lo/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ເບິ່ງແຈ້ງເຕືອນ}other{ເບິ່ງແຈ້ງເຕືອນ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ບໍ່ສາມາດເປີດໜ້າໄດ້"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ບໍ່ສາມາດແກ້ໄຂແຈ້ງເຕືອນໄດ້"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"ບໍ່ສາມາດໂຫຼດການຕັ້ງຄ່າຄືນໃໝ່"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ກວດສອບການຕັ້ງຄ່າບໍ່ໄດ້}other{ກວດສອບການຕັ້ງຄ່າບໍ່ໄດ້}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ຢຸດໂປຣໄຟລ໌ວຽກໄວ້ຊົ່ວຄາວແລ້ວ"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ບໍ່ມີຂໍ້ມູນເທື່ອ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-lt/strings.xml b/SafetyCenter/Resources/shared_res/values-lt/strings.xml
index 832ef32f1..fc1704c13 100644
--- a/SafetyCenter/Resources/shared_res/values-lt/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-lt/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Žr. įspėjimą}one{Žr. įspėjimus}few{Žr. įspėjimus}many{Žr. įspėjimus}other{Žr. įspėjimus}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Nepavyko atidaryti puslapio"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nepavyko pašalinti įspėjimo"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Nepavyko atnaujinti nustatymų"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nepavyko patikrinti nustatymo}one{Nepavyko patikrinti nustatymų}few{Nepavyko patikrinti nustatymų}many{Nepavyko patikrinti nustatymų}other{Nepavyko patikrinti nustatymų}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Darbo profilis pristabdytas"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Kol kas informacijos nėra"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-lv/strings.xml b/SafetyCenter/Resources/shared_res/values-lv/strings.xml
index eb544b15f..c27624603 100644
--- a/SafetyCenter/Resources/shared_res/values-lv/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-lv/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Skatiet brīdinājumu}zero{Skatiet brīdinājumus}one{Skatiet brīdinājumus}other{Skatiet brīdinājumus}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Nevarēja atvērt lapu"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nevarēja atrisināt ieteikumu vai brīdinājumu"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Nevarēja atsvaidzināt iestatījumus"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nevarēja pārbaudīt iestatījumu.}zero{Nevarēja pārbaudīt iestatījumus.}one{Nevarēja pārbaudīt iestatījumus.}other{Nevarēja pārbaudīt iestatījumus.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Darba profila darbība ir apturēta"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Vēl nav informācijas"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-mk/strings.xml b/SafetyCenter/Resources/shared_res/values-mk/strings.xml
index 30b405c67..2ba7a1976 100644
--- a/SafetyCenter/Resources/shared_res/values-mk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-mk/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Видете го предупредувањето}one{Видете ги предупредувањата}other{Видете ги предупредувањата}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Не можеше да се отвори страницата"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Не можеше да се реши предупредувањето"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Не можеше да се освежат поставките"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Не може да се провери поставката}one{Не може да се проверат поставките}other{Не може да се проверат поставките}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Работниот профил е паузиран"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Сѐ уште нема податоци"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ml/strings.xml b/SafetyCenter/Resources/shared_res/values-ml/strings.xml
index 4a77469da..a05a7eed1 100644
--- a/SafetyCenter/Resources/shared_res/values-ml/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ml/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{മുന്നറിയിപ്പ് കാണുക}other{മുന്നറിയിപ്പുകൾ കാണുക}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"പേജ് തുറക്കാനായില്ല"</string>
<string name="resolving_action_error" msgid="371968886143262375">"മുന്നറിയിപ്പ് പരിഹരിക്കാനായില്ല"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"ക്രമീകരണം റീഫ്രഷ് ചെയ്യാനായില്ല"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ക്രമീകരണം പരിശോധിക്കാനായില്ല}other{ക്രമീകരണം പരിശോധിക്കാനായില്ല}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ഔദ്യോഗിക പ്രൊഫൈൽ തൽക്കാലം നിർത്തിയിരിക്കുന്നു"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ഇതുവരെ വിവരങ്ങളൊന്നുമില്ല"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-mn/strings.xml b/SafetyCenter/Resources/shared_res/values-mn/strings.xml
index 88f75f648..8b2f17f37 100644
--- a/SafetyCenter/Resources/shared_res/values-mn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-mn/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Сэрэмжлүүлгийг харах}other{Сэрэмжлүүлгүүдийг харах}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Хуудсыг нээж чадсангүй"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Сэрэмжлүүлгийг шийдвэрлэж чадсангүй"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Тохиргоог сэргээж чадсангүй"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Тохиргоог шалгаж чадсангүй}other{Тохиргоог шалгаж чадсангүй}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Ажлын профайлыг түр зогсоосон"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Мэдээлэл хараахан алга"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-mr/strings.xml b/SafetyCenter/Resources/shared_res/values-mr/strings.xml
index 0b47053cd..f406a2931 100644
--- a/SafetyCenter/Resources/shared_res/values-mr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-mr/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{सूचना पहा}other{सूचना पहा}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"पेज उघडता आले नाही"</string>
<string name="resolving_action_error" msgid="371968886143262375">"इशाऱ्याचे निराकरण करता आले नाही"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"सेटिंग्ज रिफ्रेश करता आली नाही"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{सेटिंग तपासता आले नाही}other{सेटिंग्ज तपासता आली नाहीत}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"कार्य प्रोफाइल थांबवली आहे"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"अद्याप कोणतीही माहिती नाही"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ms/strings.xml b/SafetyCenter/Resources/shared_res/values-ms/strings.xml
index f8c3e2c32..bb98c69d0 100644
--- a/SafetyCenter/Resources/shared_res/values-ms/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ms/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Lihat makluman}other{Lihat makluman}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Tidak dapat membuka halaman"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Tidak dapat menyelesaikan amaran"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Tidak dapat menyegar semula tetapan"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Tidak dapat menyemak tetapan}other{Tidak dapat menyemak tetapan}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profil kerja dijeda"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Belum ada maklumat lagi"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-my/strings.xml b/SafetyCenter/Resources/shared_res/values-my/strings.xml
index 76e53c8dc..af7f71a34 100644
--- a/SafetyCenter/Resources/shared_res/values-my/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-my/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{သတိပေးချက် ကြည့်ရန်}other{သတိပေးချက်များ ကြည့်ရန်}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"စာမျက်နှာကို ဖွင့်၍မရပါ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"သတိပေးချက်ကို ဖြေရှင်း၍မရပါ"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"ဆက်တင်များကို ပြန်လည် စတင်၍မရပါ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ဆက်တင်ကြည့်၍မရပါ}other{ဆက်တင်များ ကြည့်၍မရပါ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"အလုပ်ပရိုဖိုင် ခဏရပ်ထားသည်"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"အချက်အလက် မရှိသေးပါ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-nb/strings.xml b/SafetyCenter/Resources/shared_res/values-nb/strings.xml
index 61338a390..8365aaea8 100644
--- a/SafetyCenter/Resources/shared_res/values-nb/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-nb/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Se varselet}other{Se varslene}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Kunne ikke åpne siden"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Kunne ikke løse varselet"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Kunne ikke laste inn innstillingene på nytt"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Kunne ikke sjekke innstillingen}other{Kunne ikke sjekke innstillingene}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Jobbprofilen er satt på pause"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ingen informasjon ennå"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ne/strings.xml b/SafetyCenter/Resources/shared_res/values-ne/strings.xml
index e7c392a3b..4e55d23f1 100644
--- a/SafetyCenter/Resources/shared_res/values-ne/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ne/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{अलर्ट हेर्नुहोस्}other{अलर्टहरू हेर्नुहोस्}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"पेज खोल्न सकिएन"</string>
<string name="resolving_action_error" msgid="371968886143262375">"अलर्ट समाधान गर्न सकिएन"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"सेटिङ रिफ्रेस गर्न सकिएन"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{सेटिङ जाँच गर्न सकिएन}other{सेटिङहरू जाँच गर्न सकिएन}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"कार्य प्रोफाइल पज गरिएको छ"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"कुनै जानकारी उपलब्ध छैन"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-nl/strings.xml b/SafetyCenter/Resources/shared_res/values-nl/strings.xml
index ab8edda5d..b916f37bc 100644
--- a/SafetyCenter/Resources/shared_res/values-nl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-nl/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Melding bekijken}other{Meldingen bekijken}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Kan de pagina niet openen"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Kan melding niet oplossen"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Kan instellingen niet vernieuwen"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Kan instelling niet checken}other{Kan instellingen niet checken}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Werkprofiel is onderbroken"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nog geen informatie"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-or/strings.xml b/SafetyCenter/Resources/shared_res/values-or/strings.xml
index 341149120..cf550456f 100644
--- a/SafetyCenter/Resources/shared_res/values-or/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-or/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ଆଲର୍ଟ ଦେଖନ୍ତୁ}other{ଆଲର୍ଟଗୁଡ଼ିକ ଦେଖନ୍ତୁ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ପୃଷ୍ଠାକୁ ଖୋଲା ଯାଇପାରିଲା ନାହିଁ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ଆଲର୍ଟର ସମାଧାନ କରାଯାଇପାରିଲା ନାହିଁ"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"ସେଟିଂସ ରିଫ୍ରେସ କରାଯାଇପାରିଲା ନାହିଁ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ସେଟିଂ ଯାଞ୍ଚ କରାଯାଇପାରିଲା ନାହିଁ}other{ସେଟିଂସ ଯାଞ୍ଚ କରାଯାଇପାରିଲା ନାହିଁ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ୱାର୍କ ପ୍ରୋଫାଇଲକୁ ବିରତ କରାଯାଇଛି"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ସୂଚନା ନାହିଁ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pa/strings.xml b/SafetyCenter/Resources/shared_res/values-pa/strings.xml
index 54aad5cbb..e13d6327f 100644
--- a/SafetyCenter/Resources/shared_res/values-pa/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pa/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ਅਲਰਟ ਦੇਖੋ}one{ਅਲਰਟ ਦੇਖੋ}other{ਅਲਰਟ ਦੇਖੋ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ਪੰਨਾ ਖੋਲ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ਸੁਚੇਤਨਾ ਦਾ ਹੱਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"ਸੈਟਿੰਗਾਂ ਨੂੰ ਰਿਫ੍ਰੈਸ਼ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ਸੈਟਿੰਗ ਦੀ ਜਾਂਚ ਨਹੀਂ ਕਰ ਸਕੇ}one{ਸੈਟਿੰਗ ਦੀ ਜਾਂਚ ਨਹੀਂ ਕਰ ਸਕੇ}other{ਸੈਟਿੰਗਾਂ ਦੀ ਜਾਂਚ ਨਹੀਂ ਕਰ ਸਕੇ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ਅਜੇ ਕੋਈ ਜਾਣਕਾਰੀ ਨਹੀਂ ਹੈ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pl/strings.xml b/SafetyCenter/Resources/shared_res/values-pl/strings.xml
index 89a242e7f..23a331b7f 100644
--- a/SafetyCenter/Resources/shared_res/values-pl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pl/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Zobacz alert}few{Zobacz alerty}many{Zobacz alerty}other{Zobacz alerty}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Nie udało się otworzyć strony"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nie udało się rozwiązać problemu z alertu"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Nie udało się odświeżyć ustawień"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nie udało się sprawdzić ustawienia}few{Nie udało się sprawdzić ustawień}many{Nie udało się sprawdzić ustawień}other{Nie udało się sprawdzić ustawień}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Wstrzymano profil służbowy"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nie ma jeszcze informacji"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml b/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml
index 0599f1c24..b9d887f78 100644
--- a/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Mostrar alerta}one{Mostrar alerta}many{Mostrar alertas}other{Mostrar alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Não foi possível abrir a página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Não foi possível resolver o alerta"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Não foi possível atualizar as configurações"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Não foi possível verificar a configuração}one{Não foi possível verificar a configuração}many{Não foi possível verificar as configurações}other{Não foi possível verificar as configurações}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"O perfil de trabalho está pausado"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ainda não há informações"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml b/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml
index 835b8a14f..592e1b60a 100644
--- a/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Veja o alerta}many{Veja os alertas}other{Veja os alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Não foi possível abrir a página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Não foi possível resolver o alerta"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Não foi possível atualizar as definições"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Não foi possível verificar a definição}many{Não foi possível verificar as definições}other{Não foi possível verificar as definições}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Perfil de trabalho em pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ainda sem informações"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pt/strings.xml b/SafetyCenter/Resources/shared_res/values-pt/strings.xml
index 0599f1c24..b9d887f78 100644
--- a/SafetyCenter/Resources/shared_res/values-pt/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pt/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Mostrar alerta}one{Mostrar alerta}many{Mostrar alertas}other{Mostrar alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Não foi possível abrir a página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Não foi possível resolver o alerta"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Não foi possível atualizar as configurações"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Não foi possível verificar a configuração}one{Não foi possível verificar a configuração}many{Não foi possível verificar as configurações}other{Não foi possível verificar as configurações}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"O perfil de trabalho está pausado"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ainda não há informações"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ro/strings.xml b/SafetyCenter/Resources/shared_res/values-ro/strings.xml
index 8fa57dcb4..e64b8b497 100644
--- a/SafetyCenter/Resources/shared_res/values-ro/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ro/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Vezi alerta}few{Vezi alertele}other{Vezi alertele}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Pagina nu s-a putut deschide"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nu s-a putut rezolva alerta"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Nu s-au putut actualiza setările"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nu s-a putut verifica setarea}few{Nu s-au putut verifica setările}other{Nu s-au putut verifica setările}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profilul de serviciu este întrerupt"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nu există informații încă"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ru/strings.xml b/SafetyCenter/Resources/shared_res/values-ru/strings.xml
index b72d9b0f0..832dd396c 100644
--- a/SafetyCenter/Resources/shared_res/values-ru/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ru/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Посмотрите оповещение}one{Посмотрите оповещения}few{Посмотрите оповещения}many{Посмотрите оповещения}other{Посмотрите оповещения}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Не удалось открыть страницу."</string>
<string name="resolving_action_error" msgid="371968886143262375">"Не удалось устранить проблему."</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Не удалось обновить настройки"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Не удалось проверить параметр}one{Не удалось проверить параметры}few{Не удалось проверить параметры}many{Не удалось проверить параметры}other{Не удалось проверить параметры}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Действие рабочего профиля приостановлено."</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Данных пока нет"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-si/strings.xml b/SafetyCenter/Resources/shared_res/values-si/strings.xml
index 1ee63199f..08e6787cc 100644
--- a/SafetyCenter/Resources/shared_res/values-si/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-si/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ඇඟවීම බලන්න}one{ඇඟවීම් බලන්න}other{ඇඟවීම් බලන්න}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"පිටුව විවෘත කළ නොහැකි විය"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ඇඟවීම විසඳිය නොහැකි විය"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"සැකසීම් නැවුම් කිරීමට නොහැකි විය"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{සැකසීම පරීක්ෂා කිරීමට නොහැකි විය}one{සැකසීම් පරීක්ෂා කිරීමට නොහැකි විය}other{සැකසීම් පරීක්ෂා කිරීමට නොහැකි විය}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"කාර්යාල පැතිකඩ විරාම කර ඇත"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"තවම තතු නැත"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sk/strings.xml b/SafetyCenter/Resources/shared_res/values-sk/strings.xml
index 905c3ffe5..0c6da9c5b 100644
--- a/SafetyCenter/Resources/shared_res/values-sk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sk/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Zobraziť upozornenie}few{Zobraziť upozornenia}many{See alerts}other{Zobraziť upozornenia}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Stránku sa nepodarilo otvoriť"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Upozornenie sa nepodarilo vyriešiť"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Nastavenia sa nepodarilo obnoviť"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nastavenie sa nepodarilo skontrolovať}few{Nastavenia sa nepodarilo skontrolovať}many{Nastavenia sa nepodarilo skontrolovať}other{Nastavenia sa nepodarilo skontrolovať}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Pracovný profil je pozastavený"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Zatiaľ žiadne informácie"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sl/strings.xml b/SafetyCenter/Resources/shared_res/values-sl/strings.xml
index 4502b63fd..c8b0d7d00 100644
--- a/SafetyCenter/Resources/shared_res/values-sl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sl/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ogled opozorila}one{Ogled opozoril}two{Ogled opozoril}few{Ogled opozoril}other{Ogled opozoril}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Strani ni bilo mogoče odpreti."</string>
<string name="resolving_action_error" msgid="371968886143262375">"Opozorila ni bilo mogoče odpraviti."</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Nastavitev ni bilo mogoče osvežiti."</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nastavitve ni bilo mogoče preveriti.}one{Nastavitve ni bilo mogoče preveriti.}two{Nastavitev ni bilo mogoče preveriti.}few{Nastavitev ni bilo mogoče preveriti.}other{Nastavitev ni bilo mogoče preveriti.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Delovni profil je začasno zaustavljen."</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ni še nobenega podatka."</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sq/strings.xml b/SafetyCenter/Resources/shared_res/values-sq/strings.xml
index 0e4981d2f..68a9d3e1b 100644
--- a/SafetyCenter/Resources/shared_res/values-sq/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sq/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Shiko sinjalizimin}other{Shiko sinjalizimet}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Faqja nuk mund të hapej"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Sinjalizimi nuk mund të zgjidhej"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Cilësimet nuk mund të rifreskoheshin"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Cilësimi nuk mund të kontrollohej}other{Cilësimet nuk mund të kontrolloheshin}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profili i punës është në pauzë"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nuk ka ende informacione"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sr/strings.xml b/SafetyCenter/Resources/shared_res/values-sr/strings.xml
index e7a5db675..e258d138b 100644
--- a/SafetyCenter/Resources/shared_res/values-sr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sr/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Прикажи обавештење}one{Прикажи обавештења}few{Прикажи обавештења}other{Прикажи обавештења}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Отварање странице није успело"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Решавање обавештења није успело"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Освежавање подешавања није успело"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Провера подешавања није успела}one{Провера подешавања није успела}few{Провера подешавања није успела}other{Провера подешавања није успела}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Пословни профил је паузиран"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Још нема информација"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sv/strings.xml b/SafetyCenter/Resources/shared_res/values-sv/strings.xml
index e7773a565..a14fd7c8b 100644
--- a/SafetyCenter/Resources/shared_res/values-sv/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sv/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Se varning}other{Se varningar}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Det gick inte att öppna sidan"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Det gick inte att åtgärda varningen"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Det gick inte att uppdatera inställningarna"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Det gick inte att kontrollera inställningen}other{Det gick inte att kontrollera inställningarna}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Jobbprofilen är pausad"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ingen information än"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sw/strings.xml b/SafetyCenter/Resources/shared_res/values-sw/strings.xml
index b78abb739..aaea33e79 100644
--- a/SafetyCenter/Resources/shared_res/values-sw/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sw/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Angalia tahadhari}other{Angalia tahadhari}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Imeshindwa kufungua ukurasa"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Imeshindwa kutia alama kuwa arifa imeshughulikiwa"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Tumeshindwa kuonyesha upya mipangilio"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Imeshindwa kukagua mipangilio}other{Imeshindwa kukagua mipangilio}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Wasifu wa kazini umesimamishwa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Bado hakuna maelezo"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ta/strings.xml b/SafetyCenter/Resources/shared_res/values-ta/strings.xml
index 3acc13e92..05eb793e9 100644
--- a/SafetyCenter/Resources/shared_res/values-ta/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ta/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{விழிப்பூட்டலைப் பாருங்கள்}other{விழிப்பூட்டல்களைப் பாருங்கள்}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"பக்கத்தைத் திறக்க முடியவில்லை"</string>
<string name="resolving_action_error" msgid="371968886143262375">"எச்சரிக்கையைத் தீர்க்க முடியவில்லை"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"அமைப்புகளைப் புதுப்பிக்க முடியவில்லை"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{அமைப்பைச் சரிபார்க்க முடியவில்லை}other{அமைப்புகளைச் சரிபார்க்க முடியவில்லை}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"பணிக் கணக்கு இடைநிறுத்தப்பட்டது"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"தகவல்கள் எதுவுமில்லை"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-te/strings.xml b/SafetyCenter/Resources/shared_res/values-te/strings.xml
index c9ecc0f23..1fc82228d 100644
--- a/SafetyCenter/Resources/shared_res/values-te/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-te/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{అలర్ట్‌ను చూడండి}other{అలర్ట్‌లను చూడండి}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"పేజీని తెరవడం సాధ్యపడలేదు"</string>
<string name="resolving_action_error" msgid="371968886143262375">"అలర్ట్‌ను పరిష్కరించడం సాధ్యపడలేదు"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"సెట్టింగ్‌లను రిఫ్రెష్ చేయడం సాధ్యపడలేదు"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{సెట్టింగ్‌ను చెక్ చేయడం సాధ్యపడలేదు}other{సెట్టింగ్‌లను చెక్ చేయడం సాధ్యపడలేదు}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"వర్క్ ప్రొఫైల్ పాజ్ చేయబడింది"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ఇంకా ఏ సమాచారం లేదు"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-th/strings.xml b/SafetyCenter/Resources/shared_res/values-th/strings.xml
index f879c3ae4..37f345214 100644
--- a/SafetyCenter/Resources/shared_res/values-th/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-th/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ดูการแจ้งเตือน}other{ดูการแจ้งเตือน}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"เปิดหน้าไม่ได้"</string>
<string name="resolving_action_error" msgid="371968886143262375">"แก้ไขการแจ้งเตือนไม่ได้"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"รีเฟรชการตั้งค่าไม่ได้"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ไม่สามารถตรวจสอบการตั้งค่า}other{ไม่สามารถตรวจสอบการตั้งค่า}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"โปรไฟล์งานหยุดชั่วคราว"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ยังไม่มีข้อมูล"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-tl/strings.xml b/SafetyCenter/Resources/shared_res/values-tl/strings.xml
index d2449116f..ee7210dc2 100644
--- a/SafetyCenter/Resources/shared_res/values-tl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-tl/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Tingnan ang alerto}one{Tingnan ang mga alerto}other{Tingnan ang mga alerto}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Hindi mabuksan ang page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Hindi ma-resolve ang alerto"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Hindi ma-refresh ang mga setting"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Hindi masuri ang setting}one{Hindi masuri ang mga setting}other{Hindi masuri ang mga setting}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Naka-pause ang profile sa trabaho"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Wala pang impormasyon"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-tr/strings.xml b/SafetyCenter/Resources/shared_res/values-tr/strings.xml
index 69bf874dd..5a32bcfac 100644
--- a/SafetyCenter/Resources/shared_res/values-tr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-tr/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Uyarıya göz atın}other{Uyarılara göz atın}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Sayfa açılamadı"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Uyarı sonlandırılamadı"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Ayarlar yenilenemedi"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ayar kontrol edilemedi}other{Ayarlar kontrol edilemedi}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"İş profili duraklatıldı"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Henüz bilgi yok"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-uk/strings.xml b/SafetyCenter/Resources/shared_res/values-uk/strings.xml
index 44b304f80..f103d5a4f 100644
--- a/SafetyCenter/Resources/shared_res/values-uk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-uk/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Перегляньте сповіщення}one{Перегляньте сповіщення}few{Перегляньте сповіщення}many{Перегляньте сповіщення}other{Перегляньте сповіщення}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Не вдалося відкрити сторінку"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Не вдалося закрити сповіщення"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Не вдалось оновити налаштування"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Не вдалося перевірити налаштування}one{Не вдалося перевірити налаштування}few{Не вдалося перевірити налаштування}many{Не вдалося перевірити налаштування}other{Не вдалося перевірити налаштування}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Робочий профіль призупинено"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Поки немає інформації"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ur/strings.xml b/SafetyCenter/Resources/shared_res/values-ur/strings.xml
index f0d791f1e..df25d9ac2 100644
--- a/SafetyCenter/Resources/shared_res/values-ur/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ur/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{الرٹ دیکھیں}other{الرٹس دیکھیں}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"صفحہ نہیں کھل سکا"</string>
<string name="resolving_action_error" msgid="371968886143262375">"الرٹ حل نہیں ہو سکا"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"ترتیبات ریفریش نہیں کی جا سکی"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ترتیب کی جانچ نہیں کی جا سکی}other{ترتیبات کی جانچ نہیں کی جا سکی}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"دفتری پروفائل روک دی گئی ہے"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ابھی تک کوئی معلومات نہیں ہے"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-uz/strings.xml b/SafetyCenter/Resources/shared_res/values-uz/strings.xml
index a4b933eb9..5965fb760 100644
--- a/SafetyCenter/Resources/shared_res/values-uz/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-uz/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ogohlantirish}other{Ogohlantirishlar}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Sahifa ochilmadi"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ogohlantirish hal qilinmadi"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Sozlamalar yangilanmadi"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Sozlama tekshirilmadi}other{Sozlamalar tekshirilmadi}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Ish profili pauzada"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Hali axborot olinmadi"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-vi/strings.xml b/SafetyCenter/Resources/shared_res/values-vi/strings.xml
index 8bbffeb1b..b697186e0 100644
--- a/SafetyCenter/Resources/shared_res/values-vi/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-vi/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Xem cảnh báo}other{Xem cảnh báo}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Không thể mở trang"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Không thể giải quyết vấn đề cảnh báo"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Không thể làm mới cài đặt"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Không kiểm tra được chế độ cài đặt}other{Không kiểm tra được các chế độ cài đặt}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Hồ sơ công việc của bạn đã bị tạm dừng"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Chưa có thông tin"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml b/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml
index 401c82a5f..1ed1355cf 100644
--- a/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{查看提醒}other{查看提醒}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"无法打开页面"</string>
<string name="resolving_action_error" msgid="371968886143262375">"无法解决提醒事项"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"无法刷新设置"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{无法检查设置}other{无法检查设置}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"工作资料已被暂停"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"尚无任何信息"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml b/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml
index a2f8f75a8..0222a0acc 100644
--- a/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{查看警示}other{查看警示}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"無法開啟頁面"</string>
<string name="resolving_action_error" msgid="371968886143262375">"無法解除警示"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"無法重新整理設定"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{無法檢查設定}other{無法檢查設定}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"工作設定檔已暫停"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"暫時沒有資料"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml b/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml
index beb5af4a2..1be6fa621 100644
--- a/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{查看警示}other{查看警示}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"無法開啟網頁"</string>
<string name="resolving_action_error" msgid="371968886143262375">"無法解決警示"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"無法重新整理設定"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{無法檢查設定}other{無法檢查設定}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"工作資料夾已暫停"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"目前還沒有任何資訊"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zu/strings.xml b/SafetyCenter/Resources/shared_res/values-zu/strings.xml
index 21c358f15..61096c234 100644
--- a/SafetyCenter/Resources/shared_res/values-zu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zu/strings.xml
@@ -40,6 +40,7 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Bona isixwayiso}one{Bona izixwayiso}other{Bona izixwayiso}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Ayikwazanga ukuvula ikhasi"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ayikwazanga ukuxazulula isexwayiso"</string>
+ <string name="refresh_timeout" msgid="251734999692581852">"Ayikwazanga ukuvuselela amasethingi"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ayikwazanga ukuhlola isethingi}one{Ayikwazanga ukuhlola amasethingi}other{Ayikwazanga ukuhlola amasethingi}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Iphrofayela yomsebenzi iphunyuziwe"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Alukho ulwazi okwamanje"</string>
diff --git a/TEST_MAPPING b/TEST_MAPPING
index aa594e81e..a47660767 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -6,7 +6,67 @@
],
"carpermission-presubmit" : [
{
- "name" : "CtsPermission3TestCases"
+ "name" : "CtsPermissionUiTestCases",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "carpermission-postsubmit" : [
+ {
+ "name" : "CtsPermissionUiTestCases",
+ "options": [
+ {
+ "include-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"
+ }
+ ]
}
]
}
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..cbabbb5a5
--- /dev/null
+++ b/flags/flags.aconfig
@@ -0,0 +1,8 @@
+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"
+ bug: "TODO(michaschwab): update with correct tracking bug"
+}
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/framework-s/java/android/app/role/TEST_MAPPING b/framework-s/java/android/app/role/TEST_MAPPING
index ce53dca05..a95df2fd5 100644
--- a/framework-s/java/android/app/role/TEST_MAPPING
+++ b/framework-s/java/android/app/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/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/permission/Android.bp b/tests/cts/permission/Android.bp
new file mode 100644
index 000000000..47d3b1200
--- /dev/null
+++ b/tests/cts/permission/Android.bp
@@ -0,0 +1,125 @@
+//
+// Copyright (C) 2008 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: "CtsPermissionTestCases",
+ defaults: [
+ "cts_defaults",
+ "mts-target-sdk-version-current",
+ ],
+ min_sdk_version: "30",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+ // Include both the 32 and 64 bit versions
+ compile_multilib: "both",
+ static_libs: [
+ "ctstestrunner-axt",
+ "guava",
+ "android-ex-camera2",
+ "compatibility-device-util-axt",
+ "truth-prebuilt",
+ "androidx.annotation_annotation",
+ "platformprotosnano",
+ "permission-test-util-lib",
+ "nativetesthelper",
+ // TODO(b/175251166): remove once Android migrates to JUnit 4.12,
+ // which provides assertThrows
+ "testng",
+ "bluetooth-test-util-lib",
+ "CtsAccessibilityCommon",
+ "safety-center-internal-data",
+ "sts-device-util",
+ "platform-test-rules",
+ ],
+ jni_libs: [
+ "libctspermission_jni",
+ "libpermissionmanager_native_test",
+ "libnativehelper_compat_libc++",
+ ],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.aidl",
+ "src/**/*.kt",
+ ],
+ sdk_version: "test_current",
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ data: [
+ ":AppThatDefinesUndefinedPermissionGroupElement",
+ ":AppThatDoesNotHaveBgLocationAccess",
+ ":CtsAdversarialPermissionDefinerApp",
+ ":CtsAdversarialPermissionUserApp",
+ ":CtsAppThatAccessesLocationOnCommand",
+ ":CtsAppThatAlsoDefinesPermissionA",
+ ":CtsAppThatAlsoDefinesPermissionADifferentCert",
+ ":CtsAppThatAlsoDefinesPermissionGroupADifferentCert",
+ ":CtsAppThatAlsoDefinesPermissionGroupADifferentCert30",
+ ":CtsAppThatDefinesPermissionA",
+ ":CtsAppThatDefinesPermissionInPlatformGroup",
+ ":CtsAppThatDefinesPermissionWithInvalidGroup",
+ ":CtsAppThatDefinesPermissionWithInvalidGroup30",
+ ":CtsAppThatHasNotificationListener",
+ ":CtsAppThatRequestsBluetoothPermission30",
+ ":CtsAppThatRequestsCalendarContactsBodySensorCustomPermission",
+ ":CtsAppThatRequestsBluetoothPermission31",
+ ":CtsAppThatRequestsBluetoothPermissionNeverForLocation31",
+ ":CtsAppThatRequestsContactsAndCallLogPermission16",
+ ":CtsAppThatRequestsContactsPermission15",
+ ":CtsAppThatRequestsContactsPermission16",
+ ":CtsAppThatRequestsLocationAndBackgroundPermission28",
+ ":CtsAppThatRequestsLocationAndBackgroundPermission29",
+ ":CtsAppThatRequestsBluetoothPermissionNeverForLocationNoProvider",
+ ":CtsAppThatRequestsLocationPermission22",
+ ":CtsAppThatRequestsLocationPermission28",
+ ":CtsAppThatRequestsLocationPermission29",
+ ":CtsAppThatRequestsLocationPermission29v4",
+ ":CtsAppThatRequestsOneTimePermission",
+ ":CtsAppThatRequestsPermissionAandB",
+ ":CtsAppThatRequestsPermissionAandC",
+ ":CtsAppThatRequestsStoragePermission28",
+ ":CtsAppThatRequestsStoragePermission29",
+ ":CtsAppThatRunsRationaleTests",
+ ":CtsAppToTestRevokeSelfPermission",
+ ":CtsAppWithSharedUidThatRequestsLocationPermission28",
+ ":CtsAppWithSharedUidThatRequestsLocationPermission29",
+ ":CtsAppWithSharedUidThatRequestsNoPermissions",
+ ":CtsAppWithSharedUidThatRequestsPermissions",
+ ":CtsInstallPermissionDefinerApp",
+ ":CtsInstallPermissionEscalatorApp",
+ ":CtsInstallPermissionUserApp",
+ ":CtsRuntimePermissionDefinerApp",
+ ":CtsRuntimePermissionUserApp",
+ ":CtsStorageEscalationApp28",
+ ":CtsStorageEscalationApp29Full",
+ ":CtsStorageEscalationApp29Scoped",
+ ":CtsVictimPermissionDefinerApp",
+ ":CtsAppThatRequestsMultiplePermissionsWithMinMaxSdk",
+ ":CtsAppThatRequestsSystemAlertWindow22",
+ ":CtsAppThatRequestsSystemAlertWindow23",
+ ":CtsAppThatRequestCustomCameraPermission",
+ ],
+ per_testcase_directory: true,
+}
diff --git a/tests/cts/permission/AndroidManifest.xml b/tests/cts/permission/AndroidManifest.xml
new file mode 100644
index 000000000..43fd97bb2
--- /dev/null
+++ b/tests/cts/permission/AndroidManifest.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2007 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.permission.cts"
+ android:targetSandboxVersion="2">
+
+ <!-- for android.permission.cts.PermissionGroupChange -->
+ <permission android:name="android.permission.cts.B"
+ android:protectionLevel="dangerous"
+ android:label="@string/perm_b"
+ android:permissionGroup="android.permission.cts.groupB"
+ android:description="@string/perm_b"/>
+
+ <!-- for android.permission.cts.PermissionGroupChange -->
+ <permission android:name="android.permission.cts.C"
+ android:protectionLevel="dangerous"
+ android:label="@string/perm_c"
+ android:permissionGroup="android.permission.cts.groupC"
+ android:description="@string/perm_c"/>
+
+ <!-- for android.permission.cts.LocationAccessCheckTest -->
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+
+ <!-- for android.permission.cts.NearbyDevicesRenouncePermissionTest -->
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
+
+ <!-- for android.permission.cts.PermissionGroupChange -->
+ <permission-group android:description="@string/perm_group_b"
+ android:label="@string/perm_group_b"
+ android:name="android.permission.cts.groupB"/>
+
+ <!-- for android.permission.cts.PermissionGroupChange -->
+ <permission-group android:description="@string/perm_group_c"
+ android:label="@string/perm_group_c"
+ android:name="android.permission.cts.groupC"/>
+
+ <uses-permission android:name="android.permission.INJECT_EVENTS"/>
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name="android.permission.cts.PermissionStubActivity"
+ android:label="PermissionStubActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+ </intent-filter>
+ </activity>
+
+ <service android:name="android.permission.cts.CtsNotificationListenerService"
+ android:exported="true"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService"/>
+ </intent-filter>
+ </service>
+ <service android:name=".AccessibilityTestService"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService"/>
+ </intent-filter>
+ <meta-data android:name="android.accessibilityservice"
+ android:resource="@xml/test_accessibilityservice"/>
+ </service>
+ </application>
+
+ <!--
+ The CTS stubs package cannot be used as the target application here,
+ since that requires many permissions to be set. Instead, specify this
+ package itself as the target and include any stub activities needed.
+
+ This test package uses the default InstrumentationTestRunner, because
+ the InstrumentationCtsTestRunner is only available in the stubs
+ package. That runner cannot be added to this package either, since it
+ relies on hidden APIs.
+ -->
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.permission.cts"
+ android:label="CTS tests of android.permission">
+ </instrumentation>
+
+</manifest>
diff --git a/tests/cts/permission/AndroidTest.xml b/tests/cts/permission/AndroidTest.xml
new file mode 100644
index 000000000..3773a889d
--- /dev/null
+++ b/tests/cts/permission/AndroidTest.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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 Permission 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="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
+ <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.permission.apex" />
+ <option name="config-descriptor:metadata" key="parameter" value="run_on_sdk_sandbox" />
+
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+
+ <!-- Keep screen on for Bluetooth scanning -->
+ <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="screen-always-on" value="on" />
+ <option name="disable-device-config-sync" value="true" />
+ </target_preparer>
+
+ <!-- Install main test suite apk -->
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsPermissionTestCases.apk" />
+ </target_preparer>
+
+ <!-- Create place to store apks -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/permissions" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+ </target_preparer>
+
+ <!-- Collect screen recordings -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/android.permission.cts/files" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+
+ <!-- Load additional APKs onto device -->
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="CtsAppThatRequestsPermissionAandB.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsPermissionAandB.apk" />
+ <option name="push" value="CtsAppThatRequestsPermissionAandC.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsPermissionAandC.apk" />
+ <option name="push" value="CtsAppThatRequestsBluetoothPermission30.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsBluetoothPermission30.apk" />
+ <option name="push" value="CtsAppThatRequestsBluetoothPermission31.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsBluetoothPermission31.apk" />
+ <option name="push" value="CtsAppThatRequestsBluetoothPermissionNeverForLocation31.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsBluetoothPermissionNeverForLocation31.apk" />
+ <option name="push" value="CtsAppThatRequestsBluetoothPermissionNeverForLocationNoProvider.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsBluetoothPermissionNeverForLocationNoProvider.apk" />
+ <option name="push" value="CtsAppThatRequestsContactsPermission16.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsContactsPermission16.apk" />
+ <option name="push" value="CtsAppThatRequestsContactsPermission15.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsContactsPermission15.apk" />
+ <option name="push" value="CtsAppThatRequestsContactsAndCallLogPermission16.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsContactsAndCallLogPermission16.apk" />
+ <option name="push" value="CtsAppThatRequestsLocationPermission29.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationPermission29.apk" />
+ <option name="push" value="CtsAppThatRequestsLocationPermission29v4.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationPermission29v4.apk" />
+ <option name="push" value="CtsAppThatRequestsLocationPermission28.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationPermission28.apk" />
+ <option name="push" value="CtsAppThatRequestsLocationPermission22.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationPermission22.apk" />
+ <option name="push" value="CtsAppThatRequestsStoragePermission29.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsStoragePermission29.apk" />
+ <option name="push" value="CtsAppThatRequestsStoragePermission28.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsStoragePermission28.apk" />
+ <option name="push" value="CtsAppThatRequestsLocationAndBackgroundPermission28.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationAndBackgroundPermission28.apk" />
+ <option name="push" value="CtsAppThatRequestsLocationAndBackgroundPermission29.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationAndBackgroundPermission29.apk" />
+ <option name="push" value="CtsAppThatAccessesLocationOnCommand.apk->/data/local/tmp/cts/permissions/CtsAppThatAccessesLocationOnCommand.apk" />
+ <option name="push" value="AppThatDoesNotHaveBgLocationAccess.apk->/data/local/tmp/cts/permissions/AppThatDoesNotHaveBgLocationAccess.apk" />
+ <option name="push" value="CtsAppWithSharedUidThatRequestsPermissions.apk->/data/local/tmp/cts/permissions/CtsAppWithSharedUidThatRequestsPermissions.apk" />
+ <option name="push" value="CtsAppWithSharedUidThatRequestsNoPermissions.apk->/data/local/tmp/cts/permissions/CtsAppWithSharedUidThatRequestsNoPermissions.apk" />
+ <option name="push" value="CtsAppWithSharedUidThatRequestsLocationPermission28.apk->/data/local/tmp/cts/permissions/CtsAppWithSharedUidThatRequestsLocationPermission28.apk" />
+ <option name="push" value="CtsAppWithSharedUidThatRequestsLocationPermission29.apk->/data/local/tmp/cts/permissions/CtsAppWithSharedUidThatRequestsLocationPermission29.apk" />
+ <option name="push" value="CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk" />
+ <option name="push" value="CtsAppThatRunsRationaleTests.apk->/data/local/tmp/cts/permissions/CtsAppThatRunsRationaleTests.apk" />
+ <option name="push" value="CtsAdversarialPermissionUserApp.apk->/data/local/tmp/cts/permissions/CtsAdversarialPermissionUserApp.apk" />
+ <option name="push" value="CtsAdversarialPermissionDefinerApp.apk->/data/local/tmp/cts/permissions/CtsAdversarialPermissionDefinerApp.apk" />
+ <option name="push" value="CtsVictimPermissionDefinerApp.apk->/data/local/tmp/cts/permissions/CtsVictimPermissionDefinerApp.apk" />
+ <option name="push" value="CtsRuntimePermissionDefinerApp.apk->/data/local/tmp/cts/permissions/CtsRuntimePermissionDefinerApp.apk" />
+ <option name="push" value="CtsRuntimePermissionUserApp.apk->/data/local/tmp/cts/permissions/CtsRuntimePermissionUserApp.apk" />
+ <option name="push" value="CtsInstallPermissionDefinerApp.apk->/data/local/tmp/cts/permissions/CtsInstallPermissionDefinerApp.apk" />
+ <option name="push" value="CtsInstallPermissionUserApp.apk->/data/local/tmp/cts/permissions/CtsInstallPermissionUserApp.apk" />
+ <option name="push" value="CtsInstallPermissionEscalatorApp.apk->/data/local/tmp/cts/permissions/CtsInstallPermissionEscalatorApp.apk" />
+ <option name="push" value="CtsAppThatRequestsOneTimePermission.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsOneTimePermission.apk" />
+ <option name="push" value="CtsAppToTestRevokeSelfPermission.apk->/data/local/tmp/cts/permissions/CtsAppToTestRevokeSelfPermission.apk" />
+ <option name="push" value="AppThatDefinesUndefinedPermissionGroupElement.apk->/data/local/tmp/cts/permissions/AppThatDefinesUndefinedPermissionGroupElement.apk" />
+ <option name="push" value="CtsAppThatDefinesPermissionA.apk->/data/local/tmp/cts/permissions/CtsAppThatDefinesPermissionA.apk" />
+ <option name="push" value="CtsAppThatAlsoDefinesPermissionA.apk->/data/local/tmp/cts/permissions/CtsAppThatAlsoDefinesPermissionA.apk" />
+ <option name="push" value="CtsAppThatAlsoDefinesPermissionADifferentCert.apk->/data/local/tmp/cts/permissions/CtsAppThatAlsoDefinesPermissionADifferentCert.apk" />
+ <option name="push" value="CtsAppThatAlsoDefinesPermissionGroupADifferentCert.apk->/data/local/tmp/cts/permissions/CtsAppThatAlsoDefinesPermissionGroupADifferentCert.apk" />
+ <option name="push" value="CtsAppThatDefinesPermissionInPlatformGroup.apk->/data/local/tmp/cts/permissions/CtsAppThatDefinesPermissionInPlatformGroup.apk" />
+ <option name="push" value="CtsAppThatAlsoDefinesPermissionGroupADifferentCert30.apk->/data/local/tmp/cts/permissions/CtsAppThatAlsoDefinesPermissionGroupADifferentCert30.apk" />
+ <option name="push" value="CtsAppThatDefinesPermissionWithInvalidGroup.apk->/data/local/tmp/cts/permissions/CtsAppThatDefinesPermissionWithInvalidGroup.apk" />
+ <option name="push" value="CtsAppThatDefinesPermissionWithInvalidGroup30.apk->/data/local/tmp/cts/permissions/CtsAppThatDefinesPermissionWithInvalidGroup30.apk" />
+ <option name="push" value="CtsStorageEscalationApp28.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp28.apk" />
+ <option name="push" value="CtsStorageEscalationApp29Full.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp29Full.apk" />
+ <option name="push" value="CtsStorageEscalationApp29Scoped.apk->/data/local/tmp/cts/permissions/CtsStorageEscalationApp29Scoped.apk" />
+ <option name="push" value="CtsAppThatHasNotificationListener.apk->/data/local/tmp/cts/permissions/CtsAppThatHasNotificationListener.apk" />
+ <option name="push" value="CtsAppThatRequestsMultiplePermissionsWithMinMaxSdk.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsMultiplePermissionsWithMinMaxSdk.apk" />
+ <option name="push" value="CtsAppThatRequestsSystemAlertWindow22.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsSystemAlertWindow22.apk" />
+ <option name="push" value="CtsAppThatRequestsSystemAlertWindow23.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsSystemAlertWindow23.apk" />
+ <option name="push" value="CtsAppThatRequestCustomCameraPermission.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestCustomCameraPermission.apk" />
+ </target_preparer>
+
+ <!-- Remove additional apps if installed -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- 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="teardown-command" value="pm uninstall android.permission.cts.appthatrequestpermission" />
+ <option name="teardown-command" value="pm uninstall android.permission.cts.appthatrequestnopermission" />
+ <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.AdversarialPermissionDefinerApp" />
+ <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.VictimPermissionDefinerApp" />
+ <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.userapp" />
+ <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.runtimepermissiondefinerapp" />
+ <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.runtimepermissionuserapp" />
+ <option name="teardown-command" value="pm uninstall android.permission.cts.appthathasnotificationlistener" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.permission.cts" />
+ <option name="runtime-hint" value="13m" />
+ </test>
+
+ <system_checker class="com.android.tradefed.suite.checker.UserChecker" >
+ <option name="user-cleanup" value="true" />
+ </system_checker>
+</configuration>
diff --git a/tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp b/tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp
new file mode 100644
index 000000000..fdb0be452
--- /dev/null
+++ b/tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2019 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: "CtsAppThatRequestsCalendarContactsBodySensorCustomPermission",
+ defaults: [
+ "cts_defaults",
+ "mts-target-sdk-version-current",
+ ],
+ sdk_version: "current",
+ min_sdk_version: "30",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/AndroidManifest.xml b/tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/AndroidManifest.xml
new file mode 100644
index 000000000..ef4d82dfc
--- /dev/null
+++ b/tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permission.cts.appthatrequestcustompermission"
+ android:versionCode="1">
+
+ <permission-group
+ android:name="android.permission.cts.appthatrequestcustompermission.TEST_GROUP"
+ android:label="test permission group"
+ android:protectionLevel="dangerous" />
+
+ <permission
+ android:name="android.permission.cts.appthatrequestcustompermission.TEST_PERMISSION"
+ android:label="test permission"
+ android:permissionGroup="android.permission.cts.appthatrequestcustompermission.TEST_GROUP"
+ android:protectionLevel="dangerous" />
+
+ <uses-permission android:name="android.permission.READ_CALENDAR" />
+ <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
+ <uses-permission android:name="android.permission.cts.appthatrequestcustompermission.TEST_PERMISSION" />
+
+ <application />
+</manifest>
diff --git a/tests/cts/permission/AppThatAccessesLocationOnCommand/Android.bp b/tests/cts/permission/AppThatAccessesLocationOnCommand/Android.bp
new file mode 100644
index 000000000..2bb3dd3ab
--- /dev/null
+++ b/tests/cts/permission/AppThatAccessesLocationOnCommand/Android.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2018 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: "CtsAppThatAccessesLocationOnCommand",
+ defaults: [
+ "cts_defaults",
+ "mts-target-sdk-version-current",
+ ],
+ sdk_version: "current",
+ min_sdk_version: "30",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.aidl"
+ ],
+}
diff --git a/tests/cts/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml b/tests/cts/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml
new file mode 100644
index 000000000..93836d389
--- /dev/null
+++ b/tests/cts/permission/AppThatAccessesLocationOnCommand/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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.permission.cts.appthataccesseslocation">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+
+ <application android:label="CtsLocationAccess" android:debuggable="true">
+ <service android:name=".AccessLocationOnCommand"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java b/tests/cts/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java
new file mode 100644
index 000000000..75f4a0ce5
--- /dev/null
+++ b/tests/cts/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/AccessLocationOnCommand.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2018 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.permission.cts.appthataccesseslocation;
+
+import static android.location.Criteria.ACCURACY_FINE;
+
+import android.app.Service;
+import android.content.Intent;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+
+public class AccessLocationOnCommand extends Service {
+ private IAccessLocationOnCommand.Stub mBinder = new IAccessLocationOnCommand.Stub() {
+ public void accessLocation() {
+ Criteria crit = new Criteria();
+ crit.setAccuracy(ACCURACY_FINE);
+
+ AccessLocationOnCommand.this.getSystemService(LocationManager.class)
+ .requestSingleUpdate(crit, new LocationListener() {
+ @Override
+ public void onLocationChanged(Location location) {
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status,
+ Bundle extras) {
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+ }
+ }, Looper.getMainLooper());
+ }
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ return true;
+ }
+}
diff --git a/tests/cts/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl b/tests/cts/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl
new file mode 100644
index 000000000..be92ed160
--- /dev/null
+++ b/tests/cts/permission/AppThatAccessesLocationOnCommand/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 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.permission.cts.appthataccesseslocation;
+
+interface IAccessLocationOnCommand {
+ /** Access location on command */
+ void accessLocation();
+} \ No newline at end of file
diff --git a/tests/cts/permission/AppThatAlsoDefinesPermissionA/Android.bp b/tests/cts/permission/AppThatAlsoDefinesPermissionA/Android.bp
new file mode 100644
index 000000000..46b7aecd3
--- /dev/null
+++ b/tests/cts/permission/AppThatAlsoDefinesPermissionA/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 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: "CtsAppThatAlsoDefinesPermissionA",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ certificate: ":cts-testkey1",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+}
diff --git a/tests/cts/permission/AppThatAlsoDefinesPermissionA/AndroidManifest.xml b/tests/cts/permission/AppThatAlsoDefinesPermissionA/AndroidManifest.xml
new file mode 100644
index 000000000..2a803017b
--- /dev/null
+++ b/tests/cts/permission/AppThatAlsoDefinesPermissionA/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 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.permission.cts.appthatalsodefinespermissiona">
+
+ <permission android:name="com.android.cts.duplicatepermission.permA"
+ android:permissionGroup="com.android.cts.duplicatepermission.groupA"/>
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp b/tests/cts/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp
new file mode 100644
index 000000000..c88d0f7fe
--- /dev/null
+++ b/tests/cts/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 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: "CtsAppThatAlsoDefinesPermissionADifferentCert",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ certificate: ":cts-testkey2",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+}
diff --git a/tests/cts/permission/AppThatAlsoDefinesPermissionADifferentCert/AndroidManifest.xml b/tests/cts/permission/AppThatAlsoDefinesPermissionADifferentCert/AndroidManifest.xml
new file mode 100644
index 000000000..d333bf6df
--- /dev/null
+++ b/tests/cts/permission/AppThatAlsoDefinesPermissionADifferentCert/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 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.permission.cts.appthatdefinespermissiona.differentcert">
+
+ <permission android:name="com.android.cts.duplicatepermission.permA"/>
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp b/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp
new file mode 100644
index 000000000..b1ef695ac
--- /dev/null
+++ b/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 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: "CtsAppThatAlsoDefinesPermissionGroupADifferentCert",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ certificate: ":cts-testkey2",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+}
diff --git a/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/AndroidManifest.xml b/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/AndroidManifest.xml
new file mode 100644
index 000000000..59cd518c1
--- /dev/null
+++ b/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 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.permission.cts.appthatdefinespermissiongroupa.differentcert">
+
+ <permission-group android:name="com.android.cts.duplicatepermission.groupA"
+ android:label="groupA"/>
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp b/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp
new file mode 100644
index 000000000..52900dfc3
--- /dev/null
+++ b/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 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: "CtsAppThatAlsoDefinesPermissionGroupADifferentCert30",
+ defaults: ["cts_defaults"],
+ sdk_version: "30",
+ certificate: ":cts-testkey2",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+}
diff --git a/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/AndroidManifest.xml b/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/AndroidManifest.xml
new file mode 100644
index 000000000..43ed9db58
--- /dev/null
+++ b/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 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.permission.cts.appthatdefinespermissiongroupa.differentcert30">
+
+ <permission-group android:name="com.android.cts.duplicatepermission.groupA"
+ android:label="groupA"/>
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatDefinesPermissionA/Android.bp b/tests/cts/permission/AppThatDefinesPermissionA/Android.bp
new file mode 100644
index 000000000..54a575b4f
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesPermissionA/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 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: "CtsAppThatDefinesPermissionA",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ certificate: ":cts-testkey1",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+}
diff --git a/tests/cts/permission/AppThatDefinesPermissionA/AndroidManifest.xml b/tests/cts/permission/AppThatDefinesPermissionA/AndroidManifest.xml
new file mode 100644
index 000000000..527618c7d
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesPermissionA/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 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.permission.cts.appthatdefinespermissiona">
+ <permission-group android:name="com.android.cts.duplicatepermission.groupA"
+ android:label="groupA"/>
+
+ <permission android:name="com.android.cts.duplicatepermission.permA"
+ android:permissionGroup="com.android.cts.duplicatepermission.groupA"/>
+
+ <application/>
+</manifest>
+
diff --git a/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp b/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp
new file mode 100644
index 000000000..9029b3a98
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2020 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: "CtsAppThatDefinesPermissionWithInvalidGroup",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+}
diff --git a/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup/AndroidManifest.xml b/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup/AndroidManifest.xml
new file mode 100644
index 000000000..8abd4cc4a
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 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.permission.cts.appthatdefinespermissionwithinvalidgroup">
+
+ <permission android:name="com.android.cts.duplicatepermission.permA"
+ android:permissionGroup="com.android.cts.duplicatepermission.invalid"/>
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp b/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp
new file mode 100644
index 000000000..04961e265
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2020 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: "CtsAppThatDefinesPermissionWithInvalidGroup30",
+ defaults: ["cts_defaults"],
+ sdk_version: "30",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+}
diff --git a/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup30/AndroidManifest.xml b/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup30/AndroidManifest.xml
new file mode 100644
index 000000000..2fc662c27
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup30/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 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.permission.cts.appthatdefinespermissionwithinvalidgroup30">
+
+ <permission android:name="com.android.cts.duplicatepermission.permA"
+ android:permissionGroup="com.android.cts.duplicatepermission.invalid"/>
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp b/tests/cts/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp
new file mode 100644
index 000000000..687ad7488
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2020 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: "CtsAppThatDefinesPermissionInPlatformGroup",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+}
diff --git a/tests/cts/permission/AppThatDefinesPermissionWithPlatformGroup/AndroidManifest.xml b/tests/cts/permission/AppThatDefinesPermissionWithPlatformGroup/AndroidManifest.xml
new file mode 100644
index 000000000..d4709eb9b
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesPermissionWithPlatformGroup/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2020 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.permission.cts.appthatdefinespermissioninplatformgroup">
+
+ <permission android:name="com.android.cts.duplicatepermission.permA"
+ android:permissionGroup="android.permission-group.CAMERA"/>
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp b/tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp
new file mode 100644
index 000000000..c00d26d3a
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 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: "AppThatDefinesUndefinedPermissionGroupElement",
+ defaults: ["cts_defaults"],
+ sdk_version: "test_current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+ srcs: ["src/**/*.kt"],
+}
diff --git a/tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/AndroidManifest.xml b/tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/AndroidManifest.xml
new file mode 100644
index 000000000..ab2ef79b2
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.permission.cts.appthatrequestpermission"
+ android:versionCode="2">
+
+ <permission
+ android:name="android.permission.cts.appthatrequestpermission.TEST"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED" />
+
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.cts.appthatrequestpermission.TEST" />
+
+ <application android:label="CtsPermissionUnknownGroup">
+ <activity
+ android:name=".RequestPermissions"
+ android:exported="true"
+ android:visibleToInstantApps="true"/>
+ </application>
+</manifest>
+
diff --git a/tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/src/android/permission/cts/appthatrequestpermission/RequestPermissions.kt b/tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/src/android/permission/cts/appthatrequestpermission/RequestPermissions.kt
new file mode 100644
index 000000000..39183a85c
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/src/android/permission/cts/appthatrequestpermission/RequestPermissions.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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.permission.cts.appthatrequestpermission
+
+import android.app.Activity
+
+class RequestPermissions : Activity() {
+ override fun onStart() {
+ super.onStart()
+ val permissions = intent.getStringArrayExtra(EXTRA_PERMISSIONS)!!
+ requestPermissions(permissions, 0)
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array<String>,
+ grantResults: IntArray
+ ) {
+ finish()
+ }
+
+ companion object {
+ private const val EXTRA_PERMISSIONS =
+ "android.permission.cts.appthatrequestpermission.extra.PERMISSIONS"
+ }
+} \ No newline at end of file
diff --git a/tests/cts/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp b/tests/cts/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp
new file mode 100644
index 000000000..f9ff21c6f
--- /dev/null
+++ b/tests/cts/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2018 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: "AppThatDoesNotHaveBgLocationAccess",
+ defaults: [
+ "cts_defaults",
+ "mts-target-sdk-version-current",
+ ],
+ sdk_version: "current",
+ min_sdk_version: "30",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatDoesNotHaveBgLocationAccess/AndroidManifest.xml b/tests/cts/permission/AppThatDoesNotHaveBgLocationAccess/AndroidManifest.xml
new file mode 100644
index 000000000..8e883a4ad
--- /dev/null
+++ b/tests/cts/permission/AppThatDoesNotHaveBgLocationAccess/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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.permission.cts.appthataccesseslocation">
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
+ <application android:label="CtsLocationAccess" android:debuggable="true"/>
+</manifest>
+
diff --git a/tests/cts/permission/AppThatHasNotificationListener/Android.bp b/tests/cts/permission/AppThatHasNotificationListener/Android.bp
new file mode 100644
index 000000000..419ab5d66
--- /dev/null
+++ b/tests/cts/permission/AppThatHasNotificationListener/Android.bp
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAppThatHasNotificationListener",
+ defaults: [
+ "cts_defaults",
+ "mts-target-sdk-version-current",
+ ],
+ sdk_version: "current",
+ min_sdk_version: "30",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+}
diff --git a/tests/cts/permission/AppThatHasNotificationListener/AndroidManifest.xml b/tests/cts/permission/AppThatHasNotificationListener/AndroidManifest.xml
new file mode 100644
index 000000000..03d23dfb2
--- /dev/null
+++ b/tests/cts/permission/AppThatHasNotificationListener/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission.cts.appthathasnotificationlistener"
+ android:versionCode="1">
+
+ <application android:label="CtsNotificationListener">
+ <service
+ android:name=".CtsNotificationListenerService"
+ android:label="CtsNotificationListener"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService"/>
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/tests/cts/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java b/tests/cts/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java
new file mode 100644
index 000000000..2bd423e1b
--- /dev/null
+++ b/tests/cts/permission/AppThatHasNotificationListener/src/android/permission/cts/appthathasnotificationlistener/CtsNotificationListenerService.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts.appthathasnotificationlistener;
+
+import android.service.notification.NotificationListenerService;
+
+public class CtsNotificationListenerService extends NotificationListenerService {}
diff --git a/tests/cts/permission/AppThatRequestBluetoothPermission30/Android.bp b/tests/cts/permission/AppThatRequestBluetoothPermission30/Android.bp
new file mode 100644
index 000000000..a3fe38109
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestBluetoothPermission30/Android.bp
@@ -0,0 +1,40 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "AppThatRequestBluetoothPermission",
+ srcs: [
+ "src/**/*.java",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsAppThatRequestsBluetoothPermission30",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+ srcs: [":AppThatRequestBluetoothPermission"],
+}
diff --git a/tests/cts/permission/AppThatRequestBluetoothPermission30/AndroidManifest.xml b/tests/cts/permission/AppThatRequestBluetoothPermission30/AndroidManifest.xml
new file mode 100644
index 000000000..d84e0d8f4
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestBluetoothPermission30/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission.cts.appthatrequestpermission">
+
+ <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+
+ <application>
+ <provider
+ android:name=".AccessBluetoothOnCommand"
+ android:authorities="appthatrequestpermission"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permission/AppThatRequestBluetoothPermission30/src/android/permission/cts/appthatrequestpermission/AccessBluetoothOnCommand.java b/tests/cts/permission/AppThatRequestBluetoothPermission30/src/android/permission/cts/appthatrequestpermission/AccessBluetoothOnCommand.java
new file mode 100644
index 000000000..a27da0bdc
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestBluetoothPermission30/src/android/permission/cts/appthatrequestpermission/AccessBluetoothOnCommand.java
@@ -0,0 +1,161 @@
+/*
+ * 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 android.permission.cts.appthatrequestpermission;
+
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanResult;
+import android.content.AttributionSource;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.ContextParams;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Base64;
+import android.util.Log;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class AccessBluetoothOnCommand extends ContentProvider {
+ private static final String TAG = "AccessBluetoothOnCommand";
+ private static final String DISAVOWAL_APP_PKG = "android.permission.cts.appneverforlocation";
+
+ private enum Result {
+ UNKNOWN, EXCEPTION, EMPTY, FILTERED, FULL
+ }
+
+ @Override
+ public Bundle call(String authority, String method, String arg, Bundle extras) {
+ final Bundle res = new Bundle();
+
+ BluetoothLeScanner scanner = null;
+ ScanCallback scanCallback = null;
+
+ try {
+ Context context = ("PROXY".equals(arg)) ? createProxyingContext() : getContext();
+ scanner = context.getSystemService(BluetoothManager.class)
+ .getAdapter().getBluetoothLeScanner();
+
+ final Set<String> observedScans = ConcurrentHashMap.newKeySet();
+ final AtomicInteger observedErrorCode = new AtomicInteger(0);
+
+ scanCallback = new ScanCallback() {
+ @Override
+ public void onScanResult(int callbackType, ScanResult result) {
+ Log.v(TAG, "onScanResult() - result = " + result);
+ observedScans.add(Base64.encodeToString(result.getScanRecord().getBytes(), 0));
+ }
+
+ @Override
+ public void onBatchScanResults(List<ScanResult> results) {
+ for (ScanResult result : results) {
+ onScanResult(0, result);
+ }
+ }
+
+ @Override
+ public void onScanFailed(int errorCode) {
+ Log.v(TAG, "onScanFailed() - errorCode = " + errorCode);
+ observedErrorCode.set(errorCode);
+ }
+ };
+
+ scanner.startScan(scanCallback);
+
+ // Wait a few seconds to figure out what we actually observed
+ SystemClock.sleep(3000);
+
+ if (observedErrorCode.get() > 0) {
+ throw new RuntimeException("Scan returned error code: " + observedErrorCode.get());
+ }
+
+ switch (observedScans.size()) {
+ case 0:
+ res.putInt(Intent.EXTRA_INDEX, Result.EMPTY.ordinal());
+ break;
+ case 1:
+ res.putInt(Intent.EXTRA_INDEX, Result.FILTERED.ordinal());
+ break;
+ case 5:
+ res.putInt(Intent.EXTRA_INDEX, Result.FULL.ordinal());
+ break;
+ default:
+ res.putInt(Intent.EXTRA_INDEX, Result.UNKNOWN.ordinal());
+ break;
+ }
+ } catch (Throwable t) {
+ Log.v(TAG, "Failed to scan", t);
+ res.putInt(Intent.EXTRA_INDEX, Result.EXCEPTION.ordinal());
+ } finally {
+ try {
+ scanner.stopScan(scanCallback);
+ } catch (Exception e) {
+ }
+ }
+ return res;
+ }
+
+ private Context createProxyingContext() throws PackageManager.NameNotFoundException {
+ int disavowingAppUid =
+ getContext().getPackageManager().getPackageUid(DISAVOWAL_APP_PKG, 0);
+ AttributionSource attrib = new AttributionSource.Builder(disavowingAppUid)
+ .setPackageName(DISAVOWAL_APP_PKG)
+ .build();
+ return getContext().createContext(
+ new ContextParams.Builder().setNextAttributionSource(attrib).build());
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/tests/cts/permission/AppThatRequestBluetoothPermission31/Android.bp b/tests/cts/permission/AppThatRequestBluetoothPermission31/Android.bp
new file mode 100644
index 000000000..7dc2e2445
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestBluetoothPermission31/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAppThatRequestsBluetoothPermission31",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+ srcs: [":AppThatRequestBluetoothPermission"],
+}
diff --git a/tests/cts/permission/AppThatRequestBluetoothPermission31/AndroidManifest.xml b/tests/cts/permission/AppThatRequestBluetoothPermission31/AndroidManifest.xml
new file mode 100644
index 000000000..70b381170
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestBluetoothPermission31/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission.cts.appthatrequestpermission">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+ <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+
+ <application>
+ <provider
+ android:name=".AccessBluetoothOnCommand"
+ android:authorities="appthatrequestpermission"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocation31/Android.bp b/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocation31/Android.bp
new file mode 100644
index 000000000..857f2e2f4
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocation31/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAppThatRequestsBluetoothPermissionNeverForLocation31",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+ srcs: [":AppThatRequestBluetoothPermission"],
+}
diff --git a/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocation31/AndroidManifest.xml b/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocation31/AndroidManifest.xml
new file mode 100644
index 000000000..446933d21
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocation31/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission.cts.appthatrequestpermission">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+ <uses-permission
+ android:name="android.permission.BLUETOOTH_ADVERTISE"
+ android:usesPermissionFlags="neverForLocation" />
+ <uses-permission
+ android:name="android.permission.BLUETOOTH_CONNECT"
+ android:usesPermissionFlags="neverForLocation" />
+ <uses-permission
+ android:name="android.permission.BLUETOOTH_SCAN"
+ android:usesPermissionFlags="neverForLocation" />
+
+ <application>
+ <provider
+ android:name=".AccessBluetoothOnCommand"
+ android:authorities="appthatrequestpermission"
+ android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/Android.bp b/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/Android.bp
new file mode 100644
index 000000000..6f635c0cc
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAppThatRequestsBluetoothPermissionNeverForLocationNoProvider",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/AndroidManifest.xml b/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/AndroidManifest.xml
new file mode 100644
index 000000000..6b4a991be
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission.cts.appneverforlocation">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+ <uses-permission
+ android:name="android.permission.BLUETOOTH_ADVERTISE"
+ android:usesPermissionFlags="neverForLocation" />
+ <uses-permission
+ android:name="android.permission.BLUETOOTH_CONNECT"
+ android:usesPermissionFlags="neverForLocation" />
+ <uses-permission
+ android:name="android.permission.BLUETOOTH_SCAN"
+ android:usesPermissionFlags="neverForLocation" />
+
+ <application>
+ </application>
+</manifest>
diff --git a/tests/cts/permission/AppThatRequestContactsAndCallLogPermission16/Android.bp b/tests/cts/permission/AppThatRequestContactsAndCallLogPermission16/Android.bp
new file mode 100644
index 000000000..1a057d010
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestContactsAndCallLogPermission16/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2018 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: "CtsAppThatRequestsContactsAndCallLogPermission16",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestContactsAndCallLogPermission16/AndroidManifest.xml b/tests/cts/permission/AppThatRequestContactsAndCallLogPermission16/AndroidManifest.xml
new file mode 100644
index 000000000..08f014508
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestContactsAndCallLogPermission16/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.permission.cts.appthatrequestpermission"
+ android:versionCode="3">
+
+ <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="16" />
+
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestContactsPermission15/Android.bp b/tests/cts/permission/AppThatRequestContactsPermission15/Android.bp
new file mode 100644
index 000000000..54618447e
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestContactsPermission15/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2018 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: "CtsAppThatRequestsContactsPermission15",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestContactsPermission15/AndroidManifest.xml b/tests/cts/permission/AppThatRequestContactsPermission15/AndroidManifest.xml
new file mode 100644
index 000000000..ab17c3668
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestContactsPermission15/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.permission.cts.appthatrequestpermission"
+ android:versionCode="2">
+
+ <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="15" />
+
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestContactsPermission16/Android.bp b/tests/cts/permission/AppThatRequestContactsPermission16/Android.bp
new file mode 100644
index 000000000..2dca5aa2c
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestContactsPermission16/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2018 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: "CtsAppThatRequestsContactsPermission16",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestContactsPermission16/AndroidManifest.xml b/tests/cts/permission/AppThatRequestContactsPermission16/AndroidManifest.xml
new file mode 100644
index 000000000..703bb3a75
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestContactsPermission16/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.permission.cts.appthatrequestpermission"
+ android:versionCode="1">
+
+ <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="16" />
+
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestCustomCameraPermission/Android.bp b/tests/cts/permission/AppThatRequestCustomCameraPermission/Android.bp
new file mode 100644
index 000000000..873733d07
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestCustomCameraPermission/Android.bp
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAppThatRequestCustomCameraPermission",
+ defaults: [
+ "cts_defaults",
+ "mts-target-sdk-version-current",
+ ],
+ min_sdk_version: "30",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "mts",
+ "sts",
+ "general-tests",
+ ],
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+}
diff --git a/tests/cts/permission/AppThatRequestCustomCameraPermission/AndroidManifest.xml b/tests/cts/permission/AppThatRequestCustomCameraPermission/AndroidManifest.xml
new file mode 100644
index 000000000..a8143a78e
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestCustomCameraPermission/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission.cts.appthatrequestcustomcamerapermission">
+
+ <permission android:name="appthatrequestcustomcamerapermission.CUSTOM"
+ android:permissionGroup="android.permission-group.CAMERA"
+ android:label="@string/permlab_custom"
+ android:description="@string/permdesc_custom"
+ android:protectionLevel="dangerous" />
+
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="appthatrequestcustomcamerapermission.CUSTOM" />
+
+ <application>
+ <activity android:name=".RequestCameraPermission" android:exported="true"
+ android:visibleToInstantApps="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/cts/permission/AppThatRequestCustomCameraPermission/res/values/strings.xml b/tests/cts/permission/AppThatRequestCustomCameraPermission/res/values/strings.xml
new file mode 100644
index 000000000..8de46384b
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestCustomCameraPermission/res/values/strings.xml
@@ -0,0 +1,20 @@
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<resources>
+ <string name="permlab_custom">Custom</string>
+ <string name="permdesc_custom">allows bypassing one-time permissions</string>
+</resources> \ No newline at end of file
diff --git a/tests/cts/permission/AppThatRequestCustomCameraPermission/src/android/permission/cts/appthatrequestcustomcamerapermission/RequestCameraPermission.java b/tests/cts/permission/AppThatRequestCustomCameraPermission/src/android/permission/cts/appthatrequestcustomcamerapermission/RequestCameraPermission.java
new file mode 100644
index 000000000..4bbeb53c5
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestCustomCameraPermission/src/android/permission/cts/appthatrequestcustomcamerapermission/RequestCameraPermission.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts.appthatrequestcustomcamerapermission;
+
+import static android.Manifest.permission.CAMERA;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+
+public class RequestCameraPermission extends Activity {
+
+ private static final String LOG_TAG = RequestCameraPermission.class.getSimpleName();
+
+ public static final String CUSTOM_PERMISSION = "appthatrequestcustomcamerapermission.CUSTOM";
+ private Handler mHandler;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ boolean cameraGranted =
+ checkSelfPermission(CAMERA) == PERMISSION_GRANTED;
+ boolean customGranted =
+ checkSelfPermission(CUSTOM_PERMISSION) == PERMISSION_GRANTED;
+
+ mHandler = new Handler(getMainLooper());
+
+ if (!cameraGranted && !customGranted) {
+ requestPermissions(new String[] {CAMERA}, 0);
+ } else {
+ Log.e(LOG_TAG, "Test app was opened with cameraGranted=" + cameraGranted
+ + " and customGranted=" + customGranted);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ if (requestCode == 0) {
+ if (grantResults[0] != PERMISSION_GRANTED) {
+ Log.e(LOG_TAG, "permission wasn't granted, this test should fail,"
+ + " leaving test app open.");
+ } else {
+ // Delayed request because the immediate request might show the dialog again
+ mHandler.postDelayed(() ->
+ requestPermissions(new String[] {CUSTOM_PERMISSION}, 1), 500);
+ }
+ } else if (requestCode == 1) {
+ if (grantResults[0] != PERMISSION_GRANTED) {
+ Log.e(LOG_TAG, "permission wasn't granted, this test should fail,"
+ + " leaving test app open.");
+ } else {
+ // Here camera was granted and custom was autogranted, exit process and let test
+ // verify both are revoked.
+
+ // Delayed exit because b/254675301
+ mHandler.postDelayed(() -> System.exit(0), 1000);
+ }
+ }
+
+ }
+}
diff --git a/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp b/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp
new file mode 100644
index 000000000..5a60a3fbd
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 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: "CtsAppThatRequestsLocationAndBackgroundPermission28",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission28/AndroidManifest.xml b/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission28/AndroidManifest.xml
new file mode 100644
index 000000000..626ee3d43
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission28/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.permission.cts.appthatrequestpermission"
+ android:versionCode="3">
+
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- The ACCESS_BACKGROUND_LOCATION was added for API 29. But apps targeting lower APK levels
+ can still request it to signal that they are aware of this new behavior -->
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp b/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp
new file mode 100644
index 000000000..de6c3cbb2
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2018 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: "CtsAppThatRequestsLocationAndBackgroundPermission29",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ min_sdk_version: "29",
+ target_sdk_version: "29",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission29/AndroidManifest.xml b/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission29/AndroidManifest.xml
new file mode 100644
index 000000000..285502cb2
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission29/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.permission.cts.appthatrequestpermission"
+ android:versionCode="3">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestLocationPermission22/Android.bp b/tests/cts/permission/AppThatRequestLocationPermission22/Android.bp
new file mode 100644
index 000000000..25d9893ec
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationPermission22/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2018 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: "CtsAppThatRequestsLocationPermission22",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestLocationPermission22/AndroidManifest.xml b/tests/cts/permission/AppThatRequestLocationPermission22/AndroidManifest.xml
new file mode 100644
index 000000000..78251baea
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationPermission22/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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.permission.cts.appthatrequestpermission"
+ android:versionCode="1">
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22" />
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestLocationPermission28/Android.bp b/tests/cts/permission/AppThatRequestLocationPermission28/Android.bp
new file mode 100644
index 000000000..bfeadbd58
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationPermission28/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2018 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: "CtsAppThatRequestsLocationPermission28",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestLocationPermission28/AndroidManifest.xml b/tests/cts/permission/AppThatRequestLocationPermission28/AndroidManifest.xml
new file mode 100644
index 000000000..c8cf95761
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationPermission28/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.permission.cts.appthatrequestpermission"
+ android:versionCode="2">
+
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestLocationPermission29/Android.bp b/tests/cts/permission/AppThatRequestLocationPermission29/Android.bp
new file mode 100644
index 000000000..ee3982ea4
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationPermission29/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2018 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: "CtsAppThatRequestsLocationPermission29",
+ defaults: ["cts_defaults"],
+ min_sdk_version: "29",
+ target_sdk_version: "29",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestLocationPermission29/AndroidManifest.xml b/tests/cts/permission/AppThatRequestLocationPermission29/AndroidManifest.xml
new file mode 100644
index 000000000..17a0e0d35
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationPermission29/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.permission.cts.appthatrequestpermission"
+ android:versionCode="1">
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestLocationPermission29v4/Android.bp b/tests/cts/permission/AppThatRequestLocationPermission29v4/Android.bp
new file mode 100644
index 000000000..b56bb25eb
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationPermission29v4/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2019 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: "CtsAppThatRequestsLocationPermission29v4",
+ defaults: ["cts_defaults"],
+ min_sdk_version: "29",
+ target_sdk_version: "29",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestLocationPermission29v4/AndroidManifest.xml b/tests/cts/permission/AppThatRequestLocationPermission29v4/AndroidManifest.xml
new file mode 100644
index 000000000..572222c49
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationPermission29v4/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permission.cts.appthatrequestpermission"
+ android:versionCode="4">
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestMultiplePermissionsWithMinMaxSdk/Android.bp b/tests/cts/permission/AppThatRequestMultiplePermissionsWithMinMaxSdk/Android.bp
new file mode 100644
index 000000000..5fcc6c8ba
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestMultiplePermissionsWithMinMaxSdk/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAppThatRequestsMultiplePermissionsWithMinMaxSdk",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestMultiplePermissionsWithMinMaxSdk/AndroidManifest.xml b/tests/cts/permission/AppThatRequestMultiplePermissionsWithMinMaxSdk/AndroidManifest.xml
new file mode 100644
index 000000000..e75a7f5c9
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestMultiplePermissionsWithMinMaxSdk/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission.cts.appthatrequestpermission"
+ android:versionCode="2">
+
+ <uses-permission android:name="android.permission.cts.appthatrequestpermission.permissions.MINSDK_LT_DEVICESDK" android:minSdkVersion="32" />
+ <uses-permission android:name="android.permission.cts.appthatrequestpermission.permissions.MINSDK_GT_DEVICESDK" android:minSdkVersion="2147483647" />
+
+ <uses-permission android:name="android.permission.cts.appthatrequestpermission.permissions.MAXSDK_LT_DEVICESDK" android:maxSdkVersion="32" />
+ <uses-permission android:name="android.permission.cts.appthatrequestpermission.permissions.MAXSDK_GT_DEVICESDK" android:maxSdkVersion="2147483647" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestOneTimePermission/Android.bp b/tests/cts/permission/AppThatRequestOneTimePermission/Android.bp
new file mode 100644
index 000000000..c12a70871
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestOneTimePermission/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2019 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: "CtsAppThatRequestsOneTimePermission",
+ defaults: [
+ "cts_defaults",
+ "mts-target-sdk-version-current",
+ ],
+ min_sdk_version: "30",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "mts-permission",
+ "general-tests",
+ ],
+ srcs: ["src/**/*.java"],
+}
diff --git a/tests/cts/permission/AppThatRequestOneTimePermission/AndroidManifest.xml b/tests/cts/permission/AppThatRequestOneTimePermission/AndroidManifest.xml
new file mode 100644
index 000000000..24fc537cf
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestOneTimePermission/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.permission.cts.appthatrequestpermission">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
+
+ <application>
+ <activity android:name=".RequestPermission" android:exported="true"
+ android:visibleToInstantApps="true" />
+ <service android:name=".KeepAliveForegroundService"
+ android:foregroundServiceType="specialUse"
+ android:exported="true"
+ android:visibleToInstantApps="true" >
+ <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="cts" />
+ </service>
+ </application>
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestOneTimePermission/src/android/permission/cts/appthatrequestpermission/KeepAliveForegroundService.java b/tests/cts/permission/AppThatRequestOneTimePermission/src/android/permission/cts/appthatrequestpermission/KeepAliveForegroundService.java
new file mode 100644
index 000000000..e41a47321
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestOneTimePermission/src/android/permission/cts/appthatrequestpermission/KeepAliveForegroundService.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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.permission.cts.appthatrequestpermission;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+
+public class KeepAliveForegroundService extends Service {
+
+ private static final String EXTRA_FOREGROUND_SERVICE_LIFESPAN =
+ "android.permission.cts.OneTimePermissionTest.EXTRA_FOREGROUND_SERVICE_LIFESPAN";
+
+ private static final String EXTRA_FOREGROUND_SERVICE_STICKY =
+ "android.permission.cts.OneTimePermissionTest.EXTRA_FOREGROUND_SERVICE_STICKY";
+
+ private static final String CHANNEL_ID = "channelId";
+ private static final String CHANNEL_NAME = "channelName";
+
+ private static final long DEFAULT_LIFESPAN = 5000;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ long lifespan;
+ boolean sticky;
+ if (intent == null) {
+ lifespan = DEFAULT_LIFESPAN;
+ sticky = false;
+ } else {
+ lifespan = intent.getLongExtra(EXTRA_FOREGROUND_SERVICE_LIFESPAN, DEFAULT_LIFESPAN);
+ sticky = intent.getBooleanExtra(EXTRA_FOREGROUND_SERVICE_STICKY, false);
+ }
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(
+ new NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_LOW));
+ Notification notification = new Notification.Builder(this, CHANNEL_ID)
+ .setSmallIcon(android.R.drawable.ic_lock_lock)
+ .build();
+ startForeground(1, notification);
+ new Handler(Looper.getMainLooper()).postDelayed(
+ () -> stopForeground(Service.STOP_FOREGROUND_REMOVE), lifespan);
+ if (sticky) {
+ return START_STICKY;
+ }
+ return super.onStartCommand(intent, flags, startId);
+ }
+}
diff --git a/tests/cts/permission/AppThatRequestOneTimePermission/src/android/permission/cts/appthatrequestpermission/RequestPermission.java b/tests/cts/permission/AppThatRequestOneTimePermission/src/android/permission/cts/appthatrequestpermission/RequestPermission.java
new file mode 100644
index 000000000..f2910c391
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestOneTimePermission/src/android/permission/cts/appthatrequestpermission/RequestPermission.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 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.permission.cts.appthatrequestpermission;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class RequestPermission extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ requestPermissions(new String[] {"android.permission.ACCESS_FINE_LOCATION"}, 0);
+ }
+}
diff --git a/tests/cts/permission/AppThatRequestPermissionAandB/Android.bp b/tests/cts/permission/AppThatRequestPermissionAandB/Android.bp
new file mode 100644
index 000000000..6c037b456
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestPermissionAandB/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2018 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: "CtsAppThatRequestsPermissionAandB",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+ srcs: ["src/**/*.java"],
+}
diff --git a/tests/cts/permission/AppThatRequestPermissionAandB/AndroidManifest.xml b/tests/cts/permission/AppThatRequestPermissionAandB/AndroidManifest.xml
new file mode 100644
index 000000000..4c85a262b
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestPermissionAandB/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.permission.cts.appthatrequestpermission">
+
+ <permission android:name="android.permission.cts.appthatrequestpermission.A"
+ android:protectionLevel="dangerous"
+ android:label="@string/perm_a"
+ android:permissionGroup="android.permission.cts.groupB"
+ android:description="@string/perm_a" />
+
+ <uses-permission android:name="android.permission.cts.appthatrequestpermission.A" />
+ <uses-permission android:name="android.permission.cts.B" />
+
+ <application>
+ <activity android:name=".RequestPermission" android:exported="true"
+ android:visibleToInstantApps="true" />
+ </application>
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestPermissionAandB/res/values/strings.xml b/tests/cts/permission/AppThatRequestPermissionAandB/res/values/strings.xml
new file mode 100644
index 000000000..563009789
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestPermissionAandB/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="perm_a">Permission A</string>
+</resources>
diff --git a/tests/cts/permission/AppThatRequestPermissionAandB/src/android/permission/cts/appthatrequestpermission/RequestPermission.java b/tests/cts/permission/AppThatRequestPermissionAandB/src/android/permission/cts/appthatrequestpermission/RequestPermission.java
new file mode 100644
index 000000000..26671beb7
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestPermissionAandB/src/android/permission/cts/appthatrequestpermission/RequestPermission.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 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.permission.cts.appthatrequestpermission;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class RequestPermission extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ requestPermissions(new String[] {"android.permission.cts.appthatrequestpermission.A",
+ "android.permission.cts.B"}, 0);
+ }
+}
diff --git a/tests/cts/permission/AppThatRequestPermissionAandC/Android.bp b/tests/cts/permission/AppThatRequestPermissionAandC/Android.bp
new file mode 100644
index 000000000..b94965334
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestPermissionAandC/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2018 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: "CtsAppThatRequestsPermissionAandC",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+ srcs: ["src/**/*.java"],
+}
diff --git a/tests/cts/permission/AppThatRequestPermissionAandC/AndroidManifest.xml b/tests/cts/permission/AppThatRequestPermissionAandC/AndroidManifest.xml
new file mode 100644
index 000000000..9b998311b
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestPermissionAandC/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.permission.cts.appthatrequestpermission">
+
+ <permission android:name="android.permission.cts.appthatrequestpermission.A"
+ android:protectionLevel="dangerous"
+ android:label="@string/perm_a"
+ android:permissionGroup="android.permission.cts.groupC"
+ android:description="@string/perm_a" />
+
+ <uses-permission android:name="android.permission.cts.appthatrequestpermission.A" />
+ <uses-permission android:name="android.permission.cts.C" />
+
+ <application>
+ <activity android:name=".RequestPermission" android:exported="true"
+ android:visibleToInstantApps="true" />
+ </application>
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestPermissionAandC/res/values/strings.xml b/tests/cts/permission/AppThatRequestPermissionAandC/res/values/strings.xml
new file mode 100644
index 000000000..563009789
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestPermissionAandC/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="perm_a">Permission A</string>
+</resources>
diff --git a/tests/cts/permission/AppThatRequestPermissionAandC/src/android/permission/cts/appthatrequestpermission/RequestPermission.java b/tests/cts/permission/AppThatRequestPermissionAandC/src/android/permission/cts/appthatrequestpermission/RequestPermission.java
new file mode 100644
index 000000000..ad72c4db2
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestPermissionAandC/src/android/permission/cts/appthatrequestpermission/RequestPermission.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 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.permission.cts.appthatrequestpermission;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class RequestPermission extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ requestPermissions(new String[] {"android.permission.cts.appthatrequestpermission.A",
+ "android.permission.cts.C"}, 0);
+ }
+}
diff --git a/tests/cts/permission/AppThatRequestStoragePermission28/Android.bp b/tests/cts/permission/AppThatRequestStoragePermission28/Android.bp
new file mode 100644
index 000000000..50ae9209b
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestStoragePermission28/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2019 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: "CtsAppThatRequestsStoragePermission28",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestStoragePermission28/AndroidManifest.xml b/tests/cts/permission/AppThatRequestStoragePermission28/AndroidManifest.xml
new file mode 100644
index 000000000..a847f39bd
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestStoragePermission28/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permission.cts.appthatrequestpermission"
+ android:versionCode="2">
+
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestStoragePermission29/Android.bp b/tests/cts/permission/AppThatRequestStoragePermission29/Android.bp
new file mode 100644
index 000000000..4663be9dc
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestStoragePermission29/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2019 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: "CtsAppThatRequestsStoragePermission29",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestStoragePermission29/AndroidManifest.xml b/tests/cts/permission/AppThatRequestStoragePermission29/AndroidManifest.xml
new file mode 100644
index 000000000..c783085a0
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestStoragePermission29/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permission.cts.appthatrequestpermission"
+ android:versionCode="1">
+
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppThatRequestSystemAlertWindow22/Android.bp b/tests/cts/permission/AppThatRequestSystemAlertWindow22/Android.bp
new file mode 100644
index 000000000..43cc9de97
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestSystemAlertWindow22/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAppThatRequestsSystemAlertWindow22",
+ target_sdk_version: "22",
+ certificate: ":cts-testkey2",
+ min_sdk_version: "22",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestSystemAlertWindow22/AndroidManifest.xml b/tests/cts/permission/AppThatRequestSystemAlertWindow22/AndroidManifest.xml
new file mode 100644
index 000000000..bd13612d2
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestSystemAlertWindow22/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission.cts.usesystemalertwindowpermission">
+
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+</manifest>
diff --git a/tests/cts/permission/AppThatRequestSystemAlertWindow23/Android.bp b/tests/cts/permission/AppThatRequestSystemAlertWindow23/Android.bp
new file mode 100644
index 000000000..403257d45
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestSystemAlertWindow23/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAppThatRequestsSystemAlertWindow23",
+ target_sdk_version: "23",
+ certificate: ":cts-testkey2",
+ min_sdk_version: "23",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestSystemAlertWindow23/AndroidManifest.xml b/tests/cts/permission/AppThatRequestSystemAlertWindow23/AndroidManifest.xml
new file mode 100644
index 000000000..bd13612d2
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestSystemAlertWindow23/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission.cts.usesystemalertwindowpermission">
+
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+</manifest>
diff --git a/tests/cts/permission/AppThatRunsRationaleTests/Android.bp b/tests/cts/permission/AppThatRunsRationaleTests/Android.bp
new file mode 100644
index 000000000..30019fba5
--- /dev/null
+++ b/tests/cts/permission/AppThatRunsRationaleTests/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2019 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: "CtsAppThatRunsRationaleTests",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "test_current",
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+
+ srcs: ["src/**/*.java"],
+}
diff --git a/tests/cts/permission/AppThatRunsRationaleTests/AndroidManifest.xml b/tests/cts/permission/AppThatRunsRationaleTests/AndroidManifest.xml
new file mode 100644
index 000000000..4b7214fd6
--- /dev/null
+++ b/tests/cts/permission/AppThatRunsRationaleTests/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permission.cts.appthatrunsrationaletests">
+
+ <uses-permission android:name="android.permission.READ_CONTACTS"/>
+
+ <application android:label="CtsRationaleTests">
+ <activity android:name=".TestActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permission/AppThatRunsRationaleTests/src/android/permission/cts/appthatrunsrationaletests/TestActivity.java b/tests/cts/permission/AppThatRunsRationaleTests/src/android/permission/cts/appthatrunsrationaletests/TestActivity.java
new file mode 100644
index 000000000..7544890ff
--- /dev/null
+++ b/tests/cts/permission/AppThatRunsRationaleTests/src/android/permission/cts/appthatrunsrationaletests/TestActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 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.permission.cts.appthatrunsrationaletests;
+
+import android.Manifest;
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+
+public class TestActivity extends Activity {
+ private static final String CALLBACK_KEY = "testactivitycallback";
+ private static final String RESULT_KEY = "testactivityresult";
+ private static final String PERMISSION_NAME = Manifest.permission.READ_CONTACTS;
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+
+ RemoteCallback cb = (RemoteCallback) getIntent().getExtras().get(CALLBACK_KEY);
+
+ boolean result = shouldShowRequestPermissionRationale(PERMISSION_NAME);
+ Bundle res = new Bundle();
+ res.putBoolean(RESULT_KEY, result);
+
+ finish();
+ cb.sendResult(res);
+ }
+}
diff --git a/tests/cts/permission/AppToTestRevokeSelfPermission/Android.bp b/tests/cts/permission/AppToTestRevokeSelfPermission/Android.bp
new file mode 100644
index 000000000..6e200bf32
--- /dev/null
+++ b/tests/cts/permission/AppToTestRevokeSelfPermission/Android.bp
@@ -0,0 +1,35 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAppToTestRevokeSelfPermission",
+ defaults: [
+ "cts_defaults",
+ "mts-target-sdk-version-current",
+ ],
+ min_sdk_version: "30",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "mts",
+ "general-tests",
+ ],
+ srcs: ["src/**/*.java"],
+}
diff --git a/tests/cts/permission/AppToTestRevokeSelfPermission/AndroidManifest.xml b/tests/cts/permission/AppToTestRevokeSelfPermission/AndroidManifest.xml
new file mode 100644
index 000000000..dbe58bfd5
--- /dev/null
+++ b/tests/cts/permission/AppToTestRevokeSelfPermission/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission.cts.apptotestrevokeselfpermission">
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
+
+ <application>
+ <activity android:name=".RevokePermission" android:exported="true"
+ android:visibleToInstantApps="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permission/AppToTestRevokeSelfPermission/src/android/permission/cts/apptotestselfrevokepermission/RevokePermission.java b/tests/cts/permission/AppToTestRevokeSelfPermission/src/android/permission/cts/apptotestselfrevokepermission/RevokePermission.java
new file mode 100644
index 000000000..a1971f7a8
--- /dev/null
+++ b/tests/cts/permission/AppToTestRevokeSelfPermission/src/android/permission/cts/apptotestselfrevokepermission/RevokePermission.java
@@ -0,0 +1,40 @@
+/*
+ * 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 android.permission.cts.apptotestrevokeselfpermission;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import java.util.Arrays;
+
+public class RevokePermission extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ String[] permissions = intent.getStringArrayExtra("permissions");
+ if (permissions == null) {
+ return;
+ }
+ if (permissions.length == 1) {
+ getApplicationContext().revokeSelfPermissionOnKill(permissions[0]);
+ } else {
+ getApplicationContext().revokeSelfPermissionsOnKill(Arrays.asList(permissions));
+ }
+ }
+}
diff --git a/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp b/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp
new file mode 100644
index 000000000..8214c425d
--- /dev/null
+++ b/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2019 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: "CtsAppWithSharedUidThatRequestsLocationPermission28",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission28/AndroidManifest.xml b/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission28/AndroidManifest.xml
new file mode 100644
index 000000000..ec69d1541
--- /dev/null
+++ b/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission28/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.permission.cts.appthatrequestpermission"
+ android:versionCode="2"
+ android:sharedUserId="android.permission.cts.appthatrequestpermission">
+
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp b/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp
new file mode 100644
index 000000000..3df5c9a7d
--- /dev/null
+++ b/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2019 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: "CtsAppWithSharedUidThatRequestsLocationPermission29",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission29/AndroidManifest.xml b/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission29/AndroidManifest.xml
new file mode 100644
index 000000000..234192259
--- /dev/null
+++ b/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission29/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.permission.cts.appthatrequestpermission"
+ android:versionCode="1"
+ android:sharedUserId="android.permission.cts.appthatrequestpermission">
+
+ <!-- STOPSHIP: Set to apk level that shipped the location tristate -->
+ <!-- <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" /> -->
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppWithSharedUidThatRequestsNoPermissions/Android.bp b/tests/cts/permission/AppWithSharedUidThatRequestsNoPermissions/Android.bp
new file mode 100644
index 000000000..7dd3ef638
--- /dev/null
+++ b/tests/cts/permission/AppWithSharedUidThatRequestsNoPermissions/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2019 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: "CtsAppWithSharedUidThatRequestsNoPermissions",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppWithSharedUidThatRequestsNoPermissions/AndroidManifest.xml b/tests/cts/permission/AppWithSharedUidThatRequestsNoPermissions/AndroidManifest.xml
new file mode 100644
index 000000000..0b34036ec
--- /dev/null
+++ b/tests/cts/permission/AppWithSharedUidThatRequestsNoPermissions/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.permission.cts.appthatrequestnopermission"
+ android:sharedUserId="cts.permissions">
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/AppWithSharedUidThatRequestsPermissions/Android.bp b/tests/cts/permission/AppWithSharedUidThatRequestsPermissions/Android.bp
new file mode 100644
index 000000000..c58b3e81e
--- /dev/null
+++ b/tests/cts/permission/AppWithSharedUidThatRequestsPermissions/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2019 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: "CtsAppWithSharedUidThatRequestsPermissions",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppWithSharedUidThatRequestsPermissions/AndroidManifest.xml b/tests/cts/permission/AppWithSharedUidThatRequestsPermissions/AndroidManifest.xml
new file mode 100644
index 000000000..ce02f17e1
--- /dev/null
+++ b/tests/cts/permission/AppWithSharedUidThatRequestsPermissions/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.permission.cts.appthatrequestpermission"
+ android:sharedUserId="cts.permissions">
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.READ_CALENDAR" />
+
+ <application />
+</manifest>
+
diff --git a/tests/cts/permission/OWNERS b/tests/cts/permission/OWNERS
new file mode 100644
index 000000000..6b284590c
--- /dev/null
+++ b/tests/cts/permission/OWNERS
@@ -0,0 +1,12 @@
+# Bug component: 137825
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
+
+per-file PowerManagerServicePermissionTest.java = file: platform/frameworks/base:/services/core/java/com/android/server/power/OWNERS
+per-file RequestLocation.java = tgunn@google.com
+
+per-file NoAudioPermissionTest.java = elaurent@google.com
+per-file MainlineNetworkStackPermissionTest.java = file: platform/frameworks/base:/services/net/OWNERS
+per-file Camera2PermissionTest.java = file: platform/frameworks/av:/camera/OWNERS
+per-file NoRollbackPermissionTest.java = mpgroover@google.com
+per-file EthernetManagerPermissionTest.java = file: platform/frameworks/base:/services/net/OWNERS \ No newline at end of file
diff --git a/tests/cts/permission/README b/tests/cts/permission/README
new file mode 100644
index 000000000..1ffc81f96
--- /dev/null
+++ b/tests/cts/permission/README
@@ -0,0 +1,16 @@
+Copyright (C) 2008 The Android Open Source Project
+
+Licensed under the Apache Licence, 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.
+
+In the permissions test cases, we just test the negative cases. These tests are to test the behavior of accessing the APIs without the required permission.
+
diff --git a/tests/cts/permission/StorageEscalationApp28/Android.bp b/tests/cts/permission/StorageEscalationApp28/Android.bp
new file mode 100644
index 000000000..9ea70f565
--- /dev/null
+++ b/tests/cts/permission/StorageEscalationApp28/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2016 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: "CtsStorageEscalationApp28",
+ certificate: ":cts-testkey2",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+}
diff --git a/tests/cts/permission/StorageEscalationApp28/AndroidManifest.xml b/tests/cts/permission/StorageEscalationApp28/AndroidManifest.xml
new file mode 100644
index 000000000..7b468bbf1
--- /dev/null
+++ b/tests/cts/permission/StorageEscalationApp28/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2016 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.permission.cts.storageescalation">
+
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
+
+ <application android:hasCode="false" />
+</manifest>
diff --git a/tests/cts/permission/StorageEscalationApp29Full/Android.bp b/tests/cts/permission/StorageEscalationApp29Full/Android.bp
new file mode 100644
index 000000000..dcb64e68a
--- /dev/null
+++ b/tests/cts/permission/StorageEscalationApp29Full/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2016 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: "CtsStorageEscalationApp29Full",
+ certificate: ":cts-testkey2",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+}
diff --git a/tests/cts/permission/StorageEscalationApp29Full/AndroidManifest.xml b/tests/cts/permission/StorageEscalationApp29Full/AndroidManifest.xml
new file mode 100644
index 000000000..0ed8e0024
--- /dev/null
+++ b/tests/cts/permission/StorageEscalationApp29Full/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2016 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.permission.cts.storageescalation">
+
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
+
+ <application android:hasCode="false" android:requestLegacyExternalStorage="true"/>
+</manifest>
diff --git a/tests/cts/permission/StorageEscalationApp29Scoped/Android.bp b/tests/cts/permission/StorageEscalationApp29Scoped/Android.bp
new file mode 100644
index 000000000..8a780c07b
--- /dev/null
+++ b/tests/cts/permission/StorageEscalationApp29Scoped/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2016 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: "CtsStorageEscalationApp29Scoped",
+ certificate: ":cts-testkey2",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+}
diff --git a/tests/cts/permission/StorageEscalationApp29Scoped/AndroidManifest.xml b/tests/cts/permission/StorageEscalationApp29Scoped/AndroidManifest.xml
new file mode 100644
index 000000000..0ce57c1b5
--- /dev/null
+++ b/tests/cts/permission/StorageEscalationApp29Scoped/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2016 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.permission.cts.storageescalation">
+
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
+
+ <application android:hasCode="false" android:requestLegacyExternalStorage="false"/>
+</manifest>
diff --git a/tests/cts/permission/jni/Android.bp b/tests/cts/permission/jni/Android.bp
new file mode 100644
index 000000000..59f93a098
--- /dev/null
+++ b/tests/cts/permission/jni/Android.bp
@@ -0,0 +1,60 @@
+// Copyright (C) 2010 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"],
+}
+
+cc_test_library {
+ name: "libctspermission_jni",
+ sdk_version: "current",
+ srcs: [
+ "CtsPermissionsJniOnLoad.cpp",
+ "android_permission_cts_FileUtils.cpp",
+ ],
+ shared_libs: [
+ "libnativehelper_compat_libc++",
+ "liblog",
+ ],
+ stl: "c++_static",
+ cflags: [
+ "-Wno-unused-parameter",
+ ],
+ gtest: false,
+}
+
+cc_test_library {
+ name: "libpermissionmanager_native_test",
+ sdk_version: "current",
+ compile_multilib: "both",
+ srcs: [
+ "PermissionManagerNativeJniTest.cpp"
+ ],
+ shared_libs: [
+ "libandroid",
+ "liblog",
+ ],
+ static_libs: [
+ "libbase_ndk",
+ ],
+ whole_static_libs: [
+ "libnativetesthelper_jni"
+ ],
+ gtest: false,
+ stl: "libc++_static",
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
diff --git a/tests/cts/permission/jni/CtsPermissionsJniOnLoad.cpp b/tests/cts/permission/jni/CtsPermissionsJniOnLoad.cpp
new file mode 100644
index 000000000..fab33bdc7
--- /dev/null
+++ b/tests/cts/permission/jni/CtsPermissionsJniOnLoad.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <jni.h>
+#include <stdio.h>
+
+extern int register_android_permission_cts_FileUtils(JNIEnv*);
+
+jint JNI_OnLoad(JavaVM *vm, void *reserved) {
+ JNIEnv *env = NULL;
+
+ if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+ return JNI_ERR;
+ }
+
+ if (register_android_permission_cts_FileUtils(env)) {
+ return JNI_ERR;
+ }
+
+ return JNI_VERSION_1_4;
+}
diff --git a/tests/cts/permission/jni/PermissionManagerNativeJniTest.cpp b/tests/cts/permission/jni/PermissionManagerNativeJniTest.cpp
new file mode 100644
index 000000000..392007074
--- /dev/null
+++ b/tests/cts/permission/jni/PermissionManagerNativeJniTest.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "PermissionManagerNativeJniTest"
+
+#include <android/permission_manager.h>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+
+class PermissionManagerNativeJniTest : public ::testing::Test {
+public:
+ void SetUp() override { }
+ void TearDown() override { }
+};
+
+//-------------------------------------------------------------------------------------------------
+TEST_F(PermissionManagerNativeJniTest, testCheckPermission) {
+ pid_t selfPid = ::getpid();
+ uid_t selfUid = ::getuid();
+
+ LOG(INFO) << "testCheckPermission: uid " << selfUid << ", pid" << selfPid;
+
+ int32_t result;
+ // Check some permission(s) we should have.
+ EXPECT_EQ(APermissionManager_checkPermission("android.permission.ACCESS_FINE_LOCATION",
+ selfPid, selfUid, &result),
+ PERMISSION_MANAGER_STATUS_OK);
+ EXPECT_EQ(result, PERMISSION_MANAGER_PERMISSION_GRANTED);
+
+ // Check some permission(s) we should not have.
+ EXPECT_EQ(APermissionManager_checkPermission("android.permission.MANAGE_USERS",
+ selfPid, selfUid, &result),
+ PERMISSION_MANAGER_STATUS_OK);
+ EXPECT_EQ(result, PERMISSION_MANAGER_PERMISSION_DENIED);
+}
+
diff --git a/tests/cts/permission/jni/android_permission_cts_FileUtils.cpp b/tests/cts/permission/jni/android_permission_cts_FileUtils.cpp
new file mode 100644
index 000000000..68c3c76d3
--- /dev/null
+++ b/tests/cts/permission/jni/android_permission_cts_FileUtils.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <android/log.h>
+#include <jni.h>
+#include <stdio.h>
+#include <linux/xattr.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/xattr.h>
+#include <sys/capability.h>
+#include <grp.h>
+#include <pwd.h>
+#include <string.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
+
+static jfieldID gFileStatusDevFieldID;
+static jfieldID gFileStatusInoFieldID;
+static jfieldID gFileStatusModeFieldID;
+static jfieldID gFileStatusNlinkFieldID;
+static jfieldID gFileStatusUidFieldID;
+static jfieldID gFileStatusGidFieldID;
+static jfieldID gFileStatusSizeFieldID;
+static jfieldID gFileStatusBlksizeFieldID;
+static jfieldID gFileStatusBlocksFieldID;
+static jfieldID gFileStatusAtimeFieldID;
+static jfieldID gFileStatusMtimeFieldID;
+static jfieldID gFileStatusCtimeFieldID;
+
+/*
+ * Native methods used by
+ * cts/tests/tests/permission/src/android/permission/cts/FileUtils.java
+ *
+ * Copied from hidden API: frameworks/base/core/jni/android_os_FileUtils.cpp
+ */
+
+jboolean android_permission_cts_FileUtils_getFileStatus(JNIEnv* env,
+ jobject /* thiz */, jstring path, jobject fileStatus, jboolean statLinks)
+{
+ ScopedUtfChars cPath(env, path);
+ jboolean ret = false;
+ struct stat s;
+
+ int res = statLinks == true ? lstat(cPath.c_str(), &s)
+ : stat(cPath.c_str(), &s);
+
+ if (res == 0) {
+ ret = true;
+ if (fileStatus != NULL) {
+ env->SetIntField(fileStatus, gFileStatusDevFieldID, s.st_dev);
+ env->SetIntField(fileStatus, gFileStatusInoFieldID, s.st_ino);
+ env->SetIntField(fileStatus, gFileStatusModeFieldID, s.st_mode);
+ env->SetIntField(fileStatus, gFileStatusNlinkFieldID, s.st_nlink);
+ env->SetIntField(fileStatus, gFileStatusUidFieldID, s.st_uid);
+ env->SetIntField(fileStatus, gFileStatusGidFieldID, s.st_gid);
+ env->SetLongField(fileStatus, gFileStatusSizeFieldID, s.st_size);
+ env->SetIntField(fileStatus, gFileStatusBlksizeFieldID, s.st_blksize);
+ env->SetLongField(fileStatus, gFileStatusBlocksFieldID, s.st_blocks);
+ env->SetLongField(fileStatus, gFileStatusAtimeFieldID, s.st_atime);
+ env->SetLongField(fileStatus, gFileStatusMtimeFieldID, s.st_mtime);
+ env->SetLongField(fileStatus, gFileStatusCtimeFieldID, s.st_ctime);
+ }
+ }
+
+ return ret;
+}
+
+jstring android_permission_cts_FileUtils_getUserName(JNIEnv* env,
+ jobject /* thiz */, jint uid)
+{
+ struct passwd *pwd = getpwuid(uid);
+ return env->NewStringUTF(pwd->pw_name);
+}
+
+jstring android_permission_cts_FileUtils_getGroupName(JNIEnv* env,
+ jobject /* thiz */, jint gid)
+{
+ struct group *grp = getgrgid(gid);
+ return env->NewStringUTF(grp->gr_name);
+}
+
+static jboolean isPermittedCapBitSet(JNIEnv* env, jstring path, size_t capId)
+{
+ struct vfs_cap_data capData;
+ memset(&capData, 0, sizeof(capData));
+
+ ScopedUtfChars cPath(env, path);
+ ssize_t result = getxattr(cPath.c_str(), XATTR_NAME_CAPS, &capData,
+ sizeof(capData));
+ if (result <= 0)
+ {
+ __android_log_print(ANDROID_LOG_DEBUG, NULL,
+ "isPermittedCapBitSet(): getxattr(\"%s\") call failed: "
+ "return %zd (error: %s (%d))\n",
+ cPath.c_str(), result, strerror(errno), errno);
+ return false;
+ }
+
+ return (capData.data[CAP_TO_INDEX(capId)].permitted &
+ CAP_TO_MASK(capId)) != 0;
+}
+
+jboolean android_permission_cts_FileUtils_hasSetUidCapability(JNIEnv* env,
+ jobject /* clazz */, jstring path)
+{
+ return isPermittedCapBitSet(env, path, CAP_SETUID);
+}
+
+jboolean android_permission_cts_FileUtils_hasSetGidCapability(JNIEnv* env,
+ jobject /* clazz */, jstring path)
+{
+ return isPermittedCapBitSet(env, path, CAP_SETGID);
+}
+
+static bool throwNamedException(JNIEnv* env, const char* className,
+ const char* message)
+{
+ ScopedLocalRef<jclass> eClazz(env, env->FindClass(className));
+ if (eClazz.get() == NULL)
+ {
+ __android_log_print(ANDROID_LOG_ERROR, NULL,
+ "throwNamedException(): failed to find class %s, cannot throw",
+ className);
+ return false;
+ }
+
+ env->ThrowNew(eClazz.get(), message);
+ return true;
+}
+
+// fill vfs_cap_data's permitted caps given a Java int[] of cap ids
+static bool fillPermittedCaps(vfs_cap_data* capData, JNIEnv* env, jintArray capIds)
+{
+ ScopedIntArrayRO cCapIds(env, capIds);
+ const size_t capCount = cCapIds.size();
+
+ for (size_t i = 0; i < capCount; ++i)
+ {
+ const jint capId = cCapIds[i];
+ if (!cap_valid(capId))
+ {
+ char message[64];
+ snprintf(message, sizeof(message),
+ "capability id %d out of valid range", capId);
+ throwNamedException(env, "java/lang/IllegalArgumentException",
+ message);
+
+ return false;
+ }
+ capData->data[CAP_TO_INDEX(capId)].permitted |= CAP_TO_MASK(capId);
+ }
+ return true;
+}
+
+jboolean android_permission_cts_FileUtils_CapabilitySet_fileHasOnly(JNIEnv* env,
+ jobject /* clazz */, jstring path, jintArray capIds)
+{
+ struct vfs_cap_data expectedCapData;
+ memset(&expectedCapData, 0, sizeof(expectedCapData));
+
+ expectedCapData.magic_etc = VFS_CAP_REVISION_2 | VFS_CAP_FLAGS_EFFECTIVE;
+ if (!fillPermittedCaps(&expectedCapData, env, capIds))
+ {
+ // exception thrown
+ return false;
+ }
+
+ struct vfs_cap_data actualCapData;
+ memset(&actualCapData, 0, sizeof(actualCapData));
+
+ ScopedUtfChars cPath(env, path);
+ ssize_t result = getxattr(cPath.c_str(), XATTR_NAME_CAPS, &actualCapData,
+ sizeof(actualCapData));
+ if (result <= 0)
+ {
+ __android_log_print(ANDROID_LOG_DEBUG, NULL,
+ "fileHasOnly(): getxattr(\"%s\") call failed: "
+ "return %zd (error: %s (%d))\n",
+ cPath.c_str(), result, strerror(errno), errno);
+ return false;
+ }
+
+ return (memcmp(&expectedCapData, &actualCapData,
+ sizeof(struct vfs_cap_data)) == 0);
+}
+
+static JNINativeMethod gMethods[] = {
+ { "getFileStatus", "(Ljava/lang/String;Landroid/permission/cts/FileUtils$FileStatus;Z)Z",
+ (void *) android_permission_cts_FileUtils_getFileStatus },
+ { "getUserName", "(I)Ljava/lang/String;",
+ (void *) android_permission_cts_FileUtils_getUserName },
+ { "getGroupName", "(I)Ljava/lang/String;",
+ (void *) android_permission_cts_FileUtils_getGroupName },
+ { "hasSetUidCapability", "(Ljava/lang/String;)Z",
+ (void *) android_permission_cts_FileUtils_hasSetUidCapability },
+ { "hasSetGidCapability", "(Ljava/lang/String;)Z",
+ (void *) android_permission_cts_FileUtils_hasSetGidCapability },
+};
+
+static JNINativeMethod gCapabilitySetMethods[] = {
+ { "fileHasOnly", "(Ljava/lang/String;[I)Z",
+ (void *) android_permission_cts_FileUtils_CapabilitySet_fileHasOnly },
+};
+
+int register_android_permission_cts_FileUtils(JNIEnv* env)
+{
+ jclass clazz = env->FindClass("android/permission/cts/FileUtils");
+
+ jclass fileStatusClass = env->FindClass("android/permission/cts/FileUtils$FileStatus");
+ gFileStatusDevFieldID = env->GetFieldID(fileStatusClass, "dev", "I");
+ gFileStatusInoFieldID = env->GetFieldID(fileStatusClass, "ino", "I");
+ gFileStatusModeFieldID = env->GetFieldID(fileStatusClass, "mode", "I");
+ gFileStatusNlinkFieldID = env->GetFieldID(fileStatusClass, "nlink", "I");
+ gFileStatusUidFieldID = env->GetFieldID(fileStatusClass, "uid", "I");
+ gFileStatusGidFieldID = env->GetFieldID(fileStatusClass, "gid", "I");
+ gFileStatusSizeFieldID = env->GetFieldID(fileStatusClass, "size", "J");
+ gFileStatusBlksizeFieldID = env->GetFieldID(fileStatusClass, "blksize", "I");
+ gFileStatusBlocksFieldID = env->GetFieldID(fileStatusClass, "blocks", "J");
+ gFileStatusAtimeFieldID = env->GetFieldID(fileStatusClass, "atime", "J");
+ gFileStatusMtimeFieldID = env->GetFieldID(fileStatusClass, "mtime", "J");
+ gFileStatusCtimeFieldID = env->GetFieldID(fileStatusClass, "ctime", "J");
+
+ jint result = env->RegisterNatives(clazz, gMethods,
+ sizeof(gMethods) / sizeof(JNINativeMethod));
+ if (result)
+ {
+ return result;
+ }
+
+ // register FileUtils.CapabilitySet native methods
+ jclass capClazz = env->FindClass("android/permission/cts/FileUtils$CapabilitySet");
+
+ return env->RegisterNatives(capClazz, gCapabilitySetMethods,
+ sizeof(gCapabilitySetMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/cts/permission/nativeTests/Android.bp b/tests/cts/permission/nativeTests/Android.bp
new file mode 100644
index 000000000..40f8b6e0d
--- /dev/null
+++ b/tests/cts/permission/nativeTests/Android.bp
@@ -0,0 +1,56 @@
+// Copyright (C) 2020 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"],
+}
+
+cc_test {
+ name: "CtsPermissionManagerNativeTestCases",
+
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+
+ srcs: ["src/PermissionManagerNativeTest.cpp"],
+
+ shared_libs: [
+ "liblog",
+ "libandroid",
+ ],
+
+ static_libs: [
+ "libgtest_ndk_c++",
+ "libbase_ndk",
+ ],
+ stl: "libc++_static",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ sdk_version: "current",
+}
diff --git a/tests/cts/permission/nativeTests/AndroidTest.xml b/tests/cts/permission/nativeTests/AndroidTest.xml
new file mode 100644
index 000000000..7d1bdb04f
--- /dev/null
+++ b/tests/cts/permission/nativeTests/AndroidTest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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 PermissionManager native 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="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ <option name="force-root" value="false" />
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsPermissionManagerNativeTestCases->/data/local/tmp/CtsPermissionManagerNativeTestCases" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="CtsPermissionManagerNativeTestCases" />
+ <option name="runtime-hint" value="15s" />
+ </test>
+
+ <!-- Controller that will skip the module if a native bridge situation is detected -->
+ <!-- For example: module wants to run arm and device is x86 -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.NativeBridgeModuleController" />
+</configuration>
diff --git a/tests/cts/permission/nativeTests/src/PermissionManagerNativeTest.cpp b/tests/cts/permission/nativeTests/src/PermissionManagerNativeTest.cpp
new file mode 100644
index 000000000..1b0dc06ea
--- /dev/null
+++ b/tests/cts/permission/nativeTests/src/PermissionManagerNativeTest.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "PermissionManagerNativeTest"
+
+#include <android/permission_manager.h>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+
+//-----------------------------------------------------------------
+class PermissionManagerNativeTest : public ::testing::Test {
+
+protected:
+ PermissionManagerNativeTest() { }
+
+ virtual ~PermissionManagerNativeTest() { }
+
+ /* Test setup*/
+ virtual void SetUp() { }
+
+ /* Test tear down */
+ virtual void TearDown() { }
+};
+
+//-------------------------------------------------------------------------------------------------
+TEST_F(PermissionManagerNativeTest, testCheckPermission) {
+ pid_t selfPid = ::getpid();
+ uid_t selfUid = ::getuid();
+
+ LOG(INFO) << "testCheckPermission: uid " << selfUid << ", pid" << selfPid;
+
+ // Test is set up to force unroot by RootTargetPreparer, so we should be running as SHELL.
+ // Check some permissions SHELL should definitely have or not have.
+ int32_t result;
+ EXPECT_EQ(APermissionManager_checkPermission("android.permission.DUMP",
+ selfPid, selfUid, &result),
+ PERMISSION_MANAGER_STATUS_OK);
+ EXPECT_EQ(result, PERMISSION_MANAGER_PERMISSION_GRANTED);
+
+ EXPECT_EQ(APermissionManager_checkPermission("android.permission.MANAGE_USERS",
+ selfPid, selfUid, &result),
+ PERMISSION_MANAGER_STATUS_OK);
+ EXPECT_EQ(result, PERMISSION_MANAGER_PERMISSION_DENIED);
+
+ EXPECT_EQ(APermissionManager_checkPermission("android.permission.NETWORK_STACK",
+ selfPid, selfUid, &result),
+ PERMISSION_MANAGER_STATUS_OK);
+ EXPECT_EQ(result, PERMISSION_MANAGER_PERMISSION_DENIED);
+}
diff --git a/tests/cts/permission/permissionTestUtilLib/Android.bp b/tests/cts/permission/permissionTestUtilLib/Android.bp
new file mode 100644
index 000000000..2f7004d5f
--- /dev/null
+++ b/tests/cts/permission/permissionTestUtilLib/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 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"],
+}
+
+java_library {
+ name: "permission-test-util-lib",
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ visibility: [
+ "//packages/modules/Permission:__subpackages__",
+ "//cts:__subpackages__",
+ ],
+ static_libs: [
+ "androidx.test.uiautomator_uiautomator",
+ "compatibility-device-util-axt",
+ "androidx.test.ext.junit-nodeps",
+ ],
+
+ sdk_version: "test_current",
+}
diff --git a/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerHelperRule.kt b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerHelperRule.kt
new file mode 100644
index 000000000..d992accb1
--- /dev/null
+++ b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerHelperRule.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.permission.cts
+
+import android.content.ComponentName
+import android.content.Context
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Rule that enables and disables the CTS NotificationListenerService
+ */
+class CtsNotificationListenerHelperRule(context: Context) : TestRule {
+
+ private val notificationListenerComponentName = ComponentName(
+ context,
+ CtsNotificationListenerService::class.java
+ )
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ try {
+ // Allow NLS used to verify notifications sent
+ SystemUtil.runShellCommand(ALLOW_NLS_COMMAND +
+ notificationListenerComponentName.flattenToString())
+
+ base.evaluate()
+ } finally {
+ // Disallow NLS used to verify notifications sent
+ SystemUtil.runShellCommand(DISALLOW_NLS_COMMAND +
+ notificationListenerComponentName.flattenToString())
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val ALLOW_NLS_COMMAND = "cmd notification allow_listener "
+ private const val DISALLOW_NLS_COMMAND = "cmd notification disallow_listener "
+ }
+}
diff --git a/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerService.java b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerService.java
new file mode 100644
index 000000000..6ffdd6fcf
--- /dev/null
+++ b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerService.java
@@ -0,0 +1,72 @@
+/*
+ * 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.permission.cts;
+
+import android.service.notification.NotificationListenerService;
+import android.util.Log;
+
+/**
+ * Implementation of {@link NotificationListenerService} for CTS tests.
+ *
+ * <p>In order to use this service in a test suite, ensure this service is declared in the test
+ * suite's AndroidManifest.xml as follows:
+ *
+ * <pre>{@code
+ * <service android:name="android.permission.cts.CtsNotificationListenerService"
+ * android:exported="true"
+ * android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.service.notification.NotificationListenerService"/>
+ * </intent-filter>
+ * </service>
+ * }</pre>
+ */
+public class CtsNotificationListenerService extends NotificationListenerService {
+ private static final String LOG_TAG = CtsNotificationListenerService.class.getSimpleName();
+
+ private static final Object sLock = new Object();
+
+ private static CtsNotificationListenerService sService;
+
+ @Override
+ public void onListenerConnected() {
+ Log.i(LOG_TAG, "connected");
+ synchronized (sLock) {
+ sService = this;
+ sLock.notifyAll();
+ }
+ }
+
+ public static NotificationListenerService getInstance() throws Exception {
+ synchronized (sLock) {
+ if (sService == null) {
+ sLock.wait(5000);
+ }
+
+ return sService;
+ }
+ }
+
+ @Override
+ public void onListenerDisconnected() {
+ Log.i(LOG_TAG, "disconnected");
+
+ synchronized (sLock) {
+ sService = null;
+ }
+ }
+}
diff --git a/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerServiceUtils.kt b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerServiceUtils.kt
new file mode 100644
index 000000000..bf2a32e9a
--- /dev/null
+++ b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerServiceUtils.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.permission.cts
+
+import android.permission.cts.TestUtils.ensure
+import android.permission.cts.TestUtils.eventually
+import android.service.notification.StatusBarNotification
+import org.junit.Assert
+
+object CtsNotificationListenerServiceUtils {
+
+ private const val NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS = 5000L
+ private const val NOTIFICATION_WAIT_MILLIS = 2000L
+
+ @JvmStatic
+ fun assertEmptyNotification(packageName: String, notificationId: Int) {
+ ensure({
+ Assert.assertNull(
+ "Expected no notification",
+ getNotification(packageName, notificationId))
+ }, NOTIFICATION_WAIT_MILLIS)
+ }
+
+ @JvmStatic
+ fun assertNotificationExist(packageName: String, notificationId: Int) {
+ eventually({
+ Assert.assertNotNull(
+ "Expected notification, none found",
+ getNotification(packageName, notificationId))
+ }, NOTIFICATION_WAIT_MILLIS)
+ }
+
+ @JvmStatic
+ fun cancelNotification(packageName: String, notificationId: Int) {
+ val notificationService = CtsNotificationListenerService.getInstance()
+ val notification = getNotification(packageName, notificationId)
+ if (notification != null) {
+ notificationService.cancelNotification(notification.key)
+ eventually({
+ Assert.assertTrue(getNotification(packageName, notificationId) == null)
+ }, NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS)
+ }
+ }
+
+ @JvmStatic
+ fun cancelNotifications(packageName: String) {
+ val notificationService = CtsNotificationListenerService.getInstance()
+ val notifications = getNotifications(packageName)
+ if (notifications.isNotEmpty()) {
+ notifications.forEach { notification ->
+ notificationService.cancelNotification(notification.key)
+ }
+ eventually({
+ Assert.assertTrue(getNotifications(packageName).isEmpty())
+ }, NOTIFICATION_CANCELLATION_TIMEOUT_MILLIS)
+ }
+ }
+
+ @JvmStatic
+ fun getNotification(packageName: String, notificationId: Int): StatusBarNotification? {
+ return getNotifications(packageName).firstOrNull {
+ it.id == notificationId
+ }
+ }
+
+ @JvmStatic
+ fun getNotifications(packageName: String): List<StatusBarNotification> {
+ val notifications: MutableList<StatusBarNotification> = ArrayList()
+ val notificationService = CtsNotificationListenerService.getInstance()
+ for (notification in notificationService.activeNotifications) {
+ if (notification.packageName == packageName) {
+ notifications.add(notification)
+ }
+ }
+ return notifications
+ }
+
+ /**
+ * Get a notification listener notification that is currently visible.
+ *
+ * @param cancelNotification if `true` the notification is canceled inside this method
+ * @return The notification or `null` if there is none
+ */
+ @JvmStatic
+ @Throws(Throwable::class)
+ fun getNotificationForPackageAndId(
+ pkg: String,
+ id: Int,
+ cancelNotification: Boolean
+ ): StatusBarNotification? {
+ val notifications: List<StatusBarNotification> = getNotifications(pkg)
+ if (notifications.isEmpty()) {
+ return null
+ }
+ for (notification in notifications) {
+ if (notification.id == id) {
+ if (cancelNotification) {
+ cancelNotification(pkg, id)
+ }
+ return notification
+ }
+ }
+ return null
+ }
+}
diff --git a/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/MtsIgnore.java b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/MtsIgnore.java
new file mode 100644
index 000000000..54c0c9a9c
--- /dev/null
+++ b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/MtsIgnore.java
@@ -0,0 +1,40 @@
+/*
+ * 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.permission.cts;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface MtsIgnore {
+ /**
+ * An optional bug number associated with the test. -1 Means that no bug number is associated.
+ *
+ * @return int
+ */
+ int bugId() default -1;
+
+ /**
+ * Details, such as the reason of why we're ignoring the test.
+ *
+ * @return String
+ */
+ String detail() default "";
+}
diff --git a/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java
new file mode 100644
index 000000000..632a6bc80
--- /dev/null
+++ b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2019 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.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY;
+import static android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS;
+import static android.Manifest.permission.MANAGE_APP_OPS_MODES;
+import static android.Manifest.permission.PACKAGE_USAGE_STATS;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OPSTR_GET_USAGE_STATS;
+import static android.app.AppOpsManager.permissionToOp;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+import static android.permission.cts.TestUtils.awaitJobUntilRequestedState;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.waitForBroadcastDispatch;
+
+import android.app.AppOpsManager;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Common utils for permission tests
+ */
+public class PermissionUtils {
+ private static final int TESTED_FLAGS = FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED
+ | FLAG_PERMISSION_REVOKE_ON_UPGRADE | FLAG_PERMISSION_REVIEW_REQUIRED
+ | FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
+
+ private static final String LOG_TAG = PermissionUtils.class.getSimpleName();
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private static final UiAutomation sUiAutomation =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+
+ private PermissionUtils() {
+ // this class should never be instantiated
+ }
+
+ /**
+ * Get the state of an app-op.
+ *
+ * @param packageName The package the app-op belongs to
+ * @param permission The permission the app-op belongs to
+ *
+ * @return The mode the op is on
+ */
+ public static int getAppOp(@NonNull String packageName, @NonNull String permission)
+ throws Exception {
+ return sContext.getSystemService(AppOpsManager.class).unsafeCheckOpRaw(
+ permissionToOp(permission),
+ sContext.getPackageManager().getPackageUid(packageName, 0), packageName);
+ }
+
+ /**
+ * Install an APK.
+ *
+ * @param apkFile The apk to install
+ */
+ public static void install(@NonNull String apkFile) {
+ final int sdkVersion = Build.VERSION.SDK_INT
+ + (Build.VERSION.RELEASE_OR_CODENAME.equals("REL") ? 0 : 1);
+ boolean forceQueryable = sdkVersion > Build.VERSION_CODES.Q;
+ runShellCommand("pm install -r --force-sdk "
+ + (SdkLevel.isAtLeastU() ? "--bypass-low-target-sdk-block " : "")
+ + (forceQueryable ? "--force-queryable " : "")
+ + apkFile);
+ }
+
+ /**
+ * Uninstall a package.
+ *
+ * @param packageName Name of package to be uninstalled
+ */
+ public static void uninstallApp(@NonNull String packageName) {
+ runShellCommand("pm uninstall " + packageName);
+ }
+
+ /**
+ * Set a new state for an app-op (using the permission-name)
+ *
+ * @param packageName The package the app-op belongs to
+ * @param permission The permission the app-op belongs to
+ * @param mode The new mode
+ */
+ public static void setAppOp(@NonNull String packageName, @NonNull String permission, int mode) {
+ setAppOpByName(packageName, permissionToOp(permission), mode);
+ }
+
+ /**
+ * Set a new state for an app-op (using the app-op-name)
+ *
+ * @param packageName The package the app-op belongs to
+ * @param op The name of the op
+ * @param mode The new mode
+ */
+ public static void setAppOpByName(@NonNull String packageName, @NonNull String op, int mode) {
+ runWithShellPermissionIdentity(
+ () -> sContext.getSystemService(AppOpsManager.class).setUidMode(op,
+ sContext.getPackageManager().getPackageUid(packageName, 0), mode),
+ MANAGE_APP_OPS_MODES);
+ }
+
+ /**
+ * Checks a permission. Does <u>not</u> check the appOp.
+ *
+ * <p>Users should use {@link #isGranted} instead.
+ *
+ * @param packageName The package that might have the permission granted
+ * @param permission The permission that might be granted
+ *
+ * @return {@code true} iff the permission is granted
+ */
+ public static boolean isPermissionGranted(@NonNull String packageName,
+ @NonNull String permission) throws Exception {
+ return sContext.checkPermission(permission, Process.myPid(),
+ sContext.getPackageManager().getPackageUid(packageName, 0))
+ == PERMISSION_GRANTED;
+ }
+
+ /**
+ * Checks if a permission is granted for a package.
+ *
+ * <p>This correctly handles pre-M apps by checking the app-ops instead.
+ * <p>This also correctly handles the location background permission, but does not handle any
+ * other background permission
+ *
+ * @param packageName The package that might have the permission granted
+ * @param permission The permission that might be granted
+ *
+ * @return {@code true} iff the permission is granted
+ */
+ public static boolean isGranted(@NonNull String packageName, @NonNull String permission)
+ throws Exception {
+ if (!isPermissionGranted(packageName, permission)) {
+ return false;
+ }
+
+ if (permission.equals(ACCESS_BACKGROUND_LOCATION)) {
+ // The app-op for background location is encoded into the mode of the foreground
+ // location
+ return getAppOp(packageName, ACCESS_COARSE_LOCATION) == MODE_ALLOWED;
+ } else {
+ int mode = getAppOp(packageName, permission);
+ return mode == MODE_ALLOWED || mode == MODE_FOREGROUND;
+ }
+ }
+
+ /**
+ * Grant a permission to an app.
+ *
+ * <p>This correctly handles pre-M apps by setting the app-ops.
+ * <p>This also correctly handles the location background permission, but does not handle any
+ * other background permission
+ *
+ * @param packageName The app that should have the permission granted
+ * @param permission The permission to grant
+ */
+ public static void grantPermission(@NonNull String packageName, @NonNull String permission)
+ throws Exception {
+ sUiAutomation.grantRuntimePermission(packageName, permission);
+
+ if (permission.equals(ACCESS_BACKGROUND_LOCATION)) {
+ // The app-op for background location is encoded into the mode of the foreground
+ // location
+ if (isPermissionGranted(packageName, ACCESS_COARSE_LOCATION)) {
+ setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_ALLOWED);
+ } else {
+ setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
+ }
+ } else if (permission.equals(ACCESS_COARSE_LOCATION)) {
+ // The app-op for location depends on the state of the bg location
+ if (isPermissionGranted(packageName, ACCESS_BACKGROUND_LOCATION)) {
+ setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_ALLOWED);
+ } else {
+ setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
+ }
+ } else if (permission.equals(PACKAGE_USAGE_STATS)) {
+ setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_ALLOWED);
+ } else if (permissionToOp(permission) != null) {
+ setAppOp(packageName, permission, MODE_ALLOWED);
+ }
+ }
+
+ /**
+ * Revoke a permission from an app.
+ *
+ * <p>This correctly handles pre-M apps by setting the app-ops.
+ * <p>This also correctly handles the location background permission, but does not handle any
+ * other background permission
+ *
+ * @param packageName The app that should have the permission revoked
+ * @param permission The permission to revoke
+ */
+ public static void revokePermission(@NonNull String packageName, @NonNull String permission)
+ throws Exception {
+ sUiAutomation.revokeRuntimePermission(packageName, permission);
+
+ if (permission.equals(ACCESS_BACKGROUND_LOCATION)) {
+ // The app-op for background location is encoded into the mode of the foreground
+ // location
+ if (isGranted(packageName, ACCESS_COARSE_LOCATION)) {
+ setAppOp(packageName, ACCESS_COARSE_LOCATION, MODE_FOREGROUND);
+ }
+ } else if (permission.equals(PACKAGE_USAGE_STATS)) {
+ setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_IGNORED);
+ } else if (permissionToOp(permission) != null) {
+ setAppOp(packageName, permission, MODE_IGNORED);
+ }
+ }
+
+ /**
+ * Clear permission state (not app-op state) of package.
+ *
+ * @param packageName Package to clear
+ */
+ public static void clearAppState(@NonNull String packageName) {
+ runShellCommand("pm clear --user current " + packageName);
+ }
+
+ /**
+ * Get all the flags of a permission.
+ *
+ * @param packageName Package the permission belongs to
+ * @param permission Name of the permission
+ *
+ * @return Permission flags
+ */
+ public static int getAllPermissionFlags(@NonNull String packageName,
+ @NonNull String permission) {
+ try {
+ return callWithShellPermissionIdentity(
+ () -> sContext.getPackageManager().getPermissionFlags(permission, packageName,
+ UserHandle.getUserHandleForUid(Process.myUid())),
+ GRANT_RUNTIME_PERMISSIONS);
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Get the flags of a permission.
+ *
+ * @param packageName Package the permission belongs to
+ * @param permission Name of the permission
+ *
+ * @return Permission flags
+ */
+ public static int getPermissionFlags(@NonNull String packageName, @NonNull String permission) {
+ return getAllPermissionFlags(packageName, permission) & TESTED_FLAGS;
+ }
+
+ /**
+ * Set the flags of a permission.
+ *
+ * @param packageName Package the permission belongs to
+ * @param permission Name of the permission
+ * @param mask Mask of permissions to set
+ * @param flags Permissions to set
+ */
+ public static void setPermissionFlags(@NonNull String packageName, @NonNull String permission,
+ int mask, int flags) {
+ runWithShellPermissionIdentity(
+ () -> sContext.getPackageManager().updatePermissionFlags(permission, packageName,
+ mask, flags, UserHandle.getUserHandleForUid(Process.myUid())),
+ GRANT_RUNTIME_PERMISSIONS, ADJUST_RUNTIME_PERMISSIONS_POLICY);
+ }
+
+ /**
+ * Get all permissions an app requests. This includes the split permissions.
+ *
+ * @param packageName The package that requests the permissions.
+ *
+ * @return The permissions requested by the app
+ */
+ public static @NonNull List<String> getPermissions(@NonNull String packageName)
+ throws Exception {
+ PackageInfo appInfo = sContext.getPackageManager().getPackageInfo(packageName,
+ GET_PERMISSIONS);
+
+ return Arrays.asList(appInfo.requestedPermissions);
+ }
+
+ /**
+ * Get all runtime permissions that an app requests. This includes the split permissions.
+ *
+ * @param packageName The package that requests the permissions.
+ *
+ * @return The runtime permissions requested by the app
+ */
+ public static @NonNull List<String> getRuntimePermissions(@NonNull String packageName)
+ throws Exception {
+ ArrayList<String> runtimePermissions = new ArrayList<>();
+
+ for (String perm : getPermissions(packageName)) {
+ PermissionInfo info = sContext.getPackageManager().getPermissionInfo(perm, 0);
+ if ((info.getProtection() & PROTECTION_DANGEROUS) != 0) {
+ runtimePermissions.add(perm);
+ }
+ }
+
+ return runtimePermissions;
+ }
+
+ /**
+ * Reset permission controller state & re-schedule the job.
+ */
+ public static void resetPermissionControllerJob(@NonNull UiAutomation automation,
+ @NonNull String packageName, int jobId, long timeout, @NonNull String intentAction,
+ @NonNull String onBootReceiver) throws Exception {
+ clearAppState(packageName);
+ awaitJobUntilRequestedState(packageName, jobId, timeout, automation, "unknown");
+ scheduleJob(automation, packageName, jobId, timeout, intentAction, onBootReceiver);
+
+ runShellCommand("cmd jobscheduler reset-execution-quota -u "
+ + Process.myUserHandle().getIdentifier() + " " + packageName);
+ runShellCommand("cmd jobscheduler reset-schedule-quota");
+ }
+
+ /**
+ * schedules a job for the privacy signal in Permission Controller
+ */
+ public static void scheduleJob(@NonNull UiAutomation automation,
+ @NonNull String packageName, int jobId, long timeout, @NonNull String intentAction,
+ @NonNull String broadcastReceiver) throws Exception {
+ long startTime = System.currentTimeMillis();
+ String jobStatus = "";
+ simulateReboot(packageName, intentAction, broadcastReceiver);
+
+ while ((System.currentTimeMillis() - startTime) < timeout
+ && !jobStatus.contains("waiting")) {
+ String cmd =
+ "cmd jobscheduler get-job-state -u " + Process.myUserHandle().getIdentifier()
+ + " " + packageName + " " + jobId;
+ jobStatus = runShellCommand(automation, cmd).trim();
+ Log.v(LOG_TAG, "Job: " + jobId + ", job status " + jobStatus);
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ // ignore interrupt
+ }
+ }
+ if (!jobStatus.contains("waiting")) {
+ throw new IllegalStateException("The job didn't get scheduled in time.");
+ }
+ }
+
+ private static void simulateReboot(@NonNull String packageName, @NonNull String intentAction,
+ @NonNull String broadcastReceiver) {
+ Intent jobSetupReceiverIntent = new Intent(intentAction);
+ jobSetupReceiverIntent.setPackage(packageName);
+ jobSetupReceiverIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+ // Query for the setup broadcast receiver
+ List<ResolveInfo> resolveInfos =
+ sContext.getPackageManager().queryBroadcastReceivers(jobSetupReceiverIntent, 0);
+
+ if (resolveInfos.size() > 0) {
+ sContext.sendBroadcast(jobSetupReceiverIntent);
+ } else {
+ Intent intent = new Intent();
+ intent.setClassName(packageName, broadcastReceiver);
+ intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.setPackage(packageName);
+ sContext.sendBroadcast(intent);
+ }
+ waitForBroadcastDispatch(intentAction);
+ }
+}
diff --git a/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java
new file mode 100644
index 000000000..48ccbe79f
--- /dev/null
+++ b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import android.app.UiAutomation;
+import android.os.Process;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Assert;
+
+/** Common test utilities */
+public class TestUtils {
+ private static final String LOG_TAG = TestUtils.class.getSimpleName();
+
+ /**
+ * A {@link java.util.concurrent.Callable} that can throw a {@link Throwable}
+ */
+ public interface ThrowingCallable<T> {
+ T call() throws Throwable;
+ }
+
+ /**
+ * A {@link Runnable} that can throw a {@link Throwable}
+ */
+ public interface ThrowingRunnable {
+ void run() throws Throwable;
+ }
+
+ /**
+ * Make sure that a {@link ThrowingRunnable} finishes without throwing a {@link
+ * Exception}.
+ *
+ * @param r The {@link ThrowingRunnable} to run.
+ * @param timeout the maximum time to wait
+ */
+ public static void ensure(@NonNull ThrowingRunnable r, long timeout) throws Throwable {
+ ensure(() -> {
+ r.run();
+ return 0;
+ }, timeout);
+ }
+
+ /**
+ * Make sure that a {@link ThrowingCallable} finishes without throwing a {@link
+ * Exception}.
+ *
+ * @param r The {@link ThrowingCallable} to run.
+ * @param timeout the maximum time to wait
+ * @return the return value from the callable
+ * @throws NullPointerException If the return value never becomes non-null
+ */
+ public static <T> T ensure(@NonNull ThrowingCallable<T> r, long timeout) throws Throwable {
+ long start = System.currentTimeMillis();
+
+ while (true) {
+ T res = r.call();
+ if (res == null) {
+ throw new NullPointerException("No result");
+ }
+
+ if (System.currentTimeMillis() - start < timeout) {
+ Thread.sleep(500);
+ } else {
+ return res;
+ }
+ }
+ }
+
+ /**
+ * Make sure that a {@link ThrowingRunnable} eventually finishes without throwing a {@link
+ * Exception}.
+ *
+ * @param r The {@link ThrowingRunnable} to run.
+ * @param timeout the maximum time to wait
+ */
+ public static void eventually(@NonNull ThrowingRunnable r, long timeout) throws Throwable {
+ eventually(() -> {
+ r.run();
+ return 0;
+ }, timeout);
+ }
+
+ /**
+ * Make sure that a {@link ThrowingCallable} eventually finishes without throwing a {@link
+ * Exception}.
+ *
+ * @param r The {@link ThrowingCallable} to run.
+ * @param timeout the maximum time to wait
+ * @return the return value from the callable
+ * @throws NullPointerException If the return value never becomes non-null
+ */
+ public static <T> T eventually(@NonNull ThrowingCallable<T> r, long timeout) throws Throwable {
+ long start = System.currentTimeMillis();
+
+ while (true) {
+ try {
+ T res = r.call();
+ if (res == null) {
+ throw new NullPointerException("No result");
+ }
+
+ return res;
+ } catch (Throwable e) {
+ if (System.currentTimeMillis() - start < timeout) {
+ Log.d(LOG_TAG, "Ignoring exception, occurred within valid wait time", e);
+
+ Thread.sleep(500);
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+
+ /**
+ * Run the job and then wait for completion
+ */
+ public static void runJobAndWaitUntilCompleted(
+ String packageName,
+ int jobId, long timeout) {
+ runJobAndWaitUntilCompleted(packageName, jobId, timeout,
+ InstrumentationRegistry.getInstrumentation().getUiAutomation());
+ }
+
+ /**
+ * Run the job and then wait for completion
+ */
+ public static void runJobAndWaitUntilCompleted(
+ String packageName,
+ int jobId,
+ long timeout,
+ UiAutomation automation) {
+ String runJobCmd = "cmd jobscheduler run -u " + Process.myUserHandle().getIdentifier()
+ + " -f " + packageName + " " + jobId;
+ try {
+ String result = runShellCommand(automation, runJobCmd);
+ Log.v(LOG_TAG, "jobscheduler run job command output: " + result);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ // waiting state is expected after completion for the periodic jobs.
+ awaitJobUntilRequestedState(packageName, jobId, timeout, automation, "waiting");
+ }
+
+ public static void awaitJobUntilRequestedState(
+ String packageName,
+ int jobId,
+ long timeout,
+ UiAutomation automation,
+ String requestedState) {
+ String statusCmd = "cmd jobscheduler get-job-state -u "
+ + Process.myUserHandle().getIdentifier() + " " + packageName + " " + jobId;
+ try {
+ eventually(() -> {
+ String jobState = runShellCommand(automation, statusCmd).trim();
+ Assert.assertTrue("The job doesn't have requested state " + requestedState
+ + " yet, current state: " + jobState, jobState.startsWith(requestedState));
+ }, timeout);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void awaitJobUntilRequestedState(
+ String packageName,
+ int jobId,
+ long timeout,
+ UiAutomation automation,
+ String requestedState1,
+ String requestedState2) {
+ String statusCmd = "cmd jobscheduler get-job-state -u "
+ + Process.myUserHandle().getIdentifier() + " " + packageName + " " + jobId;
+ try {
+ eventually(() -> {
+ String jobState = runShellCommand(automation, statusCmd).trim();
+ boolean jobInEitherRequestedState = jobState.startsWith(requestedState1)
+ || jobState.startsWith(requestedState2);
+ Assert.assertTrue("The job doesn't have requested state "
+ + "(" + requestedState1 + " or " + requestedState2 + ")"
+ + " yet, current state: " + jobState, jobInEitherRequestedState);
+ }, timeout);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tests/cts/permission/res/drawable/robot.png b/tests/cts/permission/res/drawable/robot.png
new file mode 100644
index 000000000..8a9e6984b
--- /dev/null
+++ b/tests/cts/permission/res/drawable/robot.png
Binary files differ
diff --git a/tests/cts/permission/res/values/strings.xml b/tests/cts/permission/res/values/strings.xml
new file mode 100644
index 000000000..bebb179ec
--- /dev/null
+++ b/tests/cts/permission/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="perm_b">Permission B</string>
+ <string name="perm_c">Permission C</string>
+ <string name="perm_group_b">Permission group B</string>
+ <string name="perm_group_c">Permission group B</string>
+</resources>
diff --git a/tests/cts/permission/res/xml/test_accessibilityservice.xml b/tests/cts/permission/res/xml/test_accessibilityservice.xml
new file mode 100644
index 000000000..fa87e2e0f
--- /dev/null
+++ b/tests/cts/permission/res/xml/test_accessibilityservice.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accessibilityEventTypes="typeAllMask"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:canRetrieveWindowContent="true"
+ android:accessibilityFlags="flagDefault"
+ android:notificationTimeout="0" />
diff --git a/tests/cts/permission/sdk28/Android.bp b/tests/cts/permission/sdk28/Android.bp
new file mode 100644
index 000000000..3043c9329
--- /dev/null
+++ b/tests/cts/permission/sdk28/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2018 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: "CtsPermissionTestCasesSdk28",
+ defaults: ["cts_defaults"],
+ sdk_version: "28",
+ srcs: ["src/**/*.java"],
+ static_libs: ["ctstestrunner-axt"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/sdk28/AndroidManifest.xml b/tests/cts/permission/sdk28/AndroidManifest.xml
new file mode 100644
index 000000000..1714052f7
--- /dev/null
+++ b/tests/cts/permission/sdk28/AndroidManifest.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.permission.cts.sdk28">
+
+ <uses-sdk android:minSdkVersion="3"
+ android:targetSdkVersion="28"
+ android:maxSdkVersion="28"/>
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name="android.permission.cts.PermissionStubActivity"
+ android:label="PermissionStubActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <!--
+ The CTS stubs package cannot be used as the target application here,
+ since that requires many permissions to be set. Instead, specify this
+ package itself as the target and include any stub activities needed.
+
+ This test package uses the default InstrumentationTestRunner, because
+ the InstrumentationCtsTestRunner is only available in the stubs
+ package. That runner cannot be added to this package either, since it
+ relies on hidden APIs.
+ -->
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.permission.cts.sdk28"
+ android:label="CTS tests of legacy android permissions as of API 28">
+ </instrumentation>
+
+</manifest>
diff --git a/tests/cts/permission/sdk28/AndroidTest.xml b/tests/cts/permission/sdk28/AndroidTest.xml
new file mode 100644
index 000000000..d1b5ac615
--- /dev/null
+++ b/tests/cts/permission/sdk28/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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 Permission test cases for TargetSdk 28">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="not-shardable" value="true" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsPermissionTestCasesSdk28.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.permission.cts.sdk28" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
diff --git a/tests/cts/permission/sdk28/OWNERS b/tests/cts/permission/sdk28/OWNERS
new file mode 100644
index 000000000..98dc09e9e
--- /dev/null
+++ b/tests/cts/permission/sdk28/OWNERS
@@ -0,0 +1 @@
+# Bug component: 137825
diff --git a/tests/cts/permission/sdk28/TEST_MAPPING b/tests/cts/permission/sdk28/TEST_MAPPING
new file mode 100644
index 000000000..b98bbaf43
--- /dev/null
+++ b/tests/cts/permission/sdk28/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPermissionTestCasesSdk28"
+ }
+ ]
+}
diff --git a/tests/cts/permission/sdk28/res/values/strings.xml b/tests/cts/permission/sdk28/res/values/strings.xml
new file mode 100644
index 000000000..9cc70f91a
--- /dev/null
+++ b/tests/cts/permission/sdk28/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="sdk28">SDK Level 28</string>
+</resources>
diff --git a/tests/cts/permission/sdk28/src/android/permission/cts/sdk28/RequestLocation.java b/tests/cts/permission/sdk28/src/android/permission/cts/sdk28/RequestLocation.java
new file mode 100644
index 000000000..8ba39cdfe
--- /dev/null
+++ b/tests/cts/permission/sdk28/src/android/permission/cts/sdk28/RequestLocation.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 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.permission.cts.sdk28;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.telephony.NeighboringCellInfo;
+import android.telephony.TelephonyManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class RequestLocation {
+
+ private TelephonyManager mTelephonyManager;
+ private boolean mHasTelephony;
+
+ @Before
+ public void setUp() throws Exception {
+ mHasTelephony = getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY);
+ mTelephonyManager = (TelephonyManager) getContext().getSystemService(
+ Context.TELEPHONY_SERVICE);
+ assertNotNull(mTelephonyManager);
+ }
+
+ /**
+ * Verify that a SecurityException is thrown when an app targeting SDK 28
+ * lacks the coarse location permission.
+ */
+ @Test
+ public void testGetNeighboringCellInfo() {
+ if (!mHasTelephony) return;
+ try {
+ List<NeighboringCellInfo> cellInfos = mTelephonyManager.getNeighboringCellInfo();
+ if (cellInfos != null && !cellInfos.isEmpty()) {
+ fail("Meaningful information returned from getNeighboringCellInfo!");
+ }
+ } catch (SecurityException expected) {
+ }
+ }
+
+ private static Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/AccessibilityPrivacySourceTest.kt b/tests/cts/permission/src/android/permission/cts/AccessibilityPrivacySourceTest.kt
new file mode 100644
index 000000000..c2927b129
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/AccessibilityPrivacySourceTest.kt
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts
+
+import android.accessibility.cts.common.InstrumentedAccessibilityService
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule
+import android.app.ActivityOptions
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.ComponentName
+import android.content.Context
+import android.os.Build
+import android.os.Process
+import android.permission.cts.CtsNotificationListenerServiceUtils.assertEmptyNotification
+import android.permission.cts.CtsNotificationListenerServiceUtils.assertNotificationExist
+import android.permission.cts.CtsNotificationListenerServiceUtils.cancelNotification
+import android.permission.cts.CtsNotificationListenerServiceUtils.cancelNotifications
+import android.permission.cts.CtsNotificationListenerServiceUtils.getNotification
+import android.permission.cts.SafetyCenterUtils.assertSafetyCenterIssueDoesNotExist
+import android.permission.cts.SafetyCenterUtils.assertSafetyCenterIssueExist
+import android.permission.cts.SafetyCenterUtils.assertSafetyCenterStarted
+import android.permission.cts.SafetyCenterUtils.deleteDeviceConfigPrivacyProperty
+import android.permission.cts.SafetyCenterUtils.deviceSupportsSafetyCenter
+import android.permission.cts.SafetyCenterUtils.setDeviceConfigPrivacyProperty
+import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.rule.ScreenRecordRule
+import android.provider.DeviceConfig
+import android.safetycenter.SafetyCenterManager
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+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.modules.utils.build.SdkLevel
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@AppModeFull(
+ reason =
+ "Cannot set system settings as instant app. Also we never show an accessibility " +
+ "notification for instant apps."
+)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+@ScreenRecordRule.ScreenRecord
+@FlakyTest
+class AccessibilityPrivacySourceTest {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context: Context = instrumentation.targetContext
+ private val permissionControllerPackage = context.packageManager.permissionControllerPackageName
+ private val accessibilityTestService =
+ ComponentName(context, AccessibilityTestService::class.java).flattenToString()
+ private val safetyCenterIssueId = "accessibility_$accessibilityTestService"
+ private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)
+
+ @get:Rule val screenRecordRule = ScreenRecordRule(false, false)
+
+ @get:Rule
+ val mAccessibilityServiceRule =
+ InstrumentedAccessibilityServiceTestRule(AccessibilityTestService::class.java, false)
+
+ @get:Rule
+ val deviceConfigSafetyCenterEnabled =
+ DeviceConfigStateChangerRule(
+ context,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SAFETY_CENTER_ENABLED,
+ true.toString()
+ )
+
+ @get:Rule
+ val deviceConfigA11ySourceEnabled =
+ DeviceConfigStateChangerRule(
+ context,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ ACCESSIBILITY_SOURCE_ENABLED,
+ true.toString()
+ )
+
+ @get:Rule
+ val deviceConfigA11yListenerDisabled =
+ DeviceConfigStateChangerRule(
+ context,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ ACCESSIBILITY_LISTENER_ENABLED,
+ false.toString()
+ )
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(deviceSupportsSafetyCenter(context))
+ InstrumentedAccessibilityService.disableAllServices()
+ runShellCommand("input keyevent KEYCODE_WAKEUP")
+ resetPermissionController()
+ // Bypass battery saving restrictions
+ runShellCommand(
+ "cmd tare set-vip " +
+ "${Process.myUserHandle().identifier} $permissionControllerPackage true"
+ )
+ cancelNotifications(permissionControllerPackage)
+ }
+
+ @After
+ fun cleanup() {
+ cancelNotifications(permissionControllerPackage)
+ // Reset battery saving restrictions
+ runShellCommand(
+ "cmd tare set-vip " +
+ "${Process.myUserHandle().identifier} $permissionControllerPackage default"
+ )
+ runWithShellPermissionIdentity { safetyCenterManager?.clearAllSafetySourceDataForTests() }
+ }
+
+ @Test
+ fun testJobSendsNotification() {
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ }
+
+ @Test
+ fun testJobSendsNotificationOnEnable() {
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+
+ setDeviceConfigPrivacyProperty(ACCESSIBILITY_LISTENER_ENABLED, true.toString())
+ cancelNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ InstrumentedAccessibilityService.disableAllServices()
+ setDeviceConfigPrivacyProperty(ACCESSIBILITY_LISTENER_ENABLED, false.toString())
+ setDeviceConfigPrivacyProperty(ACCESSIBILITY_JOB_INTERVAL_MILLIS, "0")
+
+ // enable service again and verify a notification
+ try {
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ } finally {
+ deleteDeviceConfigPrivacyProperty(ACCESSIBILITY_JOB_INTERVAL_MILLIS)
+ }
+ }
+
+ @Test
+ fun testJobSendsIssuesToSafetyCenter() {
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertSafetyCenterIssueExist(
+ SC_ACCESSIBILITY_SOURCE_ID,
+ safetyCenterIssueId,
+ SC_ACCESSIBILITY_ISSUE_TYPE_ID
+ )
+ }
+
+ @Test
+ fun testJobDoesNotSendNotificationInSecondRunForSameService() {
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+
+ cancelNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+
+ runJobAndWaitUntilCompleted()
+ assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ }
+
+ @Test
+ fun testAccessibilityListenerSendsIssueToSafetyCenter() {
+ setDeviceConfigPrivacyProperty(ACCESSIBILITY_LISTENER_ENABLED, true.toString())
+ try {
+ val automation = getAutomation()
+ mAccessibilityServiceRule.enableService()
+ TestUtils.eventually(
+ {
+ assertSafetyCenterIssueExist(
+ SC_ACCESSIBILITY_SOURCE_ID,
+ safetyCenterIssueId,
+ SC_ACCESSIBILITY_ISSUE_TYPE_ID,
+ automation
+ )
+ },
+ TIMEOUT_MILLIS
+ )
+ automation.destroy()
+ } finally {
+ setDeviceConfigPrivacyProperty(ACCESSIBILITY_LISTENER_ENABLED, false.toString())
+ }
+ }
+
+ @Test
+ fun testJobWithDisabledServiceDoesNotSendNotification() {
+ runJobAndWaitUntilCompleted()
+ assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ }
+
+ @Test
+ fun testJobWithDisabledServiceDoesNotSendIssueToSafetyCenter() {
+ runJobAndWaitUntilCompleted()
+ assertSafetyCenterIssueDoesNotExist(
+ SC_ACCESSIBILITY_SOURCE_ID,
+ safetyCenterIssueId,
+ SC_ACCESSIBILITY_ISSUE_TYPE_ID
+ )
+ }
+
+ @Test
+ fun testJobWithAccessibilityFeatureDisabledDoesNotSendNotification() {
+ setDeviceConfigPrivacyProperty(ACCESSIBILITY_SOURCE_ENABLED, false.toString())
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ }
+
+ @Test
+ fun testJobWithAccessibilityFeatureDisabledDoesNotSendIssueToSafetyCenter() {
+ setDeviceConfigPrivacyProperty(ACCESSIBILITY_SOURCE_ENABLED, false.toString())
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertSafetyCenterIssueDoesNotExist(
+ SC_ACCESSIBILITY_SOURCE_ID,
+ safetyCenterIssueId,
+ SC_ACCESSIBILITY_ISSUE_TYPE_ID
+ )
+ }
+
+ @Test
+ fun testJobWithSafetyCenterDisabledDoesNotSendNotification() {
+ setDeviceConfigPrivacyProperty(SAFETY_CENTER_ENABLED, false.toString())
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ }
+
+ @Test
+ fun testJobWithSafetyCenterDisabledDoesNotSendIssueToSafetyCenter() {
+ setDeviceConfigPrivacyProperty(SAFETY_CENTER_ENABLED, false.toString())
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertSafetyCenterIssueDoesNotExist(
+ SC_ACCESSIBILITY_SOURCE_ID,
+ safetyCenterIssueId,
+ SC_ACCESSIBILITY_ISSUE_TYPE_ID
+ )
+ }
+
+ @Test
+ fun testNotificationClickOpenSafetyCenter() {
+ mAccessibilityServiceRule.enableService()
+ runJobAndWaitUntilCompleted()
+ assertNotificationExist(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ val statusBarNotification =
+ getNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ Assert.assertNotNull(statusBarNotification)
+ val contentIntent = statusBarNotification!!.notification.contentIntent
+ if (SdkLevel.isAtLeastU()) {
+ val options =
+ ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ contentIntent.send(
+ /* context = */ null,
+ /* code = */ 0,
+ /* intent = */ null,
+ /* onFinished = */ null,
+ /* handler = */ null,
+ /* requiredPermission = */ null,
+ /* options = */ options.toBundle()
+ )
+ } else {
+ contentIntent.send()
+ }
+ assertSafetyCenterStarted()
+ }
+
+ private fun getAutomation(): UiAutomation {
+ return instrumentation.getUiAutomation(
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES
+ )
+ }
+
+ private fun runJobAndWaitUntilCompleted() {
+ TestUtils.runJobAndWaitUntilCompleted(
+ permissionControllerPackage,
+ ACCESSIBILITY_JOB_ID,
+ TIMEOUT_MILLIS,
+ getAutomation()
+ )
+ }
+
+ /** Reset the permission controllers state. */
+ @Throws(Throwable::class)
+ private fun resetPermissionController() {
+ PermissionUtils.resetPermissionControllerJob(
+ getAutomation(),
+ permissionControllerPackage,
+ ACCESSIBILITY_JOB_ID,
+ 45000,
+ ACTION_SET_UP_ACCESSIBILITY_CHECK,
+ AccessibilityOnBootReceiver
+ )
+ }
+
+ companion object {
+ private const val SC_ACCESSIBILITY_SOURCE_ID = "AndroidAccessibility"
+ private const val ACCESSIBILITY_SOURCE_ENABLED = "sc_accessibility_source_enabled"
+ private const val SAFETY_CENTER_ENABLED = "safety_center_is_enabled"
+ private const val ACCESSIBILITY_LISTENER_ENABLED = "sc_accessibility_listener_enabled"
+ private const val ACCESSIBILITY_JOB_INTERVAL_MILLIS = "sc_accessibility_job_interval_millis"
+
+ private const val ACCESSIBILITY_JOB_ID = 6
+ private const val ACCESSIBILITY_NOTIFICATION_ID = 4
+ private const val TIMEOUT_MILLIS: Long = 10000
+
+ private const val SC_ACCESSIBILITY_ISSUE_TYPE_ID = "accessibility_privacy_issue"
+
+ private const val AccessibilityOnBootReceiver =
+ "com.android.permissioncontroller.privacysources.AccessibilityOnBootReceiver"
+ private const val ACTION_SET_UP_ACCESSIBILITY_CHECK =
+ "com.android.permissioncontroller.action.SET_UP_ACCESSIBILITY_CHECK"
+
+ @get:ClassRule
+ @JvmStatic
+ val ctsNotificationListenerHelper =
+ CtsNotificationListenerHelperRule(
+ InstrumentationRegistry.getInstrumentation().targetContext
+ )
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/AccessibilityTestService.kt b/tests/cts/permission/src/android/permission/cts/AccessibilityTestService.kt
new file mode 100644
index 000000000..9f5e3f172
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/AccessibilityTestService.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts
+
+import android.accessibility.cts.common.InstrumentedAccessibilityService
+
+/**
+ * Test Accessibility Service
+ */
+class AccessibilityTestService : InstrumentedAccessibilityService()
diff --git a/tests/cts/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java b/tests/cts/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java
new file mode 100644
index 000000000..ebcda3a8b
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2019 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.permission.cts;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@AppModeFull(reason = "Tests properties of other app. Instant apps cannot interact with other apps")
+@RunWith(AndroidJUnit4ClassRunner.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+public class ActivityPermissionRationaleTest {
+ private static final String APK =
+ "/data/local/tmp/cts/permissions/CtsAppThatRunsRationaleTests.apk";
+ private static final String PACKAGE_NAME = "android.permission.cts.appthatrunsrationaletests";
+ private static final String PERMISSION_NAME = Manifest.permission.READ_CONTACTS;
+ private static final String CALLBACK_KEY = "testactivitycallback";
+ private static final String RESULT_KEY = "testactivityresult";
+ private static final int TIMEOUT = 5000;
+
+ private static Context sContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private static UiAutomation sUiAuto =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+
+ @BeforeClass
+ public static void setUp() {
+ runShellCommand("pm install -r " + APK);
+ int flag = PackageManager.FLAG_PERMISSION_USER_SET;
+ PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME, flag, flag);
+ }
+
+ @AfterClass
+ public static void unInstallApp() {
+ runShellCommand("pm uninstall " + PACKAGE_NAME);
+ }
+
+ private void assertAppShowRationaleIs(boolean expected) throws Exception {
+ CompletableFuture<Boolean> callbackReturn = new CompletableFuture<>();
+ RemoteCallback cb = new RemoteCallback((Bundle result) ->
+ callbackReturn.complete(result.getBoolean(RESULT_KEY)));
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".TestActivity"));
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(CALLBACK_KEY, cb);
+
+ sContext.startActivity(intent);
+ assertThat(callbackReturn.get(TIMEOUT, TimeUnit.MILLISECONDS)).isEqualTo(expected);
+ }
+
+ @Before
+ public void clearData() {
+ runShellCommand("pm clear --user " + sContext.getUserId()
+ + " android.permission.cts.appthatrunsrationaletests");
+ PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME,
+ PackageManager.FLAG_PERMISSION_POLICY_FIXED, 0);
+ }
+
+ @Test
+ public void permissionGrantedNoRationale() throws Exception {
+ sUiAuto.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME);
+
+ assertAppShowRationaleIs(false);
+ }
+
+ @Test
+ public void policyFixedNoRationale() throws Exception {
+ int flags = PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+ PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME, flags, flags);
+
+ assertAppShowRationaleIs(false);
+ }
+
+ @Test
+ public void userFixedNoRationale() throws Exception {
+ int flags = PackageManager.FLAG_PERMISSION_USER_FIXED;
+ PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME, flags, flags);
+
+ assertAppShowRationaleIs(false);
+ }
+
+ @Test
+ public void notUserSetNoRationale() throws Exception {
+ PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME,
+ PackageManager.FLAG_PERMISSION_USER_SET, 0);
+
+ assertAppShowRationaleIs(false);
+ }
+
+ @Test
+ public void userSetNeedRationale() throws Exception {
+ int flags = PackageManager.FLAG_PERMISSION_USER_SET;
+ PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME, flags, flags);
+
+ assertAppShowRationaleIs(true);
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/AppIdleStatePermissionTest.java b/tests/cts/permission/src/android/permission/cts/AppIdleStatePermissionTest.java
new file mode 100644
index 000000000..bba996366
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/AppIdleStatePermissionTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 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.permission.cts;
+
+import static org.junit.Assert.fail;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Build, install and run tests with following command:
+ * atest AppIdleStatePermissionTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AppIdleStatePermissionTest {
+
+ /**
+ * Verify that the {@link android.Manifest.permission#CHANGE_APP_IDLE_STATE}
+ * permission is only held by at most one package.
+ */
+ @Test
+ public void testChangeAppIdleStatePermission() throws Exception {
+ final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+ final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[]{
+ android.Manifest.permission.CHANGE_APP_IDLE_STATE
+ }, PackageManager.MATCH_SYSTEM_ONLY);
+
+ int count = 0;
+ String pkgNames = "";
+ for (PackageInfo pkg : holding) {
+ int uid = pm.getApplicationInfo(pkg.packageName, 0).uid;
+ if (UserHandle.isApp(uid)) {
+ pkgNames += pkg.packageName + "\n";
+ count++;
+ }
+ }
+ if (count > 1) {
+ fail("Only one app may hold the CHANGE_APP_IDLE_STATE permission; found packages: \n"
+ + pkgNames);
+ }
+ }
+
+ /**
+ * Verify that the {@link android.Manifest.permission#CHANGE_APP_LAUNCH_TIME_ESTIMATE}
+ * permission is only held by at most one package.
+ */
+ @Test
+ public void testChangeAppLaunchEstimatePermission() throws Exception {
+ final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+ final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[]{
+ android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE
+ }, PackageManager.MATCH_SYSTEM_ONLY);
+
+ int count = 0;
+ String pkgNames = "";
+ for (PackageInfo pkg : holding) {
+ int uid = pm.getApplicationInfo(pkg.packageName, 0).uid;
+ if (UserHandle.isApp(uid)) {
+ pkgNames += pkg.packageName + "\n";
+ count++;
+ }
+ }
+ if (count > 1) {
+ fail("Only one app may hold the CHANGE_APP_LAUNCH_TIME_ESTIMATE permission;"
+ + " found packages: \n" + pkgNames);
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/cts/permission/src/android/permission/cts/AppWidgetManagerPermissionTest.java b/tests/cts/permission/src/android/permission/cts/AppWidgetManagerPermissionTest.java
new file mode 100644
index 000000000..294896d97
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/AppWidgetManagerPermissionTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+* Test that protected AppWidgetManager APIs cannot be called without permissions
+*/
+public class AppWidgetManagerPermissionTest extends AndroidTestCase {
+
+ private AppWidgetManager mAppWidgetManager = null;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mAppWidgetManager = AppWidgetManager.getInstance(getContext());
+ }
+
+ /**
+ * Verify that calling
+ * {@link AppWidgetManager#bindAppWidgetId(int, android.content.ComponentName)}
+ * requires permissions.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#BIND_APP_WIDGET}.
+ */
+ @SmallTest
+ public void testBindAppWidget() {
+ if (!getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) {
+ return;
+ }
+ assertNotNull(mAppWidgetManager);
+
+ try {
+ final boolean bound = mAppWidgetManager.bindAppWidgetIdIfAllowed(1,
+ new ComponentName(mContext, "foo"));
+ assertFalse("Was able to call bindAppWidgetId", bound);
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/BackgroundPermissionButtonLabelTest.java b/tests/cts/permission/src/android/permission/cts/BackgroundPermissionButtonLabelTest.java
new file mode 100644
index 000000000..004f8bc8c
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/BackgroundPermissionButtonLabelTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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.permission.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class BackgroundPermissionButtonLabelTest {
+
+ // Name of the resource which provides background permission button string
+ public static final String APP_PERMISSION_BUTTON_ALLOW_ALWAYS =
+ "app_permission_button_allow_always";
+
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private final String mPermissionController =
+ mContext.getPackageManager().getPermissionControllerPackageName();
+
+ @Test
+ public void testBackgroundPermissionButtonLabel() {
+ try {
+ Context permissionControllerContext =
+ mContext.createPackageContext(mPermissionController, 0);
+ int stringId = permissionControllerContext.getResources().getIdentifier(
+ APP_PERMISSION_BUTTON_ALLOW_ALWAYS, "string",
+ "com.android.permissioncontroller");
+
+ Assert.assertEquals(mContext.getPackageManager().getBackgroundPermissionOptionLabel(),
+ permissionControllerContext.getString(stringId));
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+}
diff --git a/tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java b/tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java
new file mode 100644
index 000000000..7baaafb0d
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2018 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.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+import static android.content.pm.PermissionInfo.PROTECTION_INTERNAL;
+import static android.permission.cts.PermissionUtils.getAppOp;
+import static android.permission.cts.PermissionUtils.grantPermission;
+import static android.permission.cts.PermissionUtils.install;
+import static android.permission.cts.PermissionUtils.uninstallApp;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.AppOpsManager;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.platform.test.annotations.AppModeFull;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BackgroundPermissionsTest {
+ private static final String LOG_TAG = BackgroundPermissionsTest.class.getSimpleName();
+
+ /** The package name of all apps used in the test */
+ private static final String APP_PKG = "android.permission.cts.appthatrequestpermission";
+
+ private static final String TMP_DIR = "/data/local/tmp/cts/permissions/";
+ private static final String APK_LOCATION_BACKGROUND_29 =
+ TMP_DIR + "CtsAppThatRequestsLocationAndBackgroundPermission29.apk";
+ private static final String APK_LOCATION_29v4 =
+ TMP_DIR + "CtsAppThatRequestsLocationPermission29v4.apk";
+
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private static final UiAutomation sUiAutomation =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+
+ @After
+ public void uninstallTestApp() {
+ uninstallApp(APP_PKG);
+ }
+
+ @Test
+ @AppModeFull(reason = "Instant apps cannot read properties of other packages")
+ public void verifybackgroundPermissionsProperties() throws Exception {
+ PackageInfo pkg = sContext.getPackageManager().getPackageInfo(
+ "android", PackageManager.GET_PERMISSIONS);
+ ArrayMap<String, String> potentialBackgroundPermissionsToGroup = new ArrayMap<>();
+
+ int numPermissions = pkg.permissions.length;
+ for (int i = 0; i < numPermissions; i++) {
+ PermissionInfo permission = pkg.permissions[i];
+
+ // background permissions must be dangerous or ungrantable or role
+ if ((permission.getProtection() & PROTECTION_DANGEROUS) != 0
+ || (permission.getProtection() == PROTECTION_INTERNAL
+ && (permission.getProtectionFlags() == 0
+ || permission.getProtectionFlags() == PermissionInfo.PROTECTION_FLAG_ROLE))) {
+ potentialBackgroundPermissionsToGroup.put(permission.name, permission.group);
+ }
+ }
+
+ for (int i = 0; i < numPermissions; i++) {
+ PermissionInfo permission = pkg.permissions[i];
+ String backgroundPermissionName = permission.backgroundPermission;
+
+ if (backgroundPermissionName != null) {
+ Log.i(LOG_TAG, permission.name + "->" + backgroundPermissionName);
+
+ // foreground permissions must be dangerous
+ assertNotEquals(0, permission.getProtection() & PROTECTION_DANGEROUS);
+
+ // All foreground permissions need an app op
+ assertNotNull(AppOpsManager.permissionToOp(permission.name));
+
+ // the background permission must exist
+ assertTrue(potentialBackgroundPermissionsToGroup
+ .containsKey(backgroundPermissionName));
+ }
+ }
+ }
+
+ /**
+ * If a bg permission is lost during an upgrade, the app-op should downgrade to foreground
+ */
+ @Test
+ @AppModeFull(reason = "Instant apps cannot read properties of other packages which is needed "
+ + "to grant permissions to them. Also instant apps are never updated, hence the test "
+ + "is useless.")
+ public void appOpGetsDowngradedWhenBgPermIsNotRequestedAnymore() throws Exception {
+ install(APK_LOCATION_BACKGROUND_29);
+ grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+
+ install(APK_LOCATION_29v4);
+
+ eventually(() -> assertWithMessage("foreground app-op").that(
+ getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_FOREGROUND));
+ }
+
+ /**
+ * Make sure location switch-op is set if no location access is granted.
+ */
+ @Test
+ @AppModeFull(reason = "Instant apps cannot read properties of other packages which is needed "
+ + "to grant permissions to them. Also instant apps are never updated, hence the test "
+ + "is useless.")
+ public void appOpIsSetIfNoLocPermIsGranted() {
+ install(APK_LOCATION_BACKGROUND_29);
+
+ // Wait until the system sets the app-op automatically
+ eventually(() -> assertWithMessage("loc app-op").that(
+ getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_IGNORED));
+ }
+
+ /**
+ * Make sure location switch-op is set if only coarse location is granted
+ */
+ @Test
+ @AppModeFull(reason = "Instant apps cannot read properties of other packages which is needed "
+ + "to grant permissions to them. Also instant apps are never updated, hence the test "
+ + "is useless.")
+ public void appOpIsSetIfOnlyCoarseLocPermIsGranted() {
+ install(APK_LOCATION_BACKGROUND_29);
+ sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_COARSE_LOCATION);
+
+ // Wait until the system sets the app-op automatically
+ eventually(() -> assertWithMessage("loc app-op").that(
+ getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_FOREGROUND));
+ }
+
+ /**
+ * Make sure location switch-op is set if coarse location with background access is granted.
+ */
+ @Test
+ @AppModeFull(reason = "Instant apps cannot read properties of other packages which is needed "
+ + "to grant permissions to them. Also instant apps are never updated, hence the test "
+ + "is useless.")
+ public void appOpIsSetIfCoarseAndBgLocPermIsGranted() {
+ install(APK_LOCATION_BACKGROUND_29);
+ sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_COARSE_LOCATION);
+ sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+ // Wait until the system sets the app-op automatically
+ eventually(() -> assertWithMessage("loc app-op").that(
+ getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_ALLOWED));
+ }
+
+ /**
+ * Make sure location switch-op is set if only fine location is granted
+ */
+ @Test
+ @AppModeFull(reason = "Instant apps cannot read properties of other packages which is needed "
+ + "to grant permissions to them. Also instant apps are never updated, hence the test "
+ + "is useless.")
+ public void appOpIsSetIfOnlyFineLocPermIsGranted() {
+ install(APK_LOCATION_BACKGROUND_29);
+ sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_FINE_LOCATION);
+
+ // Wait until the system sets the app-op automatically
+ // Fine location uses background location to limit access
+ eventually(() -> assertWithMessage("loc app-op").that(
+ getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_FOREGROUND));
+ }
+
+ /**
+ * Make sure location switch-op is set if fine location with background access is granted.
+ */
+ @Test
+ @AppModeFull(reason = "Instant apps cannot read properties of other packages which is needed "
+ + "to grant permissions to them. Also instant apps are never updated, hence the test "
+ + "is useless.")
+ public void appOpIsSetIfFineAndBgLocPermIsGranted() {
+ install(APK_LOCATION_BACKGROUND_29);
+ sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_FINE_LOCATION);
+ sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+ // Wait until the system sets the app-op automatically
+ eventually(() -> assertWithMessage("loc app-op").that(
+ getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_ALLOWED));
+ }
+
+ /**
+ * Make sure location switch-op is set if fine and coarse location access is granted.
+ */
+ @Test
+ @AppModeFull(reason = "Instant apps cannot read properties of other packages which is needed "
+ + "to grant permissions to them. Also instant apps are never updated, hence the test "
+ + "is useless.")
+ public void appOpIsSetIfFineAndCoarseLocPermIsGranted() {
+ install(APK_LOCATION_BACKGROUND_29);
+ sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_FINE_LOCATION);
+ sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_COARSE_LOCATION);
+
+ // Wait until the system sets the app-op automatically
+ eventually(() -> assertWithMessage("loc app-op").that(
+ getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_FOREGROUND));
+ }
+
+ /**
+ * Make sure location switch-op is set if fine and coarse location with background access is
+ * granted.
+ */
+ @Test
+ @AppModeFull(reason = "Instant apps cannot read properties of other packages which is needed "
+ + "to grant permissions to them. Also instant apps are never updated, hence the test "
+ + "is useless.")
+ public void appOpIsSetIfFineCoarseAndBgLocPermIsGranted() {
+ install(APK_LOCATION_BACKGROUND_29);
+ sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_FINE_LOCATION);
+ sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_COARSE_LOCATION);
+ sUiAutomation.grantRuntimePermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+ // Wait until the system sets the app-op automatically
+ eventually(() -> assertWithMessage("loc app-op").that(
+ getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_ALLOWED));
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/BaseNotificationListenerCheckTest.java b/tests/cts/permission/src/android/permission/cts/BaseNotificationListenerCheckTest.java
new file mode 100644
index 000000000..3060a63d7
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/BaseNotificationListenerCheckTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import static android.os.Process.myUserHandle;
+import static android.permission.cts.TestUtils.eventually;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.app.NotificationManager;
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.DeviceConfig;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+
+import java.util.List;
+
+/**
+ * Base test class used for {@code NotificationListenerCheckTest} and
+ * {@code NotificationListenerCheckWithSafetyCenterUnsupportedTest}
+ */
+public class BaseNotificationListenerCheckTest {
+ private static final String LOG_TAG = BaseNotificationListenerCheckTest.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ protected static final String TEST_APP_PKG =
+ "android.permission.cts.appthathasnotificationlistener";
+ private static final String TEST_APP_NOTIFICATION_SERVICE =
+ TEST_APP_PKG + ".CtsNotificationListenerService";
+ protected static final String TEST_APP_NOTIFICATION_LISTENER_APK =
+ "/data/local/tmp/cts/permissions/CtsAppThatHasNotificationListener.apk";
+
+ private static final int NOTIFICATION_LISTENER_CHECK_JOB_ID = 4;
+
+ /**
+ * Device config property for whether notification listener check is enabled on the device
+ */
+ private static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED =
+ "notification_listener_check_enabled";
+
+ /**
+ * Device config property for time period in milliseconds after which current enabled
+ * notification
+ * listeners are queried
+ */
+ protected static final String PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS =
+ "notification_listener_check_interval_millis";
+
+ protected static final Long OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS =
+ SECONDS.toMillis(0);
+
+ private static final String ACTION_SET_UP_NOTIFICATION_LISTENER_CHECK =
+ "com.android.permissioncontroller.action.SET_UP_NOTIFICATION_LISTENER_CHECK";
+ private static final String NotificationListenerOnBootReceiver =
+ "com.android.permissioncontroller.privacysources.SetupPeriodicNotificationListenerCheck";
+
+ /**
+ * ID for notification shown by
+ * {@link com.android.permissioncontroller.privacysources.NotificationListenerCheck}.
+ */
+ public static final int NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID = 3;
+
+ protected static final long UNEXPECTED_TIMEOUT_MILLIS = 10000;
+ protected static final long ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS = 5000;
+
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+ private static final PackageManager sPackageManager = sContext.getPackageManager();
+ private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation();
+
+ private static final String PERMISSION_CONTROLLER_PKG = sContext.getPackageManager()
+ .getPermissionControllerPackageName();
+
+ private static List<ComponentName> sPreviouslyEnabledNotificationListeners;
+
+ // Override SafetyCenter enabled flag
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigSafetyCenterEnabled =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SafetyCenterUtils.PROPERTY_SAFETY_CENTER_ENABLED,
+ Boolean.toString(true));
+
+ // Override NlsCheck enabled flag
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckEnabled =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED,
+ Boolean.toString(true));
+
+ // Override general notification interval from once every day to once ever 1 second
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigNlsCheckIntervalMillis =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS,
+ Long.toString(OVERRIDE_NOTIFICATION_LISTENER_CHECK_INTERVAL_MILLIS));
+
+ @Rule
+ public CtsNotificationListenerHelperRule ctsNotificationListenerHelper =
+ new CtsNotificationListenerHelperRule(sContext);
+
+ @BeforeClass
+ public static void beforeClassSetup() throws Exception {
+ // Bypass battery saving restrictions
+ runShellCommand("cmd tare set-vip "
+ + myUserHandle().getIdentifier() + " " + PERMISSION_CONTROLLER_PKG + " true");
+ // Disallow any OEM enabled NLS
+ disallowPreexistingNotificationListeners();
+ }
+
+ @AfterClass
+ public static void afterClassTearDown() throws Throwable {
+ // Reset battery saving restrictions
+ runShellCommand("cmd tare set-vip "
+ + myUserHandle().getIdentifier() + " " + PERMISSION_CONTROLLER_PKG + " default");
+ // Reallow any previously OEM allowed NLS
+ reallowPreexistingNotificationListeners();
+ }
+
+ protected static void setDeviceConfigPrivacyProperty(String propertyName, String value) {
+ runWithShellPermissionIdentity(() -> {
+ boolean valueWasSet = DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ /* name = */ propertyName,
+ /* value = */ value,
+ /* makeDefault = */ false);
+ if (!valueWasSet) {
+ throw new IllegalStateException("Could not set " + propertyName + " to " + value);
+ }
+ });
+ }
+
+ /**
+ * Enable or disable notification listener check
+ */
+ protected static void setNotificationListenerCheckEnabled(boolean enabled) {
+ setDeviceConfigPrivacyProperty(
+ PROPERTY_NOTIFICATION_LISTENER_CHECK_ENABLED,
+ /* value = */ String.valueOf(enabled));
+ }
+
+ /**
+ * Allow or disallow a {@link NotificationListenerService} component for the current user
+ *
+ * @param listenerComponent {@link NotificationListenerService} component to allow or disallow
+ */
+ private static void setNotificationListenerServiceAllowed(ComponentName listenerComponent,
+ boolean allowed) {
+ String command = " cmd notification " + (allowed ? "allow_listener " : "disallow_listener ")
+ + listenerComponent.flattenToString();
+ runShellCommand(command);
+ }
+
+ private static void disallowPreexistingNotificationListeners() {
+ runWithShellPermissionIdentity(() -> {
+ NotificationManager notificationManager =
+ sContext.getSystemService(NotificationManager.class);
+ sPreviouslyEnabledNotificationListeners =
+ notificationManager.getEnabledNotificationListeners();
+ });
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Found " + sPreviouslyEnabledNotificationListeners.size()
+ + " previously allowed notification listeners. Disabling before test run.");
+ }
+ for (ComponentName listener : sPreviouslyEnabledNotificationListeners) {
+ setNotificationListenerServiceAllowed(listener, false);
+ }
+ }
+
+ private static void reallowPreexistingNotificationListeners() {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Re-allowing " + sPreviouslyEnabledNotificationListeners.size()
+ + " previously allowed notification listeners found before test run.");
+ }
+ for (ComponentName listener : sPreviouslyEnabledNotificationListeners) {
+ setNotificationListenerServiceAllowed(listener, true);
+ }
+ }
+
+ protected void allowTestAppNotificationListenerService() {
+ setNotificationListenerServiceAllowed(
+ new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), true);
+ }
+
+ protected void disallowTestAppNotificationListenerService() {
+ setNotificationListenerServiceAllowed(
+ new ComponentName(TEST_APP_PKG, TEST_APP_NOTIFICATION_SERVICE), false);
+ }
+
+ /**
+ * Force a run of the notification listener check.
+ */
+ protected static void runNotificationListenerCheck() throws Throwable {
+ TestUtils.awaitJobUntilRequestedState(
+ PERMISSION_CONTROLLER_PKG,
+ NOTIFICATION_LISTENER_CHECK_JOB_ID,
+ UNEXPECTED_TIMEOUT_MILLIS,
+ sUiAutomation,
+ "waiting"
+ );
+
+ TestUtils.runJobAndWaitUntilCompleted(
+ PERMISSION_CONTROLLER_PKG,
+ NOTIFICATION_LISTENER_CHECK_JOB_ID,
+ UNEXPECTED_TIMEOUT_MILLIS,
+ sUiAutomation
+ );
+ }
+
+ /**
+ * Skip tests for if Safety Center not supported
+ */
+ protected void assumeDeviceSupportsSafetyCenter() {
+ assumeTrue(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext));
+ }
+
+ /**
+ * Skip tests for if Safety Center IS supported
+ */
+ protected void assumeDeviceDoesNotSupportSafetyCenter() {
+ assumeFalse(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext));
+ }
+
+ protected void wakeUpAndDismissKeyguard() {
+ runShellCommand("input keyevent KEYCODE_WAKEUP");
+ runShellCommand("wm dismiss-keyguard");
+ }
+
+ /**
+ * Reset the permission controllers state before each test
+ */
+ protected void resetPermissionControllerBeforeEachTest() throws Throwable {
+ resetPermissionController();
+
+ // ensure no posted notification listener notifications exits
+ eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
+
+ // Reset job scheduler stats (to allow more jobs to be run)
+ runShellCommand(
+ "cmd jobscheduler reset-execution-quota -u " + myUserHandle().getIdentifier() + " "
+ + PERMISSION_CONTROLLER_PKG);
+ runShellCommand("cmd jobscheduler reset-schedule-quota");
+ }
+
+ /**
+ * Reset the permission controllers state.
+ */
+ private static void resetPermissionController() throws Throwable {
+ PermissionUtils.resetPermissionControllerJob(sUiAutomation, PERMISSION_CONTROLLER_PKG,
+ NOTIFICATION_LISTENER_CHECK_JOB_ID, 45000,
+ ACTION_SET_UP_NOTIFICATION_LISTENER_CHECK, NotificationListenerOnBootReceiver);
+ }
+
+ /**
+ * Preshow/dismiss cts NotificationListener notification as it negatively affects test results
+ * (can result in unexpected test pass/failures)
+ */
+ protected void triggerAndDismissCtsNotificationListenerNotification() throws Throwable {
+ // CtsNotificationListenerService isn't enabled at this point, but NotificationListener
+ // should be. Mark as notified by showing and dismissing
+ runNotificationListenerCheck();
+
+ // Ensure notification shows and dismiss
+ eventually(() -> assertNotNull(getNotification(true)),
+ UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ /**
+ * Get a notification listener notification that is currently visible.
+ *
+ * @param cancelNotification if `true` the notification is canceled inside this method
+ * @return The notification or `null` if there is none
+ */
+ protected StatusBarNotification getNotification(boolean cancelNotification) throws Throwable {
+ return CtsNotificationListenerServiceUtils.getNotificationForPackageAndId(
+ PERMISSION_CONTROLLER_PKG,
+ NOTIFICATION_LISTENER_CHECK_NOTIFICATION_ID,
+ cancelNotification);
+ }
+
+ /**
+ * Clear any notifications related to NotificationListenerCheck to ensure clean test setup
+ */
+ protected void clearNotifications() throws Throwable {
+ // Clear notification if present
+ CtsNotificationListenerServiceUtils.cancelNotifications(PERMISSION_CONTROLLER_PKG);
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/Camera2PermissionTest.java b/tests/cts/permission/src/android/permission/cts/Camera2PermissionTest.java
new file mode 100644
index 000000000..379f47815
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/Camera2PermissionTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2014 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.permission.cts;
+
+import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraCharacteristics.Key;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.platform.test.annotations.Presubmit;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for Camera2 API related Permissions. Currently, this means
+ * android.permission.CAMERA.
+ */
+public class Camera2PermissionTest extends AndroidTestCase {
+ private static final String TAG = "Camera2PermissionTest";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final int CAMERA_CLOSE_TIMEOUT_MS = 2000;
+
+ private CameraManager mCameraManager;
+ private CameraDevice mCamera;
+ private BlockingStateCallback mCameraListener;
+ private String[] mCameraIds;
+ protected Handler mHandler;
+ protected HandlerThread mHandlerThread;
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+ mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+ assertNotNull("Can't connect to camera manager!", mCameraManager);
+ }
+
+ /**
+ * Set up the camera2 test case required environments, including CameraManager,
+ * HandlerThread, Camera IDs, and CameraStateCallback etc.
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mCameraIds = mCameraManager.getCameraIdList();
+ assertNotNull("Camera ids shouldn't be null", mCameraIds);
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mCameraListener = new BlockingStateCallback();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mHandlerThread.quitSafely();
+ mHandler = null;
+
+ super.tearDown();
+ }
+
+ /**
+ * Attempt to open camera. Requires Permission:
+ * {@link android.Manifest.permission#CAMERA}.
+ */
+ public void testCameraOpen() throws Exception {
+ for (String id : mCameraIds) {
+ try {
+ openCamera(id);
+ fail("Was able to open camera " + id + " with no permission");
+ }
+ catch (SecurityException e) {
+ // expected
+ } finally {
+ closeCamera();
+ }
+ }
+ }
+
+ /**
+ * Check that no system cameras can be discovered without
+ * {@link android.Manifest.permission#CAMERA} and android.permission.SYSTEM_CAMERA
+ */
+ public void testSystemCameraDiscovery() throws Exception {
+ for (String id : mCameraIds) {
+ Log.i(TAG, "testSystemCameraDiscovery for camera id " + id);
+ CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);
+ assertNotNull("Camera characteristics shouldn't be null", characteristics);
+ int[] availableCapabilities =
+ characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+ assertTrue("Camera capabilities shouldn't be null", availableCapabilities != null);
+ List<Integer> capList = toList(availableCapabilities);
+ assertFalse("System camera device " + id + " should not be public",
+ capList.contains(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA));
+ }
+ }
+
+ /**
+ * Check the absence of camera characteristics keys that require Permission:
+ * {@link android.Manifest.permission#CAMERA}.
+ */
+ public void testCameraCharacteristicsNeedingPermission() throws Exception {
+ for (String id : mCameraIds) {
+ CameraCharacteristics capabilities = mCameraManager.getCameraCharacteristics(id);
+ assertNotNull("Camera characteristics shouldn't be null", capabilities);
+ List<Key<?>> keysNeedingPermission = capabilities.getKeysNeedingPermission();
+ if (keysNeedingPermission == null) {
+ continue;
+ }
+ List<Key<?>> keys = capabilities.getKeys();
+ assertNotNull("Camera characteristics key list shouldn't be null", keys);
+ for (Key<?> key : keysNeedingPermission) {
+ assertEquals("Key " + key.getName() + " needing permission is part of the" +
+ " available characteristics keys", -1, keys.indexOf(key));
+ assertNull("Key " + key.getName() + " needing permission must not present" +
+ " in camera characteristics", capabilities.get(key));
+ }
+ }
+ }
+
+ /**
+ * Add and remove availability listeners should work without permission.
+ */
+ @Presubmit
+ public void testAvailabilityCallback() throws Exception {
+ DummyCameraListener availabilityListener = new DummyCameraListener();
+ // Remove a not-registered listener is a no-op.
+ mCameraManager.unregisterAvailabilityCallback(availabilityListener);
+ mCameraManager.registerAvailabilityCallback(availabilityListener, mHandler);
+ mCameraManager.unregisterAvailabilityCallback(availabilityListener);
+ mCameraManager.registerAvailabilityCallback(availabilityListener, mHandler);
+ mCameraManager.registerAvailabilityCallback(availabilityListener, mHandler);
+ mCameraManager.unregisterAvailabilityCallback(availabilityListener);
+ // Remove a previously-added listener second time is a no-op.
+ mCameraManager.unregisterAvailabilityCallback(availabilityListener);
+ }
+
+ private class DummyCameraListener extends CameraManager.AvailabilityCallback {
+ @Override
+ public void onCameraAvailable(String cameraId) {
+ }
+
+ @Override
+ public void onCameraUnavailable(String cameraId) {
+ }
+ }
+
+ private void openCamera(String cameraId) throws Exception {
+ mCamera = (new BlockingCameraManager(mCameraManager)).openCamera(
+ cameraId, mCameraListener, mHandler);
+ }
+
+ private static List<Integer> toList(int[] array) {
+ List<Integer> list = new ArrayList<Integer>();
+ for (int i : array) {
+ list.add(i);
+ }
+ return list;
+ }
+
+ private void closeCamera() {
+ if (mCamera != null) {
+ mCamera.close();
+ mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+ mCamera = null;
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/CameraPermissionTest.java b/tests/cts/permission/src/android/permission/cts/CameraPermissionTest.java
new file mode 100644
index 000000000..99f2862f1
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/CameraPermissionTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import android.hardware.Camera;
+import android.os.Environment;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import java.io.FileOutputStream;
+
+/**
+ * Tests for camera-related Permissions. Currently, this means
+ * android.permission.CAMERA.
+ */
+public class CameraPermissionTest extends AndroidTestCase {
+
+ private static String PATH_PREFIX = Environment.getExternalStorageDirectory().toString();
+ private static String CAMERA_IMAGE_PATH = PATH_PREFIX + "this-should-not-exist.jpg";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ class ShutterCallback implements Camera.ShutterCallback {
+ public void onShutter() { }
+ }
+
+ class RawPictureCallback implements Camera.PictureCallback {
+ public void onPictureTaken(byte [] rawData, Camera camera) { }
+ }
+
+ class JpegPictureCallback implements Camera.PictureCallback {
+ public void onPictureTaken(byte [] jpegData, Camera camera) {
+ if (jpegData == null) {
+ // TODO: Is this good (= expected, = correct), or weird, or bad?
+ return;
+ }
+
+ try {
+ FileOutputStream s = new FileOutputStream(CAMERA_IMAGE_PATH);
+ s.write(jpegData);
+ s.flush();
+ }
+ catch (SecurityException e) {
+ // Sure, NOW they tell us (NOTE: this could be a side effect
+ // of the upcoming WRITE_EXTERNAL_STORAGE permission).
+ } catch (Exception e) {
+ // We didn't really need to save it anyway, did we?
+ }
+
+ fail("Successfully captured an image of " + jpegData.length +
+ " bytes, and saved it to " + CAMERA_IMAGE_PATH);
+ }
+ }
+
+ /**
+ * Attempt to take a picture. Requires Permission:
+ * {@link android.Manifest.permission#CAMERA}.
+ */
+ @MediumTest
+ public void testCamera() {
+ try {
+ (Camera.open()).takePicture(new ShutterCallback(),
+ new RawPictureCallback(),
+ new JpegPictureCallback());
+ fail("Was able to take a picture with the camera with no permission");
+ }
+ catch (SecurityException e) {
+ // expected
+ } catch (RuntimeException e) {
+ // expected
+ // The JNI layer isn't translating the EPERM error status into
+ // a SecurityException.
+ }
+ }
+
+}
+
diff --git a/tests/cts/permission/src/android/permission/cts/ConnectivityManagerPermissionTest.java b/tests/cts/permission/src/android/permission/cts/ConnectivityManagerPermissionTest.java
new file mode 100644
index 000000000..0e8797d20
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/ConnectivityManagerPermissionTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.OemNetworkPreferences;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+
+/**
+* Test that protected android.net.ConnectivityManager methods cannot be called without
+* permissions
+*/
+@RunWith(AndroidJUnit4.class)
+public class ConnectivityManagerPermissionTest {
+
+ private ConnectivityManager mConnectivityManager;
+ private Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+ assertNotNull(mConnectivityManager);
+ }
+
+ /**
+ * Verify that calling {@link ConnectivityManager#getNetworkInfo(int))}
+ * requires permissions.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
+ @Test
+ public void testGetNetworkInfo() {
+ try {
+ mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
+ fail("Was able to call getNetworkInfo");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that calling {@link ConnectivityManager#setOemNetworkPreference(OemNetworkPreferences,
+ * Executor, ConnectivityManager.OnSetOemNetworkPreferenceListener)}
+ * requires permissions.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#CONTROL_OEM_PAID_NETWORK_PREFERENCE}.
+ */
+ @Test
+ public void testSetOemNetworkPreference() {
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE));
+ try {
+ final String testPackage = "does.not.matter.com";
+ final int testPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+ final OemNetworkPreferences preferences =
+ new OemNetworkPreferences.Builder()
+ .addNetworkPreference(testPackage, testPref)
+ .build();
+ mConnectivityManager.setOemNetworkPreference(preferences, null, null);
+ fail("Was able to call setOemNetworkPreference");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that calling {@link ConnectivityManager#setOemNetworkPreference(OemNetworkPreferences,
+ * Executor, ConnectivityManager.OnSetOemNetworkPreferenceListener)}
+ * requires automotive feature.
+ * <p>Tests Feature:
+ * {@link PackageManager#FEATURE_AUTOMOTIVE}.
+ */
+ @Test
+ public void testSetOemNetworkPreferenceHasAutomotiveFeature() {
+ assumeFalse(mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE));
+ try {
+ final String testPackage = "does.not.matter.com";
+ final int testPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+ final OemNetworkPreferences preferences =
+ new OemNetworkPreferences.Builder()
+ .addNetworkPreference(testPackage, testPref)
+ .build();
+ mConnectivityManager.setOemNetworkPreference(preferences, null, null);
+ fail("Was able to call setOemNetworkPreference");
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/ContactsProviderTest.java b/tests/cts/permission/src/android/permission/cts/ContactsProviderTest.java
new file mode 100644
index 000000000..984fd6cfe
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/ContactsProviderTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2013 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.permission.cts;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Verify permissions are enforced.
+ */
+public class ContactsProviderTest extends AndroidTestCase {
+
+ /**
+ * Verifies that query(ContactsContract.Contacts.CONTENT_URI) requires
+ * Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission#READ_CONTACTS}.
+ */
+ @SmallTest
+ public void testQueryContacts() {
+ try {
+ getContext().getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
+ null, null, null, null);
+ fail("query(ContactsContract.Contacts.CONTENT_URI) did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that insert(ContactsContract.Contacts.CONTENT_URI) requires
+ * Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission#WRITE_CONTACTS}.
+ */
+ @SmallTest
+ public void testInsertContacts() {
+ try {
+ getContext().getContentResolver().insert(ContactsContract.Contacts.CONTENT_URI,
+ new ContentValues());
+ fail("insert(ContactsContract.Contacts.CONTENT_URI) did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that query(ContactsContract.Profile.CONTENT_URI) requires
+ * Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission#READ_CONTACTS}.
+ */
+ @SmallTest
+ public void testQueryProfile() {
+ try {
+ getContext().getContentResolver().query(ContactsContract.Profile.CONTENT_URI,
+ null, null, null, null);
+ fail("query(ContactsContract.Profile.CONTENT_URI) did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that insert(ContactsContract.Profile.CONTENT_URI) requires
+ * Permission. The provider doesn't actually let you do this even if you have the
+ * permission, but trying to do it without the permission should throw a
+ * SecurityException anyway.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission#WRITE_CONTACTS}.
+ */
+ @SmallTest
+ public void testInsertProfile() {
+ try {
+ getContext().getContentResolver().insert(ContactsContract.Profile.CONTENT_URI,
+ new ContentValues(0));
+ fail("insert(ContactsContract.Profile.CONTENT_URI) did not throw SecurityException "
+ + "as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that update(ContactsContract.Profile.CONTENT_URI) requires
+ * Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission#WRITE_CONTACTS}.
+ */
+ @SmallTest
+ public void testUpdateProfile() {
+ try {
+ getContext().getContentResolver().update(ContactsContract.Profile.CONTENT_URI,
+ new ContentValues(0), null, null);
+ fail("update(ContactsContract.Profile.CONTENT_URI) did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that query(ContactsContract.CommonDataKinds.Phone.ENTERPRISE_CONTENT_URI) requires
+ * Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission#INTERACT_ACROSS_USERS}.
+ */
+ @SmallTest
+ public void testQueryPhoneEnterprise() {
+ try {
+ getContext().getContentResolver().query(
+ ContactsContract.CommonDataKinds.Phone.ENTERPRISE_CONTENT_URI,
+ null, null, null, null);
+ fail("query(ContactsContract.CommonDataKinds.Phone.ENTERPRISE_CONTENT_URI) did not"
+ + " throw SecurityException as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that query(ContactsContract.RawContactsEntity.CORP_CONTENT_URI) requires
+ * Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission#INTERACT_ACROSS_USERS}.
+ */
+ @SmallTest
+ public void testRawContactsEntityCorp() {
+ try {
+ getContext().getContentResolver().query(
+ ContactsContract.RawContactsEntity.CORP_CONTENT_URI, null, null, null, null);
+ fail("query(ContactsContract.RawContactsEntity.CORP_CONTENT_URI) did not throw"
+ + " SecurityException as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/DebuggableTest.java b/tests/cts/permission/src/android/permission/cts/DebuggableTest.java
new file mode 100644
index 000000000..7ca734887
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/DebuggableTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 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.permission.cts;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.test.AndroidTestCase;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Verify that pre-installed packages don't have the debuggable
+ * flag set. The debuggable flag allows should only be used during
+ * development, and never for shipping devices.
+ */
+public class DebuggableTest extends AndroidTestCase {
+
+ public void testNoDebuggable() {
+ Set<String> debuggableApps = new HashSet<String>();
+ List<ApplicationInfo> apps = getContext()
+ .getPackageManager()
+ .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
+ for (ApplicationInfo app : apps) {
+ if (!app.isSystemApp()) {
+ continue;
+ }
+ String appName = app.packageName;
+ if ((app.flags & ApplicationInfo.FLAG_DEBUGGABLE) == ApplicationInfo.FLAG_DEBUGGABLE) {
+ debuggableApps.add(appName);
+ }
+ }
+ assertTrue("Packages marked debuggable: " + debuggableApps, debuggableApps.isEmpty());
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/DuplicatePermissionDefinitionsTest.kt b/tests/cts/permission/src/android/permission/cts/DuplicatePermissionDefinitionsTest.kt
new file mode 100644
index 000000000..6bd6445f8
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/DuplicatePermissionDefinitionsTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2020 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.permission.cts
+
+import android.content.pm.PackageManager
+import android.content.pm.PermissionGroupInfo
+import android.content.pm.PermissionInfo
+import android.os.Build
+import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.AsbSecurityTest
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SdkSuppress
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
+import com.android.compatibility.common.util.ShellUtils.runShellCommand
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val APK_PATH = "/data/local/tmp/cts/permissions/"
+
+private const val APK_DEFINING_PERM_A = "${APK_PATH}CtsAppThatDefinesPermissionA.apk"
+private const val APK_ALSO_DEFINING_PERM_A = "${APK_PATH}CtsAppThatAlsoDefinesPermissionA.apk"
+private const val APK_ALSO_DEFINING_PERM_A_DIFFERENT_CERT =
+ "${APK_PATH}CtsAppThatAlsoDefinesPermissionADifferentCert.apk"
+private const val APK_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT =
+ "${APK_PATH}CtsAppThatAlsoDefinesPermissionGroupADifferentCert.apk"
+private const val APK_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT_SDK_30 =
+ "${APK_PATH}CtsAppThatAlsoDefinesPermissionGroupADifferentCert30.apk"
+private const val APK_DEFINING_PERM_WITH_INVALID_GROUP =
+ "${APK_PATH}CtsAppThatDefinesPermissionWithInvalidGroup.apk"
+private const val APK_DEFINING_PERM_WITH_INVALID_GROUP_SDK_30 =
+ "${APK_PATH}CtsAppThatDefinesPermissionWithInvalidGroup30.apk"
+private const val APK_DEFINING_PERM_IN_PLATFORM_GROUP =
+ "${APK_PATH}CtsAppThatDefinesPermissionInPlatformGroup.apk"
+
+private const val APP_DEFINING_PERM_A = "android.permission.cts.appthatdefinespermissiona"
+private const val APP_ALSO_DEFINING_PERM_A = "android.permission.cts.appthatalsodefinespermissiona"
+private const val APP_ALSO_DEFINING_PERM_A_DIFFERENT_CERT =
+ "android.permission.cts.appthatdefinespermissiona.differentcert"
+private const val APP_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT =
+ "android.permission.cts.appthatdefinespermissiongroupa.differentcert"
+private const val APP_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT_SDK_30 =
+ "android.permission.cts.appthatdefinespermissiongroupa.differentcert30"
+private const val APP_DEFINING_PERM_IN_PLATFORM_GROUP =
+ "android.permission.cts.appthatdefinespermissioninplatformgroup"
+private const val APP_DEFINING_PERM_WITH_INVALID_GROUP =
+ "android.permission.cts.appthatdefinespermissionwithinvalidgroup"
+private const val APP_DEFINING_PERM_WITH_INVALID_GROUP_SDK_30 =
+ "android.permission.cts.appthatdefinespermissionwithinvalidgroup30"
+
+private const val PERM_A = "com.android.cts.duplicatepermission.permA"
+private const val GROUP_A = "com.android.cts.duplicatepermission.groupA"
+private const val INVALID_GROUP = "com.android.cts.duplicatepermission.invalid"
+
+/**
+ * Test cases where packages
+ * - define the same permission or
+ * - define the same permission group
+ * - define permissions in a group defined by another package
+ */
+@AppModeFull(reason = "Tests properties of other app. Instant apps cannot interact with other apps")
+@RunWith(AndroidJUnit4ClassRunner::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+class DuplicatePermissionDefinitionsTest {
+ private val pm = InstrumentationRegistry.getTargetContext().packageManager
+
+ private fun install(apk: String) {
+ runShellCommand("pm install $apk")
+ }
+
+ private fun uninstall(app: String) {
+ runShellCommand("pm uninstall $app")
+ }
+
+ private val allPackages: List<String>
+ get() = pm.getInstalledPackages(0).map { it.packageName }
+
+ private val permAInfo: PermissionInfo
+ get() = pm.getPermissionInfo(PERM_A, 0)!!
+
+ private val groupAInfo: PermissionGroupInfo
+ get() = pm.getPermissionGroupInfo(GROUP_A, 0)!!
+
+ @Test
+ fun canInstallAppsDefiningSamePermissionWhenSameCert() {
+ install(APK_DEFINING_PERM_A)
+ install(APK_ALSO_DEFINING_PERM_A)
+
+ assertThat(allPackages).containsAtLeast(APP_DEFINING_PERM_A, APP_ALSO_DEFINING_PERM_A)
+
+ assertThat(permAInfo.packageName).isEqualTo(APP_DEFINING_PERM_A)
+ }
+
+ @Test
+ fun cannotInstallAppsDefiningSamePermissionWhenDifferentCert() {
+ install(APK_DEFINING_PERM_A)
+ install(APK_ALSO_DEFINING_PERM_A_DIFFERENT_CERT)
+
+ assertThat(allPackages).contains(APP_DEFINING_PERM_A)
+ assertThat(allPackages).doesNotContain(APP_ALSO_DEFINING_PERM_A_DIFFERENT_CERT)
+
+ assertThat(permAInfo.packageName).isEqualTo(APP_DEFINING_PERM_A)
+ }
+
+ @Test
+ fun canInstallAppsDefiningSamePermissionGroupWhenDifferentCertIfSdk30() {
+ install(APK_DEFINING_PERM_A)
+ install(APK_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT_SDK_30)
+
+ assertThat(allPackages).containsAtLeast(APP_DEFINING_PERM_A,
+ APP_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT_SDK_30)
+
+ assertThat(groupAInfo.packageName).isEqualTo(APP_DEFINING_PERM_A)
+ }
+
+ @Test
+ @AsbSecurityTest(cveBugId = [146211400])
+ fun cannotInstallAppsDefiningSamePermissionGroupWhenDifferentCert() {
+ install(APK_DEFINING_PERM_A)
+ install(APK_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT)
+
+ assertThat(allPackages).contains(APP_DEFINING_PERM_A)
+ assertThat(allPackages).doesNotContain(APP_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT)
+
+ assertThat(groupAInfo.packageName).isEqualTo(APP_DEFINING_PERM_A)
+ }
+
+ // This is the same as cannotInstallAppsDefiningSamePermissionGroupWhenDifferentCert but this
+ // case is allowed as the package that originally defined the group is a platform.
+ @Test
+ fun canInstallAppsDefiningPermissionInPlatformGroup() {
+ install(APK_DEFINING_PERM_IN_PLATFORM_GROUP)
+
+ assertThat(allPackages).contains(APP_DEFINING_PERM_IN_PLATFORM_GROUP)
+
+ assertThat(permAInfo.packageName).isEqualTo(APP_DEFINING_PERM_IN_PLATFORM_GROUP)
+ assertThat(permAInfo.group).isEqualTo(android.Manifest.permission_group.CAMERA)
+ assertThat(pm.getPermissionGroupInfo(android.Manifest.permission_group.CAMERA, 0)!!
+ .packageName).isEqualTo("android")
+ }
+
+ @Test
+ fun canInstallAppsDefiningPermissionWithInvalidGroupSdk30() {
+ install(APK_DEFINING_PERM_WITH_INVALID_GROUP_SDK_30)
+
+ assertThat(allPackages).contains(APP_DEFINING_PERM_WITH_INVALID_GROUP_SDK_30)
+
+ assertThat(permAInfo.packageName).isEqualTo(APP_DEFINING_PERM_WITH_INVALID_GROUP_SDK_30)
+ assertThat(permAInfo.group).isEqualTo(INVALID_GROUP)
+ }
+
+ @Test(expected = PackageManager.NameNotFoundException::class)
+ @AsbSecurityTest(cveBugId = [146211400])
+ fun cannotInstallAppsDefiningPermissionWithInvalidGroup() {
+ install(APK_DEFINING_PERM_WITH_INVALID_GROUP)
+
+ assertThat(allPackages).doesNotContain(APP_DEFINING_PERM_WITH_INVALID_GROUP)
+
+ // throws a NameNotFoundException as perm info does not exist
+ permAInfo
+ }
+
+ @After
+ fun uninstallTestApps() {
+ uninstall(APP_DEFINING_PERM_A)
+ uninstall(APP_ALSO_DEFINING_PERM_A)
+ uninstall(APP_ALSO_DEFINING_PERM_A_DIFFERENT_CERT)
+ uninstall(APP_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT)
+ uninstall(APP_ALSO_DEFINING_PERM_GROUP_A_DIFFERENT_CERT_SDK_30)
+ uninstall(APP_DEFINING_PERM_IN_PLATFORM_GROUP)
+ uninstall(APP_DEFINING_PERM_WITH_INVALID_GROUP)
+ uninstall(APP_DEFINING_PERM_WITH_INVALID_GROUP_SDK_30)
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/EthernetManagerPermissionTest.java b/tests/cts/permission/src/android/permission/cts/EthernetManagerPermissionTest.java
new file mode 100644
index 000000000..3c99b34a9
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/EthernetManagerPermissionTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.EthernetManager;
+import android.net.EthernetNetworkUpdateRequest;
+import android.net.IpConfiguration;
+import android.net.NetworkCapabilities;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
+
+/**
+ * Test protected android.net.EthernetManager methods cannot be called without permissions.
+ */
+@RunWith(AndroidJUnit4.class)
+public class EthernetManagerPermissionTest {
+ private static final String TEST_IFACE = "test123abc789";
+ private EthernetManager mEthernetManager;
+ private Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mEthernetManager = mContext.getSystemService(EthernetManager.class);
+ // mEthernetManager may be null depending on the device's configuration.
+ assumeNotNull(mEthernetManager);
+ }
+
+ private EthernetNetworkUpdateRequest buildUpdateRequest() {
+ return new EthernetNetworkUpdateRequest.Builder()
+ .setIpConfiguration(new IpConfiguration.Builder().build())
+ .setNetworkCapabilities(new NetworkCapabilities.Builder().build())
+ .build();
+ }
+
+ private EthernetNetworkUpdateRequest buildUpdateRequestWithoutCapabilities() {
+ return new EthernetNetworkUpdateRequest.Builder()
+ .setIpConfiguration(new IpConfiguration.Builder().build())
+ .build();
+ }
+
+ /**
+ * Verify that calling {@link EthernetManager#updateConfiguration(String,
+ * EthernetNetworkUpdateRequest, Executor, BiConsumer)} requires permissions.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#MANAGE_ETHERNET_NETWORKS}.
+ */
+ @Test
+ public void testUpdateConfigurationRequiresPermissionManageEthernetNetworks() {
+ assertThrows("Should not be able to call updateConfiguration without permission",
+ SecurityException.class,
+ () -> mEthernetManager.updateConfiguration(TEST_IFACE,
+ buildUpdateRequestWithoutCapabilities(), null, null));
+ }
+
+ /**
+ * Verify that calling {@link EthernetManager#enableInterface}
+ * requires permissions.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#MANAGE_ETHERNET_NETWORKS}.
+ */
+ @Test
+ public void testEnableInterface() {
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE));
+ assertThrows("Should not be able to call enableInterface without permission",
+ SecurityException.class,
+ () -> mEthernetManager.enableInterface(TEST_IFACE, null, null));
+ }
+
+ /**
+ * Verify that calling {@link EthernetManager#disableInterface}
+ * requires permissions.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#MANAGE_ETHERNET_NETWORKS}.
+ */
+ @Test
+ public void testDisableInterface() {
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE));
+ assertThrows("Should not be able to call disableInterface without permission",
+ SecurityException.class,
+ () -> mEthernetManager.disableInterface(TEST_IFACE, null, null));
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/cts/permission/src/android/permission/cts/FileSystemPermissionTest.java
new file mode 100644
index 000000000..94557464f
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -0,0 +1,1279 @@
+/*
+ * Copyright (C) 2010 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.permission.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Environment;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructStatVfs;
+import android.util.Pair;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PropertyUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Verify certain permissions on the filesystem
+ *
+ * TODO: Combine this file with {@link android.os.cts.FileAccessPermissionTest}
+ */
+@RunWith(AndroidJUnit4.class)
+public class FileSystemPermissionTest {
+
+ private int dumpable;
+
+ @Before
+ public void setUp() throws Exception {
+ dumpable = Os.prctl(OsConstants.PR_GET_DUMPABLE, 0, 0, 0, 0);
+ Os.prctl(OsConstants.PR_SET_DUMPABLE, 1, 0, 0, 0);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ Os.prctl(OsConstants.PR_SET_DUMPABLE, dumpable, 0, 0, 0);
+ }
+
+ @MediumTest
+ @Test
+ public void testCreateFileHasSanePermissions() throws Exception {
+ File myFile = new File(getContext().getFilesDir(), "hello");
+ FileOutputStream stream = new FileOutputStream(myFile);
+ stream.write("hello world".getBytes());
+ stream.close();
+ try {
+ FileUtils.FileStatus status = new FileUtils.FileStatus();
+ FileUtils.getFileStatus(myFile.getAbsolutePath(), status, false);
+ int expectedPerms = FileUtils.S_IFREG
+ | FileUtils.S_IWUSR
+ | FileUtils.S_IRUSR;
+ assertEquals(
+ "Newly created files should have 0600 permissions",
+ Integer.toOctalString(expectedPerms),
+ Integer.toOctalString(status.mode));
+ } finally {
+ assertTrue(myFile.delete());
+ }
+ }
+
+ @MediumTest
+ @Test
+ public void testCreateDirectoryHasSanePermissions() throws Exception {
+ File myDir = new File(getContext().getFilesDir(), "helloDirectory");
+ assertTrue(myDir.mkdir());
+ try {
+ FileUtils.FileStatus status = new FileUtils.FileStatus();
+ FileUtils.getFileStatus(myDir.getAbsolutePath(), status, false);
+ int expectedPerms = FileUtils.S_IFDIR
+ | FileUtils.S_IWUSR
+ | FileUtils.S_IRUSR
+ | FileUtils.S_IXUSR;
+ assertEquals(
+ "Newly created directories should have 0700 permissions",
+ Integer.toOctalString(expectedPerms),
+ Integer.toOctalString(status.mode));
+
+ } finally {
+ assertTrue(myDir.delete());
+ }
+ }
+
+ @MediumTest
+ @Test
+ public void testOtherApplicationDirectoriesAreNotWritable() throws Exception {
+ Set<File> writableDirs = new HashSet<File>();
+ List<ApplicationInfo> apps = getContext()
+ .getPackageManager()
+ .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
+ String myAppDirectory = getContext().getApplicationInfo().dataDir;
+ for (ApplicationInfo app : apps) {
+ if (app.dataDir != null && !myAppDirectory.equals(app.dataDir)) {
+ writableDirs.addAll(getWritableDirectoriesAndSubdirectoriesOf(new File(app.dataDir)));
+ }
+ }
+
+ assertTrue("Found writable directories: " + writableDirs.toString(),
+ writableDirs.isEmpty());
+ }
+
+ @MediumTest
+ @Test
+ public void testApplicationParentDirectoryNotWritable() throws Exception {
+ String myDataDir = getContext().getApplicationInfo().dataDir;
+ File parentDir = new File(myDataDir).getParentFile();
+ assertFalse(parentDir.toString(), isDirectoryWritable(parentDir));
+ }
+
+ @MediumTest
+ @Test
+ public void testDataDirectoryNotWritable() throws Exception {
+ assertFalse(isDirectoryWritable(Environment.getDataDirectory()));
+ }
+
+ @MediumTest
+ @Test
+ public void testAndroidRootDirectoryNotWritable() throws Exception {
+ assertFalse(isDirectoryWritable(Environment.getRootDirectory()));
+ }
+
+ @MediumTest
+ @Test
+ public void testDownloadCacheDirectoryNotWritable() throws Exception {
+ assertFalse(isDirectoryWritable(Environment.getDownloadCacheDirectory()));
+ }
+
+ @MediumTest
+ @Test
+ public void testRootDirectoryNotWritable() throws Exception {
+ assertFalse(isDirectoryWritable(new File("/")));
+ }
+
+ @MediumTest
+ @Test
+ public void testDevDirectoryNotWritable() throws Exception {
+ assertFalse(isDirectoryWritable(new File("/dev")));
+ }
+
+ @MediumTest
+ @Test
+ public void testProcDirectoryNotWritable() throws Exception {
+ assertFalse(isDirectoryWritable(new File("/proc")));
+ }
+
+ @MediumTest
+ @Test
+ public void testDevDiagSane() throws Exception {
+ File f = new File("/dev/diag");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+ }
+
+ /* b/26813932 */
+ @MediumTest
+ @Test
+ public void testProcInterruptsNotReadable() throws Exception {
+ File f = new File("/proc/interrupts");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+ }
+
+ /* b/26813932 */
+ @MediumTest
+ @Test
+ public void testProcStatNotReadable() throws Exception {
+ File f = new File("/proc/stat");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+ }
+
+ @MediumTest
+ @Test
+ public void testDevMemSane() throws Exception {
+ File f = new File("/dev/mem");
+ assertFalse(f.exists());
+ }
+
+ @MediumTest
+ @Test
+ public void testDevkmemSane() throws Exception {
+ File f = new File("/dev/kmem");
+ assertFalse(f.exists());
+ }
+
+ @MediumTest
+ @Test
+ public void testDevPortSane() throws Exception {
+ File f = new File("/dev/port");
+ assertFalse(f.exists());
+ }
+
+ @MediumTest
+ @Test
+ public void testPn544Sane() throws Exception {
+ File f = new File("/dev/pn544");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+
+ assertFileOwnedBy(f, "nfc");
+ assertFileOwnedByGroup(f, "nfc");
+ }
+
+ @MediumTest
+ @Test
+ public void testBcm2079xSane() throws Exception {
+ File f = new File("/dev/bcm2079x");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+
+ assertFileOwnedBy(f, "nfc");
+ assertFileOwnedByGroup(f, "nfc");
+ }
+
+ @MediumTest
+ @Test
+ public void testBcm2079xi2cSane() throws Exception {
+ File f = new File("/dev/bcm2079x-i2c");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+
+ assertFileOwnedBy(f, "nfc");
+ assertFileOwnedByGroup(f, "nfc");
+ }
+
+ @MediumTest
+ @Test
+ public void testDevQtaguidSane() throws Exception {
+ File f = new File("/dev/xt_qtaguid");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+
+ assertFileOwnedBy(f, "root");
+ assertFileOwnedByGroup(f, "root");
+ }
+
+ @MediumTest
+ @Test
+ public void testProcQtaguidCtrlSane() throws Exception {
+ File f = new File("/proc/net/xt_qtaguid/ctrl");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+
+ assertFileOwnedBy(f, "root");
+ assertFileOwnedByGroup(f, "net_bw_acct");
+ }
+
+ @MediumTest
+ @Test
+ public void testProcQtaguidStatsSane() throws Exception {
+ File f = new File("/proc/net/xt_qtaguid/stats");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+
+ assertFileOwnedBy(f, "root");
+ assertFileOwnedByGroup(f, "net_bw_stats");
+ }
+
+ private static List<String> procNetFiles = Arrays.asList("anycast6", "arp", "arp_tables_matches",
+ "arp_tables_names", "arp_tables_targets", "dev", "dev_mcast", "fib_trie", "fib_triestat",
+ "hci", "icmp", "icmp6", "if_inet6", "igmp", "igmp6", "ip6_flowlabel",
+ "ip6_tables_matches", "ip6_tables_names", "ip6_tables_targets", "ip_tables_matches",
+ "ip_tables_names", "ip_tables_targets", "ipv6_route", "l2cap", "mcfilter", "mcfilter6",
+ "netlink", "netstat", "nf_conntrack", "nf_conntrack_expect", "packet", "pfkey", "pnp",
+ "pppoe", "pppol2tp", "protocols", "psched", "ptype", "raw", "raw6", "route", "rt6_stats",
+ "rt_cache", "sco", "snmp", "snmp6", "sockstat", "sockstat6", "softnet_stat", "tcp",
+ "tcp6", "udp", "udp6", "udplite", "udplite6", "unix", "wireless", "xfrm_stat");
+
+ private static void procNetSane(String path) {
+ File f = new File(path);
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+ assertFileOwnedBy(f, "root");
+ assertFileOwnedByGroup(f, "root");
+ }
+
+ @MediumTest
+ @Test
+ public void testProcNetSane() throws Exception {
+ if (PropertyUtil.isVendorApiLevelNewerThan(28)) {
+ for (String file : procNetFiles) {
+ procNetSane("/proc/net/" + file);
+ }
+ }
+ }
+
+ private static int readInt(File f) throws FileNotFoundException {
+ try (Scanner s = new Scanner(f)) {
+ return s.nextInt();
+ }
+ }
+
+ private static boolean writeInt(File f, int value) throws IOException {
+ try (FileOutputStream os = new FileOutputStream(f)) {
+ try {
+ os.write(Integer.toString(value).getBytes());
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+ }
+
+ @MediumTest
+ @Test
+ public void testProcSelfOomAdjSane() throws IOException {
+ final int OOM_DISABLE = -17;
+
+ File f = new File("/proc/self/oom_adj");
+ assertTrue(f.canRead());
+ assertFalse(f.canExecute());
+
+ int oom_adj = readInt(f);
+ assertNotEquals("unprivileged processes should not be unkillable", OOM_DISABLE, oom_adj);
+ if (f.canWrite())
+ assertFalse("unprivileged processes should not be able to reduce their oom_adj value",
+ writeInt(f, oom_adj - 1));
+ }
+
+ @MediumTest
+ @Test
+ public void testProcSelfOomScoreAdjSane() throws IOException {
+ final int OOM_SCORE_ADJ_MIN = -1000;
+
+ File f = new File("/proc/self/oom_score_adj");
+ assertTrue(f.canRead());
+ assertFalse(f.canExecute());
+
+ int oom_score_adj = readInt(f);
+ assertNotEquals("unprivileged processes should not be unkillable", OOM_SCORE_ADJ_MIN, oom_score_adj);
+ if (f.canWrite()) {
+ assertFalse(
+ "unprivileged processes should not be able to reduce their oom_score_adj value",
+ writeInt(f, oom_score_adj - 1));
+ assertTrue(
+ "unprivileged processes should be able to increase their oom_score_adj value",
+ writeInt(f, oom_score_adj + 1));
+ assertTrue("unprivileged processes should be able to restore their oom_score_adj value",
+ writeInt(f, oom_score_adj));
+ }
+ }
+
+ private static List<Pair<Long, Long>> mappedPageRanges() throws IOException {
+ final BigInteger PAGE_SIZE = new BigInteger("4096");
+
+ final Pattern mapsPattern = Pattern.compile("^(\\p{XDigit}+)-(\\p{XDigit}+)");
+ List<Pair<Long, Long>> ret = new LinkedList<>();
+
+ BufferedReader reader = new BufferedReader(new FileReader("/proc/self/maps"));
+ String line;
+ try {
+ while ((line = reader.readLine()) != null) {
+ Matcher m = mapsPattern.matcher(line);
+ m.find();
+
+ long start = new BigInteger(m.group(1), 16).divide(PAGE_SIZE).longValue();
+ long end = new BigInteger(m.group(2), 16).divide(PAGE_SIZE).longValue();
+
+ ret.add(new Pair<>(start, end));
+ }
+
+ return ret;
+ } finally {
+ reader.close();
+ }
+ }
+
+ private static boolean pfnIsZero(FileDescriptor pagemap, long start, long end) throws ErrnoException, IOException {
+ // Note: reads from /proc/self/pagemap *must* be 64-bit aligned. Use low-level android.system.Os routines to
+ // ensure this.
+ final int SIZEOF_U64 = 8;
+ final long PAGE_PRESENT = 1L << 63;
+ final long PFN_MASK = (1L << 55) - 1;
+
+ for (long page = start; page < end; page++) {
+ long offset = page * SIZEOF_U64;
+ long seek = Os.lseek(pagemap, offset, OsConstants.SEEK_SET);
+ if (offset != seek)
+ throw new IOException("lseek(" + offset + ") returned " + seek);
+
+ byte bytes[] = new byte[SIZEOF_U64];
+ ByteBuffer buf = ByteBuffer.wrap(bytes).order(ByteOrder.nativeOrder());
+ int read = Os.read(pagemap, buf);
+
+ if (read == 0)
+ // /proc/[pid]/maps may contain entries that are outside the process's VM space,
+ // like the [vectors] page on 32-bit ARM devices. In this case, seek() succeeds but
+ // read() returns 0. The kernel is telling us that there are no more pagemap
+ // entries to read, so we can stop here.
+ break;
+ else if (read != bytes.length)
+ throw new IOException("read(" + bytes.length + ") returned " + read);
+
+ buf.position(0);
+ long entry = buf.getLong();
+ if ((entry & PAGE_PRESENT) == PAGE_PRESENT && (entry & PFN_MASK) != 0)
+ return false;
+ }
+
+ return true;
+ }
+
+ @MediumTest
+ @Test
+ public void testProcSelfPagemapSane() throws ErrnoException, IOException {
+ FileDescriptor pagemap = null;
+ try {
+ pagemap = Os.open("/proc/self/pagemap", OsConstants.O_RDONLY, 0);
+
+ for (Pair<Long, Long> range : mappedPageRanges())
+ if (!pfnIsZero(pagemap, range.first, range.second))
+ fail("Device is missing the following kernel security patch: "
+ + "https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=ab676b7d6fbf4b294bf198fb27ade5b0e865c7ce");
+ } catch (ErrnoException e) {
+ if (e.errno == OsConstants.EPERM)
+ // expected before 4.2
+ return;
+
+ throw e;
+ } finally {
+ if (pagemap != null)
+ Os.close(pagemap);
+ }
+ }
+
+ @MediumTest
+ @Test
+ public void testTcpDefaultRwndSane() throws Exception {
+ File f = new File("/proc/sys/net/ipv4/tcp_default_init_rwnd");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+
+ assertFileOwnedBy(f, "root");
+ assertFileOwnedByGroup(f, "root");
+ }
+
+ @MediumTest
+ @Test
+ public void testIdletimerDirectoryExistsAndSane() throws Exception {
+ File dir = new File("/sys/class/xt_idletimer");
+ assertTrue(dir.isDirectory());
+ assertFalse(dir.canWrite());
+ assertTrue(dir.canExecute());
+
+ assertFileOwnedBy(dir, "root");
+ assertFileOwnedByGroup(dir, "root");
+ }
+
+
+ @MediumTest
+ @Test
+ public void testProcfsMmapRndBitsExistsAndSane() throws Exception {
+ String arch = System.getProperty("os.arch");
+ boolean supported = false;
+ boolean supported_64 = false;
+
+ if (arch.equals("aarch64") || arch.equals("x86_64"))
+ supported_64 = true;
+ else if (arch.startsWith("arm") || arch.endsWith("86"))
+ supported = true;
+
+ /* 64-bit OS should support running 32-bit applications */
+ if (supported_64) {
+ File f = new File("/proc/sys/vm/mmap_rnd_compat_bits");
+ assertTrue(f.exists());
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+ }
+
+ if (supported_64 || supported) {
+ File f = new File("/proc/sys/vm/mmap_rnd_bits");
+ assertTrue(f.exists());
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+ }
+ }
+
+ /**
+ * Assert that a file is owned by a specific owner. This is a noop if the
+ * file does not exist.
+ *
+ * @param file The file to check.
+ * @param expectedOwner The owner of the file.
+ */
+ private static void assertFileOwnedBy(File file, String expectedOwner) {
+ FileUtils.FileStatus status = new FileUtils.FileStatus();
+ String path = file.getAbsolutePath();
+ if (file.exists() && FileUtils.getFileStatus(path, status, true)) {
+ String actualOwner = FileUtils.getUserName(status.uid);
+ if (!expectedOwner.equals(actualOwner)) {
+ String msg = String.format("Wrong owner. Expected '%s', but found '%s' for %s.",
+ expectedOwner, actualOwner, path);
+ fail(msg);
+ }
+ }
+ }
+
+ /**
+ * Assert that a file is owned by a specific group. This is a noop if the
+ * file does not exist.
+ *
+ * @param file The file to check.
+ * @param expectedGroup The owner group of the file.
+ */
+ private static void assertFileOwnedByGroup(File file, String expectedGroup) {
+ FileUtils.FileStatus status = new FileUtils.FileStatus();
+ String path = file.getAbsolutePath();
+ if (file.exists() && FileUtils.getFileStatus(path, status, true)) {
+ String actualGroup = FileUtils.getGroupName(status.gid);
+ if (!expectedGroup.equals(actualGroup)) {
+ String msg = String.format("Wrong group. Expected '%s', but found '%s' for %s.",
+ expectedGroup, actualGroup, path);
+ fail(msg);
+ }
+ }
+ }
+
+ @MediumTest
+ @Test
+ public void testTtyO3Sane() throws Exception {
+ File f = new File("/dev/ttyO3");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+ }
+
+ @MediumTest
+ @Test
+ public void testDataMediaSane() throws Exception {
+ final File f = new File("/data/media");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+ }
+
+ @MediumTest
+ @Test
+ public void testMntShellSane() throws Exception {
+ final File f = new File("/mnt/shell");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+ }
+
+ @MediumTest
+ @Test
+ public void testMntSecureSane() throws Exception {
+ final File f = new File("/mnt/secure");
+ assertFalse(f.canRead());
+ assertFalse(f.canWrite());
+ assertFalse(f.canExecute());
+ }
+
+ private static boolean isDirectoryWritable(File directory) {
+ File toCreate = new File(directory, "hello");
+ try {
+ toCreate.createNewFile();
+ return true;
+ } catch (IOException e) {
+ // It's expected we'll get a "Permission denied" exception.
+ } finally {
+ toCreate.delete();
+ }
+ return false;
+ }
+
+ /**
+ * Verify that any publicly readable directories reachable from
+ * the root directory are not writable. An application should only be
+ * able to write to it's own home directory. World writable directories
+ * are a security hole because they enable a number of different attacks.
+ * <ul>
+ * <li><a href="http://en.wikipedia.org/wiki/Symlink_race">Symlink Races</a></li>
+ * <li>Data destruction by deleting or renaming files you don't own</li>
+ * <li>Data substitution by replacing trusted files with untrusted files</li>
+ * </ul>
+ *
+ * Note: Because not all directories are readable, this is a best-effort
+ * test only. Writable directories within unreadable subdirectories
+ * will NOT be detected by this code.
+ */
+ @LargeTest
+ @Test
+ public void testAllOtherDirectoriesNotWritable() throws Exception {
+ File start = new File("/");
+ Set<File> writableDirs = getWritableDirectoriesAndSubdirectoriesOf(start);
+
+ assertTrue("Found writable directories: " + writableDirs.toString(),
+ writableDirs.isEmpty());
+ }
+
+ private static final Set<String> OTHER_RANDOM_DIRECTORIES = new HashSet<String>(
+ Arrays.asList(
+ "/app-cache",
+ "/app-cache/ciq/socket",
+ "/cache/fotapkg",
+ "/cache/fotapkg/tmp",
+ "/data/_SamsungBnR_",
+ "/data/_SamsungBnR_/BR",
+ "/data/2nd-init",
+ "/data/amit",
+ "/data/anr",
+ "/data/app",
+ "/data/app-private",
+ "/data/backup",
+ "/data/battd",
+ "/data/bootlogo",
+ "/data/btips",
+ "/data/btips/TI",
+ "/data/btips/TI/opp",
+ "/data/cache",
+ "/data/calibration",
+ "/data/clipboard",
+ "/data/clp",
+ "/data/dalvik-cache",
+ "/data/data",
+ "/data/data/.drm",
+ "/data/data/.drm/.wmdrm",
+ "/data/data/cw",
+ "/data/data/com.android.htcprofile",
+ "/data/data/com.android.providers.drm/rights",
+ "/data/data/com.htc.android.qxdm2sd",
+ "/data/data/com.htc.android.qxdm2sd/bin",
+ "/data/data/com.htc.android.qxdm2sd/data",
+ "/data/data/com.htc.android.qxdm2sd/tmp",
+ "/data/data/com.htc.android.netlogger/data",
+ "/data/data/com.htc.messagecs/att",
+ "/data/data/com.htc.messagecs/pdu",
+ "/data/data/com.htc.loggers/bin",
+ "/data/data/com.htc.loggers/data",
+ "/data/data/com.htc.loggers/htclog",
+ "/data/data/com.htc.loggers/tmp",
+ "/data/data/com.htc.loggers/htcghost",
+ "/data/data/com.lge.ers/android",
+ "/data/data/com.lge.ers/arm9",
+ "/data/data/com.lge.ers/kernel",
+ "/data/data/com.lge.wmc",
+ "/data/data/com.redbend.vdmc/lib",
+ "/data/data/recovery",
+ "/data/data/recovery/HTCFOTA",
+ "/data/data/recovery/OMADM",
+ "/data/data/shared",
+ "/data/diag_logs",
+ "/data/dontpanic",
+ "/data/drm",
+ "/data/drm/fwdlock",
+ "/data/drm/IDM",
+ "/data/drm/IDM/HTTP",
+ "/data/drm/rights",
+ "/data/dump",
+ "/data/efslog",
+ "/data/emt",
+ "/data/factory",
+ "/data/fics",
+ "/data/fics/dev",
+ "/data/fota",
+ "/data/gps",
+ "/data/gps/log",
+ "/data/gps/var",
+ "/data/gps/var/run",
+ "/data/gpscfg",
+ "/data/hwvefs",
+ "/data/htcfs",
+ "/data/img",
+ "/data/install",
+ "/data/internal-device",
+ "/data/internal-device/DCIM",
+ "/data/last_alog",
+ "/data/last_klog",
+ "/data/local",
+ "/data/local/logs",
+ "/data/local/logs/kernel",
+ "/data/local/logs/logcat",
+ "/data/local/logs/resetlog",
+ "/data/local/logs/smem",
+ "/data/local/mono",
+ "/data/local/mono/pulse",
+ "/data/local/purple",
+ "/data/local/purple/sound",
+ "/data/local/rights",
+ "/data/local/rwsystag",
+ "/data/local/skel",
+ "/data/local/skel/default",
+ "/data/local/skel/defualt", // Mispelled "defualt" is intentional
+ "/data/local/tmp",
+ "/data/local/tmp/com.nuance.android.vsuite.vsuiteapp",
+ "/data/log",
+ "/data/logger",
+ "/data/logs",
+ "/data/logs/core",
+ "/data/lost+found",
+ "/data/mdl",
+ "/data/misc",
+ "/data/misc/bluetooth",
+ "/data/misc/bluetooth/logs",
+ "/data/misc/dhcp",
+ "/data/misc/lockscreen",
+ "/data/misc/sensor",
+ "/data/misc/webwidgets",
+ "/data/misc/webwidgets/chess",
+ "/data/misc/widgets",
+ "/data/misc/wifi",
+ "/data/misc/wifi/sockets",
+ "/data/misc/wimax",
+ "/data/misc/wimax/sockets",
+ "/data/misc/wminput",
+ "/data/misc/wpa_supplicant",
+ "/data/nv",
+ "/data/nvcam",
+ "/data/panic",
+ "/data/panicreports",
+ "/data/preinstall_md5",
+ "/data/property",
+ "/data/radio",
+ "/data/secure",
+ "/data/security",
+ "/data/sensors",
+ "/data/shared",
+ "/data/simcom",
+ "/data/simcom/btadd",
+ "/data/simcom/simlog",
+ "/data/system",
+ "/data/tmp",
+ "/data/tombstones",
+ "/data/tombstones/ramdump",
+ "/data/tpapi",
+ "/data/tpapi/etc",
+ "/data/tpapi/etc/tpa",
+ "/data/tpapi/etc/tpa/persistent",
+ "/data/tpapi/user.bin",
+ "/data/vpnch",
+ "/data/wapi",
+ "/data/wifi",
+ "/data/wimax",
+ "/data/wimax/log",
+ "/data/wiper",
+ "/data/wpstiles",
+ "/data/xt9",
+ "/dbdata/databases",
+ "/efs/.android",
+ "/mnt/sdcard",
+ "/mnt/usbdrive",
+ "/mnt_ext",
+ "/mnt_ext/badablk2",
+ "/mnt_ext/badablk3",
+ "/mnt_ext/cache",
+ "/mnt_ext/data",
+ "/system/etc/security/drm",
+ "/synthesis/hades",
+ "/synthesis/chimaira",
+ "/synthesis/shdisp",
+ "/synthesis/hdmi",
+ "/tmp"
+ )
+ );
+
+ /**
+ * Verify that directories not discoverable by
+ * testAllOtherDirectoriesNotWritable are not writable. An application
+ * should only be able to write to it's own home directory. World
+ * writable directories are a security hole because they enable a
+ * number of different attacks.
+ * <ul>
+ * <li><a href="http://en.wikipedia.org/wiki/Symlink_race">Symlink Races</a></li>
+ * <li>Data destruction by deleting or renaming files you don't own</li>
+ * <li>Data substitution by replacing trusted files with untrusted files</li>
+ * </ul>
+ *
+ * Because /data and /data/data are not readable, we blindly try to
+ * poke around in there looking for bad directories. There has to be
+ * a better way...
+ */
+ @LargeTest
+ @Test
+ public void testOtherRandomDirectoriesNotWritable() throws Exception {
+ Set<File> writableDirs = new HashSet<File>();
+ for (String dir : OTHER_RANDOM_DIRECTORIES) {
+ File start = new File(dir);
+ writableDirs.addAll(getWritableDirectoriesAndSubdirectoriesOf(start));
+ }
+
+ assertTrue("Found writable directories: " + writableDirs.toString(),
+ writableDirs.isEmpty());
+ }
+
+ @LargeTest
+ @Test
+ public void testReadingSysFilesDoesntFail() throws Exception {
+ ExecutorService executor = Executors.newCachedThreadPool();
+ tryToReadFromAllIn(new File("/sys"), executor);
+ executor.shutdownNow();
+ }
+
+ private static void tryToReadFromAllIn(File dir, ExecutorService executor) throws IOException {
+ assertTrue(dir.isDirectory());
+
+ if (isSymbolicLink(dir)) {
+ // don't examine symbolic links.
+ return;
+ }
+
+ File[] files = dir.listFiles();
+
+ if (files != null) {
+ for (File f : files) {
+ if (f.isDirectory()) {
+ tryToReadFromAllIn(f, executor);
+ } else {
+ tryFileOpenRead(f, executor);
+ }
+ }
+ }
+ }
+
+ private static void tryFileOpenRead(final File f, ExecutorService executor) throws IOException {
+ // Callable requires stack variables to be final.
+ Callable<Boolean> readFile = new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ return tryFileRead(f);
+ }
+ };
+
+ Boolean completed = false;
+ String fileName = null;
+ Future<Boolean> future = null;
+ try {
+ fileName = f.getCanonicalPath();
+
+ future = executor.submit(readFile);
+
+ // Block, waiting no more than set seconds.
+ completed = future.get(3, TimeUnit.SECONDS);
+ } catch (TimeoutException e) {
+ System.out.println("TIMEOUT: " + fileName);
+ } catch (InterruptedException e) {
+ System.out.println("INTERRUPTED: " + fileName);
+ } catch (ExecutionException e) {
+ System.out.println("TASK WAS ABORTED BY EXCEPTION: " + fileName);
+ } catch (IOException e) {
+ // File.getCanonicalPath() will throw this.
+ } finally {
+ if (future != null) {
+ future.cancel(true);
+ }
+ }
+ }
+
+ private static Boolean tryFileRead(File f) {
+ byte[] b = new byte[1024];
+ try {
+ System.out.println("looking at " + f.getCanonicalPath());
+
+ FileInputStream fis = new FileInputStream(f);
+ while((fis.available() != 0) && (fis.read(b) != -1)) {
+ // throw away data
+ }
+
+ fis.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ return true;
+ }
+
+ private static final Set<File> SYS_EXCEPTIONS = new HashSet<File>(
+ Arrays.asList(
+ new File("/sys/kernel/debug/tracing/trace_marker"),
+ new File("/sys/fs/selinux/member"),
+ new File("/sys/fs/selinux/user"),
+ new File("/sys/fs/selinux/relabel"),
+ new File("/sys/fs/selinux/create"),
+ new File("/sys/fs/selinux/access"),
+ new File("/sys/fs/selinux/context"),
+ new File("/sys/fs/selinux/validatetrans")
+ ));
+
+ @LargeTest
+ @Test
+ public void testAllFilesInSysAreNotWritable() throws Exception {
+ Set<File> writable = getAllWritableFilesInDirAndSubDir(new File("/sys"));
+ writable.removeAll(SYS_EXCEPTIONS);
+ assertTrue("Found writable: " + writable.toString(),
+ writable.isEmpty());
+ }
+
+ private static Set<File>
+ getAllWritableFilesInDirAndSubDir(File dir) throws Exception {
+ assertTrue(dir.isDirectory());
+ Set<File> retval = new HashSet<File>();
+
+ if (isSymbolicLink(dir)) {
+ // don't examine symbolic links.
+ return retval;
+ }
+
+ File[] subDirectories = dir.listFiles(new FileFilter() {
+ @Override public boolean accept(File pathname) {
+ return pathname.isDirectory();
+ }
+ });
+
+
+ /* recurse into subdirectories */
+ if (subDirectories != null) {
+ for (File f : subDirectories) {
+ retval.addAll(getAllWritableFilesInDirAndSubDir(f));
+ }
+ }
+
+ File[] filesInThisDirectory = dir.listFiles(new FileFilter() {
+ @Override public boolean accept(File pathname) {
+ return pathname.isFile();
+ }
+ });
+ if (filesInThisDirectory == null) {
+ return retval;
+ }
+
+ for (File f: filesInThisDirectory) {
+ if (f.canWrite()) {
+ retval.add(f.getCanonicalFile());
+ }
+ }
+ return retval;
+ }
+
+ @Test
+ public void testSystemMountedRO() throws Exception {
+ StructStatVfs vfs = Os.statvfs("/system");
+ assertTrue("/system is not mounted read-only", (vfs.f_flag & OsConstants.ST_RDONLY) != 0);
+ }
+
+ @Test
+ public void testRootMountedRO() throws Exception {
+ StructStatVfs vfs = Os.statvfs("/");
+ assertTrue("rootfs is not mounted read-only", (vfs.f_flag & OsConstants.ST_RDONLY) != 0);
+ }
+
+ @Test
+ public void testVendorMountedRO() throws Exception {
+ StructStatVfs vfs = Os.statvfs("/vendor");
+ assertTrue("/vendor is not mounted read-only", (vfs.f_flag & OsConstants.ST_RDONLY) != 0);
+ }
+
+ @Test
+ public void testOdmMountedRO() throws Exception {
+ StructStatVfs vfs = Os.statvfs("/odm");
+ assertTrue("/odm is not mounted read-only", (vfs.f_flag & OsConstants.ST_RDONLY) != 0);
+ }
+
+ @Test
+ public void testOemMountedRO() throws Exception {
+ StructStatVfs vfs = Os.statvfs("/oem");
+ assertTrue("/oem is not mounted read-only", (vfs.f_flag & OsConstants.ST_RDONLY) != 0);
+ }
+
+ @Test
+ public void testDataMountedNoSuidNoDev() throws Exception {
+ StructStatVfs vfs = Os.statvfs(getContext().getFilesDir().getAbsolutePath());
+ assertTrue("/data is not mounted NOSUID", (vfs.f_flag & OsConstants.ST_NOSUID) != 0);
+ assertTrue("/data is not mounted NODEV", (vfs.f_flag & OsConstants.ST_NODEV) != 0);
+ }
+
+ @Test
+ public void testAllBlockDevicesAreSecure() throws Exception {
+ Set<File> insecure = getAllInsecureDevicesInDirAndSubdir(new File("/dev"), FileUtils.S_IFBLK);
+ assertTrue("Found insecure block devices: " + insecure.toString(),
+ insecure.isEmpty());
+ }
+
+ @Test
+ public void testDevRandomWorldReadableAndWritable() throws Exception {
+ File f = new File("/dev/random");
+
+ assertTrue(f + " cannot be opened for reading", canOpenForReading(f));
+ assertTrue(f + " cannot be opened for writing", canOpenForWriting(f));
+
+ FileUtils.FileStatus status = new FileUtils.FileStatus();
+ assertTrue(FileUtils.getFileStatus(f.getPath(), status, false));
+ assertTrue(
+ f + " not world-readable/writable. Actual mode: 0"
+ + Integer.toString(status.mode, 8),
+ (status.mode & 0666) == 0666);
+ }
+
+ @Test
+ public void testDevUrandomWorldReadableAndWritable() throws Exception {
+ File f = new File("/dev/urandom");
+
+ assertTrue(f + " cannot be opened for reading", canOpenForReading(f));
+ assertTrue(f + " cannot be opened for writing", canOpenForWriting(f));
+
+ FileUtils.FileStatus status = new FileUtils.FileStatus();
+ assertTrue(FileUtils.getFileStatus(f.getPath(), status, false));
+ assertTrue(
+ f + " not world-readable/writable. Actual mode: 0"
+ + Integer.toString(status.mode, 8),
+ (status.mode & 0666) == 0666);
+ }
+
+ @Test
+ public void testProcUUIDReadable() throws Exception {
+ File f = new File("/proc/sys/kernel/random/uuid");
+
+ assertTrue(f + " cannot be opened for reading", canOpenForReading(f));
+ assertFalse(f + " can be opened for writing", canOpenForWriting(f));
+
+ FileUtils.FileStatus status = new FileUtils.FileStatus();
+ assertTrue(FileUtils.getFileStatus(f.getPath(), status, false));
+ assertTrue(
+ f + " not 0444. Actual mode: 0"
+ + Integer.toString(status.mode, 8),
+ (status.mode & 0666) == 0444);
+ }
+
+ @Test
+ public void testDevHwRandomLockedDown() throws Exception {
+ File f = new File("/dev/hw_random");
+ if (!f.exists()) {
+ // HW RNG is not required to be exposed on all devices.
+ return;
+ }
+
+ // SELinux policy should ensure that the file isn't visible to apps at
+ // all.
+ FileUtils.FileStatus status = new FileUtils.FileStatus();
+ assertFalse("stat permitted on " + f + " (SELinux issue?)",
+ FileUtils.getFileStatus(f.getPath(), status, false));
+
+ // Double-check that we really can't read/write the file.
+ assertFalse(f + " can be opened for reading (SELinux issue?)", canOpenForReading(f));
+ assertFalse(f + " can be opened for writing (SELinux issue?)", canOpenForWriting(f));
+ }
+
+ private static boolean canOpenForReading(File f) {
+ try (InputStream in = new FileInputStream(f)) {
+ return true;
+ } catch (IOException expected) {
+ return false;
+ }
+ }
+
+ private static boolean canOpenForWriting(File f) {
+ try (OutputStream out = new FileOutputStream(f)) {
+ return true;
+ } catch (IOException expected) {
+ return false;
+ }
+ }
+
+ @Test
+ public void testFileHasOnlyCapsThrowsOnInvalidCaps() throws Exception {
+ try {
+ // Ensure negative cap id fails.
+ new FileUtils.CapabilitySet()
+ .add(-1)
+ .fileHasOnly("/system/bin/run-as");
+ fail();
+ }
+ catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ // Ensure too-large cap throws.
+ new FileUtils.CapabilitySet()
+ .add(OsConstants.CAP_LAST_CAP + 1)
+ .fileHasOnly("/system/bin/run-as");
+ fail();
+ }
+ catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test that the /system/bin/run-as command has setuid and setgid
+ * attributes set on the file. If these calls fail, debugger
+ * breakpoints for native code will not work as run-as will not
+ * be able to perform required elevated-privilege functionality.
+ */
+ @Test
+ public void testRunAsHasCorrectCapabilities() throws Exception {
+ // ensure file is user and group read/executable
+ String filename = "/system/bin/run-as";
+ FileUtils.FileStatus status = new FileUtils.FileStatus();
+ assertTrue(FileUtils.getFileStatus(filename, status, false));
+ assertTrue(status.hasModeFlag(FileUtils.S_IRUSR | FileUtils.S_IXUSR));
+ assertTrue(status.hasModeFlag(FileUtils.S_IRGRP | FileUtils.S_IXGRP));
+
+ // ensure file owner/group is set correctly
+ File f = new File(filename);
+ assertFileOwnedBy(f, "root");
+ assertFileOwnedByGroup(f, "shell");
+
+ // ensure file has setuid/setgid enabled
+ assertTrue(FileUtils.hasSetUidCapability(filename));
+ assertTrue(FileUtils.hasSetGidCapability(filename));
+
+ // ensure file has *only* setuid/setgid attributes enabled
+ assertTrue(new FileUtils.CapabilitySet()
+ .add(OsConstants.CAP_SETUID)
+ .add(OsConstants.CAP_SETGID)
+ .fileHasOnly("/system/bin/run-as"));
+ }
+
+ private static Set<File>
+ getAllInsecureDevicesInDirAndSubdir(File dir, int type) throws Exception {
+ assertTrue(dir.isDirectory());
+ Set<File> retval = new HashSet<File>();
+
+ if (isSymbolicLink(dir)) {
+ // don't examine symbolic links.
+ return retval;
+ }
+
+ File[] subDirectories = dir.listFiles(new FileFilter() {
+ @Override public boolean accept(File pathname) {
+ return pathname.isDirectory();
+ }
+ });
+
+
+ /* recurse into subdirectories */
+ if (subDirectories != null) {
+ for (File f : subDirectories) {
+ retval.addAll(getAllInsecureDevicesInDirAndSubdir(f, type));
+ }
+ }
+
+ File[] filesInThisDirectory = dir.listFiles();
+ if (filesInThisDirectory == null) {
+ return retval;
+ }
+
+ for (File f: filesInThisDirectory) {
+ FileUtils.FileStatus status = new FileUtils.FileStatus();
+ FileUtils.getFileStatus(f.getAbsolutePath(), status, false);
+ if (status.isOfType(type)) {
+ if (f.canRead() || f.canWrite() || f.canExecute()) {
+ retval.add(f);
+ }
+ if (status.uid == 2000) {
+ // The shell user should not own any devices
+ retval.add(f);
+ }
+
+ // Don't allow devices owned by GIDs
+ // accessible to non-privileged applications.
+ if ((status.gid == 1007) // AID_LOG
+ || (status.gid == 1015) // AID_SDCARD_RW
+ || (status.gid == 1023) // AID_MEDIA_RW
+ || (status.gid == 1028) // AID_SDCARD_R
+ || (status.gid == 2000)) // AID_SHELL
+ {
+ if (status.hasModeFlag(FileUtils.S_IRGRP)
+ || status.hasModeFlag(FileUtils.S_IWGRP)
+ || status.hasModeFlag(FileUtils.S_IXGRP))
+ {
+ retval.add(f);
+ }
+ }
+ }
+ }
+ return retval;
+ }
+
+ private Set<File> getWritableDirectoriesAndSubdirectoriesOf(File dir) throws Exception {
+ Set<File> retval = new HashSet<File>();
+ if (!dir.isDirectory()) {
+ return retval;
+ }
+
+ if (isSymbolicLink(dir)) {
+ // don't examine symbolic links.
+ return retval;
+ }
+
+ String myHome = getContext().getApplicationInfo().dataDir;
+ String thisDir = dir.getCanonicalPath();
+ if (thisDir.startsWith(myHome)) {
+ // Don't examine directories within our home directory.
+ // We expect these directories to be writable.
+ return retval;
+ }
+
+ if (isDirectoryWritable(dir)) {
+ retval.add(dir);
+ }
+
+ File[] subFiles = dir.listFiles();
+ if (subFiles == null) {
+ return retval;
+ }
+
+ for (File f : subFiles) {
+ retval.addAll(getWritableDirectoriesAndSubdirectoriesOf(f));
+ }
+
+ return retval;
+ }
+
+ private static boolean isSymbolicLink(File f) throws IOException {
+ return !f.getAbsolutePath().equals(f.getCanonicalPath());
+ }
+
+}
diff --git a/tests/cts/permission/src/android/permission/cts/FileUtils.java b/tests/cts/permission/src/android/permission/cts/FileUtils.java
new file mode 100644
index 000000000..0743cd30a
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/FileUtils.java
@@ -0,0 +1,128 @@
+package android.permission.cts;
+
+/*
+ * Copyright (C) 2010 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.
+ */
+
+import android.system.OsConstants;
+
+import com.google.common.primitives.Ints;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/** Bits and pieces copied from hidden API of android.os.FileUtils. */
+public class FileUtils {
+
+ public static final int S_IFMT = 0170000;
+ public static final int S_IFSOCK = 0140000;
+ public static final int S_IFLNK = 0120000;
+ public static final int S_IFREG = 0100000;
+ public static final int S_IFBLK = 0060000;
+ public static final int S_IFDIR = 0040000;
+ public static final int S_IFCHR = 0020000;
+ public static final int S_IFIFO = 0010000;
+
+ public static final int S_ISUID = 0004000;
+ public static final int S_ISGID = 0002000;
+ public static final int S_ISVTX = 0001000;
+
+ public static final int S_IRWXU = 00700;
+ public static final int S_IRUSR = 00400;
+ public static final int S_IWUSR = 00200;
+ public static final int S_IXUSR = 00100;
+
+ public static final int S_IRWXG = 00070;
+ public static final int S_IRGRP = 00040;
+ public static final int S_IWGRP = 00020;
+ public static final int S_IXGRP = 00010;
+
+ public static final int S_IRWXO = 00007;
+ public static final int S_IROTH = 00004;
+ public static final int S_IWOTH = 00002;
+ public static final int S_IXOTH = 00001;
+
+ static {
+ System.loadLibrary("ctspermission_jni");
+ }
+
+ public static class FileStatus {
+
+ public int dev;
+ public int ino;
+ public int mode;
+ public int nlink;
+ public int uid;
+ public int gid;
+ public int rdev;
+ public long size;
+ public int blksize;
+ public long blocks;
+ public long atime;
+ public long mtime;
+ public long ctime;
+
+ public boolean hasModeFlag(int flag) {
+ if (((S_IRWXU | S_IRWXG | S_IRWXO) & flag) != flag) {
+ throw new IllegalArgumentException("Inappropriate flag " + flag);
+ }
+ return (mode & flag) == flag;
+ }
+
+ public boolean isOfType(int type) {
+ if ((type & S_IFMT) != type) {
+ throw new IllegalArgumentException("Unknown type " + type);
+ }
+ return (mode & S_IFMT) == type;
+ }
+ }
+
+ public static class CapabilitySet {
+
+ private final Set<Integer> mCapabilities = new HashSet<Integer>();
+
+ public CapabilitySet add(int capability) {
+ if ((capability < 0) || (capability > OsConstants.CAP_LAST_CAP)) {
+ throw new IllegalArgumentException(String.format(
+ "capability id %d out of valid range", capability));
+ }
+ mCapabilities.add(capability);
+ return this;
+ }
+
+ private native static boolean fileHasOnly(String path,
+ int[] capabilities);
+
+ public boolean fileHasOnly(String path) {
+ return fileHasOnly(path, Ints.toArray(mCapabilities));
+ }
+ }
+
+ /**
+ * @param path of the file to stat
+ * @param status object to set the fields on
+ * @param statLinks or don't stat links (lstat vs stat)
+ * @return whether or not we were able to stat the file
+ */
+ public native static boolean getFileStatus(String path, FileStatus status, boolean statLinks);
+
+ public native static String getUserName(int uid);
+
+ public native static String getGroupName(int gid);
+
+ public native static boolean hasSetUidCapability(String path);
+
+ public native static boolean hasSetGidCapability(String path);
+}
diff --git a/tests/cts/permission/src/android/permission/cts/IgnoreAllTestsRule.java b/tests/cts/permission/src/android/permission/cts/IgnoreAllTestsRule.java
new file mode 100644
index 000000000..45288bd48
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/IgnoreAllTestsRule.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 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.permission.cts;
+
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+/**
+ * A {@link org.junit.Rule} that will cause all tests in the class
+ * to be ignored if the argument to the constructor is true.
+ */
+public class IgnoreAllTestsRule implements MethodRule {
+
+ private boolean mIgnore;
+
+ /**
+ * Creates a new IgnoreAllTestsRule
+ * @param ignore If true, all tests in the class will be ignored. If false, this rule will
+ * do nothing.
+ */
+ public IgnoreAllTestsRule(boolean ignore) {
+ mIgnore = ignore;
+ }
+
+ @Override
+ public Statement apply(Statement base, FrameworkMethod method, Object target) {
+ if (mIgnore) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ }
+ };
+ } else {
+ return base;
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/LocationAccessCheckTest.java b/tests/cts/permission/src/android/permission/cts/LocationAccessCheckTest.java
new file mode 100644
index 000000000..a4945653a
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/LocationAccessCheckTest.java
@@ -0,0 +1,849 @@
+/*
+ * Copyright (C) 2018 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.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.app.AppOpsManager.OPSTR_FINE_LOCATION;
+import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_NOT_FOREGROUND;
+import static android.location.Criteria.ACCURACY_FINE;
+import static android.os.Process.myUserHandle;
+import static android.provider.Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS;
+import static android.provider.Settings.Secure.LOCATION_ACCESS_CHECK_INTERVAL_MILLIS;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.waitForBroadcasts;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Process;
+import android.permission.cts.appthataccesseslocation.IAccessLocationOnCommand;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AsbSecurityTest;
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.SystemUserOnly;
+import android.platform.test.rule.ScreenRecordRule;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
+import com.android.compatibility.common.util.mainline.MainlineModule;
+import com.android.compatibility.common.util.mainline.ModuleDetector;
+import com.android.modules.utils.build.SdkLevel;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Tests the {@code LocationAccessCheck} in permission controller.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Cannot set system settings as instant app. Also we never show a location "
+ + "access check notification for instant apps.")
+@ScreenRecordRule.ScreenRecord
+@FlakyTest
+public class LocationAccessCheckTest {
+
+ private static final String LOG_TAG = LocationAccessCheckTest.class.getSimpleName();
+
+ private static final String TEST_APP_PKG = "android.permission.cts.appthataccesseslocation";
+ private static final String TEST_APP_LABEL = "CtsLocationAccess";
+ private static final String TEST_APP_SERVICE = TEST_APP_PKG + ".AccessLocationOnCommand";
+ private static final String TEST_APP_LOCATION_BG_ACCESS_APK =
+ "/data/local/tmp/cts/permissions/CtsAppThatAccessesLocationOnCommand.apk";
+ private static final String TEST_APP_LOCATION_FG_ACCESS_APK =
+ "/data/local/tmp/cts/permissions/AppThatDoesNotHaveBgLocationAccess.apk";
+ private static final String ACTION_SET_UP_LOCATION_ACCESS_CHECK =
+ "com.android.permissioncontroller.action.SET_UP_LOCATION_ACCESS_CHECK";
+ private static final int LOCATION_ACCESS_CHECK_JOB_ID = 0;
+ private static final int LOCATION_ACCESS_CHECK_NOTIFICATION_ID = 0;
+
+ /**
+ * Whether to show location access check notifications.
+ */
+ private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED =
+ "location_access_check_enabled";
+ private static final String PROPERTY_LOCATION_ACCESS_CHECK_DELAY_MILLIS =
+ "location_access_check_delay_millis";
+ private static final String PROPERTY_LOCATION_ACCESS_PERIODIC_INTERVAL_MILLIS =
+ "location_access_check_periodic_interval_millis";
+ private static final String PROPERTY_BG_LOCATION_CHECK_ENABLED = "bg_location_check_is_enabled";
+
+ private static final long UNEXPECTED_TIMEOUT_MILLIS = 10000;
+ private static final long EXPECTED_TIMEOUT_MILLIS = 15000;
+ private static final long LOCATION_ACCESS_TIMEOUT_MILLIS = 15000;
+
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+ private static final ActivityManager sActivityManager =
+ sContext.getSystemService(ActivityManager.class);
+ private static final PackageManager sPackageManager = sContext.getPackageManager();
+ private static final AppOpsManager sAppOpsManager =
+ sContext.getSystemService(AppOpsManager.class);
+ private static final LocationManager sLocationManager =
+ sContext.getSystemService(LocationManager.class);
+ private static final UiAutomation sUiAutomation = InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation();
+
+ private static final String PERMISSION_CONTROLLER_PKG = sContext.getPackageManager()
+ .getPermissionControllerPackageName();
+ private static final String LocationAccessCheckOnBootReceiver =
+ "com.android.permissioncontroller.permission.service"
+ + ".LocationAccessCheck$SetupPeriodicBackgroundLocationAccessCheck";
+
+
+ /**
+ * The result of {@link #assumeCanGetFineLocation()}, so we don't have to run it over and over
+ * again.
+ */
+ private static Boolean sCanAccessFineLocation = null;
+
+ private static ServiceConnection sConnection;
+ private static IAccessLocationOnCommand sLocationAccessor;
+
+ private static void assumeNotPlayManaged() throws Exception {
+ assumeFalse(ModuleDetector.moduleIsPlayManaged(
+ sContext.getPackageManager(), MainlineModule.PERMISSION_CONTROLLER));
+ }
+
+ @Rule
+ public final ScreenRecordRule mScreenRecordRule = new ScreenRecordRule(false, false);
+
+ // Override location access check flag
+ @Rule
+ public DeviceConfigStateChangerRule mPrivacyDeviceConfig =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_ACCESS_CHECK_ENABLED,
+ Boolean.toString(true));
+
+ // Override SafetyCenter enabled flag
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigSafetyCenterEnabled =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SafetyCenterUtils.PROPERTY_SAFETY_CENTER_ENABLED,
+ Boolean.toString(true));
+
+ // Override BG location enabled flag
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigBgLocationCheckEnabled =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_BG_LOCATION_CHECK_ENABLED,
+ Boolean.toString(true));
+
+ // Override general notification interval
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigBgCheckIntervalMillis =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_ACCESS_PERIODIC_INTERVAL_MILLIS,
+ "100");
+
+ // Override general delay interval
+ @Rule
+ public DeviceConfigStateChangerRule sPrivacyDeviceConfigBgCheckDelayMillis =
+ new DeviceConfigStateChangerRule(sContext,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_ACCESS_CHECK_DELAY_MILLIS,
+ "50");
+
+ private static boolean sWasLocationEnabled = true;
+
+ @BeforeClass
+ public static void beforeClassSetup() throws Exception {
+ reduceDelays();
+ allowNotificationAccess();
+ installBackgroundAccessApp();
+ runWithShellPermissionIdentity(() -> {
+ sWasLocationEnabled = sLocationManager.isLocationEnabled();
+ if (!sWasLocationEnabled) {
+ sLocationManager.setLocationEnabledForUser(true, Process.myUserHandle());
+ }
+ });
+ }
+
+ /**
+ * Change settings so that permission controller can show location access notifications more
+ * often.
+ */
+ public static void reduceDelays() {
+ runWithShellPermissionIdentity(() -> {
+ ContentResolver cr = sContext.getContentResolver();
+ // New settings will be applied in when permission controller is reset
+ Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS, 100);
+ Settings.Secure.putLong(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS, 50);
+ });
+ }
+
+ @AfterClass
+ public static void cleanupAfterClass() throws Throwable {
+ resetDelays();
+ uninstallTestApp();
+ disallowNotificationAccess();
+ runWithShellPermissionIdentity(() -> {
+ if (!sWasLocationEnabled) {
+ sLocationManager.setLocationEnabledForUser(false, Process.myUserHandle());
+ }
+ });
+ }
+
+ /**
+ * Reset settings so that permission controller runs normally.
+ */
+ public static void resetDelays() throws Throwable {
+ runWithShellPermissionIdentity(() -> {
+ ContentResolver cr = sContext.getContentResolver();
+ Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_INTERVAL_MILLIS);
+ Settings.Secure.resetToDefaults(cr, LOCATION_ACCESS_CHECK_DELAY_MILLIS);
+ });
+ }
+
+ /**
+ * Connected to {@value #TEST_APP_PKG} and make it access the location in the background
+ */
+ private void accessLocation() throws Throwable {
+ if (sConnection == null || sLocationAccessor == null) {
+ bindService();
+ }
+
+ long beforeAccess = System.currentTimeMillis();
+ // Wait a little to avoid raciness in timing between threads
+ Thread.sleep(1000);
+
+ // Try again until binder call goes though. It might not go through if the sLocationAccessor
+ // is not bound yet
+ eventually(() -> {
+ assertNotNull(sLocationAccessor);
+ sLocationAccessor.accessLocation();
+ }, EXPECTED_TIMEOUT_MILLIS);
+
+ // Wait until the access is recorded
+ eventually(() -> {
+ List<AppOpsManager.PackageOps> ops = runWithShellPermissionIdentity(
+ () -> sAppOpsManager.getOpsForPackage(
+ sPackageManager.getPackageUid(TEST_APP_PKG, 0), TEST_APP_PKG,
+ OPSTR_FINE_LOCATION));
+
+ // Background access must have happened after "beforeAccess"
+ assertTrue(ops.get(0).getOps().get(0).getLastAccessBackgroundTime(OP_FLAGS_ALL_TRUSTED)
+ >= beforeAccess);
+ }, EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ /**
+ * A {@link java.util.concurrent.Callable} that can throw a {@link Throwable}
+ */
+ private interface ThrowingCallable<T> {
+ T call() throws Throwable;
+ }
+
+ /**
+ * A {@link Runnable} that can throw a {@link Throwable}
+ */
+ private interface ThrowingRunnable {
+ void run() throws Throwable;
+ }
+
+ /**
+ * Make sure that a {@link ThrowingRunnable} eventually finishes without throwing a {@link
+ * Exception}.
+ *
+ * @param r The {@link ThrowingRunnable} to run.
+ * @param timeout the maximum time to wait
+ */
+ public static void eventually(@NonNull ThrowingRunnable r, long timeout) throws Throwable {
+ eventually(() -> {
+ r.run();
+ return 0;
+ }, timeout);
+ }
+
+ /**
+ * Make sure that a {@link ThrowingCallable} eventually finishes without throwing a {@link
+ * Exception}.
+ *
+ * @param r The {@link ThrowingCallable} to run.
+ * @param timeout the maximum time to wait
+ * @return the return value from the callable
+ * @throws NullPointerException If the return value never becomes non-null
+ */
+ public static <T> T eventually(@NonNull ThrowingCallable<T> r, long timeout) throws Throwable {
+ long start = System.currentTimeMillis();
+
+ while (true) {
+ try {
+ T res = r.call();
+ if (res == null) {
+ throw new NullPointerException("No result");
+ }
+
+ return res;
+ } catch (Throwable e) {
+ if (System.currentTimeMillis() - start < timeout) {
+ Log.d(LOG_TAG, "Ignoring exception", e);
+
+ Thread.sleep(500);
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+
+ /**
+ * Clear all data of a package including permissions and files.
+ *
+ * @param pkg The name of the package to be cleared
+ */
+ private static void clearPackageData(@NonNull String pkg) {
+ unbindService();
+ runShellCommand("pm clear --user -2 " + pkg);
+ }
+
+ private static boolean isJobReady() {
+ String jobStatus = runShellCommand("cmd jobscheduler get-job-state -u "
+ + Process.myUserHandle().getIdentifier() + " " + PERMISSION_CONTROLLER_PKG
+ + " " + LOCATION_ACCESS_CHECK_JOB_ID);
+ return jobStatus.contains("waiting");
+ }
+
+ /**
+ * Force a run of the location check.
+ */
+ private static void runLocationCheck() throws Throwable {
+ if (!isJobReady()) {
+ PermissionUtils.scheduleJob(sUiAutomation, PERMISSION_CONTROLLER_PKG,
+ LOCATION_ACCESS_CHECK_JOB_ID, EXPECTED_TIMEOUT_MILLIS,
+ ACTION_SET_UP_LOCATION_ACCESS_CHECK, LocationAccessCheckOnBootReceiver);
+ }
+
+ TestUtils.awaitJobUntilRequestedState(
+ PERMISSION_CONTROLLER_PKG,
+ LOCATION_ACCESS_CHECK_JOB_ID,
+ EXPECTED_TIMEOUT_MILLIS,
+ sUiAutomation,
+ "waiting"
+ );
+
+ TestUtils.runJobAndWaitUntilCompleted(
+ PERMISSION_CONTROLLER_PKG,
+ LOCATION_ACCESS_CHECK_JOB_ID,
+ EXPECTED_TIMEOUT_MILLIS,
+ sUiAutomation
+ );
+ }
+
+ /**
+ * Get a location access notification that is currently visible.
+ *
+ * @param cancelNotification if {@code true} the notification is canceled inside this method
+ * @return The notification or {@code null} if there is none
+ */
+ private StatusBarNotification getNotification(boolean cancelNotification) throws Throwable {
+ return CtsNotificationListenerServiceUtils.getNotificationForPackageAndId(
+ PERMISSION_CONTROLLER_PKG, LOCATION_ACCESS_CHECK_NOTIFICATION_ID,
+ cancelNotification);
+ }
+
+ /**
+ * Grant a permission to the {@value #TEST_APP_PKG}.
+ *
+ * @param permission The permission to grant
+ */
+ private void grantPermissionToTestApp(@NonNull String permission) {
+ sUiAutomation.grantRuntimePermission(TEST_APP_PKG, permission);
+ }
+
+ /**
+ * Register {@link CtsNotificationListenerService}.
+ */
+ public static void allowNotificationAccess() {
+ runShellCommand("cmd notification allow_listener " + (new ComponentName(sContext,
+ CtsNotificationListenerService.class).flattenToString()));
+ }
+
+ public static void installBackgroundAccessApp() throws Exception {
+ String output = runShellCommand("pm install -r -g " + TEST_APP_LOCATION_BG_ACCESS_APK);
+ assertTrue(output.contains("Success"));
+ // Wait for user sensitive to be updated, which is checked by LocationAccessCheck.
+ Thread.sleep(5000);
+ }
+
+ public static void uninstallTestApp() {
+ unbindService();
+ runShellCommand("pm uninstall " + TEST_APP_PKG);
+ }
+
+ private static void unbindService() {
+ if (sConnection != null) {
+ sContext.unbindService(sConnection);
+ }
+ sConnection = null;
+ sLocationAccessor = null;
+ }
+
+ private void setDeviceConfigProperty(
+ @NonNull String propertyName,
+ @NonNull String value) {
+ runWithShellPermissionIdentity(() -> {
+ boolean valueWasSet = DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ propertyName,
+ value,
+ false);
+ if (!valueWasSet) {
+ throw new IllegalStateException("Could not set " + propertyName + " to " + value);
+ }
+ });
+ }
+
+
+ private static void installForegroundAccessApp() throws Exception {
+ unbindService();
+ runShellCommand("pm install -r -g " + TEST_APP_LOCATION_FG_ACCESS_APK);
+ // Wait for user sensitive to be updated, which is checked by LocationAccessCheck.
+ Thread.sleep(5000);
+ }
+
+ /**
+ * Skip each test for low ram device
+ */
+ public void assumeIsNotLowRamDevice() {
+ assumeFalse(sActivityManager.isLowRamDevice());
+ }
+
+ public void wakeUpAndDismissKeyguard() {
+ runShellCommand("input keyevent KEYCODE_WAKEUP");
+ runShellCommand("wm dismiss-keyguard");
+ }
+
+ public void bindService() {
+ sConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ sLocationAccessor = IAccessLocationOnCommand.Stub.asInterface(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ sConnection = null;
+ sLocationAccessor = null;
+ }
+ };
+
+ Intent testAppService = new Intent();
+ testAppService.setComponent(new ComponentName(TEST_APP_PKG, TEST_APP_SERVICE));
+
+ sContext.bindService(testAppService, sConnection, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND);
+ }
+
+ @Before
+ public void beforeEachTestSetup() throws Throwable {
+ assumeIsNotLowRamDevice();
+ wakeUpAndDismissKeyguard();
+ bindService();
+ resetPermissionControllerBeforeEachTest();
+ bypassBatterySavingRestrictions();
+ assumeCanGetFineLocation();
+ }
+
+ /**
+ * Reset the permission controllers state before each test
+ */
+ public void resetPermissionControllerBeforeEachTest() throws Throwable {
+ // Has to be before resetPermissionController to make sure enablement time is the reset time
+ // of permission controller
+ enableLocationAccessCheck();
+
+ resetPermissionController();
+
+ eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
+
+ // Reset job scheduler stats (to allow more jobs to be run)
+ runShellCommand(
+ "cmd jobscheduler reset-execution-quota -u " + myUserHandle().getIdentifier() + " "
+ + PERMISSION_CONTROLLER_PKG);
+ runShellCommand("cmd jobscheduler reset-schedule-quota");
+ }
+
+ public void bypassBatterySavingRestrictions() {
+ runShellCommand("cmd tare set-vip " + myUserHandle().getIdentifier()
+ + " " + PERMISSION_CONTROLLER_PKG + " true");
+ }
+
+ /**
+ * Enable location access check
+ */
+ public void enableLocationAccessCheck() throws Throwable {
+ setDeviceConfigProperty(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED,
+ "true");
+ // Run a location access check to update enabled state inside permission controller
+ runLocationCheck();
+ }
+
+ /**
+ * Disable location access check
+ */
+ private void disableLocationAccessCheck() throws Throwable {
+ setDeviceConfigProperty(PROPERTY_LOCATION_ACCESS_CHECK_ENABLED,
+ "false");
+ // Run a location access check to update enabled state inside permission controller
+ runLocationCheck();
+ }
+
+ /**
+ * Make sure fine location can be accessed at all.
+ */
+ public void assumeCanGetFineLocation() {
+ if (sCanAccessFineLocation == null) {
+ Criteria crit = new Criteria();
+ crit.setAccuracy(ACCURACY_FINE);
+
+ CountDownLatch locationCounter = new CountDownLatch(1);
+ sContext.getSystemService(LocationManager.class).requestSingleUpdate(crit,
+ new LocationListener() {
+ @Override
+ public void onLocationChanged(Location location) {
+ locationCounter.countDown();
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+ }
+ }, Looper.getMainLooper());
+
+
+ try {
+ sCanAccessFineLocation = locationCounter.await(LOCATION_ACCESS_TIMEOUT_MILLIS,
+ MILLISECONDS);
+ } catch (InterruptedException ignored) {
+ }
+ }
+
+ assumeTrue(sCanAccessFineLocation);
+ }
+
+ /**
+ * Reset the permission controllers state.
+ */
+ private static void resetPermissionController() throws Throwable {
+ unbindService();
+ PermissionUtils.resetPermissionControllerJob(sUiAutomation, PERMISSION_CONTROLLER_PKG,
+ LOCATION_ACCESS_CHECK_JOB_ID, 45000,
+ ACTION_SET_UP_LOCATION_ACCESS_CHECK, LocationAccessCheckOnBootReceiver);
+ }
+
+ /**
+ * Unregister {@link CtsNotificationListenerService}.
+ */
+ public static void disallowNotificationAccess() {
+ runShellCommand("cmd notification disallow_listener " + (new ComponentName(sContext,
+ CtsNotificationListenerService.class)).flattenToString());
+ }
+
+ @After
+ public void cleanupAfterEachTest() throws Throwable {
+ resetPrivacyConfig();
+ locationUnbind();
+ resetBatterySavingRestrictions();
+ }
+
+ /**
+ * Reset location access check
+ */
+ public void resetPrivacyConfig() throws Throwable {
+ // Run a location access check to update enabled state inside permission controller
+ runLocationCheck();
+ }
+
+ public void locationUnbind() throws Throwable {
+ unbindService();
+ }
+
+ public void resetBatterySavingRestrictions() {
+ runShellCommand("cmd tare set-vip " + myUserHandle().getIdentifier()
+ + " " + PERMISSION_CONTROLLER_PKG + " default");
+ }
+
+ @Test
+ public void notificationIsShown() throws Throwable {
+ accessLocation();
+ runLocationCheck();
+ eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ @AsbSecurityTest(cveBugId = 141028068)
+ public void notificationIsShownOnlyOnce() throws Throwable {
+ assumeNotPlayManaged();
+
+ accessLocation();
+ runLocationCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
+
+ accessLocation();
+ runLocationCheck();
+
+ assertNull(getNotification(true));
+ }
+
+ @SystemUserOnly(reason = "b/172259935")
+ @Test
+ @AsbSecurityTest(cveBugId = 141028068)
+ public void notificationIsShownAgainAfterClear() throws Throwable {
+ assumeNotPlayManaged();
+ accessLocation();
+ runLocationCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
+
+ clearPackageData(TEST_APP_PKG);
+
+ // Wait until package is cleared and permission controller has cleared the state
+ Thread.sleep(10000);
+ waitForBroadcasts();
+
+ // Clearing removed the permissions, hence grant them again
+ grantPermissionToTestApp(ACCESS_FINE_LOCATION);
+ grantPermissionToTestApp(ACCESS_BACKGROUND_LOCATION);
+
+ accessLocation();
+ runLocationCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @SystemUserOnly(reason = "b/172259935")
+ @Test
+ public void notificationIsShownAgainAfterUninstallAndReinstall() throws Throwable {
+ accessLocation();
+ runLocationCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
+
+ uninstallTestApp();
+
+ // Wait until package permission controller has cleared the state
+ Thread.sleep(2000);
+
+ installBackgroundAccessApp();
+ waitForBroadcasts();
+ accessLocation();
+ runLocationCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ @AsbSecurityTest(cveBugId = 141028068)
+ public void removeNotificationOnUninstall() throws Throwable {
+ assumeNotPlayManaged();
+
+ accessLocation();
+ runLocationCheck();
+
+ eventually(() -> assertNotNull(getNotification(false)), EXPECTED_TIMEOUT_MILLIS);
+
+ uninstallTestApp();
+ // wait for permission controller (broadcast receiver) to clean up things
+ Thread.sleep(5000);
+ waitForBroadcasts();
+
+ try {
+ eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
+ } finally {
+ installBackgroundAccessApp();
+ }
+ }
+
+ @Test
+ public void notificationIsNotShownAfterAppDoesNotRequestLocationAnymore() throws Throwable {
+ accessLocation();
+ runLocationCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
+
+ // Update to app to a version that does not request permission anymore
+ installForegroundAccessApp();
+
+ try {
+ resetPermissionController();
+
+ runLocationCheck();
+
+ // We don't expect a notification, but try to trigger one anyway
+ assertNull(getNotification(false));
+ } finally {
+ installBackgroundAccessApp();
+ }
+ }
+
+ @Test
+ @AsbSecurityTest(cveBugId = 141028068)
+ public void noNotificationIfFeatureDisabled() throws Throwable {
+ assumeNotPlayManaged();
+
+ disableLocationAccessCheck();
+
+ accessLocation();
+ runLocationCheck();
+
+ assertNull(getNotification(false));
+ }
+
+ @Test
+ @AsbSecurityTest(cveBugId = 141028068)
+ public void notificationOnlyForAccessesSinceFeatureWasEnabled() throws Throwable {
+ assumeNotPlayManaged();
+
+ disableLocationAccessCheck();
+
+ accessLocation();
+ runLocationCheck();
+
+ // No notification expected for accesses before enabling the feature
+ assertNull(getNotification(false));
+
+ enableLocationAccessCheck();
+ Thread.sleep(2000);
+
+ // Trigger update of location enable time. In the real world it enabling happens on the
+ // first location check. I.e. accesses before this location check are ignored.
+ runLocationCheck();
+
+ // No notification expected for accesses before enabling the feature (even after feature is
+ // enabled now)
+ assertNull(getNotification(false));
+
+ // Notification expected for access after enabling the feature
+ accessLocation();
+ runLocationCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ @AsbSecurityTest(cveBugId = 141028068)
+ public void noNotificationIfBlamerNotSystemOrLocationProvider() throws Throwable {
+ assumeNotPlayManaged();
+
+ // Blame the app for access from an untrusted for notification purposes package.
+ runWithShellPermissionIdentity(() -> {
+ AppOpsManager appOpsManager = sContext.getSystemService(AppOpsManager.class);
+ appOpsManager.noteProxyOpNoThrow(OPSTR_FINE_LOCATION, TEST_APP_PKG,
+ sContext.getPackageManager().getPackageUid(TEST_APP_PKG, 0));
+ });
+ runLocationCheck();
+
+ assertNull(getNotification(false));
+ }
+
+ @Test
+ // Mark as flaky until b/286874765 is fixed
+ @FlakyTest
+ @MtsIgnore
+ @AsbSecurityTest(cveBugId = 141028068)
+ public void testOpeningLocationSettingsDoesNotTriggerAccess() throws Throwable {
+ assumeNotPlayManaged();
+
+ Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ sContext.startActivity(intent);
+
+ runLocationCheck();
+ assertNull(getNotification(false));
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ public void notificationOnClickOpensSafetyCenter() throws Throwable {
+ assumeTrue(SafetyCenterUtils.deviceSupportsSafetyCenter(sContext));
+ accessLocation();
+ runLocationCheck();
+
+ StatusBarNotification currentNotification = eventually(() -> {
+ StatusBarNotification notification = getNotification(false);
+ assertNotNull(notification);
+ return notification;
+ }, EXPECTED_TIMEOUT_MILLIS);
+
+ // Verify content intent
+ PendingIntent contentIntent = currentNotification.getNotification().contentIntent;
+ if (SdkLevel.isAtLeastU()) {
+ contentIntent.send(null, 0, null, null, null, null,
+ ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle());
+ } else {
+ contentIntent.send();
+ }
+
+ SafetyCenterUtils.assertSafetyCenterStarted();
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/MainlineNetworkStackPermissionTest.java b/tests/cts/permission/src/android/permission/cts/MainlineNetworkStackPermissionTest.java
new file mode 100644
index 000000000..adac0befa
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/MainlineNetworkStackPermissionTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 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.permission.cts;
+
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MainlineNetworkStackPermissionTest{
+ private final Context mContext = InstrumentationRegistry.getContext();
+
+ /**
+ * Test that a package defining android.permission.MAINLINE_NETWORK_STACK is installed,
+ * and is a system package
+ */
+ @Test
+ @AppModeFull(reason = "Instant apps cannot access PackageManager#getPermissionInfo")
+ public void testPackageWithMainlineNetworkStackPermission() throws Exception {
+ final PackageManager packageManager = mContext.getPackageManager();
+ assertNotNull("Unable to find PackageManager.", packageManager);
+
+ final PermissionInfo permissioninfo =
+ packageManager.getPermissionInfo(PERMISSION_MAINLINE_NETWORK_STACK, 0);
+ assertNotNull("Network stack permission is not defined.", permissioninfo);
+
+ PackageInfo packageInfo = packageManager.getPackageInfo(permissioninfo.packageName,
+ PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_PERMISSIONS);
+ assertNotNull("Package defining the network stack permission is not a system package.",
+ packageInfo.permissions);
+
+ for (PermissionInfo permission : packageInfo.permissions) {
+ if (PERMISSION_MAINLINE_NETWORK_STACK.equals(permission.name)) {
+ return;
+ }
+ }
+
+ fail("Expect a system package defining " + PERMISSION_MAINLINE_NETWORK_STACK
+ + " is installed.");
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/MinMaxSdkVersionTest.kt b/tests/cts/permission/src/android/permission/cts/MinMaxSdkVersionTest.kt
new file mode 100644
index 000000000..fff5c42dd
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/MinMaxSdkVersionTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts
+
+import android.app.Instrumentation
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.permission.cts.PermissionUtils.install
+import android.platform.test.annotations.AppModeFull
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import androidx.test.uiautomator.UiDevice
+import com.android.compatibility.common.util.CddTest
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+@CddTest(requirement = "9.1/C-0-1")
+@AppModeFull(reason = "Instant apps cannot read state of other packages.")
+class MinMaxSdkVersionTest {
+ private val mInstrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private var mUiDevice: UiDevice? = null
+ private var mContext: Context? = null
+
+ @Before
+ fun setup() {
+ mUiDevice = UiDevice.getInstance(mInstrumentation)
+ mContext = mInstrumentation?.targetContext
+ }
+
+ @Test
+ fun givenMinSdkVersionInRangeThenPermissionIsRequested() {
+ install(TEST_APP_PATH)
+
+ Assert.assertTrue(appRequestsPermission(MINSDK_LT_DEVICESDK))
+ }
+
+ @Test
+ fun givenMinSdkVersionOutOfRangeThenPermissionIsNotRequested() {
+ install(TEST_APP_PATH)
+
+ Assert.assertFalse(appRequestsPermission(MINSDK_GT_DEVICESDK))
+ }
+
+ @Test
+ fun givenMaxSdkVersionInRangeThenPermissionIsRequested() {
+ install(TEST_APP_PATH)
+
+ Assert.assertTrue(appRequestsPermission(MAXSDK_GT_DEVICESDK))
+ }
+
+ @Test
+ fun givenMaxSdkVersionOutOfRangeThenPermissionIsNotRequested() {
+ install(TEST_APP_PATH)
+
+ Assert.assertFalse(appRequestsPermission(MAXSDK_LT_DEVICESDK))
+ }
+
+ private fun appRequestsPermission(permName: String): Boolean {
+ val packageInfo = mContext!!.packageManager.getPackageInfo(
+ TEST_APP_PKG_NAME, PackageManager.GET_PERMISSIONS)
+ return packageInfo.requestedPermissions.any { it == permName }
+ }
+
+ companion object {
+ private const val TEST_APP_NAME =
+ "CtsAppThatRequestsMultiplePermissionsWithMinMaxSdk.apk"
+ private const val TMP_DIR = "/data/local/tmp/cts/permissions/"
+ private const val TEST_APP_PATH = TMP_DIR + TEST_APP_NAME
+ private const val TEST_APP_PKG_NAME = "android.permission.cts.appthatrequestpermission"
+ private const val CUSTOM_PERMS = "$TEST_APP_PKG_NAME.permissions"
+ private const val MINSDK_LT_DEVICESDK = "$CUSTOM_PERMS.MINSDK_LT_DEVICESDK"
+ private const val MINSDK_GT_DEVICESDK = "$CUSTOM_PERMS.MINSDK_GT_DEVICESDK"
+ private const val MAXSDK_LT_DEVICESDK = "$CUSTOM_PERMS.MAXSDK_LT_DEVICESDK"
+ private const val MAXSDK_GT_DEVICESDK = "$CUSTOM_PERMS.MAXSDK_GT_DEVICESDK"
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NearbyDevicesPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NearbyDevicesPermissionTest.java
new file mode 100644
index 000000000..5041adcf2
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NearbyDevicesPermissionTest.java
@@ -0,0 +1,328 @@
+/*
+ * 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 android.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
+import static android.permission.cts.PermissionUtils.grantPermission;
+import static android.permission.cts.PermissionUtils.install;
+import static android.permission.cts.PermissionUtils.revokePermission;
+import static android.permission.cts.PermissionUtils.uninstallApp;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.cts.BTAdapterUtils;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests the behavior of the
+ * {@link android.Manifest.permission_group#NEARBY_DEVICES} permission group
+ * under various permutations of grant states.
+ *
+ * Note that some tests will be recognized as known failures with the new permission subsystem
+ * until b/273999500 is fixed.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+public class NearbyDevicesPermissionTest {
+ private static final String TEST_APP_PKG = "android.permission.cts.appthatrequestpermission";
+ private static final String TEST_APP_AUTHORITY = "appthatrequestpermission";
+ private static final String DISAVOWAL_APP_PKG = "android.permission.cts.appneverforlocation";
+
+ private static final String TMP_DIR = "/data/local/tmp/cts/permissions/";
+ private static final String APK_BLUETOOTH_30 = TMP_DIR
+ + "CtsAppThatRequestsBluetoothPermission30.apk";
+ private static final String APK_BLUETOOTH_31 = TMP_DIR
+ + "CtsAppThatRequestsBluetoothPermission31.apk";
+ private static final String APK_BLUETOOTH_NEVER_FOR_LOCATION_31 = TMP_DIR
+ + "CtsAppThatRequestsBluetoothPermissionNeverForLocation31.apk";
+ private static final String APK_BLUETOOTH_NEVER_FOR_LOCATION_NO_PROVIDER = TMP_DIR
+ + "CtsAppThatRequestsBluetoothPermissionNeverForLocationNoProvider.apk";
+
+ private enum Result {
+ UNKNOWN, EXCEPTION, EMPTY, FILTERED, FULL
+ }
+
+ private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private BluetoothAdapter mBluetoothAdapter;
+ private boolean mBluetoothAdapterWasEnabled;
+
+ @Before
+ public void enableBluetooth() {
+ assumeTrue(supportsBluetooth());
+ mBluetoothAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter();
+ mBluetoothAdapterWasEnabled = mBluetoothAdapter.isEnabled();
+ assertTrue(BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext));
+ enableTestMode();
+ }
+
+ @After
+ public void disableBluetooth() {
+ assumeTrue(supportsBluetooth());
+ disableTestMode();
+ if (!mBluetoothAdapterWasEnabled) {
+ assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
+ }
+ }
+
+ @Before
+ @After
+ public void uninstallTestApp() {
+ uninstallApp(TEST_APP_PKG);
+ uninstallApp(DISAVOWAL_APP_PKG);
+ }
+
+ @Test
+ @CddTest(requirement="7.4.3/C-6-1")
+ public void testRequestBluetoothPermission30_Default() throws Throwable {
+ assumeTrue(supportsBluetoothLe());
+
+ install(APK_BLUETOOTH_30);
+ assertScanBluetoothResult(Result.EMPTY);
+ }
+
+ @Test
+ @CddTest(requirement="7.4.3/C-6-1")
+ public void testRequestBluetoothPermission30_GrantLocation() throws Throwable {
+ assumeTrue(supportsBluetoothLe());
+
+ install(APK_BLUETOOTH_30);
+ grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION);
+ grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION);
+ assertScanBluetoothResult(Result.FULL);
+ }
+
+ @Test
+ @CddTest(requirement="7.4.3/C-6-1")
+ public void testRequestBluetoothPermission31_Default() throws Throwable {
+ install(APK_BLUETOOTH_31);
+ assertScanBluetoothResult(Result.EXCEPTION);
+ }
+
+ @Test
+ @CddTest(requirement="7.4.3/C-6-1")
+ public void testRequestBluetoothPermission31_GrantNearby() throws Throwable {
+ assumeTrue(supportsBluetoothLe());
+
+ install(APK_BLUETOOTH_31);
+ grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT);
+ grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN);
+ assertScanBluetoothResult(Result.EMPTY);
+ }
+
+ @Test
+ @CddTest(requirement="7.4.3/C-6-1")
+ public void testRequestBluetoothPermission31_GrantLocation() throws Throwable {
+ install(APK_BLUETOOTH_31);
+ grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION);
+ grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION);
+ assertScanBluetoothResult(Result.EXCEPTION);
+ }
+
+ @Test
+ @CddTest(requirement="7.4.3/C-6-1")
+ public void testRequestBluetoothPermission31_GrantNearby_GrantLocation() throws Throwable {
+ assumeTrue(supportsBluetoothLe());
+
+ install(APK_BLUETOOTH_31);
+ grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT);
+ grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN);
+ grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION);
+ grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION);
+ assertScanBluetoothResult(Result.FULL);
+ }
+
+ @Test
+ @CddTest(requirement="7.4.3/C-6-1")
+ public void testRequestBluetoothPermissionNeverForLocation31_Default() throws Throwable {
+ install(APK_BLUETOOTH_NEVER_FOR_LOCATION_31);
+ assertScanBluetoothResult(Result.EXCEPTION);
+ }
+
+ @Test
+ @CddTest(requirement="7.4.3/C-6-1")
+ public void testRequestBluetoothPermissionNeverForLocation31_GrantNearby() throws Throwable {
+ assumeTrue(supportsBluetoothLe());
+
+ install(APK_BLUETOOTH_NEVER_FOR_LOCATION_31);
+ grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT);
+ grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN);
+ assertScanBluetoothResult(Result.FILTERED);
+ }
+
+ @Test
+ @CddTest(requirement="7.4.3/C-6-1")
+ public void testRequestBluetoothPermissionNeverForLocation31_GrantLocation() throws Throwable {
+ install(APK_BLUETOOTH_NEVER_FOR_LOCATION_31);
+ grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION);
+ grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION);
+ assertScanBluetoothResult(Result.EXCEPTION);
+ }
+
+ @Test
+ @CddTest(requirement="7.4.3/C-6-1")
+ public void testRequestBluetoothPermissionNeverForLocation31_GrantNearby_GrantLocation()
+ throws Throwable {
+ assumeTrue(supportsBluetoothLe());
+
+ install(APK_BLUETOOTH_NEVER_FOR_LOCATION_31);
+ grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT);
+ grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN);
+ grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION);
+ grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION);
+ assertScanBluetoothResult(Result.FILTERED);
+ }
+
+ @Test
+ public void testRequestBluetoothPermission31_OnBehalfOfDisavowingApp() throws Throwable {
+ assumeTrue(supportsBluetoothLe());
+
+ install(APK_BLUETOOTH_31);
+ install(APK_BLUETOOTH_NEVER_FOR_LOCATION_NO_PROVIDER);
+ grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT);
+ grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN);
+ grantPermission(DISAVOWAL_APP_PKG, BLUETOOTH_CONNECT);
+ grantPermission(DISAVOWAL_APP_PKG, BLUETOOTH_SCAN);
+ assertScanBluetoothResult("PROXY", Result.FILTERED);
+ }
+
+ /**
+ * Verify that a legacy app that was unable to interact with Bluetooth
+ * devices is still unable to interact with them after updating to a modern
+ * SDK; they'd always need to involve the user to gain permissions.
+ */
+ @Test
+ public void testRequestBluetoothPermission_Default_Upgrade() throws Throwable {
+ assumeTrue(supportsBluetoothLe());
+
+ install(APK_BLUETOOTH_30);
+ assertScanBluetoothResult(Result.EMPTY);
+
+ // Upgrading to target a new SDK level means they need to explicitly
+ // request the new runtime permission; by default it's denied
+ install(APK_BLUETOOTH_NEVER_FOR_LOCATION_31);
+ assertScanBluetoothResult(Result.EXCEPTION);
+
+ // If the user does grant it, they can scan again
+ grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT);
+ grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN);
+ assertScanBluetoothResult(Result.FILTERED);
+ }
+
+ /**
+ * Verify that a legacy app that was able to interact with Bluetooth devices
+ * is still able to interact with them after updating to a modern SDK.
+ */
+ @Test
+ public void testRequestBluetoothPermission_GrantLocation_Upgrade() throws Throwable {
+ assumeTrue(supportsBluetoothLe());
+
+ install(APK_BLUETOOTH_30);
+ grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION);
+ grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION);
+ assertScanBluetoothResult(Result.FULL);
+
+ // Upgrading to target a new SDK level means they still have the access
+ // they enjoyed as a legacy app
+ install(APK_BLUETOOTH_31);
+ assertScanBluetoothResult(Result.FULL);
+ }
+
+ /**
+ * Verify that downgrading an app doesn't gain them any access to Bluetooth
+ * scan results; they'd always need to involve the user to gain permissions.
+ */
+ @Test
+ public void testRequestBluetoothPermission_Downgrade() throws Throwable {
+ assumeTrue(supportsBluetoothLe());
+
+ install(APK_BLUETOOTH_31);
+ grantPermission(TEST_APP_PKG, BLUETOOTH_CONNECT);
+ grantPermission(TEST_APP_PKG, BLUETOOTH_SCAN);
+ grantPermission(TEST_APP_PKG, ACCESS_FINE_LOCATION);
+ grantPermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION);
+ assertScanBluetoothResult(Result.FULL);
+
+ // Revoking nearby permission means modern app can't scan
+ revokePermission(TEST_APP_PKG, BLUETOOTH_CONNECT);
+ revokePermission(TEST_APP_PKG, BLUETOOTH_SCAN);
+ assertScanBluetoothResult(Result.EXCEPTION);
+
+ // And if they attempt to downgrade, confirm that they can't obtain the
+ // split-permission grant from the older non-runtime permissions
+ install(APK_BLUETOOTH_30);
+ assertScanBluetoothResult(Result.EXCEPTION);
+ }
+
+ private void assertScanBluetoothResult(Result expected) {
+ assertScanBluetoothResult(null, expected);
+ }
+
+ private void assertScanBluetoothResult(String arg, Result expected) {
+ SystemClock.sleep(1000); // Wait for location permissions to propagate
+ final ContentResolver resolver = InstrumentationRegistry.getTargetContext()
+ .getContentResolver();
+ final Bundle res = resolver.call(TEST_APP_AUTHORITY, "", arg, null);
+ Result actual = Result.values()[res.getInt(Intent.EXTRA_INDEX)];
+ assertEquals(expected, actual);
+ }
+
+ private boolean supportsBluetooth() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
+ }
+
+ private boolean supportsBluetoothLe() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
+ }
+
+ private void enableTestMode() {
+ runShellCommandOrThrow("dumpsys activity service"
+ + " com.android.bluetooth.btservice.AdapterService set-test-mode enabled");
+ }
+
+ private void disableTestMode() {
+ runShellCommandOrThrow("dumpsys activity service"
+ + " com.android.bluetooth.btservice.AdapterService set-test-mode disabled");
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NearbyDevicesRenouncePermissionTest.java b/tests/cts/permission/src/android/permission/cts/NearbyDevicesRenouncePermissionTest.java
new file mode 100644
index 000000000..15cd15dfd
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NearbyDevicesRenouncePermissionTest.java
@@ -0,0 +1,301 @@
+/*
+ * 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 android.permission.cts;
+
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.app.AppOpsManager.OPSTR_FINE_LOCATION;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.AppOpsManager;
+import android.app.AsyncNotedAppOp;
+import android.app.SyncNotedAppOp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.cts.BTAdapterUtils;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanResult;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.content.ContextParams;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.util.ArraySet;
+import android.util.Base64;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests behaviour when performing bluetooth scans with renounced location permission.
+ */
+public class NearbyDevicesRenouncePermissionTest {
+
+ private static final String TAG = "NearbyDevicesRenouncePermissionTest";
+ private static final String OPSTR_BLUETOOTH_SCAN = "android:bluetooth_scan";
+
+ private AppOpsManager mAppOpsManager;
+ private int mLocationNoteCount;
+ private int mScanNoteCount;
+ private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private BluetoothAdapter mBluetoothAdapter;
+ private boolean mBluetoothAdapterWasEnabled;
+
+ private enum Result {
+ UNKNOWN, EXCEPTION, EMPTY, FILTERED, FULL
+ }
+
+ private enum Scenario {
+ DEFAULT, RENOUNCE, RENOUNCE_MIDDLE, RENOUNCE_END
+ }
+
+ @Before
+ public void enableBluetooth() {
+ assumeTrue(supportsBluetooth());
+ mBluetoothAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter();
+ mBluetoothAdapterWasEnabled = mBluetoothAdapter.isEnabled();
+ assertTrue(BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext));
+ enableTestMode();
+ }
+
+ @After
+ public void disableBluetooth() {
+ assumeTrue(supportsBluetooth());
+ disableTestMode();
+ if (!mBluetoothAdapterWasEnabled) {
+ assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
+ }
+ }
+
+ @Before
+ public void setUp() {
+ mAppOpsManager = getApplicationContext().getSystemService(AppOpsManager.class);
+ mAppOpsManager.setOnOpNotedCallback(getApplicationContext().getMainExecutor(),
+ new AppOpsManager.OnOpNotedCallback() {
+ @Override
+ public void onNoted(SyncNotedAppOp op) {
+ switch (op.getOp()) {
+ case OPSTR_FINE_LOCATION:
+ mLocationNoteCount++;
+ break;
+ case OPSTR_BLUETOOTH_SCAN:
+ mScanNoteCount++;
+ break;
+ default:
+ }
+ }
+
+ @Override
+ public void onSelfNoted(SyncNotedAppOp op) {
+ }
+
+ @Override
+ public void onAsyncNoted(AsyncNotedAppOp asyncOp) {
+ switch (asyncOp.getOp()) {
+ case OPSTR_FINE_LOCATION:
+ mLocationNoteCount++;
+ break;
+ case OPSTR_BLUETOOTH_SCAN:
+ mScanNoteCount++;
+ break;
+ default:
+ }
+ }
+ });
+ }
+
+ @After
+ public void tearDown() {
+ mAppOpsManager.setOnOpNotedCallback(null, null);
+ }
+
+ private void clearNoteCounts() {
+ mLocationNoteCount = 0;
+ mScanNoteCount = 0;
+ }
+
+ @AppModeFull
+ @Test
+ public void scanWithoutRenouncingNotesBluetoothAndLocation() throws Exception {
+ assumeTrue(supportsBluetoothLe());
+
+ clearNoteCounts();
+ assertThat(performScan(Scenario.DEFAULT)).isEqualTo(Result.FULL);
+ SystemUtil.eventually(() -> {
+ assertThat(mLocationNoteCount).isGreaterThan(0);
+ assertThat(mScanNoteCount).isGreaterThan(0);
+ });
+ }
+
+ @AppModeFull
+ @Test
+ public void scanRenouncingLocationNotesBluetoothButNotLocation() throws Exception {
+ assumeTrue(supportsBluetoothLe());
+
+ clearNoteCounts();
+ assertThat(performScan(Scenario.RENOUNCE)).isEqualTo(Result.FILTERED);
+ SystemUtil.eventually(() -> {
+ assertThat(mLocationNoteCount).isEqualTo(0);
+ assertThat(mScanNoteCount).isGreaterThan(0);
+ });
+ }
+
+ @AppModeFull
+ @Test
+ public void scanRenouncingInMiddleOfChainNotesBluetoothButNotLocation() throws Exception {
+ assumeTrue(supportsBluetoothLe());
+
+ clearNoteCounts();
+ assertThat(performScan(Scenario.RENOUNCE_MIDDLE)).isEqualTo(Result.FILTERED);
+ SystemUtil.eventually(() -> {
+ assertThat(mLocationNoteCount).isEqualTo(0);
+ assertThat(mScanNoteCount).isGreaterThan(0);
+ });
+ }
+
+ @AppModeFull
+ @Test
+ public void scanRenouncingAtEndOfChainNotesBluetoothButNotLocation() throws Exception {
+ clearNoteCounts();
+ assertThat(performScan(Scenario.RENOUNCE_END)).isEqualTo(Result.FILTERED);
+ SystemUtil.eventually(() -> {
+ assertThat(mLocationNoteCount).isEqualTo(0);
+ assertThat(mScanNoteCount).isGreaterThan(0);
+ });
+ }
+
+ private Result performScan(Scenario scenario) {
+ try {
+ Context context = createContext(scenario, getApplicationContext());
+
+ final BluetoothManager bm = context.getSystemService(BluetoothManager.class);
+ final BluetoothLeScanner scanner = bm.getAdapter().getBluetoothLeScanner();
+
+ final HashSet<String> observed = new HashSet<>();
+
+ ScanCallback callback = new ScanCallback() {
+ public void onScanResult(int callbackType, ScanResult result) {
+ Log.v(TAG, String.valueOf(result));
+ observed.add(Base64.encodeToString(result.getScanRecord().getBytes(), 0));
+ }
+
+ public void onBatchScanResults(List<ScanResult> results) {
+ for (ScanResult result : results) {
+ onScanResult(0, result);
+ }
+ }
+ };
+ scanner.startScan(callback);
+
+ // Wait a few seconds to figure out what we actually observed
+ SystemClock.sleep(3000);
+ scanner.stopScan(callback);
+ switch (observed.size()) {
+ case 0: return Result.EMPTY;
+ case 1: return Result.FILTERED;
+ case 5: return Result.FULL;
+ default: return Result.UNKNOWN;
+ }
+ } catch (Throwable t) {
+ Log.v(TAG, "Failed to scan", t);
+ return Result.EXCEPTION;
+ }
+ }
+
+ private Context createContext(Scenario scenario, Context context) throws Exception {
+ if (scenario == Scenario.DEFAULT) {
+ return context;
+ }
+
+ Set<String> renouncedPermissions = new ArraySet<>();
+ renouncedPermissions.add(ACCESS_FINE_LOCATION);
+
+ switch (scenario) {
+ case RENOUNCE:
+ return SystemUtil.callWithShellPermissionIdentity(() ->
+ context.createContext(
+ new ContextParams.Builder()
+ .setRenouncedPermissions(renouncedPermissions)
+ .setAttributionTag(context.getAttributionTag())
+ .build())
+ );
+ case RENOUNCE_MIDDLE:
+ AttributionSource nextAttrib = new AttributionSource(
+ Process.SHELL_UID, "com.android.shell", null, (Set<String>) null, null);
+ return SystemUtil.callWithShellPermissionIdentity(() ->
+ context.createContext(
+ new ContextParams.Builder()
+ .setRenouncedPermissions(renouncedPermissions)
+ .setAttributionTag(context.getAttributionTag())
+ .setNextAttributionSource(nextAttrib)
+ .build())
+ );
+ case RENOUNCE_END:
+ nextAttrib = new AttributionSource(
+ Process.SHELL_UID, "com.android.shell", null, renouncedPermissions, null);
+ return SystemUtil.callWithShellPermissionIdentity(() ->
+ context.createContext(
+ new ContextParams.Builder()
+ .setAttributionTag(context.getAttributionTag())
+ .setNextAttributionSource(nextAttrib)
+ .build())
+ );
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+
+ private boolean supportsBluetooth() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
+ }
+
+ private boolean supportsBluetoothLe() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
+ }
+
+ private void enableTestMode() {
+ runShellCommandOrThrow("dumpsys activity service"
+ + " com.android.bluetooth.btservice.AdapterService set-test-mode enabled");
+ }
+
+ private void disableTestMode() {
+ runShellCommandOrThrow("dumpsys activity service"
+ + " com.android.bluetooth.btservice.AdapterService set-test-mode disabled");
+ }
+
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NfcPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NfcPermissionTest.java
new file mode 100644
index 000000000..1b3f65ee6
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NfcPermissionTest.java
@@ -0,0 +1,221 @@
+/*
+ * 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 android.permission.cts;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.ActivityManager;
+import android.content.pm.PackageManager;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcAdapter.ControllerAlwaysOnListener;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.Executor;
+
+@RunWith(JUnit4.class)
+public final class NfcPermissionTest {
+
+ private NfcAdapter mNfcAdapter;
+ private static final String PKG_NAME = "com.android.packagename";
+
+ private boolean supportsHardware() {
+ final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+ return pm.hasSystemFeature(PackageManager.FEATURE_NFC);
+ }
+
+ @Before
+ public void setUp() {
+ assumeTrue(supportsHardware());
+ mNfcAdapter = NfcAdapter.getDefaultAdapter(InstrumentationRegistry.getTargetContext());
+ }
+
+ /**
+ * Verifies that isControllerAlwaysOnSupported() requires Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission#NFC_SET_CONTROLLER_ALWAYS_ON}.
+ */
+ @Test
+ @AppModeFull
+ public void testIsControllerAlwaysOnSupported() {
+ try {
+ mNfcAdapter.isControllerAlwaysOnSupported();
+ fail("mNfcAdapter.isControllerAlwaysOnSupported() did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that isControllerAlwaysOn() requires Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission#NFC_SET_CONTROLLER_ALWAYS_ON}.
+ */
+ @Test
+ @AppModeFull
+ public void testIsControllerAlwaysOn() {
+ try {
+ mNfcAdapter.isControllerAlwaysOn();
+ fail("mNfcAdapter.isControllerAlwaysOn() did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that setControllerAlwaysOn(true) requires Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission#NFC_SET_CONTROLLER_ALWAYS_ON}.
+ */
+ @Test
+ @AppModeFull
+ public void testSetControllerAlwaysOnTrue() {
+ try {
+ mNfcAdapter.setControllerAlwaysOn(true);
+ fail("mNfcAdapter.setControllerAlwaysOn(true) did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that setControllerAlwaysOn(false) requires Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission#NFC_SET_CONTROLLER_ALWAYS_ON}.
+ */
+ @Test
+ @AppModeFull
+ public void testSetControllerAlwaysOnFalse() {
+ try {
+ mNfcAdapter.setControllerAlwaysOn(false);
+ fail("mNfcAdapter.setControllerAlwaysOn(true) did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that registerControllerAlwaysOnListener() requires Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission#NFC_SET_CONTROLLER_ALWAYS_ON}.
+ */
+ @Test
+ @AppModeFull
+ public void testRegisterControllerAlwaysOnListener() {
+ try {
+ mNfcAdapter.registerControllerAlwaysOnListener(
+ new SynchronousExecutor(), new AlwaysOnStateListener());
+ fail("mNfcAdapter.registerControllerAlwaysOnListener did not throw"
+ + "SecurityException as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that unregisterControllerAlwaysOnListener() requires Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission#NFC_SET_CONTROLLER_ALWAYS_ON}.
+ */
+ @Test
+ @AppModeFull
+ public void testUnregisterControllerAlwaysOnListener() {
+ try {
+ mNfcAdapter.unregisterControllerAlwaysOnListener(new AlwaysOnStateListener());
+ fail("mNfcAdapter.unregisterControllerAlwaysOnListener did not throw"
+ + "SecurityException as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that isTagIntentAppPreferenceSupported() requires Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission.WRITE_SECURE_SETTINGS}.
+ */
+ @Test
+ @AppModeFull
+ public void testIsTagIntentAppPreferenceSupported() {
+ try {
+ mNfcAdapter.isTagIntentAppPreferenceSupported();
+ fail("mNfcAdapter.isTagIntentAppPreferenceSupported() did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that getTagIntentAppPreferenceForUser() requires Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission.WRITE_SECURE_SETTINGS}.
+ */
+ @Test
+ @AppModeFull
+ public void testGetTagIntentAppPreferenceForUser() {
+ try {
+ mNfcAdapter.getTagIntentAppPreferenceForUser(ActivityManager.getCurrentUser());
+ fail("mNfcAdapter.getTagIntentAppPreferenceForUser() did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ /**
+ * Verifies that setTagIntentAppPreferenceForUser() requires Permission.
+ * <p>
+ * Requires Permission: {@link android.Manifest.permission.WRITE_SECURE_SETTINGS}.
+ */
+ @Test
+ @AppModeFull
+ public void testSetTagIntentAppPreferenceForUser() {
+ try {
+ mNfcAdapter.setTagIntentAppPreferenceForUser(ActivityManager.getCurrentUser(),
+ PKG_NAME, true);
+ fail("mNfcAdapter.setTagIntentAppPreferenceForUser() did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException se) {
+ // Expected Exception
+ }
+ }
+
+ private class SynchronousExecutor implements Executor {
+ public void execute(Runnable r) {
+ r.run();
+ }
+ }
+
+ private class AlwaysOnStateListener implements ControllerAlwaysOnListener {
+ @Override
+ public void onControllerAlwaysOnChanged(boolean isEnabled) {
+ // Do nothing
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java
new file mode 100644
index 000000000..835ba124c
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import java.util.List;
+
+/**
+ * Verify the Activity related operations require specific permissions.
+ */
+public class NoActivityRelatedPermissionTest
+ extends ActivityInstrumentationTestCase2<PermissionStubActivity> {
+
+ private PermissionStubActivity mActivity;
+
+ public NoActivityRelatedPermissionTest() {
+ super("android.permission.cts", PermissionStubActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mActivity = getActivity();
+ }
+
+ /**
+ * Verify that get task requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#GET_TASKS}
+ */
+ @MediumTest
+ public void testGetTask() {
+ ActivityManager manager = (ActivityManager) getActivity()
+ .getSystemService(Context.ACTIVITY_SERVICE);
+ List<ActivityManager.RunningTaskInfo> runningTasks = manager.getRunningTasks(10);
+ // Current implementation should only return tasks for home and the caller. Since there can
+ // be multiple home tasks, we remove them from the list and then check that there is one or
+ // less task left in the list.
+ removeHomeRunningTasks(runningTasks);
+ assertTrue("Found tasks: " + runningTasks,
+ runningTasks == null || runningTasks.size() <= 1);
+
+ List<ActivityManager.RecentTaskInfo> recentTasks = manager.getRecentTasks(10,
+ ActivityManager.RECENT_WITH_EXCLUDED);
+ // Current implementation should only return tasks for home and the caller. Since there can
+ // be multiple home tasks, we remove them from the list and then check that there is one or
+ // less task left in the list.
+ removeHomeRecentsTasks(recentTasks);
+ assertTrue("Found tasks: " + recentTasks, recentTasks == null || recentTasks.size() <= 1);
+ }
+
+ private void removeHomeRecentsTasks(List<ActivityManager.RecentTaskInfo> tasks) {
+ for (int i = tasks.size() -1; i >= 0; i--) {
+ ActivityManager.RecentTaskInfo task = tasks.get(i);
+ if (task.baseIntent != null && isHomeIntent(task.baseIntent)) {
+ tasks.remove(i);
+ }
+ }
+ }
+
+ private void removeHomeRunningTasks(List<ActivityManager.RunningTaskInfo> tasks) {
+ for (int i = tasks.size() -1; i >= 0; i--) {
+ ActivityManager.RunningTaskInfo task = tasks.get(i);
+ if (task.baseIntent != null && isHomeIntent(task.baseIntent)) {
+ tasks.remove(i);
+ }
+ }
+ }
+
+ private boolean isHomeIntent(Intent intent) {
+ return Intent.ACTION_MAIN.equals(intent.getAction())
+ && (intent.hasCategory(Intent.CATEGORY_HOME)
+ || intent.hasCategory(Intent.CATEGORY_SECONDARY_HOME))
+ && intent.getCategories().size() == 1
+ && intent.getData() == null
+ && intent.getType() == null;
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NoAudioPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoAudioPermissionTest.java
new file mode 100644
index 000000000..c2c42a10d
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoAudioPermissionTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+/**
+ * Verify the audio related operations require specific permissions.
+ */
+public class NoAudioPermissionTest extends AndroidTestCase {
+ private static final String TAG = NoAudioPermissionTest.class.getSimpleName();
+ private AudioManager mAudioManager;
+ private static final int MODE_COUNT = 3;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ assertNotNull(mAudioManager);
+ }
+
+ private boolean hasMicrophone() {
+ return getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_MICROPHONE);
+ }
+
+ /**
+ * Verify that AudioManager.setMicrophoneMute, AudioManager.setMode requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
+ */
+ @SmallTest
+ public void testSetMicrophoneMute() {
+ boolean muteState = mAudioManager.isMicrophoneMute();
+ int originalMode = mAudioManager.getMode();
+ // If there is no permission of MODIFY_AUDIO_SETTINGS, setMicrophoneMute does nothing.
+ if (muteState) {
+ Log.w(TAG, "Mic seems muted by hardware! Please unmute and rerrun the test.");
+ } else {
+ mAudioManager.setMicrophoneMute(!muteState);
+ assertEquals(muteState, mAudioManager.isMicrophoneMute());
+ }
+
+ // If there is no permission of MODIFY_AUDIO_SETTINGS, setMode does nothing.
+ assertTrue(AudioManager.MODE_NORMAL != AudioManager.MODE_RINGTONE);
+
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
+ assertEquals(originalMode, mAudioManager.getMode());
+
+ mAudioManager.setMode(AudioManager.MODE_RINGTONE);
+ assertEquals(originalMode, mAudioManager.getMode());
+ }
+
+ /**
+ * Verify that AudioManager routing methods require permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}.
+ */
+ @SuppressWarnings("deprecation")
+ @SmallTest
+ public void testRouting() {
+
+ // If there is no permission of MODIFY_AUDIO_SETTINGS, setSpeakerphoneOn does nothing.
+ boolean prevState = mAudioManager.isSpeakerphoneOn();
+ mAudioManager.setSpeakerphoneOn(!prevState);
+ assertEquals(prevState, mAudioManager.isSpeakerphoneOn());
+
+ // If there is no permission of MODIFY_AUDIO_SETTINGS, setBluetoothScoOn does nothing.
+ prevState = mAudioManager.isBluetoothScoOn();
+ mAudioManager.setBluetoothScoOn(!prevState);
+ assertEquals(prevState, mAudioManager.isBluetoothScoOn());
+ }
+
+ /**
+ * Verify that {@link android.media.AudioRecord.Builder#build} and
+ * {@link android.media.AudioRecord#AudioRecord} require permission
+ * {@link android.Manifest.permission#RECORD_AUDIO}.
+ */
+ @SmallTest
+ public void testRecordPermission() {
+ if (!hasMicrophone()) return;
+
+ // test builder
+ assertThrows(java.lang.UnsupportedOperationException.class, () -> {
+ final AudioRecord record = new AudioRecord.Builder().build();
+ record.release();
+ });
+
+ // test constructor
+ final int sampleRate = 8000;
+ final int halfSecondInBytes = sampleRate;
+ AudioRecord record = new AudioRecord(
+ MediaRecorder.AudioSource.DEFAULT, sampleRate, AudioFormat.CHANNEL_IN_MONO,
+ AudioFormat.ENCODING_PCM_16BIT, halfSecondInBytes);
+ final int state = record.getState();
+ record.release();
+ assertEquals(AudioRecord.STATE_UNINITIALIZED, state);
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NoBroadcastPackageRemovedPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoBroadcastPackageRemovedPermissionTest.java
new file mode 100644
index 000000000..5630c5b8c
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoBroadcastPackageRemovedPermissionTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Verify Context related methods without specific BROADCAST series permissions.
+ */
+public class NoBroadcastPackageRemovedPermissionTest extends AndroidTestCase {
+ private static final String TEST_RECEIVER_PERMISSION = "receiverPermission";
+
+ /**
+ * Verify that Context#sendStickyBroadcast(Intent),
+ * Context#removeStickyBroadcast(Intent)
+ * requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#BROADCAST_STICKY }.
+ */
+ @SmallTest
+ public void testSendOrRemoveStickyBroadcast() {
+ try {
+ mContext.sendStickyBroadcast(createIntent(Intent.ACTION_WALLPAPER_CHANGED));
+ fail("Context.sendStickyBroadcast did not throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ try {
+ mContext.removeStickyBroadcast(createIntent(Intent.ACTION_WALLPAPER_CHANGED));
+ fail("Context.removeStickyBroadcast did not throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that Context#sendBroadcast(Intent),
+ * Context#sendBroadcast(Intent, String)
+ * Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver,
+ * Handler, int, String, Bundle)
+ * Context#sendOrderedBroadcast(Intent, String) with ACTION_UID_REMOVED
+ * with ACTION_PACKAGE_REMOVED requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#BROADCAST_PACKAGE_REMOVED}.
+ */
+ @SmallTest
+ public void testSendBroadcast() {
+ try {
+ mContext.sendBroadcast(createIntent(Intent.ACTION_PACKAGE_REMOVED));
+ fail("Context.sendBroadcast did not throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ try {
+ mContext.sendBroadcast(createIntent(Intent.ACTION_PACKAGE_REMOVED),
+ TEST_RECEIVER_PERMISSION);
+ fail("Context.sendBroadcast did not throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ try {
+ mContext.sendOrderedBroadcast(createIntent(Intent.ACTION_PACKAGE_REMOVED),
+ TEST_RECEIVER_PERMISSION, null, null, 0, "initialData", Bundle.EMPTY);
+ fail("Context.sendOrderedBroadcast did not throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ try {
+ mContext.sendOrderedBroadcast(createIntent(Intent.ACTION_PACKAGE_REMOVED),
+ TEST_RECEIVER_PERMISSION);
+ fail("Context.sendOrderedBroadcast did not throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ private Intent createIntent(String action) {
+ Intent intent = new Intent();
+ intent.setAction(action);
+ return intent;
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NoCaptureVideoPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoCaptureVideoPermissionTest.java
new file mode 100644
index 000000000..6ad048308
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoCaptureVideoPermissionTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2013 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.permission.cts;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.DisplayMetrics;
+
+/**
+ * Verify the capture system video output permission requirements.
+ */
+public class NoCaptureVideoPermissionTest extends AndroidTestCase {
+ private static final String NAME = "VirtualDisplayTest";
+ private static final int WIDTH = 720;
+ private static final int HEIGHT = 480;
+ private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM;
+
+ /**
+ * Verify that DisplayManager.createVirtualDisplay() requires permissions to
+ * create public displays.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CAPTURE_VIDEO_OUTPUT} or
+ * {@link android.Manifest.permission#CAPTURE_SECURE_VIDEO_OUTPUT}.
+ */
+ @SmallTest
+ public void testCreatePublicVirtualDisplay() {
+ DisplayManager displayManager =
+ (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
+ ImageReader reader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBX_8888, 1);
+ try {
+ displayManager.createVirtualDisplay(NAME, WIDTH, HEIGHT, DENSITY,
+ reader.getSurface(), DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC);
+ fail("DisplayManager.createVirtualDisplay() didn't throw SecurityException "
+ + "as expected when creating public virtual display.");
+ } catch (SecurityException e) {
+ // expected
+ } finally {
+ reader.close();
+ }
+ }
+
+ /**
+ * Verify that DisplayManager.createVirtualDisplay() requires permissions to
+ * create secure displays.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CAPTURE_SECURE_VIDEO_OUTPUT}.
+ */
+ @SmallTest
+ public void testCreateSecureVirtualDisplay() {
+ DisplayManager displayManager =
+ (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
+ ImageReader reader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBX_8888, 1);
+ try {
+ displayManager.createVirtualDisplay(NAME, WIDTH, HEIGHT, DENSITY,
+ reader.getSurface(), DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE);
+ fail("DisplayManager.createVirtualDisplay() didn't throw SecurityException "
+ + "as expected when creating secure virtual display.");
+ } catch (SecurityException e) {
+ // expected
+ } finally {
+ reader.close();
+ }
+ }
+
+ /**
+ * Verify that DisplayManager.createVirtualDisplay() does not requires permissions to
+ * create private displays.
+ */
+ @SmallTest
+ public void testCreatePrivateVirtualDisplay() {
+ DisplayManager displayManager =
+ (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
+ ImageReader reader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBX_8888, 1);
+ try {
+ VirtualDisplay display = displayManager.createVirtualDisplay(
+ NAME, WIDTH, HEIGHT, DENSITY,
+ reader.getSurface(), 0);
+ display.release();
+ } catch (SecurityException e) {
+ fail("DisplayManager.createVirtualDisplay() should not throw SecurityException "
+ + "when creating private virtual display.");
+ } finally {
+ reader.close();
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NoKeyPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoKeyPermissionTest.java
new file mode 100644
index 000000000..5a0b25993
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoKeyPermissionTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Verify the key input related operations require specific permissions.
+ */
+public class NoKeyPermissionTest extends AndroidTestCase {
+ KeyguardManager mKeyManager;
+ KeyguardManager.KeyguardLock mKeyLock;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mKeyManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ if (mKeyManager != null) {
+ mKeyLock = mKeyManager.newKeyguardLock("testTag");
+ }
+ }
+
+ /**
+ * Verify that KeyguardManager.KeyguardLock.disableKeyguard requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#DISABLE_KEYGUARD}.
+ */
+ @SmallTest
+ public void testDisableKeyguard() {
+ // KeyguardManager was not accessible, pass.
+ if (mKeyManager == null) {
+ return;
+ }
+ try {
+ mKeyLock.disableKeyguard();
+ fail("KeyguardManager.KeyguardLock.disableKeyguard did not throw SecurityException as"
+ + " expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that KeyguardManager.KeyguardLock.reenableKeyguard requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#DISABLE_KEYGUARD}.
+ */
+ @SmallTest
+ public void testReenableKeyguard() {
+ // KeyguardManager was not accessible, pass.
+ if (mKeyManager == null) {
+ return;
+ }
+ try {
+ mKeyLock.reenableKeyguard();
+ fail("KeyguardManager.KeyguardLock.reenableKeyguard did not throw SecurityException as"
+ + " expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that KeyguardManager.exitKeyguardSecurely requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#DISABLE_KEYGUARD}.
+ */
+ @SmallTest
+ public void testExitKeyguardSecurely() {
+ // KeyguardManager was not accessible, pass.
+ if (mKeyManager == null) {
+ return;
+ }
+ try {
+ mKeyManager.exitKeyguardSecurely(null);
+ fail("KeyguardManager.exitKeyguardSecurely did not throw SecurityException as"
+ + " expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NoNetworkStatePermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoNetworkStatePermissionTest.java
new file mode 100644
index 000000000..b8d2ee21a
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoNetworkStatePermissionTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.net.InetAddress;
+
+/**
+ * Verify ConnectivityManager related methods without specific network state permissions.
+ */
+public class NoNetworkStatePermissionTest extends AndroidTestCase {
+ private ConnectivityManager mConnectivityManager;
+ private static final int TEST_NETWORK_TYPE = ConnectivityManager.TYPE_MOBILE;
+ private static final String TEST_FEATURE = "enableHIPRI";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ assertNotNull(mConnectivityManager);
+ }
+
+ /**
+ * Verify that ConnectivityManager#getActiveNetworkInfo() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
+ @SmallTest
+ public void testGetActiveNetworkInfo() {
+ try {
+ mConnectivityManager.getActiveNetworkInfo();
+ fail("ConnectivityManager.getActiveNetworkInfo didn't throw SecurityException as"
+ + " expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that ConnectivityManager#getNetworkInfo() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
+ @SmallTest
+ public void testGetNetworkInfo() {
+ try {
+ mConnectivityManager.getNetworkInfo(TEST_NETWORK_TYPE);
+ fail("ConnectivityManager.getNetworkInfo didn't throw SecurityException as"
+ + " expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that ConnectivityManager#getAllNetworkInfo() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
+ @SmallTest
+ public void testGetAllNetworkInfo() {
+ try {
+ mConnectivityManager.getAllNetworkInfo();
+ fail("ConnectivityManager.getAllNetworkInfo didn't throw SecurityException as"
+ + " expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @SmallTest
+ public void testSecurityExceptionFromDns() throws Exception {
+ try {
+ InetAddress.getByName("www.google.com");
+ fail();
+ } catch (SecurityException expected) {
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NoReadLogsPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
new file mode 100644
index 000000000..b6fb84dc7
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructStat;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * Verify the read system log require specific permissions.
+ */
+public class NoReadLogsPermissionTest extends AndroidTestCase {
+ /**
+ * Verify that we'll only get our logs without the READ_LOGS permission.
+ *
+ * We test this by examining the logs looking for ActivityManager lines.
+ * Since ActivityManager runs as a different UID, we shouldn't see
+ * any of those log entries.
+ *
+ * @throws IOException
+ */
+ @MediumTest
+ public void testLogcat() throws IOException {
+ Process logcatProc = null;
+ BufferedReader reader = null;
+ try {
+ logcatProc = Runtime.getRuntime().exec(new String[]
+ {"logcat", "-v", "brief", "-d", "ActivityManager:* *:S" });
+
+ reader = new BufferedReader(new InputStreamReader(logcatProc.getInputStream()));
+
+ int lineCt = 0;
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (!line.startsWith("--------- beginning of ")) {
+ lineCt++;
+ }
+ }
+
+ // no permission get an empty log buffer.
+ // Logcat returns only one line:
+ // "--------- beginning of <log device>"
+
+ assertEquals("Unexpected logcat entries. Are you running the "
+ + "the latest logger.c from the Android kernel?",
+ 0, lineCt);
+
+ } finally {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+ }
+
+ public void testEventsLogSane() throws ErrnoException {
+ testLogIsSane("/dev/log/events");
+ }
+
+ public void testMainLogSane() throws ErrnoException {
+ testLogIsSane("/dev/log/main");
+ }
+
+ public void testRadioLogSane() throws ErrnoException {
+ testLogIsSane("/dev/log/radio");
+ }
+
+ public void testSystemLogSane() throws ErrnoException {
+ testLogIsSane("/dev/log/system");
+ }
+
+ private static void testLogIsSane(String log) throws ErrnoException {
+ try {
+ StructStat stat = Os.stat(log);
+ assertEquals("not owned by uid=0", 0, stat.st_uid);
+ assertEquals("not owned by gid=logs", "log", FileUtils.getGroupName(stat.st_gid));
+ } catch (ErrnoException e) {
+ if (e.errno != OsConstants.ENOENT && e.errno != OsConstants.EACCES) {
+ throw e;
+ }
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NoRollbackPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoRollbackPermissionTest.java
new file mode 100644
index 000000000..50b84fa70
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoRollbackPermissionTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 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.permission.cts;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.pm.PackageInstaller;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+
+@AppModeFull(reason = "PackageInstaller cannot be accessed by instant apps")
+public class NoRollbackPermissionTest {
+ @Test
+ public void testCreateInstallSessionWithReasonRollbackFails() throws Exception {
+ // The INSTALL_REASON_ROLLBACK allows an APK to be rolled back to a previous signing key
+ // without setting the ROLLBACK capability in the lineage. Since only signature|privileged
+ // apps can hold the necessary permission to initiate a rollback ensure apps without this
+ // permission cannot set rollback as the install reason.
+ PackageInstaller packageInstaller =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager()
+ .getPackageInstaller();
+ PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ parentParams.setRequestDowngrade(true);
+ parentParams.setMultiPackage();
+ // The constant PackageManager.INSTALL_REASON_ROLLBACK is hidden from apps, but an app can
+ // still use its constant value.
+ parentParams.setInstallReason(5);
+ assertThrows(SecurityException.class, () -> packageInstaller.createSession(parentParams));
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java
new file mode 100644
index 000000000..0ccebed04
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.os.Vibrator;
+import android.platform.test.annotations.AppModeFull;
+import android.telephony.gsm.SmsManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.TimeZone;
+
+/**
+ * Verify the system function require specific permissions.
+ */
+@SuppressWarnings("deprecation")
+public class NoSystemFunctionPermissionTest extends AndroidTestCase {
+
+ /**
+ * Verify that ActivityManager.restartPackage() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#RESTART_PACKAGES}.
+ */
+ @SmallTest
+ public void testRestartPackage() {
+ ActivityManager activityManager = (ActivityManager) mContext.getSystemService(
+ Context.ACTIVITY_SERVICE);
+
+ try {
+ activityManager.restartPackage("packageName");
+ fail("ActivityManager.restartPackage() didn't throw SecurityException as expected.");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that AlarmManager.setTimeZone() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#SET_TIME_ZONE}.
+ */
+ @SmallTest
+ public void testSetTimeZone() {
+ AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
+ Context.ALARM_SERVICE);
+ String[] timeZones = TimeZone.getAvailableIDs();
+ String timeZone = timeZones[0];
+
+ try {
+ alarmManager.setTimeZone(timeZone);
+ fail("AlarmManager.setTimeZone() did not throw SecurityException as expected.");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that setting wallpaper relate methods require permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#SET_WALLPAPER}.
+ * @throws IOException
+ */
+ @AppModeFull(reason = "Instant apps cannot access the WallpaperManager")
+ @SmallTest
+ public void testSetWallpaper() throws IOException {
+ if (!WallpaperManager.getInstance(mContext).isWallpaperSupported()) {
+ return;
+ }
+
+ Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
+
+ try {
+ mContext.setWallpaper(bitmap);
+ fail("Context.setWallpaper(BitMap) did not throw SecurityException as expected.");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ try {
+ mContext.setWallpaper((InputStream) null);
+ fail("Context.setWallpaper(InputStream) did not throw SecurityException as expected.");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ try {
+ mContext.clearWallpaper();
+ fail("Context.clearWallpaper() did not throw SecurityException as expected.");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that Vibrator's vibrating related methods requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#VIBRATE}.
+ */
+ @SmallTest
+ public void testVibrator() {
+ Vibrator vibrator = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE);
+
+ try {
+ vibrator.cancel();
+ fail("Vibrator.cancel() did not throw SecurityException as expected.");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ try {
+ vibrator.vibrate(1);
+ fail("Vibrator.vibrate(long) did not throw SecurityException as expected.");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ long[] testPattern = {1, 1, 1, 1, 1};
+
+ try {
+ vibrator.vibrate(testPattern, 1);
+ fail("Vibrator.vibrate(long[], int) not throw SecurityException as expected.");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that sending sms requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#SMS}.
+ */
+ @SmallTest
+ public void testSendSms() {
+ if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ SmsManager smsManager = SmsManager.getDefault();
+ byte[] testData = new byte[10];
+ try {
+ smsManager.sendDataMessage("1233", "1233", (short) 0, testData, null, null);
+ fail("SmsManager.sendDataMessage() did not throw SecurityException as expected.");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NoWakeLockPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoWakeLockPermissionTest.java
new file mode 100644
index 000000000..030f341aa
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoWakeLockPermissionTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import static android.content.pm.PackageManager.FEATURE_WIFI;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.WifiLock;
+import android.os.PowerManager;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Verify the Wake Lock related operations require specific permissions.
+ */
+public class NoWakeLockPermissionTest extends AndroidTestCase {
+ private PowerManager mPowerManager;
+
+ private PowerManager.WakeLock mWakeLock;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "tag");
+ }
+
+ /**
+ * Verify that WifiManager.WifiLock.acquire() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#WAKE_LOCK}.
+ */
+ @AppModeFull(reason = "Instant apps cannot access the WifiManager")
+ @SmallTest
+ public void testWifiLockAcquire() {
+ if (!mContext.getPackageManager().hasSystemFeature(FEATURE_WIFI)) {
+ return;
+ }
+
+ final WifiManager wifiManager = (WifiManager) mContext.getSystemService(
+ Context.WIFI_SERVICE);
+ final WifiLock wifiLock = wifiManager.createWifiLock("WakeLockPermissionTest");
+ try {
+ wifiLock.acquire();
+ fail("WifiManager.WifiLock.acquire() didn't throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that MediaPlayer.start() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#WAKE_LOCK}.
+ */
+ @SmallTest
+ public void testMediaPlayerWakeLock() {
+ final MediaPlayer mediaPlayer = new MediaPlayer();
+ mediaPlayer.setWakeMode(mContext, PowerManager.FULL_WAKE_LOCK);
+ try {
+ mediaPlayer.start();
+ fail("MediaPlayer.setWakeMode() did not throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ mediaPlayer.stop();
+ }
+
+ /**
+ * Verify that PowerManager.WakeLock.acquire() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#WAKE_LOCK}.
+ */
+ @SmallTest
+ public void testPowerManagerWakeLockAcquire() {
+ try {
+ mWakeLock.acquire();
+ fail("WakeLock.acquire() did not throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that PowerManager.WakeLock.acquire(long) requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#WAKE_LOCK}.
+ */
+ @SmallTest
+ public void testPowerManagerWakeLockAcquire2() {
+ // Tset acquire(long)
+ try {
+ mWakeLock.acquire(1);
+ fail("WakeLock.acquire(long) did not throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NoWallpaperPermissionsTest.java b/tests/cts/permission/src/android/permission/cts/NoWallpaperPermissionsTest.java
new file mode 100644
index 000000000..18e4375bc
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoWallpaperPermissionsTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017 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.permission.cts;
+
+import static android.app.WallpaperManager.FLAG_LOCK;
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.function.ThrowingRunnable;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+/**
+ * Verify that Wallpaper-related operations enforce the correct permissions.
+ */
+@AppModeFull(reason = "instant apps cannot access the WallpaperManager")
+public class NoWallpaperPermissionsTest extends AndroidTestCase {
+ private WallpaperManager mWM;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mWM = (WallpaperManager) mContext.getSystemService(Context.WALLPAPER_SERVICE);
+ }
+
+ /**
+ * Verify that the setResource(...) methods enforce the SET_WALLPAPER permission
+ */
+ @SmallTest
+ public void testSetResource() throws IOException {
+ if (wallpaperNotSupported()) {
+ return;
+ }
+
+ try {
+ mWM.setResource(R.drawable.robot);
+ fail("WallpaperManager.setResource(id) did not enforce SET_WALLPAPER");
+ } catch (SecurityException expected) { /* expected */ }
+
+ try {
+ mWM.setResource(R.drawable.robot, FLAG_LOCK);
+ fail("WallpaperManager.setResource(id, which) did not enforce SET_WALLPAPER");
+ } catch (SecurityException expected) { /* expected */ }
+ }
+
+ /**
+ * Verify that the setBitmap(...) methods enforce the SET_WALLPAPER permission
+ */
+ @SmallTest
+ public void testSetBitmap() throws IOException {
+ if (wallpaperNotSupported()) {
+ return;
+ }
+
+ Bitmap b = Bitmap.createBitmap(160, 120, Bitmap.Config.RGB_565);
+
+ try {
+ mWM.setBitmap(b);
+ fail("setBitmap(b) did not enforce SET_WALLPAPER");
+ } catch (SecurityException expected) { /* expected */ }
+
+ try {
+ mWM.setBitmap(b, null, false);
+ fail("setBitmap(b, crop, allowBackup) did not enforce SET_WALLPAPER");
+ } catch (SecurityException expected) { /* expected */ }
+
+ try {
+ mWM.setBitmap(b, null, false, FLAG_SYSTEM);
+ fail("setBitmap(b, crop, allowBackup, which) did not enforce SET_WALLPAPER");
+ } catch (SecurityException expected) { /* expected */ }
+ }
+
+ /**
+ * Verify that the setStream(...) methods enforce the SET_WALLPAPER permission
+ */
+ @SmallTest
+ public void testSetStream() throws IOException {
+ if (wallpaperNotSupported()) {
+ return;
+ }
+
+ ByteArrayInputStream stream = new ByteArrayInputStream(new byte[32]);
+
+ try {
+ mWM.setStream(stream);
+ fail("setStream(stream) did not enforce SET_WALLPAPER");
+ } catch (SecurityException expected) { /* expected */ }
+
+ try {
+ mWM.setStream(stream, null, false);
+ fail("setStream(stream, crop, allowBackup) did not enforce SET_WALLPAPER");
+ } catch (SecurityException expected) { /* expected */ }
+
+ try {
+ mWM.setStream(stream, null, false, FLAG_LOCK);
+ fail("setStream(stream, crop, allowBackup, which) did not enforce SET_WALLPAPER");
+ } catch (SecurityException expected) { /* expected */ }
+ }
+
+ /**
+ * Verify that the clearWallpaper(...) methods enforce the SET_WALLPAPER permission
+ */
+ @SmallTest
+ public void testClearWallpaper() throws IOException {
+ if (wallpaperNotSupported()) {
+ return;
+ }
+
+ try {
+ mWM.clear();
+ fail("clear() did not enforce SET_WALLPAPER");
+ } catch (SecurityException expected) { /* expected */ }
+
+ try {
+ mWM.clear(FLAG_SYSTEM);
+ fail("clear(which) did not enforce SET_WALLPAPER");
+ } catch (SecurityException expected) { /* expected */ }
+ }
+
+ /**
+ * Verify that reading the current wallpaper enforce the READ_WALLPAPER_INTERNAL permission.
+ * The methods concerned are:
+ * getDrawable, peekDrawable, getFastDrawable, peekFastDrawable, getWallpaperFile.
+ *
+ * These methods should throw a SecurityException, even if MANAGE_EXTERNAL_STORAGE is granted.
+ */
+ public void testReadWallpaper() {
+ if (wallpaperNotSupported()) {
+ return;
+ }
+ String message = "with no permissions, getDrawable should throw a SecurityException";
+ assertSecurityException(mWM::getDrawable, message);
+ assertSecurityException(() -> mWM.getDrawable(FLAG_SYSTEM), message);
+ assertSecurityException(() -> mWM.getDrawable(FLAG_LOCK), message);
+
+ message = "with no permissions, peekDrawable should throw a SecurityException";
+ assertSecurityException(mWM::peekDrawable, message);
+ assertSecurityException(() -> mWM.peekDrawable(FLAG_SYSTEM), message);
+ assertSecurityException(() -> mWM.peekDrawable(FLAG_LOCK), message);
+
+ message = "with no permissions, getFastDrawable should throw a SecurityException";
+ assertSecurityException(mWM::getFastDrawable, message);
+ assertSecurityException(() -> mWM.getFastDrawable(FLAG_SYSTEM), message);
+ assertSecurityException(() -> mWM.getFastDrawable(FLAG_LOCK), message);
+
+ message = "with no permissions, peekFastDrawable should throw a SecurityException";
+ assertSecurityException(mWM::peekFastDrawable, message);
+ assertSecurityException(() -> mWM.peekFastDrawable(FLAG_SYSTEM), message);
+ assertSecurityException(() -> mWM.peekFastDrawable(FLAG_LOCK), message);
+
+ message = "with no permissions, getWallpaperFile should throw a SecurityException";
+ assertSecurityException(() -> mWM.getWallpaperFile(FLAG_SYSTEM), message);
+ assertSecurityException(() -> mWM.getWallpaperFile(FLAG_LOCK), message);
+ }
+
+ // ---------- Utility methods ----------
+
+ private boolean wallpaperNotSupported() {
+ return !(mWM.isWallpaperSupported() && mWM.isSetWallpaperAllowed());
+ }
+
+ private void assertSecurityException(ThrowingRunnable runnable, String errorMessage) {
+ assertThrows(errorMessage, SecurityException.class, runnable);
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NoWifiStatePermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoWifiStatePermissionTest.java
new file mode 100644
index 000000000..9fff22747
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoWifiStatePermissionTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import static android.content.pm.PackageManager.FEATURE_WIFI;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Verify WifiManager related methods without specific Wifi state permissions.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Instant apps cannot access the WifiManager")
+@SmallTest
+public class NoWifiStatePermissionTest {
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ private static final int TEST_NET_ID = 1;
+ private static final WifiConfiguration TEST_WIFI_CONFIGURATION = new WifiConfiguration();
+ private WifiManager mWifiManager;
+
+ @Before
+ public void setUp() {
+ boolean hasWifi = sContext.getPackageManager().hasSystemFeature(FEATURE_WIFI);
+ assumeTrue(hasWifi);
+
+ mWifiManager = (WifiManager) sContext.getSystemService(Context.WIFI_SERVICE);
+ assertNotNull(mWifiManager);
+ }
+
+ /**
+ * Verify that WifiManager#getWifiState() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#ACCESS_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testGetWifiState() {
+ mWifiManager.getWifiState();
+ }
+
+ /**
+ * Verify that WifiManager#getConfiguredNetworks() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#ACCESS_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testGetConfiguredNetworks() {
+ mWifiManager.getConfiguredNetworks();
+ }
+
+ /**
+ * Verify that WifiManager#getConnectionInfo() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#ACCESS_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testGetConnectionInfo() {
+ mWifiManager.getConnectionInfo();
+ }
+
+ /**
+ * Verify that WifiManager#getScanResults() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#ACCESS_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testGetScanResults() {
+ mWifiManager.getScanResults();
+ }
+
+ /**
+ * Verify that WifiManager#getDhcpInfo() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#ACCESS_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testGetDhcpInfo() {
+ mWifiManager.getDhcpInfo();
+ }
+
+ /**
+ * Verify that WifiManager#disconnect() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testDisconnect() {
+ mWifiManager.disconnect();
+ }
+
+ /**
+ * Verify that WifiManager#reconnect() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testReconnect() {
+ mWifiManager.reconnect();
+ }
+
+ /**
+ * Verify that WifiManager#reassociate() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testReassociate() {
+ mWifiManager.reassociate();
+ }
+
+ /**
+ * Verify that WifiManager#addNetwork() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testAddNetwork() {
+ mWifiManager.addNetwork(TEST_WIFI_CONFIGURATION);
+ }
+
+ /**
+ * Verify that WifiManager#updateNetwork() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testUpdateNetwork() {
+ TEST_WIFI_CONFIGURATION.networkId = 2;
+ mWifiManager.updateNetwork(TEST_WIFI_CONFIGURATION);
+ }
+ /**
+ * Verify that WifiManager#removeNetwork() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testRemoveNetwork() {
+ mWifiManager.removeNetwork(TEST_NET_ID);
+ }
+
+ /**
+ * Verify that WifiManager#enableNetwork() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testEnableNetwork() {
+ mWifiManager.enableNetwork(TEST_NET_ID, false);
+ }
+
+ /**
+ * Verify that WifiManager#disableNetwork() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testDisableNetwork() {
+ mWifiManager.disableNetwork(TEST_NET_ID);
+ }
+
+ /**
+ * Verify that WifiManager#pingSupplicant() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testPingSupplicant() {
+ mWifiManager.pingSupplicant();
+ }
+
+ /**
+ * Verify that WifiManager#startScan() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testStartScan() {
+ mWifiManager.startScan();
+ }
+
+ /**
+ * Verify that WifiManager#setWifiEnabled() requires permissions.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CHANGE_WIFI_STATE}.
+ */
+ @Test(expected = SecurityException.class)
+ public void testSetWifiEnabled() {
+ mWifiManager.setWifiEnabled(true);
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NotificationListenerCheckTest.java b/tests/cts/permission/src/android/permission/cts/NotificationListenerCheckTest.java
new file mode 100644
index 000000000..0561f3699
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NotificationListenerCheckTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import static android.permission.cts.PermissionUtils.clearAppState;
+import static android.permission.cts.PermissionUtils.install;
+import static android.permission.cts.PermissionUtils.uninstallApp;
+import static android.permission.cts.TestUtils.ensure;
+import static android.permission.cts.TestUtils.eventually;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.rule.ScreenRecordRule;
+import android.service.notification.StatusBarNotification;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests the {@code NotificationListenerCheck} in permission controller.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Cannot set system settings as instant app. Also we never show a notification"
+ + " listener check notification for instant apps.")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+@ScreenRecordRule.ScreenRecord
+@FlakyTest
+public class NotificationListenerCheckTest extends BaseNotificationListenerCheckTest {
+
+ public final ScreenRecordRule mScreenRecordRule = new ScreenRecordRule(false, false);
+
+ @Before
+ public void setup() throws Throwable {
+ // Skip tests if safety center not allowed
+ assumeDeviceSupportsSafetyCenter();
+
+ wakeUpAndDismissKeyguard();
+ resetPermissionControllerBeforeEachTest();
+
+ // Cts NLS is required to verify sent Notifications, however, we don't want it to show up in
+ // testing
+ triggerAndDismissCtsNotificationListenerNotification();
+
+ clearNotifications();
+
+ // Install and allow the app with NLS for testing
+ install(TEST_APP_NOTIFICATION_LISTENER_APK);
+ allowTestAppNotificationListenerService();
+ }
+
+ @After
+ public void tearDown() throws Throwable {
+ // Disallow and uninstall the app with NLS for testing
+ disallowTestAppNotificationListenerService();
+ uninstallApp(TEST_APP_PKG);
+
+ clearNotifications();
+ }
+
+ @Test
+ public void noNotificationIfFeatureDisabled() throws Throwable {
+ setNotificationListenerCheckEnabled(false);
+
+ runNotificationListenerCheck();
+
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void noNotificationIfSafetyCenterDisabled() throws Throwable {
+ SafetyCenterUtils.setSafetyCenterEnabled(false);
+
+ runNotificationListenerCheck();
+
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void notificationIsShown() throws Throwable {
+ runNotificationListenerCheck();
+
+ eventually(() -> assertNotNull("Expected notification, none found", getNotification(false)),
+ UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void notificationIsShownOnlyOnce() throws Throwable {
+ runNotificationListenerCheck();
+ eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+
+ runNotificationListenerCheck();
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void notificationIsShownAgainAfterClear() throws Throwable {
+ runNotificationListenerCheck();
+ eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+
+ clearAppState(TEST_APP_PKG);
+ // Wait until package is cleared and permission controller has cleared the state
+ Thread.sleep(2000);
+
+ allowTestAppNotificationListenerService();
+ runNotificationListenerCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void notificationIsShownAgainAfterUninstallAndReinstall() throws Throwable {
+ runNotificationListenerCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+
+ uninstallApp(TEST_APP_PKG);
+
+ // Wait until package permission controller has cleared the state
+ Thread.sleep(2000);
+
+ install(TEST_APP_NOTIFICATION_LISTENER_APK);
+
+ allowTestAppNotificationListenerService();
+ runNotificationListenerCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void notificationIsShownAgainAfterDisableAndReenableAppNotificationListener()
+ throws Throwable {
+ runNotificationListenerCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+
+ // Disallow NLS, and run NLS check job. This should clear NLS off notified list
+ disallowTestAppNotificationListenerService();
+ runNotificationListenerCheck();
+
+ // Re-allow NLS, and run NLS check job. This work now that it's cleared NLS off notified
+ // list
+ allowTestAppNotificationListenerService();
+ runNotificationListenerCheck();
+
+ eventually(() -> assertNotNull(getNotification(true)), UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void removeNotificationOnUninstall() throws Throwable {
+ runNotificationListenerCheck();
+
+ eventually(() -> assertNotNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
+
+ uninstallApp(TEST_APP_PKG);
+
+ // Wait until package permission controller has cleared the state
+ Thread.sleep(2000);
+
+ eventually(() -> assertNull(getNotification(false)), UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void notificationIsNotShownAfterDisableAppNotificationListener() throws Throwable {
+ disallowTestAppNotificationListenerService();
+
+ runNotificationListenerCheck();
+
+ // We don't expect a notification, but try to trigger one anyway
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void notificationOnClick_opensSafetyCenter() throws Throwable {
+ runNotificationListenerCheck();
+
+ StatusBarNotification currentNotification = eventually(
+ () -> {
+ StatusBarNotification notification = getNotification(false);
+ assertNotNull(notification);
+ return notification;
+ }, UNEXPECTED_TIMEOUT_MILLIS);
+
+ // Verify content intent
+ PendingIntent contentIntent = currentNotification.getNotification().contentIntent;
+ if (SdkLevel.isAtLeastU()) {
+ contentIntent.send(null, 0, null, null, null, null,
+ ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle());
+ } else {
+ contentIntent.send();
+ }
+
+ SafetyCenterUtils.assertSafetyCenterStarted();
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NotificationListenerCheckWithSafetyCenterUnsupportedTest.java b/tests/cts/permission/src/android/permission/cts/NotificationListenerCheckWithSafetyCenterUnsupportedTest.java
new file mode 100644
index 000000000..a346de6fd
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NotificationListenerCheckWithSafetyCenterUnsupportedTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import static android.permission.cts.PermissionUtils.install;
+import static android.permission.cts.PermissionUtils.uninstallApp;
+import static android.permission.cts.TestUtils.ensure;
+
+import static org.junit.Assert.assertNull;
+
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests the {@code NotificationListenerCheck} in permission controller.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Cannot set system settings as instant app. Also we never show a notification"
+ + " listener check notification for instant apps.")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+public class NotificationListenerCheckWithSafetyCenterUnsupportedTest
+ extends BaseNotificationListenerCheckTest {
+
+ @Before
+ public void setup() throws Throwable {
+ // Skip tests if safety center is supported
+ assumeDeviceDoesNotSupportSafetyCenter();
+
+ wakeUpAndDismissKeyguard();
+ resetPermissionControllerBeforeEachTest();
+
+ clearNotifications();
+
+ // Install and allow the app with NLS for testing
+ install(TEST_APP_NOTIFICATION_LISTENER_APK);
+ allowTestAppNotificationListenerService();
+ }
+
+ @After
+ public void tearDown() throws Throwable {
+ // Disallow and uninstall the app with NLS for testing
+ disallowTestAppNotificationListenerService();
+ uninstallApp(TEST_APP_PKG);
+
+ clearNotifications();
+ }
+
+ @Test
+ public void noNotifications_featureEnabled_safetyCenterEnabled() throws Throwable {
+ runNotificationListenerCheck();
+
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void noNotifications_featureDisabled_safetyCenterEnabled() throws Throwable {
+ setNotificationListenerCheckEnabled(false);
+
+ runNotificationListenerCheck();
+
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void noNotifications_featureEnabled_safetyCenterDisabled() throws Throwable {
+ SafetyCenterUtils.setSafetyCenterEnabled(false);
+
+ runNotificationListenerCheck();
+
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void noNotifications_featureDisabled_safetyCenterDisabled() throws Throwable {
+ setNotificationListenerCheckEnabled(false);
+ SafetyCenterUtils.setSafetyCenterEnabled(false);
+
+ runNotificationListenerCheck();
+
+ ensure(() -> assertNull("Expected no notifications", getNotification(false)),
+ ENSURE_NOTIFICATION_NOT_SHOWN_EXPECTED_TIMEOUT_MILLIS);
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/OneTimePermissionTest.java b/tests/cts/permission/src/android/permission/cts/OneTimePermissionTest.java
new file mode 100644
index 000000000..1c07a32b1
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/OneTimePermissionTest.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2019 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.permission.cts;
+
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.CAMERA;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assume.assumeFalse;
+
+import android.app.ActivityManager;
+import android.app.DreamManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AsbSecurityTest;
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.rule.ScreenRecordRule;
+import android.provider.DeviceConfig;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+
+import com.android.compatibility.common.util.FeatureUtil;
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.UiAutomatorUtils2;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@ScreenRecordRule.ScreenRecord
+public class OneTimePermissionTest {
+
+ private static final String APP_PKG_NAME = "android.permission.cts.appthatrequestpermission";
+ private static final String CUSTOM_CAMERA_PERM_APP_PKG_NAME =
+ "android.permission.cts.appthatrequestcustomcamerapermission";
+ private static final String APK =
+ "/data/local/tmp/cts/permissions/CtsAppThatRequestsOneTimePermission.apk";
+ private static final String CUSTOM_CAMERA_PERM_APK =
+ "/data/local/tmp/cts/permissions/CtsAppThatRequestCustomCameraPermission.apk";
+ private static final String EXTRA_FOREGROUND_SERVICE_LIFESPAN =
+ "android.permission.cts.OneTimePermissionTest.EXTRA_FOREGROUND_SERVICE_LIFESPAN";
+ private static final String EXTRA_FOREGROUND_SERVICE_STICKY =
+ "android.permission.cts.OneTimePermissionTest.EXTRA_FOREGROUND_SERVICE_STICKY";
+
+ public static final String CUSTOM_PERMISSION = "appthatrequestcustomcamerapermission.CUSTOM";
+
+ private static final long ONE_TIME_TIMEOUT_MILLIS = 5000;
+ private static final long ONE_TIME_KILLED_DELAY_MILLIS = 5000;
+ private static final long ONE_TIME_TIMER_LOWER_GRACE_PERIOD = 1000;
+ private static final long ONE_TIME_TIMER_UPPER_GRACE_PERIOD = 10000;
+
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private final PackageManager mPackageManager = mContext.getPackageManager();
+ private final UiDevice mUiDevice =
+ UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ private final ActivityManager mActivityManager =
+ mContext.getSystemService(ActivityManager.class);
+ private String mOldOneTimePermissionTimeoutValue;
+ private String mOldOneTimePermissionKilledDelayValue;
+
+ @Rule
+ public final ScreenRecordRule sScreenRecordRule = new ScreenRecordRule(false, false);
+
+ @Rule
+ public IgnoreAllTestsRule mIgnoreAutomotive = new IgnoreAllTestsRule(
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+
+ @Before
+ public void wakeUpScreen() {
+ SystemUtil.runShellCommand("input keyevent KEYCODE_WAKEUP");
+
+ SystemUtil.runShellCommand("input keyevent 82");
+ }
+
+ @Before
+ public void installApp() {
+ runShellCommand("pm install -r " + APK);
+ runShellCommand("pm install -r " + CUSTOM_CAMERA_PERM_APK);
+ }
+
+ @Before
+ public void prepareDeviceForOneTime() {
+ runWithShellPermissionIdentity(() -> {
+ mOldOneTimePermissionTimeoutValue = DeviceConfig.getProperty("permissions",
+ "one_time_permissions_timeout_millis");
+ mOldOneTimePermissionKilledDelayValue = DeviceConfig.getProperty("permissions",
+ "one_time_permissions_killed_delay_millis");
+ DeviceConfig.setProperty("permissions", "one_time_permissions_timeout_millis",
+ Long.toString(ONE_TIME_TIMEOUT_MILLIS), false);
+ DeviceConfig.setProperty("permissions",
+ "one_time_permissions_killed_delay_millis",
+ Long.toString(ONE_TIME_KILLED_DELAY_MILLIS), false);
+ });
+ }
+
+ @After
+ public void uninstallApp() {
+ runShellCommand("pm uninstall " + APP_PKG_NAME);
+ runShellCommand("pm uninstall " + CUSTOM_CAMERA_PERM_APP_PKG_NAME);
+ }
+
+ @After
+ public void restoreDeviceForOneTime() {
+ runWithShellPermissionIdentity(
+ () -> {
+ DeviceConfig.setProperty("permissions", "one_time_permissions_timeout_millis",
+ mOldOneTimePermissionTimeoutValue, false);
+ DeviceConfig.setProperty("permissions",
+ "one_time_permissions_killed_delay_millis",
+ mOldOneTimePermissionKilledDelayValue, false);
+ });
+ }
+
+ @Test
+ public void testOneTimePermission() throws Throwable {
+ startApp();
+
+ CompletableFuture<Long> exitTime = registerAppExitListener();
+
+ clickOneTimeButton();
+
+ exitApp();
+
+ assertGranted(5000);
+
+ assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD);
+
+ assertExpectedLifespan(exitTime, ONE_TIME_TIMEOUT_MILLIS);
+ }
+
+ @Ignore
+ @Test
+ public void testForegroundServiceMaintainsPermission() throws Throwable {
+ startApp();
+
+ CompletableFuture<Long> exitTime = registerAppExitListener();
+
+ clickOneTimeButton();
+
+ long expectedLifespanMillis = 2 * ONE_TIME_TIMEOUT_MILLIS;
+ startAppForegroundService(expectedLifespanMillis, false);
+
+ exitApp();
+
+ assertGranted(5000);
+
+ assertDenied(expectedLifespanMillis + ONE_TIME_TIMER_UPPER_GRACE_PERIOD);
+
+ assertExpectedLifespan(exitTime, expectedLifespanMillis);
+
+ }
+
+ @Test
+ public void testPermissionRevokedOnKill() throws Throwable {
+ startApp();
+
+ clickOneTimeButton();
+
+ exitApp();
+
+ assertGranted(5000);
+
+ mUiDevice.waitForIdle();
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ mActivityManager.killBackgroundProcesses(APP_PKG_NAME));
+
+ runWithShellPermissionIdentity(
+ () -> Thread.sleep(DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
+ "one_time_permissions_killed_delay_millis", 5000L)));
+ assertDenied(500);
+ }
+
+ @Test
+ @FlakyTest
+ public void testStickyServiceMaintainsPermissionOnRestart() throws Throwable {
+ startApp();
+
+ clickOneTimeButton();
+
+ startAppForegroundService(2 * ONE_TIME_TIMEOUT_MILLIS, true);
+
+ exitApp();
+
+ assertGranted(5000);
+ mUiDevice.waitForIdle();
+ Thread.sleep(ONE_TIME_TIMEOUT_MILLIS);
+
+ runShellCommand("am crash " + APP_PKG_NAME);
+
+ eventually(() -> runWithShellPermissionIdentity(() -> {
+ if (mActivityManager.getPackageImportance(APP_PKG_NAME) <= IMPORTANCE_CACHED) {
+ throw new AssertionError("App was never killed");
+ }
+ }));
+
+ eventually(() -> runWithShellPermissionIdentity(() -> {
+ if (mActivityManager.getPackageImportance(APP_PKG_NAME)
+ > IMPORTANCE_FOREGROUND_SERVICE) {
+ throw new AssertionError("Foreground service never resumed");
+ }
+ Assert.assertEquals("Service resumed without permission",
+ PackageManager.PERMISSION_GRANTED, mContext.getPackageManager()
+ .checkPermission(ACCESS_FINE_LOCATION, APP_PKG_NAME));
+ }));
+ }
+
+ @Test
+ @AsbSecurityTest(cveBugId = 237405974L)
+ public void testCustomPermissionIsGrantedOneTime() throws Throwable {
+ startApp(new ComponentName(CUSTOM_CAMERA_PERM_APP_PKG_NAME,
+ CUSTOM_CAMERA_PERM_APP_PKG_NAME + ".RequestCameraPermission"));
+
+ // We're only manually granting CAMERA, but the app will later request CUSTOM and get it
+ // granted silently. This is intentional since it's in the same group but both should
+ // eventually be revoked
+ clickOneTimeButton();
+
+ // Just waiting for the revocation
+ eventually(() -> Assert.assertEquals(PackageManager.PERMISSION_DENIED,
+ mContext.getPackageManager()
+ .checkPermission(CAMERA, CUSTOM_CAMERA_PERM_APP_PKG_NAME)));
+
+ // This checks the vulnerability
+ eventually(() -> Assert.assertEquals(PackageManager.PERMISSION_DENIED,
+ mContext.getPackageManager()
+ .checkPermission(CUSTOM_PERMISSION, CUSTOM_CAMERA_PERM_APP_PKG_NAME)));
+
+ }
+
+ private void assertGrantedState(String s, int permissionGranted, long timeoutMillis) {
+ eventually(() -> Assert.assertEquals(s,
+ permissionGranted, mPackageManager
+ .checkPermission(ACCESS_FINE_LOCATION, APP_PKG_NAME)), timeoutMillis);
+ }
+
+ private void assertGranted(long timeoutMillis) {
+ assertGrantedState("Permission was never granted", PackageManager.PERMISSION_GRANTED,
+ timeoutMillis);
+ }
+
+ private void assertDenied(long timeoutMillis) {
+ assertGrantedState("Permission was never revoked", PackageManager.PERMISSION_DENIED,
+ timeoutMillis);
+ }
+
+ private void assertExpectedLifespan(CompletableFuture<Long> exitTime, long expectedLifespan)
+ throws InterruptedException, java.util.concurrent.ExecutionException,
+ java.util.concurrent.TimeoutException {
+ long grantedLength = System.currentTimeMillis() - exitTime.get(0, TimeUnit.MILLISECONDS);
+ if (grantedLength + ONE_TIME_TIMER_LOWER_GRACE_PERIOD < expectedLifespan) {
+ throw new AssertionError(
+ "The one time permission lived shorter than expected. expected: "
+ + expectedLifespan + "ms but was: " + grantedLength + "ms");
+ }
+ }
+
+ private void exitApp() {
+ boolean[] hasExited = {false};
+ try {
+ new Thread(() -> {
+ while (!hasExited[0]) {
+ DreamManager mDreamManager = mContext.getSystemService(DreamManager.class);
+ mUiDevice.pressHome();
+ mUiDevice.pressBack();
+ runWithShellPermissionIdentity(() -> {
+ if (mDreamManager.isDreaming()) {
+ mDreamManager.stopDream();
+ }
+ });
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ }
+ }
+ }).start();
+ eventually(() -> {
+ runWithShellPermissionIdentity(() -> {
+ if (mActivityManager.getPackageImportance(APP_PKG_NAME)
+ <= IMPORTANCE_FOREGROUND) {
+ throw new AssertionError("Unable to exit application");
+ }
+ });
+ });
+ } finally {
+ hasExited[0] = true;
+ }
+ }
+
+ private void clickOneTimeButton() throws Throwable {
+ final UiObject2 uiObject = UiAutomatorUtils2.waitFindObject(By.res(
+ "com.android.permissioncontroller:id/permission_allow_one_time_button"), 10000);
+ Thread.sleep(500);
+ uiObject.click();
+ }
+
+ /**
+ * Start the app. The app will request the permissions.
+ */
+ private void startApp(ComponentName componentName) {
+ // One time permission is not applicable for Wear OS.
+ // The only permissions available are Allow or Deny
+ assumeFalse(
+ "Skipping test: One time permission is not supported in Wear OS",
+ FeatureUtil.isWatch());
+ Intent startApp = new Intent();
+ startApp.setComponent(componentName);
+ startApp.setFlags(FLAG_ACTIVITY_NEW_TASK);
+
+ mContext.startActivity(startApp);
+ }
+
+ /**
+ * Start the default app for these tests. The app will request the permissions.
+ */
+ private void startApp() {
+ startApp(new ComponentName(APP_PKG_NAME, APP_PKG_NAME + ".RequestPermission"));
+ }
+
+ private void startAppForegroundService(long lifespanMillis, boolean sticky) {
+ Intent intent = new Intent()
+ .setComponent(new ComponentName(
+ APP_PKG_NAME, APP_PKG_NAME + ".KeepAliveForegroundService"))
+ .putExtra(EXTRA_FOREGROUND_SERVICE_LIFESPAN, lifespanMillis)
+ .putExtra(EXTRA_FOREGROUND_SERVICE_STICKY, sticky);
+ mContext.startService(intent);
+ }
+
+ private CompletableFuture<Long> registerAppExitListener() {
+ CompletableFuture<Long> exitTimeCallback = new CompletableFuture<>();
+ try {
+ int uid = mContext.getPackageManager().getPackageUid(APP_PKG_NAME, 0);
+ runWithShellPermissionIdentity(() ->
+ mActivityManager.addOnUidImportanceListener(new SingleAppExitListener(
+ uid, IMPORTANCE_FOREGROUND, exitTimeCallback), IMPORTANCE_FOREGROUND));
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new AssertionError("Package not found.", e);
+ }
+ return exitTimeCallback;
+ }
+
+ private class SingleAppExitListener implements ActivityManager.OnUidImportanceListener {
+
+ private final int mUid;
+ private final int mImportance;
+ private final CompletableFuture<Long> mCallback;
+
+ SingleAppExitListener(int uid, int importance, CompletableFuture<Long> callback) {
+ mUid = uid;
+ mImportance = importance;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ if (uid == mUid && importance > mImportance) {
+ mCallback.complete(System.currentTimeMillis());
+ mActivityManager.removeOnUidImportanceListener(this);
+ }
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java b/tests/cts/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java
new file mode 100644
index 000000000..df3ec3c64
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Verify the PackageManager related operations require specific permissions.
+ */
+@SmallTest
+public class PackageManagerRequiringPermissionsTest extends AndroidTestCase {
+ // Must be a known-present application package other than the one hosting this class
+ private static final String PACKAGE_NAME = "android";
+
+ private PackageManager mPackageManager;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mPackageManager = getContext().getPackageManager();
+ assertNotNull(mPackageManager);
+ }
+
+ /**
+ * Verify that PackageManager.setApplicationEnabledSetting requires permission.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#CHANGE_COMPONENT_ENABLED_STATE}.
+ */
+ public void testSetApplicationEnabledSetting() {
+ try {
+ mPackageManager.setApplicationEnabledSetting(PACKAGE_NAME,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ fail("PackageManager.setApplicationEnabledSetting did not throw SecurityException as"
+ + "expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that PackageManager.addPreferredActivity requires permission.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#SET_PREFERRED_APPLICATIONS}.
+ */
+ public void testAddPreferredActivity() {
+ try {
+ IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
+ filter.addCategory(Intent.CATEGORY_HOME);
+ mPackageManager.addPreferredActivity(filter, 0, null, null);
+ fail("PackageManager.addPreferredActivity did not throw" +
+ " SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that PackageManager.clearPackagePreferredActivities requires permission.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#SET_PREFERRED_APPLICATIONS}.
+ */
+ @AppModeFull(reason = "clearPackagePreferredActivities always returns null for instant apps "
+ + "(it does not even check for permissions)")
+ public void testClearPackagePreferredActivities() {
+ try {
+ mPackageManager.clearPackagePreferredActivities(null);
+ fail("PackageManager.clearPackagePreferredActivities did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that PackageManager.verifyPendingInstall requires permission.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#PACKAGE_VERIFICATION_AGENT}
+ */
+ public void testVerifyPendingInstall() {
+ try {
+ mPackageManager.verifyPendingInstall(1, 1);
+ fail("PackageManager.verifyPendingInstall did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that PackageManager.extendVerificationTimeout requires permission.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#PACKAGE_VERIFICATION_AGENT}.
+ */
+ public void testExtendVerificationTimeout() {
+ try {
+ mPackageManager.extendVerificationTimeout(1, 1, 10000);
+ fail("PackageManager.extendVerificationTimeout did not throw SecurityException"
+ + " as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/PermissionControllerTest.java b/tests/cts/permission/src/android/permission/cts/PermissionControllerTest.java
new file mode 100644
index 000000000..7c23156ef
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PermissionControllerTest.java
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2018 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.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.BODY_SENSORS;
+import static android.Manifest.permission.READ_CALENDAR;
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.Manifest.permission.WRITE_CALENDAR;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.permissionToOp;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.permission.PermissionControllerManager.COUNT_ONLY_WHEN_GRANTED;
+import static android.permission.PermissionControllerManager.REASON_INSTALLER_POLICY_VIOLATION;
+import static android.permission.PermissionControllerManager.REASON_MALWARE;
+import static android.permission.cts.PermissionUtils.grantPermission;
+import static android.permission.cts.PermissionUtils.isGranted;
+import static android.permission.cts.PermissionUtils.isPermissionGranted;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.Collections.singletonList;
+
+import android.app.AppOpsManager;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.PermissionGroupInfo;
+import android.permission.PermissionControllerManager;
+import android.permission.RuntimePermissionPresentationInfo;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Test {@link PermissionControllerManager}
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Instant apps cannot talk to permission controller")
+public class PermissionControllerTest {
+ private static final String APK =
+ "/data/local/tmp/cts/permissions/CtsAppThatAccessesLocationOnCommand.apk";
+ private static final String APP = "android.permission.cts.appthataccesseslocation";
+ private static final String APK2 =
+ "/data/local/tmp/cts/permissions/"
+ + "CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk";
+ private static final String APP2 = "android.permission.cts.appthatrequestcustompermission";
+ private static final String CUSTOM_PERMISSION =
+ "android.permission.cts.appthatrequestcustompermission.TEST_PERMISSION";
+
+ private static final UiAutomation sUiAutomation =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+ private static final PermissionControllerManager sController =
+ sContext.getSystemService(PermissionControllerManager.class);
+
+ @Before
+ @After
+ public void resetAppState() {
+ runWithShellPermissionIdentity(() -> {
+ sUiAutomation.grantRuntimePermission(APP, ACCESS_FINE_LOCATION);
+ sUiAutomation.grantRuntimePermission(APP, ACCESS_BACKGROUND_LOCATION);
+ setAppOp(APP, ACCESS_FINE_LOCATION, MODE_ALLOWED);
+ });
+ }
+
+ @BeforeClass
+ public static void installApp() {
+ runShellCommand("pm install -r -g " + APK);
+ runShellCommand("pm install -r " + APK2);
+ }
+
+ @AfterClass
+ public static void uninstallApp() {
+ runShellCommand("pm uninstall " + APP);
+ runShellCommand("pm uninstall " + APP2);
+ }
+
+ private @NonNull Map<String, List<String>> revokePermissions(
+ @NonNull Map<String, List<String>> request, boolean doDryRun, int reason,
+ @NonNull Executor executor) throws Exception {
+ AtomicReference<Map<String, List<String>>> result = new AtomicReference<>();
+
+ sController.revokeRuntimePermissions(request, doDryRun, reason, executor,
+ new PermissionControllerManager.OnRevokeRuntimePermissionsCallback() {
+ @Override
+ public void onRevokeRuntimePermissions(@NonNull Map<String, List<String>> r) {
+ synchronized (result) {
+ result.set(r);
+ result.notifyAll();
+ }
+ }
+ });
+
+ synchronized (result) {
+ while (result.get() == null) {
+ result.wait();
+ }
+ }
+
+ return result.get();
+ }
+
+ private @NonNull Map<String, List<String>> revokePermissions(
+ @NonNull Map<String, List<String>> request, boolean doDryRun, boolean adoptShell)
+ throws Exception {
+ if (adoptShell) {
+ Map<String, List<String>> revokeRet =
+ callWithShellPermissionIdentity(() -> revokePermissions(
+ request, doDryRun, REASON_MALWARE, sContext.getMainExecutor()));
+ return revokeRet;
+ }
+ return revokePermissions(request, doDryRun, REASON_MALWARE, sContext.getMainExecutor());
+ }
+
+ private @NonNull Map<String, List<String>> revokePermissions(
+ @NonNull Map<String, List<String>> request, boolean doDryRun) throws Exception {
+ return revokePermissions(request, doDryRun, true);
+ }
+
+ private void setAppOp(@NonNull String pkg, @NonNull String perm, int mode) throws Exception {
+ sContext.getSystemService(AppOpsManager.class).setUidMode(permissionToOp(perm),
+ sContext.getPackageManager().getPackageUid(pkg, 0), mode);
+ }
+
+ private Map<String, List<String>> buildRevokeRequest(@NonNull String app,
+ @NonNull String permission) {
+ return Collections.singletonMap(app, singletonList(permission));
+ }
+
+ private void assertRuntimePermissionLabelsAreValid(List<String> runtimePermissions,
+ List<RuntimePermissionPresentationInfo> permissionInfos, int expectedRuntimeGranted,
+ String app) throws Exception {
+ int numRuntimeGranted = 0;
+ for (String permission : runtimePermissions) {
+ if (isPermissionGranted(app, permission)) {
+ numRuntimeGranted++;
+ }
+ }
+ assertThat(numRuntimeGranted).isEqualTo(expectedRuntimeGranted);
+
+ ArrayList<CharSequence> maybeStandardPermissionLabels = new ArrayList<>();
+ ArrayList<CharSequence> nonStandardPermissionLabels = new ArrayList<>();
+ for (PermissionGroupInfo permGroup : sContext.getPackageManager().getAllPermissionGroups(
+ 0)) {
+ CharSequence permissionGroupLabel = permGroup.loadLabel(sContext.getPackageManager());
+ if (permGroup.packageName.equals("android")) {
+ maybeStandardPermissionLabels.add(permissionGroupLabel);
+ } else {
+ nonStandardPermissionLabels.add(permissionGroupLabel);
+ }
+ }
+
+ int numInfosGranted = 0;
+
+ for (RuntimePermissionPresentationInfo permissionInfo : permissionInfos) {
+ CharSequence permissionGroupLabel = permissionInfo.getLabel();
+
+ // PermissionInfo should be included in exactly one of existing (possibly) standard
+ // or nonstandard permission groups
+ if (permissionInfo.isStandard()) {
+ assertThat(maybeStandardPermissionLabels).contains(permissionGroupLabel);
+ } else {
+ assertThat(nonStandardPermissionLabels).contains(permissionGroupLabel);
+ }
+ if (permissionInfo.isGranted()) {
+ numInfosGranted++;
+ }
+ }
+
+ // Each permissionInfo represents one or more runtime permissions, but we don't have a
+ // mapping, so we check that we have at least as many runtimePermissions as permissionInfos
+ assertThat(numRuntimeGranted).isAtLeast(numInfosGranted);
+ }
+
+ @Test
+ public void revokePermissionsDryRunSinglePermission() throws Exception {
+ Map<String, List<String>> request = buildRevokeRequest(APP, ACCESS_BACKGROUND_LOCATION);
+
+ Map<String, List<String>> result = revokePermissions(request, true);
+
+ assertThat(result.size()).isEqualTo(1);
+ assertThat(result.get(APP)).isNotNull();
+ assertThat(result.get(APP)).containsExactly(ACCESS_BACKGROUND_LOCATION);
+ }
+
+ @Test
+ public void revokePermissionsSinglePermission() throws Exception {
+ Map<String, List<String>> request = buildRevokeRequest(APP, ACCESS_BACKGROUND_LOCATION);
+
+ revokePermissions(request, false);
+
+ assertThat(sContext.getPackageManager().checkPermission(ACCESS_BACKGROUND_LOCATION,
+ APP)).isEqualTo(PERMISSION_DENIED);
+ }
+
+ @Test
+ public void revokePermissionsDoNotAlreadyRevokedPermission() throws Exception {
+ // Properly revoke the permission
+ runWithShellPermissionIdentity(() -> {
+ sUiAutomation.revokeRuntimePermission(APP, ACCESS_BACKGROUND_LOCATION);
+ setAppOp(APP, ACCESS_FINE_LOCATION, MODE_FOREGROUND);
+ });
+
+ Map<String, List<String>> request = buildRevokeRequest(APP, ACCESS_BACKGROUND_LOCATION);
+ Map<String, List<String>> result = revokePermissions(request, false);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void revokePermissionsDryRunForegroundPermission() throws Exception {
+ Map<String, List<String>> request = buildRevokeRequest(APP, ACCESS_FINE_LOCATION);
+
+ Map<String, List<String>> result = revokePermissions(request, true);
+
+ assertThat(result.size()).isEqualTo(1);
+ assertThat(result.get(APP)).isNotNull();
+ assertThat(result.get(APP)).containsExactly(ACCESS_FINE_LOCATION,
+ ACCESS_BACKGROUND_LOCATION, ACCESS_COARSE_LOCATION);
+ }
+
+ @Test
+ public void revokePermissionsUnrequestedPermission() throws Exception {
+ Map<String, List<String>> request = buildRevokeRequest(APP, READ_CONTACTS);
+
+ Map<String, List<String>> result = revokePermissions(request, false);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void revokeFromUnknownPackage() throws Exception {
+ Map<String, List<String>> request = buildRevokeRequest("invalid.app", READ_CONTACTS);
+
+ Map<String, List<String>> result = revokePermissions(request, false);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void revokePermissionsFromUnknownPermission() throws Exception {
+ Map<String, List<String>> request = buildRevokeRequest(APP, "unknown.permission");
+
+ Map<String, List<String>> result = revokePermissions(request, false);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void revokePermissionsPolicyViolationFromWrongPackage() throws Exception {
+ Map<String, List<String>> request = buildRevokeRequest(APP, ACCESS_FINE_LOCATION);
+ Map<String, List<String>> result = callWithShellPermissionIdentity(
+ () -> revokePermissions(request,
+ false, REASON_INSTALLER_POLICY_VIOLATION, sContext.getMainExecutor()));
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void revokePermissionsWithExecutorForCallback() throws Exception {
+ Map<String, List<String>> request = buildRevokeRequest(APP, ACCESS_BACKGROUND_LOCATION);
+
+ AtomicBoolean wasRunOnExecutor = new AtomicBoolean();
+ runWithShellPermissionIdentity(() ->
+ revokePermissions(request, true, REASON_MALWARE, command -> {
+ wasRunOnExecutor.set(true);
+ command.run();
+ }));
+
+ assertThat(wasRunOnExecutor.get()).isTrue();
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void revokePermissionsWithNullPkg() throws Exception {
+ Map<String, List<String>> request = Collections.singletonMap(null,
+ singletonList(ACCESS_FINE_LOCATION));
+
+ revokePermissions(request, true);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void revokePermissionsWithNullPermissions() throws Exception {
+ Map<String, List<String>> request = Collections.singletonMap(APP, null);
+
+ revokePermissions(request, true);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void revokePermissionsWithNullPermission() throws Exception {
+ Map<String, List<String>> request = Collections.singletonMap(APP,
+ singletonList(null));
+
+ revokePermissions(request, true);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void revokePermissionsWithNullRequests() {
+ sController.revokeRuntimePermissions(null, false, REASON_MALWARE,
+ sContext.getMainExecutor(),
+ new PermissionControllerManager.OnRevokeRuntimePermissionsCallback() {
+ @Override
+ public void onRevokeRuntimePermissions(
+ @NonNull Map<String, List<String>> revoked) {
+ }
+ });
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void revokePermissionsWithNullCallback() {
+ Map<String, List<String>> request = buildRevokeRequest(APP, ACCESS_BACKGROUND_LOCATION);
+
+ sController.revokeRuntimePermissions(request, false, REASON_MALWARE,
+ sContext.getMainExecutor(), null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void revokePermissionsWithNullExecutor() {
+ Map<String, List<String>> request = buildRevokeRequest(APP, ACCESS_BACKGROUND_LOCATION);
+
+ sController.revokeRuntimePermissions(request, false, REASON_MALWARE, null,
+ new PermissionControllerManager.OnRevokeRuntimePermissionsCallback() {
+ @Override
+ public void onRevokeRuntimePermissions(
+ @NonNull Map<String, List<String>> revoked) {
+
+ }
+ });
+ }
+
+ @Test(expected = SecurityException.class)
+ public void revokePermissionsWithoutPermission() throws Exception {
+ Map<String, List<String>> request = buildRevokeRequest(APP, ACCESS_BACKGROUND_LOCATION);
+
+ // This will fail as the test-app does not have the required permission
+ revokePermissions(request, true, false);
+ }
+
+ @Test
+ public void getAppPermissionsForApp() throws Exception {
+ CompletableFuture<List<RuntimePermissionPresentationInfo>> futurePermissionInfos =
+ new CompletableFuture<>();
+
+ List<String> runtimePermissions;
+ List<RuntimePermissionPresentationInfo> permissionInfos;
+
+ sUiAutomation.adoptShellPermissionIdentity();
+ try {
+ sController.getAppPermissions(APP, futurePermissionInfos::complete, null);
+ runtimePermissions = PermissionUtils.getRuntimePermissions(APP);
+ assertThat(runtimePermissions).isNotEmpty();
+ permissionInfos = futurePermissionInfos.get();
+ } finally {
+ sUiAutomation.dropShellPermissionIdentity();
+ }
+
+ assertRuntimePermissionLabelsAreValid(runtimePermissions, permissionInfos, 3, APP);
+ }
+
+ @Test
+ public void getAppPermissionsForCustomApp() throws Exception {
+ CompletableFuture<List<RuntimePermissionPresentationInfo>> futurePermissionInfos =
+ new CompletableFuture<>();
+
+ // Grant all requested permissions except READ_CALENDAR
+ sUiAutomation.grantRuntimePermission(APP2, CUSTOM_PERMISSION);
+ PermissionUtils.grantPermission(APP2, BODY_SENSORS);
+ PermissionUtils.grantPermission(APP2, READ_CONTACTS);
+ PermissionUtils.grantPermission(APP2, WRITE_CALENDAR);
+
+ List<String> runtimePermissions;
+ List<RuntimePermissionPresentationInfo> permissionInfos;
+ sUiAutomation.adoptShellPermissionIdentity();
+ try {
+ sController.getAppPermissions(APP2, futurePermissionInfos::complete, null);
+ runtimePermissions = PermissionUtils.getRuntimePermissions(APP2);
+
+ permissionInfos = futurePermissionInfos.get();
+ } finally {
+ sUiAutomation.dropShellPermissionIdentity();
+ }
+
+ assertThat(permissionInfos).isNotEmpty();
+ assertThat(runtimePermissions.size()).isEqualTo(5);
+ assertRuntimePermissionLabelsAreValid(runtimePermissions, permissionInfos, 4, APP2);
+ }
+
+ @Test
+ public void revokePermissionAutomaticallyExtendsToWholeGroup() throws Exception {
+ grantPermission(APP2, READ_CALENDAR);
+ grantPermission(APP2, WRITE_CALENDAR);
+
+ runWithShellPermissionIdentity(
+ () -> {
+ sController.revokeRuntimePermission(APP2, READ_CALENDAR);
+
+ eventually(() -> {
+ assertThat(isGranted(APP2, READ_CALENDAR)).isEqualTo(false);
+ // revokePermission automatically extends the revocation to whole group
+ assertThat(isGranted(APP2, WRITE_CALENDAR)).isEqualTo(false);
+ });
+ });
+ }
+
+ @Test
+ public void revokePermissionCustom() throws Exception {
+ sUiAutomation.grantRuntimePermission(APP2, CUSTOM_PERMISSION);
+
+ runWithShellPermissionIdentity(
+ () -> {
+ sController.revokeRuntimePermission(APP2, CUSTOM_PERMISSION);
+
+ eventually(() -> {
+ assertThat(isPermissionGranted(APP2, CUSTOM_PERMISSION)).isEqualTo(false);
+ });
+ });
+ }
+
+ @Test
+ public void revokePermissionWithInvalidPkg() throws Exception {
+ // No return value, call is ignored
+ runWithShellPermissionIdentity(
+ () -> sController.revokeRuntimePermission("invalid.package", READ_CALENDAR));
+ }
+
+ @Test
+ public void revokePermissionWithInvalidPermission() throws Exception {
+ // No return value, call is ignored
+ runWithShellPermissionIdentity(
+ () -> sController.revokeRuntimePermission(APP2, "invalid.permission"));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void revokePermissionWithNullPkg() throws Exception {
+ sController.revokeRuntimePermission(null, READ_CALENDAR);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void revokePermissionWithNullPermission() throws Exception {
+ sController.revokeRuntimePermission(APP2, null);
+ }
+
+ // TODO: Add more tests for countPermissionAppsGranted when the method can be safely called
+ // multiple times in a row
+
+ @Test
+ public void countPermissionAppsGranted() {
+ runWithShellPermissionIdentity(
+ () -> {
+ CompletableFuture<Integer> numApps = new CompletableFuture<>();
+
+ sController.countPermissionApps(singletonList(ACCESS_FINE_LOCATION),
+ COUNT_ONLY_WHEN_GRANTED, numApps::complete, null);
+
+ // TODO: Better would be to count before, grant a permission, count again and
+ // then compare before and after
+ assertThat(numApps.get()).isAtLeast(1);
+ });
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void countPermissionAppsNullPermission() {
+ sController.countPermissionApps(null, 0, (n) -> { }, null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void countPermissionAppsInvalidFlags() {
+ sController.countPermissionApps(singletonList(ACCESS_FINE_LOCATION), -1, (n) -> { }, null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void countPermissionAppsNullCallback() {
+ sController.countPermissionApps(singletonList(ACCESS_FINE_LOCATION), 0, null, null);
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/PermissionFlagsTest.java b/tests/cts/permission/src/android/permission/cts/PermissionFlagsTest.java
new file mode 100644
index 000000000..8ed9b9c27
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PermissionFlagsTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2019 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.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.READ_CALL_LOG;
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.permission.cts.PermissionUtils.clearAppState;
+import static android.permission.cts.PermissionUtils.getAllPermissionFlags;
+import static android.permission.cts.PermissionUtils.getPermissionFlags;
+import static android.permission.cts.PermissionUtils.install;
+import static android.permission.cts.PermissionUtils.isGranted;
+import static android.permission.cts.PermissionUtils.setPermissionFlags;
+import static android.permission.cts.PermissionUtils.uninstallApp;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AsbSecurityTest;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests how permission flags behave.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Cannot read permission flags of other app.")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+public class PermissionFlagsTest {
+ /** The package name of most apps used in the test */
+ private static final String APP_PKG = "android.permission.cts.appthatrequestpermission";
+ private static final String APP_SYSTEM_ALERT_WINDOW_PKG =
+ "android.permission.cts.usesystemalertwindowpermission";
+
+ private static final String TMP_DIR = "/data/local/tmp/cts/permissions/";
+ private static final String APK_CONTACTS_15 =
+ TMP_DIR + "CtsAppThatRequestsContactsPermission15.apk";
+ private static final String APK_LOCATION_22 =
+ TMP_DIR + "CtsAppThatRequestsLocationPermission22.apk";
+ private static final String APK_LOCATION_28 =
+ TMP_DIR + "CtsAppThatRequestsLocationPermission28.apk";
+ private static final String APK_SYSTEM_ALERT_WINDOW_23 =
+ TMP_DIR + "CtsAppThatRequestsSystemAlertWindow23.apk";
+
+ @After
+ @Before
+ public void uninstallTestApp() {
+ uninstallApp(APP_PKG);
+ uninstallApp(APP_SYSTEM_ALERT_WINDOW_PKG);
+ }
+
+ @Test
+ public void implicitPermission() {
+ install(APK_LOCATION_28);
+
+ assertEquals(FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
+ getPermissionFlags(APP_PKG, ACCESS_BACKGROUND_LOCATION));
+ }
+
+ @Test
+ public void regularPermission() {
+ install(APK_LOCATION_28);
+
+ assertEquals(0, getPermissionFlags(APP_PKG, ACCESS_COARSE_LOCATION));
+ }
+
+ @Test
+ public void regularPermissionPreM() {
+ install(APK_CONTACTS_15);
+
+ assertEquals(FLAG_PERMISSION_REVIEW_REQUIRED,
+ getPermissionFlags(APP_PKG, READ_CONTACTS) & FLAG_PERMISSION_REVIEW_REQUIRED);
+ }
+
+ @Test
+ public void clearRegularPermissionPreM() {
+ install(APK_CONTACTS_15);
+
+ int defaultState = getPermissionFlags(APP_PKG, READ_CONTACTS);
+ setPermissionFlags(APP_PKG, READ_CONTACTS, FLAG_PERMISSION_REVIEW_REQUIRED, 0);
+ setPermissionFlags(APP_PKG, READ_CONTACTS,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED);
+
+ clearAppState(APP_PKG);
+
+ eventually(() -> assertEquals(defaultState, getPermissionFlags(APP_PKG, READ_CONTACTS)));
+ }
+
+ @Test
+ public void clearImplicitPermissionPreM() {
+ install(APK_CONTACTS_15);
+
+ int defaultState = getPermissionFlags(APP_PKG, READ_CALL_LOG);
+ setPermissionFlags(APP_PKG, READ_CALL_LOG, FLAG_PERMISSION_REVIEW_REQUIRED, 0);
+ setPermissionFlags(APP_PKG, READ_CALL_LOG,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED);
+
+ clearAppState(APP_PKG);
+
+ eventually(() -> assertEquals(defaultState, getPermissionFlags(APP_PKG, READ_CALL_LOG)));
+ }
+
+ @Test
+ public void clearRegularPermission() {
+ install(APK_LOCATION_28);
+
+ int defaultState = getPermissionFlags(APP_PKG, ACCESS_COARSE_LOCATION);
+ setPermissionFlags(APP_PKG, ACCESS_COARSE_LOCATION,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED);
+
+ clearAppState(APP_PKG);
+
+ eventually(() -> assertEquals(defaultState,
+ getPermissionFlags(APP_PKG, ACCESS_COARSE_LOCATION)));
+ }
+
+ @Test
+ public void clearImplicitPermission() {
+ install(APK_LOCATION_28);
+
+ int defaultState = getPermissionFlags(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+ setPermissionFlags(APP_PKG, ACCESS_BACKGROUND_LOCATION,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED);
+
+ clearAppState(APP_PKG);
+
+ eventually(() -> assertEquals(defaultState,
+ getPermissionFlags(APP_PKG, ACCESS_BACKGROUND_LOCATION)));
+ }
+
+ @Test
+ public void reinstallPreM() {
+ install(APK_CONTACTS_15);
+ install(APK_CONTACTS_15);
+
+ assertEquals(FLAG_PERMISSION_REVIEW_REQUIRED,
+ getPermissionFlags(APP_PKG, READ_CONTACTS) & FLAG_PERMISSION_REVIEW_REQUIRED);
+ }
+
+ @Test
+ public void reinstallDoesNotOverrideChangesPreM() {
+ install(APK_CONTACTS_15);
+
+ setPermissionFlags(APP_PKG, READ_CONTACTS, FLAG_PERMISSION_REVIEW_REQUIRED, 0);
+ setPermissionFlags(APP_PKG, READ_CONTACTS,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED);
+
+ install(APK_CONTACTS_15);
+
+ assertEquals(FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED,
+ getPermissionFlags(APP_PKG, READ_CONTACTS) & (FLAG_PERMISSION_USER_SET
+ | FLAG_PERMISSION_USER_FIXED | FLAG_PERMISSION_REVIEW_REQUIRED));
+ }
+
+ @Test
+ public void reinstall() {
+ install(APK_LOCATION_28);
+ install(APK_LOCATION_28);
+
+ assertEquals(0, getPermissionFlags(APP_PKG, ACCESS_COARSE_LOCATION));
+ assertEquals(FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
+ getPermissionFlags(APP_PKG, ACCESS_BACKGROUND_LOCATION));
+ }
+
+ @Test
+ public void reinstallDoesNotOverrideChanges() {
+ install(APK_LOCATION_28);
+
+ setPermissionFlags(APP_PKG, ACCESS_COARSE_LOCATION,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED);
+ setPermissionFlags(APP_PKG, ACCESS_BACKGROUND_LOCATION,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED,
+ FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED);
+
+ install(APK_LOCATION_28);
+
+ assertEquals(FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED,
+ getPermissionFlags(APP_PKG, ACCESS_COARSE_LOCATION));
+
+ assertEquals(FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED
+ | FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
+ getPermissionFlags(APP_PKG, ACCESS_BACKGROUND_LOCATION));
+ }
+
+ @Test
+ public void revokeOnUpgrade() throws Exception {
+ install(APK_LOCATION_22);
+
+ install(APK_LOCATION_28);
+
+ assertFalse(isGranted(APP_PKG, ACCESS_COARSE_LOCATION));
+ assertFalse(isGranted(APP_PKG, ACCESS_BACKGROUND_LOCATION));
+ assertEquals(0,getPermissionFlags(APP_PKG, ACCESS_COARSE_LOCATION)
+ & FLAG_PERMISSION_REVOKED_COMPAT);
+ assertEquals(0,getPermissionFlags(APP_PKG, ACCESS_BACKGROUND_LOCATION)
+ & FLAG_PERMISSION_REVOKED_COMPAT);
+ }
+
+ @AsbSecurityTest(cveBugId = 283006437)
+ @Test
+ public void nonRuntimePermissionFlagsPreservedAfterReinstall() throws Exception {
+ install(APK_SYSTEM_ALERT_WINDOW_23);
+
+ int flags = FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_GRANTED_BY_ROLE;
+ setPermissionFlags(APP_SYSTEM_ALERT_WINDOW_PKG, SYSTEM_ALERT_WINDOW, flags, flags);
+ assertEquals(flags, getAllPermissionFlags(APP_SYSTEM_ALERT_WINDOW_PKG, SYSTEM_ALERT_WINDOW)
+ & flags);
+
+ install(APK_SYSTEM_ALERT_WINDOW_23);
+
+ assertEquals(flags, getAllPermissionFlags(APP_SYSTEM_ALERT_WINDOW_PKG, SYSTEM_ALERT_WINDOW)
+ & flags);
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/PermissionGroupChange.java b/tests/cts/permission/src/android/permission/cts/PermissionGroupChange.java
new file mode 100644
index 000000000..c89fcecfb
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PermissionGroupChange.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2018 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.permission.cts;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AsbSecurityTest;
+import android.widget.ScrollView;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiScrollable;
+import androidx.test.uiautomator.UiSelector;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+public class PermissionGroupChange {
+ private static final String APP_PKG_NAME = "android.permission.cts.appthatrequestpermission";
+ private static final long EXPECTED_BEHAVIOR_TIMEOUT_SEC = 15;
+ private static final long UNEXPECTED_BEHAVIOR_TIMEOUT_SEC = 2;
+
+ private Context mContext;
+ private UiDevice mUiDevice;
+ private String mAllowButtonText = null;
+
+ @Before
+ public void setContextAndUiDevice() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ @Before
+ public void uninstallAndWakeUpScreen() {
+ runShellCommand("pm uninstall " + APP_PKG_NAME);
+ SystemUtil.runShellCommand("input keyevent KEYCODE_WAKEUP");
+ }
+
+ /**
+ * Retry for a time until the runnable stops throwing.
+ *
+ * @param runnable The condition to execute
+ * @param timeoutSec The time to try
+ */
+ private void eventually(ThrowingRunnable runnable, long timeoutSec) throws Throwable {
+ long startTime = System.nanoTime();
+ while (true) {
+ try {
+ runnable.run();
+ return;
+ } catch (Throwable t) {
+ if (System.nanoTime() - startTime < TimeUnit.SECONDS.toNanos(timeoutSec)) {
+ Thread.sleep(100);
+ continue;
+ }
+
+ throw t;
+ }
+ }
+ }
+
+
+ private void scrollToBottomIfWatch() throws Exception {
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ UiScrollable scrollable = new UiScrollable(
+ new UiSelector().className(ScrollView.class));
+ if (scrollable.exists()) {
+ scrollable.flingToEnd(10);
+ }
+ }
+ }
+
+ protected void clickAllowButton() throws Exception {
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ if (mAllowButtonText == null) {
+ mAllowButtonText = getPermissionControllerString("grant_dialog_button_allow");
+ }
+ mUiDevice.findObject(By.text(Pattern.compile(Pattern.quote(mAllowButtonText),
+ Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE))).click();
+ } else {
+ mUiDevice.findObject(By.res(
+ "com.android.permissioncontroller:id/permission_allow_button")).click();
+ }
+ }
+
+ private void grantPermissionViaUi() throws Throwable {
+ eventually(() -> {
+ scrollToBottomIfWatch();
+ clickAllowButton();
+ }, EXPECTED_BEHAVIOR_TIMEOUT_SEC);
+ }
+
+ private void waitUntilPermissionGranted(String permName, long timeoutSec) throws Throwable {
+ eventually(() -> {
+ PackageInfo appInfo = mContext.getPackageManager().getPackageInfo(APP_PKG_NAME,
+ GET_PERMISSIONS);
+
+ for (int i = 0; i < appInfo.requestedPermissions.length; i++) {
+ if (appInfo.requestedPermissions[i].equals(permName)
+ && ((appInfo.requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED)
+ != 0)) {
+ return;
+ }
+ }
+
+ fail(permName + " not granted");
+ }, timeoutSec);
+ }
+
+ private void installApp(String apk) {
+ String installResult = SystemUtil.runShellCommand(
+ "pm install -r data/local/tmp/cts/permissions/" + apk + ".apk");
+ assertEquals("Success", installResult.trim());
+ }
+
+ /**
+ * Start the app. The app will request the permissions.
+ */
+ private void startApp() {
+ Intent startApp = new Intent();
+ startApp.setComponent(new ComponentName(APP_PKG_NAME, APP_PKG_NAME + ".RequestPermission"));
+ startApp.setFlags(FLAG_ACTIVITY_NEW_TASK);
+
+ mContext.startActivity(startApp);
+ }
+
+ @After
+ public void uninstallTestApp() {
+ runShellCommand("pm uninstall android.permission.cts.appthatrequestpermission");
+ }
+
+ @Test
+ @AppModeFull
+ @AsbSecurityTest(cveBugId = 72710897)
+ public void permissionGroupShouldNotBeAutoGrantedIfNewMember() throws Throwable {
+ installApp("CtsAppThatRequestsPermissionAandB");
+
+ startApp();
+ grantPermissionViaUi();
+ waitUntilPermissionGranted("android.permission.cts.appthatrequestpermission.A",
+ EXPECTED_BEHAVIOR_TIMEOUT_SEC);
+
+ // Update app which changes the permission group of "android.permission.cts
+ // .appthatrequestpermission.A" to the same as "android.permission.cts.C"
+ installApp("CtsAppThatRequestsPermissionAandC");
+
+ startApp();
+ try {
+ // The permission should not be auto-granted
+ waitUntilPermissionGranted("android.permission.cts.C", UNEXPECTED_BEHAVIOR_TIMEOUT_SEC);
+ fail("android.permission.cts.C was auto-granted");
+ } catch (Throwable expected) {
+ assertEquals("android.permission.cts.C not granted", expected.getMessage());
+ }
+ }
+
+ private String getPermissionControllerString(String res)
+ throws PackageManager.NameNotFoundException {
+ Resources permissionControllerResources = mContext.createPackageContext(
+ mContext.getPackageManager().getPermissionControllerPackageName(), 0)
+ .getResources();
+ return permissionControllerResources.getString(permissionControllerResources
+ .getIdentifier(res, "string", "com.android.permissioncontroller"));
+ }
+
+ private interface ThrowingRunnable {
+ void run() throws Throwable;
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/PermissionManagerNativeJniTest.java b/tests/cts/permission/src/android/permission/cts/PermissionManagerNativeJniTest.java
new file mode 100644
index 000000000..868b3d1fc
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PermissionManagerNativeJniTest.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 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.permission.cts;
+
+import org.junit.runner.RunWith;
+import com.android.gtestrunner.GtestRunner;
+import com.android.gtestrunner.TargetLibrary;
+
+@RunWith(GtestRunner.class)
+@TargetLibrary("permissionmanager_native_test")
+public class PermissionManagerNativeJniTest {}
+
diff --git a/tests/cts/permission/src/android/permission/cts/PermissionManagerTest.java b/tests/cts/permission/src/android/permission/cts/PermissionManagerTest.java
new file mode 100644
index 000000000..6fa940aa0
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PermissionManagerTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 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.permission.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.permission.PermissionManager;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link PermissionManager}
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Instant apps cannot talk to permission manager")
+public class PermissionManagerTest {
+ private final Context mContext = InstrumentationRegistry.getTargetContext();
+
+ @Test
+ public void testRuntimePermissionsVersion() throws Exception {
+ final PermissionManager permissionManager =
+ mContext.getSystemService(PermissionManager.class);
+ final int version = callWithShellPermissionIdentity(() ->
+ permissionManager.getRuntimePermissionsVersion());
+ assertThat(version).isAtLeast(0);
+ runWithShellPermissionIdentity(() ->
+ permissionManager.setRuntimePermissionsVersion(version));
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/PermissionStubActivity.java b/tests/cts/permission/src/android/permission/cts/PermissionStubActivity.java
new file mode 100644
index 000000000..dd611777c
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PermissionStubActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.os.Bundle;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ListView;
+
+/**
+ * A minimal application for Window test.
+ */
+public class PermissionStubActivity extends Activity {
+ private ListView mListView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mListView = new ListView(this);
+ mListView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+
+ setContentView(mListView);
+ }
+
+ public Dialog getDialog() {
+ return new AlertDialog.Builder(PermissionStubActivity.this).create();
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/PermissionUpdateListenerTest.java b/tests/cts/permission/src/android/permission/cts/PermissionUpdateListenerTest.java
new file mode 100644
index 000000000..afcd9e965
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PermissionUpdateListenerTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 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.permission.cts;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.OnPermissionsChangedListener;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.annotation.NonNull;
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@AppModeFull(reason = "Instant apps cannot access properties of other apps")
+@RunWith(AndroidJUnit4ClassRunner.class)
+public class PermissionUpdateListenerTest {
+ private static final String APK =
+ "/data/local/tmp/cts/permissions/"
+ + "CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk";
+ private static final String PACKAGE_NAME =
+ "android.permission.cts.appthatrequestcustompermission";
+ private static final String PERMISSION_NAME = "android.permission.READ_CONTACTS";
+ private static final int TIMEOUT = 30000;
+
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+ private static final PackageManager sPm = sContext.getPackageManager();
+ private static int sUid;
+
+ @BeforeClass
+ public static void installApp() throws PackageManager.NameNotFoundException {
+ runShellCommand("pm install -r " + APK);
+ sUid = sPm.getPackageUid(PACKAGE_NAME, 0);
+ }
+
+ @AfterClass
+ public static void unInstallApp() {
+ runShellCommand("pm uninstall " + PACKAGE_NAME);
+ }
+
+ private class LatchWithPermissionsChangedListener extends CountDownLatch
+ implements OnPermissionsChangedListener {
+
+ LatchWithPermissionsChangedListener() {
+ super(1);
+ }
+
+ public void onPermissionsChanged(int uid) {
+ if (uid == sUid) {
+ countDown();
+ }
+ }
+ }
+
+ private void waitForLatchAndRemoveListener(@NonNull LatchWithPermissionsChangedListener latch)
+ throws Exception {
+ latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+ runWithShellPermissionIdentity(() -> sPm.removeOnPermissionsChangeListener(latch));
+ assertThat(latch.getCount()).isEqualTo((long) 0);
+ }
+
+ @Test
+ public void grantNotifiesListener() throws Exception {
+ LatchWithPermissionsChangedListener listenerCalled =
+ new LatchWithPermissionsChangedListener();
+
+ runWithShellPermissionIdentity(() -> {
+ sPm.revokeRuntimePermission(PACKAGE_NAME, PERMISSION_NAME, sContext.getUser());
+ sPm.addOnPermissionsChangeListener(listenerCalled);
+ sPm.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME, sContext.getUser());
+ });
+ waitForLatchAndRemoveListener(listenerCalled);
+ }
+
+ @Test
+ public void revokeNotifiesListener() throws Exception {
+ LatchWithPermissionsChangedListener listenerCalled =
+ new LatchWithPermissionsChangedListener();
+
+ runWithShellPermissionIdentity(() -> {
+ sPm.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME, sContext.getUser());
+ sPm.addOnPermissionsChangeListener(listenerCalled);
+ sPm.revokeRuntimePermission(PACKAGE_NAME, PERMISSION_NAME, sContext.getUser());
+ });
+ waitForLatchAndRemoveListener(listenerCalled);
+ }
+
+ @Test
+ public void updateFlagsNotifiesListener() throws Exception {
+ LatchWithPermissionsChangedListener listenerCalled =
+ new LatchWithPermissionsChangedListener();
+
+ runWithShellPermissionIdentity(() -> {
+ sPm.addOnPermissionsChangeListener(listenerCalled);
+ int flag = PackageManager.FLAG_PERMISSION_USER_SET;
+ sPm.updatePermissionFlags(PERMISSION_NAME, PACKAGE_NAME, flag, flag,
+ sContext.getUser());
+ });
+ waitForLatchAndRemoveListener(listenerCalled);
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/PlatformPermissionGroupMappingTest.kt b/tests/cts/permission/src/android/permission/cts/PlatformPermissionGroupMappingTest.kt
new file mode 100644
index 000000000..7f84722f5
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PlatformPermissionGroupMappingTest.kt
@@ -0,0 +1,56 @@
+/*
+ * 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 android.permission.cts
+
+import android.os.Build
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import org.junit.Test
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+class PlatformPermissionGroupMappingTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context = instrumentation.context
+ private val packageManager = context.packageManager
+
+ @Test
+ fun platformPermissionHasPermissionGroup() {
+ val future = CompletableFuture<String>()
+ packageManager.getGroupOfPlatformPermission(
+ android.Manifest.permission.READ_CALENDAR, context.mainExecutor
+ ) { future.complete(it) }
+ val permissionGroupName = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ assertThat(permissionGroupName).isEqualTo(android.Manifest.permission_group.CALENDAR)
+ }
+
+ @Test
+ fun platformPermissionGroupHasPermission() {
+ val future = CompletableFuture<List<String>>()
+ packageManager.getPlatformPermissionsForGroup(
+ android.Manifest.permission_group.CALENDAR, context.mainExecutor
+ ) { future.complete(it) }
+ val permissionNames = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ assertThat(permissionNames).contains(android.Manifest.permission.READ_CALENDAR)
+ }
+
+ companion object {
+ private const val TIMEOUT_MILLIS = 15 * 1000L
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java b/tests/cts/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
new file mode 100644
index 000000000..b842cc0cf
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 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.permission.cts;
+
+import android.os.PowerManager;
+import android.test.AndroidTestCase;
+
+import java.time.Duration;
+
+public class PowerManagerServicePermissionTest extends AndroidTestCase {
+
+ public void testSetBatterySaver_requiresPermissions() {
+ PowerManager manager = getContext().getSystemService(PowerManager.class);
+ boolean batterySaverOn = manager.isPowerSaveMode();
+
+ try {
+ manager.setPowerSaveModeEnabled(!batterySaverOn);
+ fail("Toggling battery saver requires POWER_SAVER or DEVICE_POWER permission");
+ } catch (SecurityException e) {
+ // Expected Exception
+ }
+ }
+
+ public void testSetDynamicPowerSavings_requiresPermissions() {
+ try {
+ PowerManager manager = getContext().getSystemService(PowerManager.class);
+ manager.setDynamicPowerSaveHint(true, 0);
+ fail("Updating the dynamic power savings state requires the POWER_SAVER permission");
+ } catch (SecurityException e) {
+ // Expected Exception
+ }
+ }
+
+ public void testSetBatteryDischargePrediction_requiresPermissions() {
+ try {
+ PowerManager manager = getContext().getSystemService(PowerManager.class);
+ manager.setBatteryDischargePrediction(Duration.ofMillis(1000), false);
+ fail("Updating the discharge prediction requires the DEVICE_POWER"
+ + " or BATTERY_PREDICTION permission");
+ } catch (SecurityException e) {
+ // Expected Exception
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/ProviderPermissionTest.java b/tests/cts/permission/src/android/permission/cts/ProviderPermissionTest.java
new file mode 100644
index 000000000..9f5a813d1
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/ProviderPermissionTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
+import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
+
+import android.app.UiAutomation;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AppModeInstant;
+import android.provider.CallLog;
+import android.provider.Contacts;
+import android.provider.ContactsContract;
+import android.provider.Settings;
+import android.provider.Telephony;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Tests Permissions related to reading from and writing to providers
+ */
+@MediumTest
+public class ProviderPermissionTest extends AndroidTestCase {
+
+ private static final String TAG = ProviderPermissionTest.class.getSimpleName();
+
+ private static final List<Uri> CONTACT_URIS = List.of(
+ Contacts.People.CONTENT_URI, // Deprecated.
+ ContactsContract.Contacts.CONTENT_FILTER_URI,
+ ContactsContract.Contacts.CONTENT_GROUP_URI,
+ ContactsContract.Contacts.CONTENT_LOOKUP_URI,
+ ContactsContract.CommonDataKinds.Email.CONTENT_URI,
+ ContactsContract.CommonDataKinds.Email.CONTENT_FILTER_URI,
+ ContactsContract.Directory.CONTENT_URI,
+ ContactsContract.Directory.ENTERPRISE_CONTENT_URI,
+ ContactsContract.Profile.CONTENT_URI);
+
+ /**
+ * Verify that reading contacts requires permissions.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#READ_CONTACTS}
+ */
+ public void testReadContacts() {
+ for (Uri uri : CONTACT_URIS) {
+ Log.d(TAG, "Checking contacts URI " + uri);
+ assertReadingContentUriRequiresPermission(uri,
+ android.Manifest.permission.READ_CONTACTS);
+ }
+ }
+
+ /**
+ * Verify that writing contacts requires permissions.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#WRITE_CONTACTS}
+ */
+ public void testWriteContacts() {
+ assertWritingContentUriRequiresPermission(Contacts.People.CONTENT_URI,
+ android.Manifest.permission.WRITE_CONTACTS);
+ }
+
+ /**
+ * Verify that reading call logs requires permissions.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#READ_CALL_LOG}
+ */
+ @AppModeFull
+ public void testReadCallLog() {
+ assertReadingContentUriRequiresPermission(CallLog.CONTENT_URI,
+ android.Manifest.permission.READ_CALL_LOG);
+ }
+
+ /**
+ * Verify that writing call logs requires permissions.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#WRITE_CALL_LOG}
+ */
+ @AppModeFull
+ public void testWriteCallLog() {
+ assertWritingContentUriRequiresPermission(CallLog.CONTENT_URI,
+ android.Manifest.permission.WRITE_CALL_LOG);
+ }
+
+ /**
+ * Verify that reading from call-log (a content provider that is not accessible to instant apps)
+ * returns null
+ */
+ @AppModeInstant
+ public void testReadCallLogInstant() {
+ assertNull(getContext().getContentResolver().query(CallLog.CONTENT_URI, null, null, null,
+ null));
+ }
+
+ /**
+ * Verify that writing to call-log (a content provider that is not accessible to instant apps)
+ * yields an IAE.
+ */
+ @AppModeInstant
+ public void testWriteCallLogInstant() {
+ try {
+ getContext().getContentResolver().insert(CallLog.CONTENT_URI, new ContentValues());
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ /**
+ * Verify that reading already received SMS messages requires permissions.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#READ_SMS}
+ *
+ * <p>Note: The WRITE_SMS permission has been removed.
+ */
+ @AppModeFull
+ public void testReadSms() {
+ if (!mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ assertReadingContentUriRequiresPermission(Telephony.Sms.CONTENT_URI,
+ android.Manifest.permission.READ_SMS);
+ }
+
+ /**
+ * Verify that reading from 'sms' (a content provider that is not accessible to instant apps)
+ * returns null
+ */
+ @AppModeInstant
+ public void testReadSmsInstant() {
+ if (!mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ assertNull(getContext().getContentResolver().query(Telephony.Sms.CONTENT_URI, null, null,
+ null, null));
+ }
+
+ /**
+ * Verify that write to settings requires permissions.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#WRITE_SETTINGS}
+ */
+ public void testWriteSettings() {
+ final String permission = android.Manifest.permission.WRITE_SETTINGS;
+ ContentValues value = new ContentValues();
+ value.put(Settings.System.NAME, "name");
+ value.put(Settings.System.VALUE, "value_insert");
+
+ try {
+ getContext().getContentResolver().insert(Settings.System.CONTENT_URI, value);
+ fail("expected SecurityException requiring " + permission);
+ } catch (SecurityException expected) {
+ assertNotNull("security exception's error message.", expected.getMessage());
+ assertTrue("error message should contain \"" + permission + "\". Got: \""
+ + expected.getMessage() + "\".",
+ expected.getMessage().contains(permission));
+ }
+ }
+
+ /**
+ * Verify that the {@link android.Manifest.permission#MANAGE_DOCUMENTS}
+ * permission is only held by up to one package: whoever handles the
+ * {@link android.content.Intent#ACTION_OPEN_DOCUMENT} intent, if any.
+ * <p>
+ * No other apps should <em>ever</em> attempt to acquire this permission,
+ * since it would give those apps extremely broad access to all storage
+ * providers on the device without user involvement in the arbitration
+ * process. Apps should instead always rely on Uri permission grants for
+ * access, using
+ * {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} and related
+ * APIs.
+ */
+ public void testManageDocuments() {
+ final PackageManager pm = getContext().getPackageManager();
+
+ final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("*/*");
+ final ResolveInfo ri = pm.resolveActivity(intent, 0);
+
+ if (ri != null) {
+ final String validPkg = ri.activityInfo.packageName;
+
+ final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
+ android.Manifest.permission.MANAGE_DOCUMENTS
+ }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+ for (PackageInfo pi : holding) {
+ if (!Objects.equals(pi.packageName, validPkg)) {
+ fail("Exactly one package (must be " + validPkg
+ + ") can request the MANAGE_DOCUMENTS permission; found package "
+ + pi.packageName + " which must be revoked for security reasons");
+ }
+ }
+ }
+ }
+
+ /**
+ * The {@link android.Manifest.permission#WRITE_MEDIA_STORAGE} permission is
+ * a very powerful permission that grants raw storage access to all devices,
+ * and as such it's only appropriate to be granted to the media stack.
+ * <p>
+ * CDD now requires that all apps requesting this permission also hold the
+ * "Storage" runtime permission, to give users visibility into the
+ * capabilities of each app, and control over those capabilities.
+ * <p>
+ * If the end user revokes the "Storage" permission from an app, but that
+ * app still has raw access to storage via {@code WRITE_MEDIA_STORAGE}, that
+ * would be a CDD violation and a privacy incident.
+ */
+ public void testWriteMediaStorage() throws Exception {
+ final UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ final PackageManager pm = getContext().getPackageManager();
+ final UserHandle userHandle = getContext().getUser();
+ final List<PackageInfo> pkgs = pm.getInstalledPackages(
+ PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_PERMISSIONS);
+ for (PackageInfo pkg : pkgs) {
+ final int appUid = userHandle.getAppId(pkg.applicationInfo.uid);
+ final boolean isSystem = appUid == android.os.Process.SYSTEM_UID;
+ final boolean hasFrontDoor = pm.getLaunchIntentForPackage(pkg.packageName) != null;
+ final boolean grantedMedia = pm.checkPermission(WRITE_MEDIA_STORAGE,
+ pkg.packageName) == PackageManager.PERMISSION_GRANTED;
+
+ if (!isSystem && hasFrontDoor && grantedMedia) {
+ final boolean requestsStorage = contains(pkg.requestedPermissions,
+ MANAGE_EXTERNAL_STORAGE);
+ if (!requestsStorage) {
+ fail("Found " + pkg.packageName + " holding WRITE_MEDIA_STORAGE permission "
+ + "without also requesting MANAGE_EXTERNAL_STORAGE; these permissions "
+ + "must be requested together");
+ }
+
+ final boolean grantedStorage = pm.checkPermission(MANAGE_EXTERNAL_STORAGE,
+ pkg.packageName) == PackageManager.PERMISSION_GRANTED;
+ if (grantedStorage) {
+ final int flags;
+ ui.adoptShellPermissionIdentity("android.permission.GET_RUNTIME_PERMISSIONS");
+ try {
+ flags = pm.getPermissionFlags(MANAGE_EXTERNAL_STORAGE, pkg.packageName,
+ android.os.Process.myUserHandle());
+ } finally {
+ ui.dropShellPermissionIdentity();
+ }
+
+ final boolean isFixed = (flags & (PackageManager.FLAG_PERMISSION_USER_FIXED
+ | PackageManager.FLAG_PERMISSION_POLICY_FIXED
+ | PackageManager.FLAG_PERMISSION_SYSTEM_FIXED)) != 0;
+ if (isFixed) {
+ fail("Found " + pkg.packageName + " holding MANAGE_EXTERNAL_STORAGE in a "
+ + "fixed state; this permission must be revokable by the user");
+ }
+ }
+ }
+ }
+ }
+
+ private static boolean contains(String[] haystack, String needle) {
+ if (haystack != null) {
+ for (String test : haystack) {
+ if (Objects.equals(test, needle)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/RebootPermissionTest.java b/tests/cts/permission/src/android/permission/cts/RebootPermissionTest.java
new file mode 100644
index 000000000..b1d3d5afb
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/RebootPermissionTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2009 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.permission.cts;
+
+import android.content.Intent;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Verify that rebooting requires Permission.
+ */
+public class RebootPermissionTest extends AndroidTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ /**
+ * Verify that rebooting by sending a broadcast Intent requires Permission.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#REBOOT}.
+ */
+ @SmallTest
+ public void testBroadcastReboot() {
+ try {
+ mContext.sendBroadcast(new Intent(Intent.ACTION_REBOOT));
+ fail("SecurityException expected!");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+}
diff --git a/tests/cts/permission/src/android/permission/cts/RemovePermissionTest.java b/tests/cts/permission/src/android/permission/cts/RemovePermissionTest.java
new file mode 100644
index 000000000..fa53ee999
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/RemovePermissionTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2019 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.permission.cts;
+
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AsbSecurityTest;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+
+@AppModeFull(reason = "Instant apps cannot read state of other packages.")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+public class RemovePermissionTest extends StsExtraBusinessLogicTestCase {
+ private static final String APP_PKG_NAME_BASE =
+ "android.permission.cts.revokepermissionwhenremoved";
+ private static final String ADVERSARIAL_PERMISSION_DEFINER_PKG_NAME =
+ APP_PKG_NAME_BASE + ".AdversarialPermissionDefinerApp";
+ private static final String VICTIM_PERMISSION_DEFINER_PKG_NAME =
+ APP_PKG_NAME_BASE + ".VictimPermissionDefinerApp";
+ private static final String ADVERSARIAL_PERMISSION_USER_PKG_NAME =
+ APP_PKG_NAME_BASE + ".userapp";
+ private static final String RUNTIME_PERMISSION_USER_PKG_NAME =
+ APP_PKG_NAME_BASE + ".runtimepermissionuserapp";
+ private static final String RUNTIME_PERMISSION_DEFINER_PKG_NAME =
+ APP_PKG_NAME_BASE + ".runtimepermissiondefinerapp";
+ private static final String INSTALL_PERMISSION_USER_PKG_NAME =
+ APP_PKG_NAME_BASE + ".installpermissionuserapp";
+ private static final String INSTALL_PERMISSION_DEFINER_PKG_NAME =
+ APP_PKG_NAME_BASE + ".installpermissiondefinerapp";
+ private static final String INSTALL_PERMISSION_ESCALATOR_PKG_NAME =
+ APP_PKG_NAME_BASE + ".installpermissionescalatorapp";
+
+ private static final String TEST_PERMISSION =
+ "android.permission.cts.revokepermissionwhenremoved.TestPermission";
+ private static final String TEST_RUNTIME_PERMISSION =
+ APP_PKG_NAME_BASE + ".TestRuntimePermission";
+ private static final String TEST_INSTALL_PERMISSION =
+ APP_PKG_NAME_BASE + ".TestInstallPermission";
+
+ private static final String ADVERSARIAL_PERMISSION_DEFINER_APK_NAME =
+ "CtsAdversarialPermissionDefinerApp";
+ private static final String ADVERSARIAL_PERMISSION_USER_APK_NAME =
+ "CtsAdversarialPermissionUserApp";
+ private static final String VICTIM_PERMISSION_DEFINER_APK_NAME =
+ "CtsVictimPermissionDefinerApp";
+ private static final String RUNTIME_PERMISSION_DEFINER_APK_NAME =
+ "CtsRuntimePermissionDefinerApp";
+ private static final String RUNTIME_PERMISSION_USER_APK_NAME =
+ "CtsRuntimePermissionUserApp";
+ private static final String INSTALL_PERMISSION_DEFINER_APK_NAME =
+ "CtsInstallPermissionDefinerApp";
+ private static final String INSTALL_PERMISSION_USER_APK_NAME =
+ "CtsInstallPermissionUserApp";
+ private static final String INSTALL_PERMISSION_ESCALATOR_APK_NAME =
+ "CtsInstallPermissionEscalatorApp";
+
+ private Context mContext;
+ private Instrumentation mInstrumentation;
+
+ @Before
+ public void setContextAndInstrumentation() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ }
+
+ @Before
+ public void wakeUpScreen() {
+ SystemUtil.runShellCommand("input keyevent KEYCODE_WAKEUP");
+ }
+
+ @After
+ public void cleanUpTestApps() throws Exception {
+ uninstallApp(ADVERSARIAL_PERMISSION_DEFINER_PKG_NAME, true);
+ uninstallApp(ADVERSARIAL_PERMISSION_USER_PKG_NAME, true);
+ uninstallApp(VICTIM_PERMISSION_DEFINER_PKG_NAME, true);
+ uninstallApp(RUNTIME_PERMISSION_DEFINER_PKG_NAME, true);
+ uninstallApp(RUNTIME_PERMISSION_USER_PKG_NAME, true);
+ uninstallApp(INSTALL_PERMISSION_USER_PKG_NAME, true);
+ uninstallApp(INSTALL_PERMISSION_DEFINER_PKG_NAME, true);
+ uninstallApp(INSTALL_PERMISSION_ESCALATOR_PKG_NAME, true);
+ Thread.sleep(5000);
+ }
+
+ private boolean permissionGranted(String pkgName, String permName)
+ throws PackageManager.NameNotFoundException {
+ PackageInfo appInfo = mContext.getPackageManager().getPackageInfo(pkgName,
+ GET_PERMISSIONS);
+
+ for (int i = 0; i < appInfo.requestedPermissions.length; i++) {
+ if (appInfo.requestedPermissions[i].equals(permName)
+ && ((appInfo.requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED)
+ != 0)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void installApp(String apk) throws InterruptedException {
+ String installResult = SystemUtil.runShellCommand(
+ "pm install -r -d data/local/tmp/cts/permissions/" + apk + ".apk");
+ assertEquals("Success", installResult.trim());
+ Thread.sleep(5000);
+ }
+
+ private void uninstallApp(String pkg) throws InterruptedException {
+ uninstallApp(pkg, false);
+ }
+
+ private void uninstallApp(String pkg, boolean cleanUp) throws InterruptedException {
+ String uninstallResult = SystemUtil.runShellCommand("pm uninstall " + pkg);
+ if (!cleanUp) {
+ assertEquals("Success", uninstallResult.trim());
+ Thread.sleep(5000);
+ }
+ }
+
+ private void grantPermission(String pkg, String permission) {
+ mInstrumentation.getUiAutomation().grantRuntimePermission(
+ pkg, permission);
+ }
+
+ @Test
+ @AsbSecurityTest(cveBugId = 67319274)
+ public void runtimePermissionShouldBeRevokedIfRemoved() throws Throwable {
+ installApp(ADVERSARIAL_PERMISSION_DEFINER_APK_NAME);
+ installApp(ADVERSARIAL_PERMISSION_USER_APK_NAME);
+
+ grantPermission(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION);
+ assertTrue(permissionGranted(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION));
+
+ // Uninstall app which defines a permission with the same name as in victim app.
+ // Install the victim app.
+ uninstallApp(ADVERSARIAL_PERMISSION_DEFINER_PKG_NAME);
+ installApp(VICTIM_PERMISSION_DEFINER_APK_NAME);
+ assertFalse(permissionGranted(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION));
+ }
+
+ @Test
+ public void runtimePermissionShouldRemainGrantedAfterAppUpdate() throws Throwable {
+ installApp(RUNTIME_PERMISSION_DEFINER_APK_NAME);
+ installApp(RUNTIME_PERMISSION_USER_APK_NAME);
+
+ grantPermission(RUNTIME_PERMISSION_USER_PKG_NAME, TEST_RUNTIME_PERMISSION);
+ assertTrue(permissionGranted(RUNTIME_PERMISSION_USER_PKG_NAME, TEST_RUNTIME_PERMISSION));
+
+ // Install app which defines a permission. This is similar to update the app
+ // operation
+ installApp(RUNTIME_PERMISSION_DEFINER_APK_NAME);
+ assertTrue(permissionGranted(RUNTIME_PERMISSION_USER_PKG_NAME, TEST_RUNTIME_PERMISSION));
+ }
+
+ @Test
+ public void runtimePermissionDependencyTest() throws Throwable {
+ installApp(ADVERSARIAL_PERMISSION_USER_APK_NAME);
+ // Should fail to grant permission because its definer is not installed yet
+ try {
+ grantPermission(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION);
+ fail("Should have thrown security exception above");
+ } catch (SecurityException expected) {
+ }
+ assertFalse(permissionGranted(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION));
+ // Now install the permission definer; should be able to grant permission to user package
+ installApp(ADVERSARIAL_PERMISSION_DEFINER_APK_NAME);
+ grantPermission(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION);
+ assertTrue(permissionGranted(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION));
+ // Now uninstall the permission definer; the user packages' permission should be revoked
+ uninstallApp(ADVERSARIAL_PERMISSION_DEFINER_PKG_NAME);
+ assertFalse(permissionGranted(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION));
+ }
+
+ @Test
+ @AsbSecurityTest(cveBugId = 155648771)
+ public void installPermissionShouldBeRevokedIfRemoved() throws Throwable {
+ installApp(INSTALL_PERMISSION_DEFINER_APK_NAME);
+ installApp(INSTALL_PERMISSION_USER_APK_NAME);
+ assertTrue(permissionGranted(INSTALL_PERMISSION_USER_PKG_NAME, TEST_INSTALL_PERMISSION));
+
+ // Uninstall the app which defines the install permission, and install another app
+ // redefining it as a runtime permission.
+ uninstallApp(INSTALL_PERMISSION_DEFINER_PKG_NAME);
+ installApp(INSTALL_PERMISSION_ESCALATOR_APK_NAME);
+ assertFalse(permissionGranted(INSTALL_PERMISSION_USER_PKG_NAME, TEST_INSTALL_PERMISSION));
+ }
+
+ @Test
+ public void installPermissionShouldRemainGrantedAfterAppUpdate() throws Throwable {
+ installApp(INSTALL_PERMISSION_DEFINER_APK_NAME);
+ installApp(INSTALL_PERMISSION_USER_APK_NAME);
+ assertTrue(permissionGranted(INSTALL_PERMISSION_USER_PKG_NAME, TEST_INSTALL_PERMISSION));
+
+ // Install the app which defines the install permission again, similar to updating the app.
+ installApp(INSTALL_PERMISSION_DEFINER_APK_NAME);
+ assertTrue(permissionGranted(INSTALL_PERMISSION_USER_PKG_NAME, TEST_INSTALL_PERMISSION));
+ }
+
+ @Test
+ public void installPermissionDependencyTest() throws Throwable {
+ installApp(INSTALL_PERMISSION_USER_APK_NAME);
+ // Should not have the permission auto-granted
+ assertFalse(permissionGranted(INSTALL_PERMISSION_USER_PKG_NAME, TEST_INSTALL_PERMISSION));
+
+ // Now install the permission definer; user package should have the permission auto granted
+ installApp(INSTALL_PERMISSION_DEFINER_APK_NAME);
+ installApp(INSTALL_PERMISSION_USER_APK_NAME);
+ assertTrue(permissionGranted(INSTALL_PERMISSION_USER_PKG_NAME, TEST_INSTALL_PERMISSION));
+
+ // Now uninstall the permission definer; the user package's permission should be revoked
+ uninstallApp(INSTALL_PERMISSION_DEFINER_PKG_NAME);
+ assertFalse(permissionGranted(INSTALL_PERMISSION_USER_PKG_NAME, TEST_INSTALL_PERMISSION));
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/RevokePermissionTest.kt b/tests/cts/permission/src/android/permission/cts/RevokePermissionTest.kt
new file mode 100644
index 000000000..9f6488137
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/RevokePermissionTest.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2020 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.permission.cts
+
+import android.Manifest.permission.CAMERA
+import android.Manifest.permission.READ_CALENDAR
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.os.Process
+import android.platform.test.annotations.AppModeFull
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+
+class RevokePermissionTest {
+
+ private val APP_PKG_NAME = "android.permission.cts.appthatrequestcustompermission"
+ private val APK = "/data/local/tmp/cts/permissions/" +
+ "CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk"
+
+ @Before
+ fun installApp() {
+ runShellCommand("pm install -r -g $APK")
+ }
+
+ @Test
+ @AppModeFull(reason = "Instant apps can't revoke permissions.")
+ fun testRevokePermission() {
+ testRevoke(
+ packageName = APP_PKG_NAME,
+ permission = READ_CALENDAR,
+ isGranted = true)
+ }
+
+ @Test
+ @AppModeFull(reason = "Instant apps can't revoke permissions.")
+ fun testRevokePermissionNotRequested() {
+ testRevoke(
+ packageName = APP_PKG_NAME,
+ permission = CAMERA,
+ throwableType = SecurityException::class.java,
+ throwableMessages = listOf(
+ "has not requested permission",
+ "Permission $CAMERA isn't requested by package $APP_PKG_NAME"
+ ))
+ }
+
+ @Test
+ @AppModeFull(reason = "Instant apps can't revoke permissions.")
+ fun testRevokeFakePermission() {
+ val fakePermissionName = "FAKE_PERMISSION"
+ testRevoke(
+ packageName = APP_PKG_NAME,
+ permission = fakePermissionName,
+ throwableType = java.lang.IllegalArgumentException::class.java,
+ throwableMessages = listOf(
+ "Unknown permission: $fakePermissionName",
+ "Unknown permission $fakePermissionName"
+ ))
+ }
+
+ @Test
+ @AppModeFull(reason = "Instant apps can't revoke permissions.")
+ fun testRevokeFakePackage() {
+ val fakePackageName = "fake.package.name.which.should.not.exist"
+ assertPackageNotInstalled(fakePackageName)
+ testRevoke(
+ packageName = fakePackageName,
+ permission = READ_CALENDAR)
+ }
+
+ @Test
+ @AppModeFull(reason = "Instant apps can't revoke permissions.")
+ fun testRevokePermissionWithReason() {
+ testRevoke(
+ packageName = APP_PKG_NAME,
+ permission = READ_CALENDAR,
+ reason = "test reason",
+ isGranted = true)
+ }
+
+ @Test
+ @AppModeFull(reason = "Instant apps can't revoke permissions.")
+ fun testRevokePermissionNotRequestedWithReason() {
+ testRevoke(
+ packageName = APP_PKG_NAME,
+ permission = CAMERA,
+ reason = "test reason",
+ throwableType = SecurityException::class.java,
+ throwableMessages = listOf(
+ "has not requested permission",
+ "Permission $CAMERA isn't requested by package $APP_PKG_NAME"
+ ))
+ }
+
+ @Test
+ @AppModeFull(reason = "Instant apps can't revoke permissions.")
+ fun testRevokeFakePermissionWithReason() {
+ val fakePermissionName = "FAKE_PERMISSION"
+ testRevoke(
+ packageName = APP_PKG_NAME,
+ permission = fakePermissionName,
+ reason = "test reason",
+ throwableType = java.lang.IllegalArgumentException::class.java,
+ throwableMessages = listOf(
+ "Unknown permission: $fakePermissionName",
+ "Unknown permission $fakePermissionName"
+ ))
+ }
+
+ @Test
+ @AppModeFull(reason = "Instant apps can't revoke permissions.")
+ fun testRevokeFakePackageWithReason() {
+ val fakePackageName = "fake.package.name.which.should.not.exist"
+ assertPackageNotInstalled(fakePackageName)
+ testRevoke(
+ packageName = fakePackageName,
+ permission = READ_CALENDAR,
+ reason = "test reason")
+ }
+
+ @After
+ fun uninstallApp() {
+ runShellCommand("pm uninstall $APP_PKG_NAME")
+ }
+
+ private fun testRevoke(
+ packageName: String,
+ permission: String,
+ reason: String? = null,
+ isGranted: Boolean = false,
+ throwableType: Class<*>? = null,
+ throwableMessages: List<String> = listOf("")
+ ) {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ val pm = context.packageManager
+
+ if (isGranted) {
+ assertEquals(PERMISSION_GRANTED, pm.checkPermission(READ_CALENDAR, APP_PKG_NAME))
+ }
+
+ runWithShellPermissionIdentity {
+ if (throwableType == null) {
+ if (reason == null) {
+ pm.revokeRuntimePermission(packageName, permission, Process.myUserHandle())
+ } else {
+ pm.revokeRuntimePermission(packageName, permission, Process.myUserHandle(),
+ reason)
+ }
+ } else {
+ try {
+ if (reason == null) {
+ pm.revokeRuntimePermission(packageName, permission, Process.myUserHandle())
+ } else {
+ pm.revokeRuntimePermission(packageName, permission, Process.myUserHandle(),
+ reason)
+ }
+ } catch (t: Throwable) {
+ if (t::class.java.name == throwableType.name &&
+ throwableMessages.any { t.message!!.contains(it) }) {
+ return@runWithShellPermissionIdentity
+ }
+ throw RuntimeException("Unexpected throwable", t)
+ }
+ throw RuntimeException("revokeRuntimePermission expected to throw.")
+ }
+ }
+ }
+
+ private fun assertPackageNotInstalled(packageName: String) {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ val pm = context.packageManager
+ try {
+ pm.getPackageInfo(packageName, 0)
+ throw RuntimeException("$packageName exists on this device")
+ } catch (e: PackageManager.NameNotFoundException) {
+ // Expected
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/RevokeSawPermissionTest.kt b/tests/cts/permission/src/android/permission/cts/RevokeSawPermissionTest.kt
new file mode 100644
index 000000000..559b16c65
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/RevokeSawPermissionTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts
+
+import android.content.pm.PackageManager
+import android.platform.test.annotations.AsbSecurityTest
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.After
+import org.junit.Assert
+import org.junit.Test
+
+private val APP_PKG_NAME = "android.permission.cts.usesystemalertwindowpermission"
+private val APK_22 = "/data/local/tmp/cts/permissions/" +
+ "CtsAppThatRequestsSystemAlertWindow22.apk"
+private val APK_23 = "/data/local/tmp/cts/permissions/" +
+ "CtsAppThatRequestsSystemAlertWindow23.apk"
+
+class RevokeSawPermissionTest {
+
+ fun installApp(apk: String) {
+ PermissionUtils.install(apk)
+ }
+
+ @After
+ fun uninstallApp() {
+ PermissionUtils.uninstallApp(APP_PKG_NAME)
+ }
+
+ @AsbSecurityTest(cveBugId = [221040577L])
+ @Test
+ fun testPre23AppsWithSystemAlertWindowGetDeniedOnUpgrade() {
+ installApp(APK_22)
+ assertAppHasPermission(android.Manifest.permission.SYSTEM_ALERT_WINDOW, true)
+ installApp(APK_23)
+ assertAppHasPermission(android.Manifest.permission.SYSTEM_ALERT_WINDOW, false)
+ }
+
+ private fun assertAppHasPermission(permissionName: String, expectPermission: Boolean) {
+ Assert.assertEquals(
+ if (expectPermission) {
+ PackageManager.PERMISSION_GRANTED
+ } else {
+ PackageManager.PERMISSION_DENIED
+ },
+ InstrumentationRegistry.getInstrumentation().getTargetContext().packageManager
+ .checkPermission(permissionName, APP_PKG_NAME)
+ )
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/RevokeSelfPermissionTest.java b/tests/cts/permission/src/android/permission/cts/RevokeSelfPermissionTest.java
new file mode 100644
index 000000000..e57c7908f
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/RevokeSelfPermissionTest.java
@@ -0,0 +1,360 @@
+/*
+ * 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 android.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.CAMERA;
+import static android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.permission.cts.PermissionUtils.getPermissionFlags;
+import static android.permission.cts.PermissionUtils.grantPermission;
+import static android.permission.cts.PermissionUtils.setPermissionFlags;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.os.Build;
+import android.os.Process;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+@AppModeFull(reason = "Null permission info in instant mode")
+public class RevokeSelfPermissionTest {
+ private static final String APP_PKG_NAME =
+ "android.permission.cts.apptotestrevokeselfpermission";
+ private static final String APK =
+ "/data/local/tmp/cts/permissions/CtsAppToTestRevokeSelfPermission.apk";
+ private static final long ONE_TIME_TIMEOUT_MILLIS = 500;
+ private static final long ONE_TIME_TIMER_UPPER_GRACE_PERIOD = 5000;
+
+ private final Instrumentation mInstrumentation =
+ InstrumentationRegistry.getInstrumentation();
+ private final Context mContext = mInstrumentation.getTargetContext();
+ private final ActivityManager mActivityManager =
+ mContext.getSystemService(ActivityManager.class);
+ private final UiDevice mUiDevice = UiDevice.getInstance(mInstrumentation);
+ private String mOldOneTimePermissionTimeoutValue;
+ private String mOldScreenOffTimeoutValue;
+ private String mOldSleepTimeoutValue;
+
+ @Before
+ public void wakeUpScreen() {
+ SystemUtil.runShellCommand("input keyevent KEYCODE_WAKEUP");
+ SystemUtil.runShellCommand("input keyevent 82");
+ mOldScreenOffTimeoutValue = SystemUtil.runShellCommand(
+ "settings get system screen_off_timeout");
+ mOldSleepTimeoutValue = SystemUtil.runShellCommand("settings get secure sleep_timeout");
+ SystemUtil.runShellCommand("settings put system screen_off_timeout -1");
+ SystemUtil.runShellCommand("settings put secure sleep_timeout -1");
+ }
+
+ @Before
+ public void prepareDeviceForOneTime() {
+ runWithShellPermissionIdentity(() -> {
+ mOldOneTimePermissionTimeoutValue = DeviceConfig.getProperty("permissions",
+ "one_time_permissions_timeout_millis");
+ DeviceConfig.setProperty("permissions", "one_time_permissions_timeout_millis",
+ Long.toString(ONE_TIME_TIMEOUT_MILLIS), false);
+ });
+ }
+
+ @After
+ public void uninstallApp() {
+ runShellCommand("pm uninstall " + APP_PKG_NAME);
+ }
+
+ @After
+ public void restoreDeviceForOneTime() {
+ runWithShellPermissionIdentity(() -> {
+ DeviceConfig.setProperty("permissions", "one_time_permissions_timeout_millis",
+ mOldOneTimePermissionTimeoutValue, false);
+ });
+ SystemUtil.runShellCommand("settings put system screen_off_timeout "
+ + mOldScreenOffTimeoutValue);
+ SystemUtil.runShellCommand("settings put secure sleep_timeout " + mOldSleepTimeoutValue);
+ }
+
+ @Test
+ public void testMultiplePermissions() throws Throwable {
+ // Trying to revoke multiple permissions including some from the same permission group
+ // should work.
+ installApp();
+ String[] permissions = new String[] {ACCESS_COARSE_LOCATION, ACCESS_BACKGROUND_LOCATION,
+ CAMERA};
+ for (String permission : permissions) {
+ grantPermission(APP_PKG_NAME, permission);
+ assertGranted(ONE_TIME_TIMER_UPPER_GRACE_PERIOD, permission);
+ }
+ revokePermissions(permissions);
+ placeAppInBackground();
+ for (String permission : permissions) {
+ assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ permission);
+ }
+ uninstallApp();
+ }
+
+ @Test
+ public void testNormalPermission() throws Throwable {
+ // Trying to revoke a normal (non-runtime) permission should not actually revoke it.
+ installApp();
+ revokePermission(HIGH_SAMPLING_RATE_SENSORS);
+ placeAppInBackground();
+ try {
+ waitUntilPermissionRevoked(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ HIGH_SAMPLING_RATE_SENSORS);
+ fail("android.permission.HIGH_SAMPLING_RATE_SENSORS was revoked");
+ } catch (Throwable expected) {
+ assertEquals(HIGH_SAMPLING_RATE_SENSORS + " not revoked",
+ expected.getMessage());
+ }
+ uninstallApp();
+ }
+
+ @Test
+ public void testKillTriggersRevocation() throws Throwable {
+ // Killing the process should start the revocation right away
+ installApp();
+ grantPermission(APP_PKG_NAME, ACCESS_FINE_LOCATION);
+ assertGranted(ONE_TIME_TIMER_UPPER_GRACE_PERIOD, ACCESS_FINE_LOCATION);
+ revokePermission(ACCESS_FINE_LOCATION);
+ killApp();
+ assertDenied(ONE_TIME_TIMER_UPPER_GRACE_PERIOD, ACCESS_FINE_LOCATION);
+ uninstallApp();
+ }
+
+ @Test
+ public void testNoRevocationWhileForeground() throws Throwable {
+ // Even after calling revokeSelfPermissionOnKill, the permission should stay granted while
+ // the package is in the foreground.
+ installApp();
+ grantPermission(APP_PKG_NAME, ACCESS_FINE_LOCATION);
+ assertGranted(ONE_TIME_TIMER_UPPER_GRACE_PERIOD, ACCESS_FINE_LOCATION);
+ revokePermission(ACCESS_FINE_LOCATION);
+ keepAppInForeground(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD);
+ try {
+ waitUntilPermissionRevoked(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_FINE_LOCATION);
+ fail("android.permission.ACCESS_FINE_LOCATION was revoked");
+ } catch (Throwable expected) {
+ assertEquals(ACCESS_FINE_LOCATION + " not revoked",
+ expected.getMessage());
+ }
+ uninstallApp();
+ }
+
+ @Test
+ public void testRevokeLocationPermission() throws Throwable {
+ // Test behavior specific to location group: revoking fine location should not revoke coarse
+ // location, and background location should not be revoked as long as a foreground
+ // permission is still granted
+ installApp();
+ grantPermission(APP_PKG_NAME, ACCESS_COARSE_LOCATION);
+ assertGranted(ONE_TIME_TIMER_UPPER_GRACE_PERIOD, ACCESS_COARSE_LOCATION);
+ grantPermission(APP_PKG_NAME, ACCESS_FINE_LOCATION);
+ assertGranted(ONE_TIME_TIMER_UPPER_GRACE_PERIOD, ACCESS_FINE_LOCATION);
+ grantPermission(APP_PKG_NAME, ACCESS_BACKGROUND_LOCATION);
+ assertGranted(ONE_TIME_TIMER_UPPER_GRACE_PERIOD, ACCESS_BACKGROUND_LOCATION);
+ revokePermission(ACCESS_BACKGROUND_LOCATION);
+ killApp();
+ assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_BACKGROUND_LOCATION);
+ assertGranted(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_COARSE_LOCATION);
+ assertGranted(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_FINE_LOCATION);
+ grantPermission(APP_PKG_NAME, ACCESS_BACKGROUND_LOCATION);
+ setPermissionFlags(APP_PKG_NAME, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_ONE_TIME, 0);
+ assertGranted(ONE_TIME_TIMER_UPPER_GRACE_PERIOD, ACCESS_BACKGROUND_LOCATION);
+ revokePermission(ACCESS_FINE_LOCATION);
+ killApp();
+ assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_FINE_LOCATION);
+ assertGranted(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_COARSE_LOCATION);
+ assertGranted(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_BACKGROUND_LOCATION);
+ revokePermission(ACCESS_COARSE_LOCATION);
+ killApp();
+ assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_COARSE_LOCATION);
+ assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_BACKGROUND_LOCATION);
+ uninstallApp();
+ }
+
+ @Test
+ public void testNoRepromptWhenUserFixed() throws Throwable {
+ // If a permission has been USER_FIXED to not granted, then revoking the permission group
+ // should leave the USER_FIXED flag.
+ installApp();
+ grantPermission(APP_PKG_NAME, ACCESS_FINE_LOCATION);
+ setPermissionFlags(APP_PKG_NAME, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_FIXED,
+ FLAG_PERMISSION_USER_FIXED);
+ revokePermission(ACCESS_FINE_LOCATION);
+ placeAppInBackground();
+ assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+ ACCESS_FINE_LOCATION);
+ int flags = getPermissionFlags(APP_PKG_NAME, ACCESS_BACKGROUND_LOCATION);
+ assertEquals(FLAG_PERMISSION_USER_FIXED, flags & FLAG_PERMISSION_USER_FIXED);
+ uninstallApp();
+ }
+
+
+ private void installApp() {
+ runShellCommand("pm install -r " + APK);
+ }
+
+ private void keepAppInForeground(long timeoutMillis) {
+ new Thread(() -> {
+ long start = System.currentTimeMillis();
+ while (System.currentTimeMillis() < start + timeoutMillis) {
+ runWithShellPermissionIdentity(() -> {
+ if (mActivityManager.getPackageImportance(APP_PKG_NAME)
+ > IMPORTANCE_FOREGROUND) {
+ runShellCommand("am start-activity -W -n " + APP_PKG_NAME
+ + "/.RevokePermission");
+ }
+ });
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+ }
+ }).start();
+ }
+
+ private void placeAppInBackground() {
+ boolean[] hasExited = {false};
+ try {
+ new Thread(() -> {
+ while (!hasExited[0]) {
+ mUiDevice.pressHome();
+ mUiDevice.pressBack();
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ }
+ }
+ }).start();
+ eventually(() -> {
+ runWithShellPermissionIdentity(() -> {
+ if (mActivityManager.getPackageImportance(APP_PKG_NAME)
+ <= IMPORTANCE_FOREGROUND) {
+ throw new AssertionError("Unable to exit application");
+ }
+ });
+ });
+ } finally {
+ hasExited[0] = true;
+ }
+ }
+
+ /**
+ * Start the app. The app will revoke the permission.
+ */
+ private void revokePermission(String permName) {
+ revokePermissions(new String[] { permName });
+ }
+
+ private void revokePermissions(String[] permissions) {
+ runShellCommand("am start-activity -W -n " + APP_PKG_NAME + "/.RevokePermission"
+ + " --esa permissions " + String.join(",", permissions));
+ PackageManager pkgMgr = mContext.getPackageManager();
+ eventually(() -> runWithShellPermissionIdentity(() -> {
+ for (int i = 0; i < permissions.length; i++) {
+ if ((pkgMgr.getPermissionInfo(permissions[i], 0).getProtection()
+ & PermissionInfo.PROTECTION_DANGEROUS) != 0) {
+ int permissionFlags = pkgMgr.getPermissionFlags(permissions[i], APP_PKG_NAME,
+ Process.myUserHandle());
+ Assert.assertTrue((permissionFlags & FLAG_PERMISSION_ONE_TIME) != 0);
+ }
+ }
+ }));
+ }
+
+ private void killApp() {
+ runShellCommand("am force-stop " + APP_PKG_NAME);
+ }
+
+ private void assertGrantedState(String s, String permissionName, int permissionGranted,
+ long timeoutMillis) {
+ eventually(() -> Assert.assertEquals(s, permissionGranted,
+ mContext.getPackageManager().checkPermission(permissionName, APP_PKG_NAME)),
+ timeoutMillis);
+ }
+
+ private void assertGranted(long timeoutMillis, String permissionName) {
+ assertGrantedState("Permission was never granted", permissionName,
+ PackageManager.PERMISSION_GRANTED, timeoutMillis);
+ }
+
+ private void assertDenied(long timeoutMillis, String permissionName) {
+ assertGrantedState("Permission was never revoked", permissionName,
+ PackageManager.PERMISSION_DENIED, timeoutMillis);
+ }
+
+ private void waitUntilPermissionRevoked(long timeoutMillis, String permName) throws Throwable {
+ try {
+ eventually(() -> {
+ PackageInfo appInfo = mContext.getPackageManager().getPackageInfo(APP_PKG_NAME,
+ GET_PERMISSIONS);
+
+ for (int i = 0; i < appInfo.requestedPermissions.length; i++) {
+ if (appInfo.requestedPermissions[i].equals(permName)
+ && (
+ (appInfo.requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED)
+ == 0)) {
+ return;
+ }
+ }
+
+ fail(permName + " not revoked");
+ }, timeoutMillis);
+ } catch (RuntimeException e) {
+ throw e.getCause();
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/RuntimePermissionPresentationInfoTest.java b/tests/cts/permission/src/android/permission/cts/RuntimePermissionPresentationInfoTest.java
new file mode 100644
index 000000000..9294c0aff
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/RuntimePermissionPresentationInfoTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 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.permission.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.permission.RuntimePermissionPresentationInfo;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link RuntimePermissionPresentationInfoTest}
+ */
+@RunWith(AndroidJUnit4.class)
+public class RuntimePermissionPresentationInfoTest {
+ @Test
+ public void runtimePermissionLabelSet() {
+ assertThat(new RuntimePermissionPresentationInfo("test", true,
+ true).getLabel()).isEqualTo("test");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void runtimePermissionLabelSetNull() {
+ RuntimePermissionPresentationInfo info = new RuntimePermissionPresentationInfo(null, true,
+ true);
+ }
+
+ @Test
+ public void runtimePermissionGrantedCanBeTrue() {
+ assertThat(new RuntimePermissionPresentationInfo("", true, true).isGranted()).isTrue();
+ }
+
+ @Test
+ public void runtimePermissionGrantedCanBeFalse() {
+ assertThat(new RuntimePermissionPresentationInfo("", false, true).isGranted()).isFalse();
+ }
+
+ @Test
+ public void runtimePermissionStandardCanBeTrue() {
+ assertThat(new RuntimePermissionPresentationInfo("", true, true).isStandard()).isTrue();
+ }
+
+ @Test
+ public void runtimePermissionStandardCanBeFalse() {
+ assertThat(new RuntimePermissionPresentationInfo("", true, false).isStandard()).isFalse();
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/SafetyCenterUtils.kt b/tests/cts/permission/src/android/permission/cts/SafetyCenterUtils.kt
new file mode 100644
index 000000000..c10c0bdbe
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/SafetyCenterUtils.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts
+
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.os.Build
+import android.os.UserHandle
+import android.provider.DeviceConfig
+import android.safetycenter.SafetyCenterIssue
+import android.safetycenter.SafetyCenterManager
+import androidx.annotation.RequiresApi
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject
+import com.android.safetycenter.internaldata.SafetyCenterIds
+import com.android.safetycenter.internaldata.SafetyCenterIssueId
+import com.android.safetycenter.internaldata.SafetyCenterIssueKey
+import org.junit.Assert
+
+object SafetyCenterUtils {
+ /** Name of the flag that determines whether SafetyCenter is enabled. */
+ const val PROPERTY_SAFETY_CENTER_ENABLED = "safety_center_is_enabled"
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ /** Returns whether the device supports Safety Center. */
+ @JvmStatic
+ fun deviceSupportsSafetyCenter(context: Context): Boolean {
+ return context.resources.getBoolean(
+ Resources.getSystem().getIdentifier("config_enableSafetyCenter", "bool", "android"))
+ }
+
+ /** Enabled or disable Safety Center */
+ @JvmStatic
+ fun setSafetyCenterEnabled(enabled: Boolean) {
+ setDeviceConfigPrivacyProperty(PROPERTY_SAFETY_CENTER_ENABLED, enabled.toString())
+ }
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ @JvmStatic
+ fun startSafetyCenterActivity(context: Context) {
+ context.startActivity(
+ Intent(Intent.ACTION_SAFETY_CENTER)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
+ }
+
+ @JvmStatic
+ fun assertSafetyCenterStarted() {
+ // CollapsingToolbar title can't be found by text, so using description instead.
+ waitFindObject(By.desc("Security & privacy"))
+ }
+
+ @JvmStatic
+ fun setDeviceConfigPrivacyProperty(
+ propertyName: String,
+ value: String,
+ uiAutomation: UiAutomation = instrumentation.uiAutomation
+ ) {
+ runWithShellPermissionIdentity(uiAutomation) {
+ val valueWasSet =
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ /* name = */ propertyName,
+ /* value = */ value,
+ /* makeDefault = */ false)
+ check(valueWasSet) { "Could not set $propertyName to $value" }
+ }
+ }
+
+ @JvmStatic
+ fun deleteDeviceConfigPrivacyProperty(
+ propertyName: String,
+ uiAutomation: UiAutomation = instrumentation.uiAutomation
+ ) {
+ runWithShellPermissionIdentity(uiAutomation) {
+ DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_PRIVACY, propertyName)
+ }
+ }
+
+ @JvmStatic
+ private fun getSafetyCenterIssues(
+ automation: UiAutomation = instrumentation.uiAutomation
+ ): List<SafetyCenterIssue> {
+ val safetyCenterManager =
+ instrumentation.targetContext.getSystemService(SafetyCenterManager::class.java)
+ val issues = ArrayList<SafetyCenterIssue>()
+ runWithShellPermissionIdentity(automation) {
+ val safetyCenterData = safetyCenterManager!!.safetyCenterData
+ issues.addAll(safetyCenterData.issues)
+ }
+ return issues
+ }
+
+ @JvmStatic
+ fun assertSafetyCenterIssueExist(
+ sourceId: String,
+ issueId: String,
+ issueTypeId: String,
+ automation: UiAutomation = instrumentation.uiAutomation
+ ) {
+ val safetyCenterIssueId = safetyCenterIssueId(sourceId, issueId, issueTypeId)
+ Assert.assertTrue(
+ "Expect issues in safety center",
+ getSafetyCenterIssues(automation).any { safetyCenterIssueId == it.id })
+ }
+
+ @JvmStatic
+ fun assertSafetyCenterIssueDoesNotExist(
+ sourceId: String,
+ issueId: String,
+ issueTypeId: String,
+ automation: UiAutomation = instrumentation.uiAutomation
+ ) {
+ val safetyCenterIssueId = safetyCenterIssueId(sourceId, issueId, issueTypeId)
+ Assert.assertTrue(
+ "Expect no issue in safety center",
+ getSafetyCenterIssues(automation).none { safetyCenterIssueId == it.id })
+ }
+
+ private fun safetyCenterIssueId(sourceId: String, sourceIssueId: String, issueTypeId: String) =
+ SafetyCenterIds.encodeToString(
+ SafetyCenterIssueId.newBuilder()
+ .setSafetyCenterIssueKey(
+ SafetyCenterIssueKey.newBuilder()
+ .setSafetySourceId(sourceId)
+ .setSafetySourceIssueId(sourceIssueId)
+ .setUserId(UserHandle.myUserId())
+ .build())
+ .setIssueTypeId(issueTypeId)
+ .build())
+}
diff --git a/tests/cts/permission/src/android/permission/cts/SdkSandboxPermissionTest.java b/tests/cts/permission/src/android/permission/cts/SdkSandboxPermissionTest.java
new file mode 100644
index 000000000..88fcaec45
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/SdkSandboxPermissionTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Process;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for permission handling for sdk sandbox uid range.
+ */
+@AppModeFull(reason = "Instant apps can't access PermissionManager")
+@RunWith(AndroidJUnit4ClassRunner.class)
+public class SdkSandboxPermissionTest {
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ public void testSdkSandboxHasInternetPermission() throws Exception {
+ final Context ctx = getInstrumentation().getContext();
+ int ret = ctx.checkPermission(
+ Manifest.permission.INTERNET,
+ /* pid= */ -1 /* invalid pid */,
+ Process.toSdkSandboxUid(19999));
+ assertThat(ret).isEqualTo(PackageManager.PERMISSION_GRANTED);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ public void testSdkSandboxDoesNotHaveFineLocationPermission() throws Exception {
+ final Context ctx = getInstrumentation().getContext();
+ int ret = ctx.checkPermission(
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ /* pid= */ -1 /* invalid pid */,
+ Process.toSdkSandboxUid(19999));
+ assertThat(ret).isEqualTo(PackageManager.PERMISSION_DENIED);
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/SecureElementPermissionTest.java b/tests/cts/permission/src/android/permission/cts/SecureElementPermissionTest.java
new file mode 100644
index 000000000..1f04b1ccf
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/SecureElementPermissionTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 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.permission.cts;
+
+import static org.junit.Assert.fail;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Process;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+@RunWith(JUnit4.class)
+public final class SecureElementPermissionTest {
+ // Needed because SECURE_ELEMENT_PRIVILEGED_PERMISSION is a systemapi
+ public static final String SECURE_ELEMENT_PRIVILEGED_PERMISSION =
+ "android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION";
+
+ @Test
+ public void testSecureElementPrivilegedPermission() {
+ PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+ List<Integer> specialUids = Arrays.asList(Process.SYSTEM_UID, Process.PHONE_UID);
+
+ List<PackageInfo> holding = pm.getPackagesHoldingPermissions(
+ new String[] { SECURE_ELEMENT_PRIVILEGED_PERMISSION },
+ PackageManager.MATCH_DISABLED_COMPONENTS);
+
+ List<Integer> nonSpecialPackages = holding.stream()
+ .map(pi -> {
+ try {
+ return pm.getPackageUid(pi.packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return Process.INVALID_UID;
+ }
+ })
+ .filter(uid -> !specialUids.contains(uid))
+ .collect(Collectors.toList());
+
+ if (nonSpecialPackages.size() > 1) {
+ fail("Only one app on the device is allowed to hold the " +
+ "SECURE_ELEMENT_PRIVILEGED_OPERATION permission.");
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/ServicePermissionTest.java b/tests/cts/permission/src/android/permission/cts/ServicePermissionTest.java
new file mode 100644
index 000000000..6c10f1d31
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/ServicePermissionTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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.permission.cts;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.test.AndroidTestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The security designs of many system features require that a special
+ * permission is only ever granted to the core system (typically
+ * {@code system_server}), since it's the only process that should be binding
+ * into sensitive app code.
+ * <p>
+ * No apps outside the {@code system_server} should <em>ever</em> attempt to
+ * acquire these permissions.
+ */
+public class ServicePermissionTest extends AndroidTestCase {
+ public static String[] sServicePermissions = {
+ android.Manifest.permission.ACCOUNT_MANAGER,
+ android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
+ android.Manifest.permission.BIND_AUTOFILL_SERVICE,
+ android.Manifest.permission.BIND_CHOOSER_TARGET_SERVICE,
+ android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE,
+ // android.Manifest.permission.BIND_DEVICE_ADMIN,
+ android.Manifest.permission.BIND_DREAM_SERVICE,
+ android.Manifest.permission.BIND_INPUT_METHOD,
+ android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE,
+ // android.Manifest.permission.BIND_NFC_SERVICE,
+ android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE,
+ android.Manifest.permission.BIND_PRINT_SERVICE,
+ // android.Manifest.permission.BIND_QUICK_SETTINGS_TILE,
+ android.Manifest.permission.BIND_TEXT_SERVICE,
+ android.Manifest.permission.BIND_VOICE_INTERACTION,
+ android.Manifest.permission.BIND_VPN_SERVICE,
+ android.Manifest.permission.BIND_VR_LISTENER_SERVICE,
+ };
+
+ public void testServicePermissions() {
+ final PackageManager pm = getContext().getPackageManager();
+
+ final List<String> failures = new ArrayList<>();
+ for (String perm : sServicePermissions) {
+ final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(
+ new String[] { perm }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+ for (PackageInfo pi : holding) {
+ if (!Objects.equals("android", pi.packageName)) {
+ failures.add(perm + " held by " + pi.packageName);
+ }
+ }
+ }
+ if (!failures.isEmpty()) {
+ fail("Found permissions granted to packages outside of the core system: "
+ + failures.toString());
+ }
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/ServicesInstantAppsCannotAccessTests.java b/tests/cts/permission/src/android/permission/cts/ServicesInstantAppsCannotAccessTests.java
new file mode 100644
index 000000000..8609e3379
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/ServicesInstantAppsCannotAccessTests.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 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.permission.cts;
+
+import static android.content.Context.DEVICE_POLICY_SERVICE;
+import static android.content.Context.FINGERPRINT_SERVICE;
+import static android.content.Context.SHORTCUT_SERVICE;
+import static android.content.Context.USB_SERVICE;
+import static android.content.Context.WALLPAPER_SERVICE;
+import static android.content.Context.WIFI_AWARE_SERVICE;
+import static android.content.Context.WIFI_P2P_SERVICE;
+import static android.content.Context.WIFI_SERVICE;
+
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.platform.test.annotations.AppModeInstant;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Some services are not available to instant apps, see {@link Context#getSystemService}.
+ */
+@AppModeInstant
+@RunWith(AndroidJUnit4.class)
+public class ServicesInstantAppsCannotAccessTests {
+ @Test
+ public void cannotGetDevicePolicyManager() {
+ assertNull(InstrumentationRegistry.getTargetContext().getSystemService(
+ DEVICE_POLICY_SERVICE));
+ }
+
+ @Test
+ public void cannotGetFingerprintManager() {
+ assertNull(InstrumentationRegistry.getTargetContext().getSystemService(
+ FINGERPRINT_SERVICE));
+ }
+
+ @Test
+ public void cannotGetShortcutManager() {
+ assertNull(InstrumentationRegistry.getTargetContext().getSystemService(
+ SHORTCUT_SERVICE));
+ }
+
+ @Test
+ public void cannotGetUsbManager() {
+ assertNull(InstrumentationRegistry.getTargetContext().getSystemService(
+ USB_SERVICE));
+ }
+
+ @Test
+ public void cannotGetWallpaperManager() {
+ assertNull(InstrumentationRegistry.getTargetContext().getSystemService(
+ WALLPAPER_SERVICE));
+ }
+
+ @Test
+ public void cannotGetWifiP2pManager() {
+ assertNull(InstrumentationRegistry.getTargetContext().getSystemService(
+ WIFI_P2P_SERVICE));
+ }
+
+ @Test
+ public void cannotGetWifiManager() {
+ assertNull(InstrumentationRegistry.getTargetContext().getSystemService(
+ WIFI_SERVICE));
+ }
+
+ @Test
+ public void cannotGetWifiAwareManager() {
+ assertNull(InstrumentationRegistry.getTargetContext().getSystemService(
+ WIFI_AWARE_SERVICE));
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/SharedUidPermissionsTest.java b/tests/cts/permission/src/android/permission/cts/SharedUidPermissionsTest.java
new file mode 100644
index 000000000..94d5bfd62
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/SharedUidPermissionsTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 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.permission.cts;
+
+import static android.Manifest.permission.INTERNET;
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.permission.cts.PermissionUtils.grantPermission;
+import static android.permission.cts.PermissionUtils.install;
+import static android.permission.cts.PermissionUtils.isPermissionGranted;
+import static android.permission.cts.PermissionUtils.revokePermission;
+import static android.permission.cts.PermissionUtils.uninstallApp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.FlakyTest;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Instant apps cannot read properties of other packages which is needed "
+ + "to grant permissions to them.")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+public class SharedUidPermissionsTest {
+ /** The package name of all apps used in the test */
+ private static final String PKG_THAT_REQUESTS_PERMISSIONS =
+ "android.permission.cts.appthatrequestpermission";
+ private static final String PKG_THAT_REQUESTS_NO_PERMISSIONS =
+ "android.permission.cts.appthatrequestnopermission";
+
+ private static final String TMP_DIR = "/data/local/tmp/cts/permissions/";
+ private static final String APK_THAT_REQUESTS_PERMISSIONS =
+ TMP_DIR + "CtsAppWithSharedUidThatRequestsPermissions.apk";
+ private static final String APK_THAT_REQUESTS_NO_PERMISSIONS =
+ TMP_DIR + "CtsAppWithSharedUidThatRequestsNoPermissions.apk";
+
+ @Before
+ @After
+ public void uninstallTestApps() {
+ uninstallApp(PKG_THAT_REQUESTS_PERMISSIONS);
+ uninstallApp(PKG_THAT_REQUESTS_NO_PERMISSIONS);
+ }
+
+ @Test
+ public void packageGainsRuntimePermissionsWhenJoiningSharedUid() throws Exception {
+ install(APK_THAT_REQUESTS_PERMISSIONS);
+ grantPermission(PKG_THAT_REQUESTS_PERMISSIONS, READ_CONTACTS);
+ install(APK_THAT_REQUESTS_NO_PERMISSIONS);
+
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_PERMISSIONS, READ_CONTACTS)).isTrue();
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_NO_PERMISSIONS, READ_CONTACTS)).isTrue();
+ }
+
+ @Test
+ public void packageGainsNormalPermissionsWhenJoiningSharedUid() throws Exception {
+ install(APK_THAT_REQUESTS_PERMISSIONS);
+ install(APK_THAT_REQUESTS_NO_PERMISSIONS);
+
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_PERMISSIONS, INTERNET)).isTrue();
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_NO_PERMISSIONS, INTERNET)).isTrue();
+ }
+
+ @Test
+ public void grantingRuntimePermissionAffectsAllPackageInSharedUid() throws Exception {
+ install(APK_THAT_REQUESTS_PERMISSIONS);
+ install(APK_THAT_REQUESTS_NO_PERMISSIONS);
+ grantPermission(PKG_THAT_REQUESTS_PERMISSIONS, READ_CONTACTS);
+
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_PERMISSIONS, READ_CONTACTS)).isTrue();
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_NO_PERMISSIONS, READ_CONTACTS)).isTrue();
+ }
+
+ @Test
+ public void revokingRuntimePermissionAffectsAllPackageInSharedUid() throws Exception {
+ install(APK_THAT_REQUESTS_PERMISSIONS);
+ install(APK_THAT_REQUESTS_NO_PERMISSIONS);
+ grantPermission(PKG_THAT_REQUESTS_PERMISSIONS, READ_CONTACTS);
+ revokePermission(PKG_THAT_REQUESTS_PERMISSIONS, READ_CONTACTS);
+
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_PERMISSIONS, READ_CONTACTS)).isFalse();
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_NO_PERMISSIONS, READ_CONTACTS)).isFalse();
+ }
+
+ @Test
+ public void runtimePermissionsCanBeRevokedOnPackageThatDoesNotDeclarePermission()
+ throws Exception {
+ install(APK_THAT_REQUESTS_PERMISSIONS);
+ install(APK_THAT_REQUESTS_NO_PERMISSIONS);
+ grantPermission(PKG_THAT_REQUESTS_PERMISSIONS, READ_CONTACTS);
+ revokePermission(PKG_THAT_REQUESTS_NO_PERMISSIONS, READ_CONTACTS);
+
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_PERMISSIONS, READ_CONTACTS)).isFalse();
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_NO_PERMISSIONS, READ_CONTACTS)).isFalse();
+ }
+
+ @Test
+ @FlakyTest
+ public void runtimePermissionsCanBeGrantedOnPackageThatDoesNotDeclarePermission()
+ throws Exception {
+ install(APK_THAT_REQUESTS_PERMISSIONS);
+ install(APK_THAT_REQUESTS_NO_PERMISSIONS);
+ grantPermission(PKG_THAT_REQUESTS_NO_PERMISSIONS, READ_CONTACTS);
+
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_PERMISSIONS, READ_CONTACTS)).isTrue();
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_NO_PERMISSIONS, READ_CONTACTS)).isTrue();
+ }
+
+ @Test
+ public void sharedUidLoosesRuntimePermissionWhenLastAppDeclaringItGetsUninstalled()
+ throws Exception {
+ install(APK_THAT_REQUESTS_PERMISSIONS);
+ install(APK_THAT_REQUESTS_NO_PERMISSIONS);
+ grantPermission(PKG_THAT_REQUESTS_PERMISSIONS, READ_CONTACTS);
+ uninstallApp(PKG_THAT_REQUESTS_PERMISSIONS);
+
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_NO_PERMISSIONS, READ_CONTACTS)).isFalse();
+ }
+
+ @Test
+ @FlakyTest
+ public void sharedUidLoosesNormalPermissionWhenLastAppDeclaringItGetsUninstalled()
+ throws Exception {
+ install(APK_THAT_REQUESTS_PERMISSIONS);
+ install(APK_THAT_REQUESTS_NO_PERMISSIONS);
+ uninstallApp(PKG_THAT_REQUESTS_PERMISSIONS);
+
+ assertThat(isPermissionGranted(PKG_THAT_REQUESTS_NO_PERMISSIONS, INTERNET)).isFalse();
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/ShellCommandPermissionTest.java b/tests/cts/permission/src/android/permission/cts/ShellCommandPermissionTest.java
new file mode 100644
index 000000000..e70f07bce
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/ShellCommandPermissionTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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 android.permission.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+/**
+ * Test shell command capability enforcement
+ */
+@RunWith(AndroidJUnit4.class)
+public class ShellCommandPermissionTest {
+
+ private void executeShellCommandAndRequireError(String command, String required)
+ throws Exception {
+ try {
+ java.lang.Process proc = Runtime.getRuntime().exec(command);
+
+ // read output of command
+ BufferedReader reader =
+ new BufferedReader(new InputStreamReader(proc.getErrorStream()));
+ StringBuilder stderr = new StringBuilder();
+ String line = reader.readLine();
+ while (line != null) {
+ stderr.append(line);
+ line = reader.readLine();
+ }
+ reader.close();
+ proc.waitFor();
+
+ String stderrString = stderr.toString();
+ assertTrue("Expected command stderr '" + stderrString
+ + " to contain '" + required + "'",
+ stderrString.contains(required));
+ } catch (InterruptedException e) {
+ fail("Unsuccessful shell command");
+ }
+ }
+
+ @Test
+ public void testTraceIpc() throws Exception {
+ executeShellCommandAndRequireError(
+ "cmd activity trace-ipc stop --dump-file /data/system/last-fstrim",
+ "Error:");
+ }
+
+ @Test
+ public void testDumpheap() throws Exception {
+ executeShellCommandAndRequireError(
+ "cmd activity dumpheap system_server /data/system/last-fstrim",
+ "Error:");
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/ShellPermissionTest.java b/tests/cts/permission/src/android/permission/cts/ShellPermissionTest.java
new file mode 100644
index 000000000..c4c66564c
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/ShellPermissionTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 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.permission.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.SystemUserOnly;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Tests that shell has acceptable permissions.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ShellPermissionTest {
+ private static final String LOG_TAG = ShellPermissionTest.class.getSimpleName();
+
+ /** Permissions that shell is NOT permitted to have. */
+ private static final String[] BLACKLISTED_PERMISSIONS = {
+ "android.permission.MANAGE_USERS",
+ "android.permission.NETWORK_STACK",
+ "android.permission.MANAGE_WIFI_COUNTRY_CODE",
+ };
+
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+
+ /**
+ * Verify that the shell uid does not have any of the permissions listed in
+ * {@link #BLACKLISTED_PERMISSIONS}.
+ */
+ @Test
+ @AppModeFull(reason = "Instant apps cannot read properties of other packages. Also the shell "
+ + "is never an instant app, hence this test does not matter for instant apps.")
+ public void testBlacklistedPermissions() throws Exception {
+ final Set<String> blacklist = new HashSet<>(Arrays.asList(BLACKLISTED_PERMISSIONS));
+
+ final PackageManager pm = sContext.getPackageManager();
+ int uid = UserHandle.getUid(UserHandle.myUserId(), UserHandle.getAppId(Process.SHELL_UID));
+ final String[] pkgs = pm.getPackagesForUid(uid);
+ Log.d(LOG_TAG, "SHELL_UID: " + Process.SHELL_UID + " myUserId: "
+ + UserHandle.myUserId() + " uid: " + uid + " pkgs.length: " + pkgs.length);
+ assertNotNull("No SHELL packages were found", pkgs);
+ assertNotEquals("SHELL package list had 0 size", 0, pkgs.length);
+ String pkg = pkgs[0];
+
+ final PackageInfo packageInfo = pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
+ assertNotNull("No permissions found for " + pkg, packageInfo.requestedPermissions);
+
+ for (String permission : packageInfo.requestedPermissions) {
+ Log.d(LOG_TAG, "SHELL as " + pkg + " uses permission " + permission + " uid: "
+ + uid);
+ assertFalse("SHELL as " + pkg + " contains the illegal permission " + permission,
+ blacklist.contains(permission));
+ }
+ }
+
+ @Test
+ @SystemUserOnly
+ @AppModeFull(reason = "Instant apps cannot read properties of other packages. Also the shell "
+ + "is never an instant app, hence this test does not matter for instant apps.")
+ public void testBlacklistedPermissionsForSystemUser() throws Exception {
+ testBlacklistedPermissions();
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/SmsManagerPermissionTest.java b/tests/cts/permission/src/android/permission/cts/SmsManagerPermissionTest.java
new file mode 100644
index 000000000..dd0d9f234
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/SmsManagerPermissionTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2018 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.permission.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+/**
+ * Test that sending SMS and MMS messages requires permissions.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SmsManagerPermissionTest {
+
+ private static final String SOURCE_ADDRESS = "+15550000000";
+ private static final String DESTINATION_ADDRESS = "+15550000001";
+
+ private boolean mHasTelephony;
+ private SmsManager mSmsManager;
+ private Context mContext;
+ private TelephonyManager mTelephonyManager;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ int subId = SubscriptionManager.getDefaultSmsSubscriptionId();
+ mHasTelephony = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY);
+ assumeTrue(mHasTelephony); // Don't run these tests if FEATURE_TELEPHONY is not available.
+
+ Log.d("SmsManagerPermissionTest", "mSubId=" + subId);
+
+ mSmsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
+ assertNotNull(mSmsManager);
+ }
+
+ @Test
+ public void testSendTextMessage() {
+ assertFalse("[RERUN] Device does not have SIM card. Use a suitable SIM Card.",
+ isSimCardAbsent());
+
+ assertThrows(SecurityException.class, () -> mSmsManager.sendTextMessage(
+ DESTINATION_ADDRESS, SOURCE_ADDRESS, "Message text", null, null));
+ }
+
+ @Test
+ public void testSendTextMessageWithoutPersisting() {
+ assertFalse("[RERUN] Device does not have SIM card. Use a suitable SIM Card.",
+ isSimCardAbsent());
+
+ assertThrows(SecurityException.class, () -> mSmsManager.sendTextMessageWithoutPersisting(
+ DESTINATION_ADDRESS, SOURCE_ADDRESS, "Message text", null, null));
+ }
+
+ @Test
+ public void testSendMultipartTextMessage() {
+ assertFalse("[RERUN] Device does not have SIM card. Use a suitable SIM Card.",
+ isSimCardAbsent());
+
+ ArrayList<String> messageParts = new ArrayList<>();
+ messageParts.add("Message text");
+ assertThrows(SecurityException.class, () -> mSmsManager.sendMultipartTextMessage(
+ DESTINATION_ADDRESS, SOURCE_ADDRESS, messageParts, null, null));
+ }
+
+ @Test
+ public void testSendDataMessage() {
+ assertFalse("[RERUN] Device does not have SIM card. Use a suitable SIM Card.",
+ isSimCardAbsent());
+
+ assertThrows(SecurityException.class, () -> mSmsManager.sendDataMessage(
+ DESTINATION_ADDRESS, SOURCE_ADDRESS, (short) 1, new byte[]{0, 0, 0}, null, null));
+ }
+
+ @Test
+ public void testSendMultimediaMessage() {
+ assertFalse("[RERUN] Device does not have SIM card. Use a suitable SIM Card.",
+ isSimCardAbsent());
+
+ // Ideally we would provide an Uri to an existing resource, to make sure the
+ // SecurityException is not due to the invalid Uri.
+ Uri uri = Uri.parse("android.resource://android.permission.cts/some-image.png");
+ assertThrows(SecurityException.class,
+ () -> mSmsManager.sendMultimediaMessage(mContext, uri, "", null, null));
+ }
+
+ private boolean isSimCardAbsent() {
+ return ((mTelephonyManager.getSimState() == TelephonyManager.SIM_STATE_ABSENT)
+ || (SubscriptionManager.getDefaultSmsSubscriptionId()
+ == SubscriptionManager.INVALID_SUBSCRIPTION_ID));
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/SplitPermissionTest.java b/tests/cts/permission/src/android/permission/cts/SplitPermissionTest.java
new file mode 100644
index 000000000..46235f1c8
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/SplitPermissionTest.java
@@ -0,0 +1,584 @@
+/*
+ * Copyright (C) 2018 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.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.READ_CALL_LOG;
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.permission.cts.PermissionUtils.getAppOp;
+import static android.permission.cts.PermissionUtils.getPermissionFlags;
+import static android.permission.cts.PermissionUtils.getPermissions;
+import static android.permission.cts.PermissionUtils.grantPermission;
+import static android.permission.cts.PermissionUtils.isGranted;
+import static android.permission.cts.PermissionUtils.revokePermission;
+import static android.permission.cts.PermissionUtils.setPermissionFlags;
+import static android.permission.cts.PermissionUtils.uninstallApp;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.UiAutomation;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.SystemUserOnly;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests how split permissions behave.
+ *
+ * <ul>
+ * <li>Default permission grant behavior</li>
+ * <li>Changes to the grant state during upgrade of apps with split permissions</li>
+ * <li>Special behavior of background location</li>
+ * </ul>
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Instant apps cannot read state of other packages.")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+public class SplitPermissionTest {
+ /** The package name of all apps used in the test */
+ private static final String APP_PKG = "android.permission.cts.appthatrequestpermission";
+
+ private static final String TMP_DIR = "/data/local/tmp/cts/permissions/";
+ private static final String APK_CONTACTS_16 =
+ TMP_DIR + "CtsAppThatRequestsContactsPermission16.apk";
+ private static final String APK_CONTACTS_15 =
+ TMP_DIR + "CtsAppThatRequestsContactsPermission15.apk";
+ private static final String APK_CONTACTS_CALLLOG_16 =
+ TMP_DIR + "CtsAppThatRequestsContactsAndCallLogPermission16.apk";
+ private static final String APK_STORAGE_29 =
+ TMP_DIR + "CtsAppThatRequestsStoragePermission29.apk";
+ private static final String APK_STORAGE_28 =
+ TMP_DIR + "CtsAppThatRequestsStoragePermission28.apk";
+ private static final String APK_LOCATION_29 =
+ TMP_DIR + "CtsAppThatRequestsLocationPermission29.apk";
+ private static final String APK_LOCATION_28 =
+ TMP_DIR + "CtsAppThatRequestsLocationPermission28.apk";
+ private static final String APK_LOCATION_22 =
+ TMP_DIR + "CtsAppThatRequestsLocationPermission22.apk";
+ private static final String APK_LOCATION_BACKGROUND_28 =
+ TMP_DIR + "CtsAppThatRequestsLocationAndBackgroundPermission28.apk";
+ private static final String APK_LOCATION_BACKGROUND_29 =
+ TMP_DIR + "CtsAppThatRequestsLocationAndBackgroundPermission29.apk";
+ private static final String APK_SHARED_UID_LOCATION_29 =
+ TMP_DIR + "CtsAppWithSharedUidThatRequestsLocationPermission29.apk";
+ private static final String APK_SHARED_UID_LOCATION_28 =
+ TMP_DIR + "CtsAppWithSharedUidThatRequestsLocationPermission28.apk";
+
+ private static final UiAutomation sUiAutomation =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+
+ /**
+ * Assert that {@link #APP_PKG} requests a certain permission.
+ *
+ * @param permName The permission that needs to be requested
+ */
+ private void assertRequestsPermission(@NonNull String permName) throws Exception {
+ assertThat(getPermissions(APP_PKG)).contains(permName);
+ }
+
+ /**
+ * Assert that {@link #APP_PKG} <u>does not</u> request a certain permission.
+ *
+ * @param permName The permission that needs to be not requested
+ */
+ private void assertNotRequestsPermission(@NonNull String permName) throws Exception {
+ assertThat(getPermissions(APP_PKG)).doesNotContain(permName);
+ }
+
+ /**
+ * Assert that a permission is granted to {@link #APP_PKG}.
+ *
+ * @param permName The permission that needs to be granted
+ */
+ private void assertPermissionGranted(@NonNull String permName) throws Exception {
+ eventually(() -> assertWithMessage(permName + " is granted").that(
+ isGranted(APP_PKG, permName)).isTrue());
+ }
+
+ /**
+ * Assert that a permission is <u>not </u> granted to {@link #APP_PKG}.
+ *
+ * @param permName The permission that should not be granted
+ */
+ private void assertPermissionRevoked(@NonNull String permName) throws Exception {
+ assertWithMessage(permName + " is granted").that(isGranted(APP_PKG, permName)).isFalse();
+ }
+
+ /**
+ * Install an APK.
+ *
+ * @param apkFile The apk to install
+ */
+ public void install(@NonNull String apkFile) {
+ PermissionUtils.install(apkFile);
+ }
+
+ @After
+ public void uninstallTestApp() {
+ uninstallApp(APP_PKG);
+ }
+
+ /**
+ * Apps with a targetSDK after the split should <u>not</u> have been added implicitly the new
+ * permission.
+ */
+ @Test
+ public void permissionsDoNotSplitWithHighTargetSDK() throws Exception {
+ install(APK_LOCATION_29);
+
+ assertRequestsPermission(ACCESS_COARSE_LOCATION);
+ assertNotRequestsPermission(ACCESS_BACKGROUND_LOCATION);
+ }
+
+ /**
+ * Apps with a targetSDK after the split should <u>not</u> have been added implicitly the new
+ * permission.
+ *
+ * <p>(Pre-M version of test)
+ */
+ @Test
+ public void permissionsDoNotSplitWithHighTargetSDKPreM() throws Exception {
+ install(APK_CONTACTS_16);
+
+ assertRequestsPermission(READ_CONTACTS);
+ assertNotRequestsPermission(READ_CALL_LOG);
+ }
+
+ /**
+ * Apps with a targetSDK before the split should have been added implicitly the new permission.
+ */
+ @Test
+ public void permissionsSplitWithLowTargetSDK() throws Exception {
+ install(APK_LOCATION_28);
+
+ assertRequestsPermission(ACCESS_COARSE_LOCATION);
+ assertRequestsPermission(ACCESS_BACKGROUND_LOCATION);
+ }
+
+ /**
+ * Apps with a targetSDK before the split should have been added implicitly the new permission.
+ *
+ * <p>(Pre-M version of test)
+ */
+ @Test
+ public void permissionsSplitWithLowTargetSDKPreM() throws Exception {
+ install(APK_CONTACTS_15);
+
+ assertRequestsPermission(READ_CONTACTS);
+ assertRequestsPermission(READ_CALL_LOG);
+ }
+
+ /**
+ * Permissions are revoked by default for post-M apps
+ */
+ @Test
+ public void nonInheritedStateHighTargetSDK() throws Exception {
+ install(APK_LOCATION_29);
+
+ assertPermissionRevoked(ACCESS_COARSE_LOCATION);
+ }
+
+ /**
+ * Permissions are granted by default for pre-M apps
+ */
+ @Test
+ public void nonInheritedStateHighLowTargetSDKPreM() throws Exception {
+ install(APK_CONTACTS_15);
+
+ assertPermissionGranted(READ_CONTACTS);
+ }
+
+ /**
+ * Permissions are revoked by default for post-M apps. This also applies to permissions added
+ * implicitly due to splits.
+ */
+ @Test
+ public void nonInheritedStateLowTargetSDK() throws Exception {
+ install(APK_LOCATION_28);
+
+ assertPermissionRevoked(ACCESS_COARSE_LOCATION);
+ assertPermissionRevoked(ACCESS_BACKGROUND_LOCATION);
+ }
+
+ /**
+ * Permissions are granted by default for pre-M apps. This also applies to permissions added
+ * implicitly due to splits.
+ */
+ @Test
+ @SystemUserOnly(reason = "Secondary users have the DISALLOW_OUTGOING_CALLS user restriction")
+ public void nonInheritedStateLowTargetSDKPreM() throws Exception {
+ Assume.assumeTrue("Secondary users have the DISALLOW_OUTGOING_CALLS user restriction",
+ UserHandle.SYSTEM.equals(Process.myUserHandle()));
+
+ install(APK_CONTACTS_15);
+
+ assertPermissionGranted(READ_CONTACTS);
+ assertPermissionGranted(READ_CALL_LOG);
+ }
+
+ /**
+ * The background location permission granted by default for pre-M apps.
+ */
+ @Test
+ public void backgroundLocationPermissionDefaultGrantPreM() throws Exception {
+ install(APK_LOCATION_22);
+
+ assertPermissionGranted(ACCESS_COARSE_LOCATION);
+ assertPermissionGranted(ACCESS_BACKGROUND_LOCATION);
+ }
+
+ /**
+ * If a permission was granted before the split happens, the new permission should inherit the
+ * granted state.
+ */
+ @FlakyTest(bugId = 152580253)
+ @MtsIgnore(bugId = 152580253)
+ @Test
+ public void inheritGrantedPermissionState() throws Exception {
+ install(APK_LOCATION_29);
+ grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+
+ install(APK_LOCATION_28);
+
+ assertPermissionGranted(ACCESS_BACKGROUND_LOCATION);
+ }
+
+ /**
+ * If a permission was granted before the split happens, the new permission should inherit the
+ * granted state.
+ *
+ * <p>App using a shared uid
+ */
+ @Test
+ public void inheritGrantedPermissionStateSharedUidApp() throws Exception {
+ install(APK_SHARED_UID_LOCATION_29);
+ grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+
+ install(APK_SHARED_UID_LOCATION_28);
+
+ assertPermissionGranted(ACCESS_BACKGROUND_LOCATION);
+ }
+
+ /**
+ * If a permission has flags before the split happens, the new permission should inherit the
+ * flags.
+ *
+ * <p>(Pre-M version of test)
+ */
+ @FlakyTest(bugId = 152580253)
+ @MtsIgnore(bugId = 152580253)
+ @Test
+ public void inheritFlagsPreM() {
+ install(APK_CONTACTS_16);
+ setPermissionFlags(APP_PKG, READ_CONTACTS, FLAG_PERMISSION_USER_SET,
+ FLAG_PERMISSION_USER_SET);
+
+ install(APK_CONTACTS_15);
+
+ assertEquals(FLAG_PERMISSION_USER_SET,
+ getPermissionFlags(APP_PKG, READ_CALL_LOG) & FLAG_PERMISSION_USER_SET);
+ }
+
+ /**
+ * If a permission has flags before the split happens, the new permission should inherit the
+ * flags.
+ */
+ @FlakyTest(bugId = 152580253)
+ @MtsIgnore(bugId = 152580253)
+ @Test
+ public void inheritFlags() {
+ install(APK_LOCATION_29);
+ setPermissionFlags(APP_PKG, ACCESS_COARSE_LOCATION, FLAG_PERMISSION_USER_SET,
+ FLAG_PERMISSION_USER_SET);
+
+ install(APK_LOCATION_28);
+
+ assertEquals(FLAG_PERMISSION_USER_SET,
+ getPermissionFlags(APP_PKG, ACCESS_BACKGROUND_LOCATION) & FLAG_PERMISSION_USER_SET);
+ }
+
+ /**
+ * If a permission was granted before the split happens, the new permission should inherit the
+ * granted state.
+ *
+ * <p>(Pre-M version of test)
+ */
+ @Test
+ @SystemUserOnly(reason = "Secondary users have the DISALLOW_OUTGOING_CALLS user restriction")
+ public void inheritGrantedPermissionStatePreM() throws Exception {
+ Assume.assumeTrue("Secondary users have the DISALLOW_OUTGOING_CALLS user restriction",
+ UserHandle.SYSTEM.equals(Process.myUserHandle()));
+
+ install(APK_CONTACTS_16);
+
+ install(APK_CONTACTS_15);
+
+ assertPermissionGranted(READ_CALL_LOG);
+ }
+
+ /**
+ * If a permission was revoked before the split happens, the new permission should inherit the
+ * revoked state.
+ *
+ * <p>(Pre-M version of test)
+ */
+ @Test
+ public void inheritRevokedPermissionState() throws Exception {
+ install(APK_LOCATION_29);
+
+ install(APK_LOCATION_28);
+
+ assertPermissionRevoked(ACCESS_BACKGROUND_LOCATION);
+ }
+
+ /**
+ * If a permission was revoked before the split happens, the new permission should inherit the
+ * revoked state.
+ *
+ * <p>(Pre-M version of test)
+ */
+ @Test
+ public void inheritRevokedPermissionStatePreM() throws Exception {
+ install(APK_CONTACTS_16);
+ revokePermission(APP_PKG, READ_CONTACTS);
+
+ install(APK_CONTACTS_15);
+
+ /*
+ * Ideally the new permission should inherit from it's base permission, but this is tricky
+ * to implement.
+ * The new permissions need to be reviewed, hence the pre-review state really does not
+ * matter anyway.
+ */
+ // assertPermissionRevoked(READ_CALL_LOG);
+ assertThat(getPermissionFlags(APP_PKG, READ_CALL_LOG)
+ & FLAG_PERMISSION_REVIEW_REQUIRED).isEqualTo(FLAG_PERMISSION_REVIEW_REQUIRED);
+ }
+
+ /**
+ * It should be possible to grant a permission implicitly added due to a split.
+ */
+ @Test
+ public void grantNewSplitPermissionState() throws Exception {
+ install(APK_LOCATION_28);
+
+ // Background permission can only be granted together with foreground permission
+ grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+ grantPermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+ assertPermissionGranted(ACCESS_BACKGROUND_LOCATION);
+ }
+
+ /**
+ * It should be possible to grant a permission implicitly added due to a split.
+ *
+ * <p>(Pre-M version of test)
+ */
+ @Test
+ @SystemUserOnly(reason = "Secondary users have the DISALLOW_OUTGOING_CALLS user restriction")
+ public void grantNewSplitPermissionStatePreM() throws Exception {
+ Assume.assumeTrue("Secondary users have the DISALLOW_OUTGOING_CALLS user restriction",
+ UserHandle.SYSTEM.equals(Process.myUserHandle()));
+
+ install(APK_CONTACTS_15);
+ revokePermission(APP_PKG, READ_CONTACTS);
+
+ grantPermission(APP_PKG, READ_CALL_LOG);
+
+ assertPermissionGranted(READ_CALL_LOG);
+ }
+
+ /**
+ * It should be possible to revoke a permission implicitly added due to a split.
+ */
+ @Test
+ public void revokeNewSplitPermissionState() throws Exception {
+ install(APK_LOCATION_28);
+
+ // Background permission can only be granted together with foreground permission
+ grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+ grantPermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+ revokePermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+ assertPermissionRevoked(ACCESS_BACKGROUND_LOCATION);
+ }
+
+ /**
+ * It should be possible to revoke a permission implicitly added due to a split.
+ *
+ * <p>(Pre-M version of test)
+ */
+ @Test
+ public void revokeNewSplitPermissionStatePreM() throws Exception {
+ install(APK_CONTACTS_15);
+
+ revokePermission(APP_PKG, READ_CALL_LOG);
+
+ assertPermissionRevoked(READ_CALL_LOG);
+ }
+
+ /**
+ * An implicit permission should get revoked when the app gets updated and now requests the
+ * permission.
+ */
+ @Test
+ public void newPermissionGetRevokedOnUpgrade() throws Exception {
+ install(APK_LOCATION_28);
+
+ // Background permission can only be granted together with foreground permission
+ grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+ grantPermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+ install(APK_LOCATION_BACKGROUND_29);
+
+ assertPermissionRevoked(ACCESS_BACKGROUND_LOCATION);
+ }
+
+ /**
+ * An implicit background permission should get revoked when the app gets updated and now
+ * requests the permission. Revoking a background permission should have changed the app-op of
+ * the foreground permission.
+ */
+ @Test
+ public void newBackgroundPermissionGetRevokedOnUpgrade() throws Exception {
+ install(APK_LOCATION_28);
+
+ // Background permission can only be granted together with foreground permission
+ grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+ grantPermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+ install(APK_LOCATION_BACKGROUND_29);
+
+ eventually(() -> assertWithMessage("foreground app-op").that(
+ getAppOp(APP_PKG, ACCESS_COARSE_LOCATION)).isEqualTo(MODE_FOREGROUND));
+ }
+
+ /**
+ * An implicit permission should get revoked when the app gets updated and now requests the
+ * permission. This even happens if the app is not targeting the SDK the permission was split
+ * in.
+ */
+ @Test
+ public void newPermissionGetRevokedOnUpgradeBeforeSplitSDK() throws Exception {
+ install(APK_LOCATION_28);
+
+ // Background permission can only be granted together with foreground permission
+ grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+ grantPermission(APP_PKG, ACCESS_BACKGROUND_LOCATION);
+
+ // Background location was introduced in SDK 29. Hence an app targeting 28 is usually
+ // unaware of this permission. If the app declares that it is aware by adding the permission
+ // in the manifest the permission will get revoked. This allows the app to request the
+ // permission from the user.
+ install(APK_LOCATION_BACKGROUND_28);
+
+ assertPermissionRevoked(ACCESS_BACKGROUND_LOCATION);
+ }
+
+ /**
+ * An implicit permission should <u>not</u> get revoked when the app gets updated as pre-M apps
+ * cannot deal with revoked permissions. Hence only the user should ever explicitly do that.
+ */
+ @Test
+ @SystemUserOnly(reason = "Secondary users have the DISALLOW_OUTGOING_CALLS user restriction")
+ public void newPermissionGetRevokedOnUpgradePreM() throws Exception {
+ Assume.assumeTrue("Secondary users have the DISALLOW_OUTGOING_CALLS user restriction",
+ UserHandle.SYSTEM.equals(Process.myUserHandle()));
+
+ install(APK_CONTACTS_15);
+
+ install(APK_CONTACTS_CALLLOG_16);
+
+ assertPermissionGranted(READ_CALL_LOG);
+ }
+
+ /**
+ * When a requested permission was granted before upgrade it should still be granted.
+ */
+ @Test
+ public void oldPermissionStaysGrantedOnUpgrade() throws Exception {
+ install(APK_LOCATION_28);
+ grantPermission(APP_PKG, ACCESS_COARSE_LOCATION);
+
+ install(APK_LOCATION_BACKGROUND_29);
+
+ assertPermissionGranted(ACCESS_COARSE_LOCATION);
+ }
+
+ /**
+ * When a requested permission was granted before upgrade it should still be granted.
+ *
+ * <p>(Pre-M version of test)
+ */
+ @Test
+ public void oldPermissionStaysGrantedOnUpgradePreM() throws Exception {
+ install(APK_CONTACTS_15);
+
+ install(APK_CONTACTS_CALLLOG_16);
+
+ assertPermissionGranted(READ_CONTACTS);
+ }
+
+ /**
+ * When a requested permission was revoked before upgrade it should still be revoked.
+ */
+ @Test
+ public void oldPermissionStaysRevokedOnUpgrade() throws Exception {
+ install(APK_LOCATION_28);
+
+ install(APK_LOCATION_BACKGROUND_29);
+
+ assertPermissionRevoked(ACCESS_COARSE_LOCATION);
+ }
+
+ /**
+ * When a requested permission was revoked before upgrade it should still be revoked.
+ *
+ * <p>(Pre-M version of test)
+ */
+ @Test
+ public void oldPermissionStaysRevokedOnUpgradePreM() throws Exception {
+ install(APK_CONTACTS_15);
+ revokePermission(APP_PKG, READ_CONTACTS);
+
+ install(APK_CONTACTS_CALLLOG_16);
+
+ assertPermissionRevoked(READ_CONTACTS);
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java b/tests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
new file mode 100755
index 000000000..776a1065e
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2018 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.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.ACCESS_MEDIA_LOCATION;
+import static android.Manifest.permission.BLUETOOTH;
+import static android.Manifest.permission.BLUETOOTH_ADMIN;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
+import static android.Manifest.permission.BODY_SENSORS;
+import static android.Manifest.permission.BODY_SENSORS_BACKGROUND;
+import static android.Manifest.permission.READ_CALL_LOG;
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.READ_MEDIA_AUDIO;
+import static android.Manifest.permission.READ_MEDIA_IMAGES;
+import static android.Manifest.permission.READ_MEDIA_VIDEO;
+import static android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED;
+import static android.Manifest.permission.READ_PHONE_STATE;
+import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+import static android.Manifest.permission.WRITE_CALL_LOG;
+import static android.Manifest.permission.WRITE_CONTACTS;
+import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.os.Build;
+import android.permission.PermissionManager;
+import android.permission.PermissionManager.SplitPermissionInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+public class SplitPermissionsSystemTest {
+
+ private static final int NO_TARGET = Build.VERSION_CODES.CUR_DEVELOPMENT + 1;
+
+ private List<SplitPermissionInfo> mSplitPermissions;
+
+ @Before
+ public void before() {
+ Context context = InstrumentationRegistry.getContext();
+ PermissionManager permissionManager = (PermissionManager) context.getSystemService(
+ Context.PERMISSION_SERVICE);
+ mSplitPermissions = permissionManager.getSplitPermissions();
+ }
+
+ @Test
+ public void validateAndroidSystem() {
+ assumeTrue(ApiLevelUtil.isAtLeast(Build.VERSION_CODES.Q));
+
+ Set<SplitPermissionInfo> seenSplits = new HashSet<>(6);
+
+ for (SplitPermissionInfo split : mSplitPermissions) {
+ String splitPermission = split.getSplitPermission();
+ boolean isAndroid = splitPermission.startsWith("android");
+
+ if (!isAndroid) {
+ continue;
+ }
+
+ assertThat(seenSplits).doesNotContain(split);
+ seenSplits.add(split);
+
+ List<String> newPermissions = split.getNewPermissions();
+
+ switch (splitPermission) {
+ case ACCESS_FINE_LOCATION:
+ // Q declares multiple for ACCESS_FINE_LOCATION, so assert both exist
+ if (newPermissions.contains(ACCESS_COARSE_LOCATION)) {
+ assertSplit(split, NO_TARGET, ACCESS_COARSE_LOCATION);
+ } else {
+ assertSplit(split, Build.VERSION_CODES.Q, ACCESS_BACKGROUND_LOCATION);
+ }
+ break;
+ case WRITE_EXTERNAL_STORAGE:
+ if (newPermissions.contains(READ_EXTERNAL_STORAGE)) {
+ assertSplit(split, NO_TARGET, READ_EXTERNAL_STORAGE);
+ } else if (newPermissions.contains(ACCESS_MEDIA_LOCATION)) {
+ assertSplit(split, Build.VERSION_CODES.Q, ACCESS_MEDIA_LOCATION);
+ } else if (newPermissions.contains(READ_MEDIA_AUDIO)) {
+ assertSplit(split, Build.VERSION_CODES.S_V2 + 1, READ_MEDIA_AUDIO);
+ } else if (newPermissions.contains(READ_MEDIA_VIDEO)) {
+ assertSplit(split, Build.VERSION_CODES.S_V2 + 1, READ_MEDIA_VIDEO);
+ } else if (newPermissions.contains(READ_MEDIA_IMAGES)) {
+ assertSplit(split, Build.VERSION_CODES.S_V2 + 1, READ_MEDIA_IMAGES);
+ }
+ break;
+ case READ_CONTACTS:
+ assertSplit(split, Build.VERSION_CODES.JELLY_BEAN, READ_CALL_LOG);
+ break;
+ case WRITE_CONTACTS:
+ assertSplit(split, Build.VERSION_CODES.JELLY_BEAN, WRITE_CALL_LOG);
+ break;
+ case ACCESS_COARSE_LOCATION:
+ assertSplit(split, Build.VERSION_CODES.Q, ACCESS_BACKGROUND_LOCATION);
+ break;
+ case READ_EXTERNAL_STORAGE:
+ if (newPermissions.contains(ACCESS_MEDIA_LOCATION)) {
+ assertSplit(split, Build.VERSION_CODES.Q, ACCESS_MEDIA_LOCATION);
+ } else if (newPermissions.contains(READ_MEDIA_AUDIO)) {
+ assertSplit(split, Build.VERSION_CODES.S_V2 + 1, READ_MEDIA_AUDIO);
+ } else if (newPermissions.contains(READ_MEDIA_VIDEO)) {
+ assertSplit(split, Build.VERSION_CODES.S_V2 + 1, READ_MEDIA_VIDEO);
+ } else if (newPermissions.contains(READ_MEDIA_IMAGES)) {
+ assertSplit(split, Build.VERSION_CODES.S_V2 + 1, READ_MEDIA_IMAGES);
+ }
+ break;
+ case READ_PRIVILEGED_PHONE_STATE:
+ assertSplit(split, NO_TARGET, READ_PHONE_STATE);
+ break;
+ case BLUETOOTH_CONNECT:
+ assertSplit(split, Build.VERSION_CODES.S, BLUETOOTH, BLUETOOTH_ADMIN);
+ break;
+ case BLUETOOTH_SCAN:
+ assertSplit(split, Build.VERSION_CODES.S, BLUETOOTH, BLUETOOTH_ADMIN);
+ break;
+ case BODY_SENSORS:
+ assertSplit(split, Build.VERSION_CODES.TIRAMISU, BODY_SENSORS_BACKGROUND);
+ break;
+ case ACCESS_MEDIA_LOCATION:
+ case READ_MEDIA_IMAGES:
+ case READ_MEDIA_VIDEO:
+ assertSplit(split, READ_MEDIA_VISUAL_USER_SELECTED);
+ break;
+ }
+ }
+
+ assertEquals(24, seenSplits.size());
+ }
+
+ private void assertSplit(SplitPermissionInfo split, int targetSdk, String... permission) {
+ assertThat(split.getNewPermissions()).containsExactlyElementsIn(permission);
+ assertThat(split.getTargetSdk()).isEqualTo(targetSdk);
+ }
+
+ private void assertSplit(SplitPermissionInfo split, String... permission) {
+ assertThat(split.getNewPermissions()).containsExactlyElementsIn(permission);
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/StorageEscalationTest.kt b/tests/cts/permission/src/android/permission/cts/StorageEscalationTest.kt
new file mode 100644
index 000000000..8c672e691
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/StorageEscalationTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2020 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.permission.cts
+
+import android.Manifest.permission.ACCESS_MEDIA_LOCATION
+import android.Manifest.permission.READ_EXTERNAL_STORAGE
+import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Process
+import android.os.UserHandle
+import android.platform.test.annotations.AppModeFull
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeNoException
+import org.junit.Before
+import org.junit.Test
+
+@AppModeFull
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+class StorageEscalationTest {
+ companion object {
+ private const val APK_DIRECTORY = "/data/local/tmp/cts/permissions"
+ const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsStorageEscalationApp28.apk"
+ const val APP_APK_PATH_29_SCOPED = "$APK_DIRECTORY/CtsStorageEscalationApp29Scoped.apk"
+ const val APP_APK_PATH_29_FULL = "$APK_DIRECTORY/CtsStorageEscalationApp29Full.apk"
+ const val APP_PACKAGE_NAME = "android.permission.cts.storageescalation"
+ const val DELAY_TIME_MS: Long = 200
+ val permissions = listOf<String>(READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE,
+ ACCESS_MEDIA_LOCATION)
+ }
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context: Context = instrumentation.context
+ private val uiAutomation: UiAutomation = instrumentation.uiAutomation
+ private var secondaryUserId: Int? = null
+
+ @Before
+ @After
+ fun uninstallApp() {
+ SystemUtil.runShellCommand("pm uninstall $APP_PACKAGE_NAME --user ALL")
+ }
+
+ private fun installPackage(apk: String) {
+ var userString = ""
+ secondaryUserId?.let { userId ->
+ userString = " --user $userId"
+ }
+ val result = SystemUtil.runShellCommand("pm install -r$userString $apk")
+ assertTrue("Expected output to contain \"Success\", but was \"$result\"",
+ result.contains("Success"))
+ }
+
+ private fun createSecondaryUser() {
+ val createUserOutput: String = SystemUtil.runShellCommand("pm create-user secondary")
+ var formatException: Exception? = null
+ val userId = try {
+ createUserOutput.split(" id ".toRegex())[1].trim { it <= ' ' }.toInt()
+ } catch (e: Exception) {
+ formatException = e
+ -1
+ }
+ assumeNoException("Failed to parse userId from $createUserOutput", formatException)
+ SystemUtil.runShellCommand("am start-user -w $userId")
+ secondaryUserId = userId
+ }
+
+ @After
+ fun removeSecondaryUser() {
+ secondaryUserId?.let { userId ->
+ SystemUtil.runShellCommand("pm remove-user $userId")
+ secondaryUserId = null
+ }
+ }
+
+ private fun grantStoragePermissions() {
+ for (permName in permissions) {
+ var user = Process.myUserHandle()
+ secondaryUserId?.let {
+ user = UserHandle.of(it)
+ }
+ uiAutomation.grantRuntimePermissionAsUser(APP_PACKAGE_NAME, permName, user)
+ }
+ }
+
+ private fun assertStoragePermissionState(granted: Boolean) {
+ for (permName in permissions) {
+ var userContext = context
+ secondaryUserId?.let { userId ->
+ SystemUtil.runWithShellPermissionIdentity {
+ userContext = context.createPackageContextAsUser(
+ APP_PACKAGE_NAME, 0, UserHandle.of(userId))
+ }
+ }
+ Assert.assertEquals(granted, userContext.packageManager.checkPermission(permName,
+ APP_PACKAGE_NAME) == PackageManager.PERMISSION_GRANTED)
+ }
+ }
+
+ @Test
+ fun testCannotEscalateWithSdkDowngrade() {
+ runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_28)
+ }
+
+ @Test
+ fun testCannotEscalateWithNewManifestLegacyRequest() {
+ runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_29_FULL)
+ }
+
+ @Test
+ fun testCannotEscalateWithSdkDowngradeSecondary() {
+ createSecondaryUser()
+ runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_28)
+ }
+
+ @Test
+ fun testCannotEscalateWithNewManifestLegacyRequestSecondary() {
+ createSecondaryUser()
+ runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_29_FULL)
+ }
+
+ private fun runStorageEscalationTest(startPackageApk: String, finishPackageApk: String) {
+ installPackage(startPackageApk)
+ grantStoragePermissions()
+ assertStoragePermissionState(granted = true)
+ installPackage(finishPackageApk)
+ // permission revoke is async, so wait a short period
+ Thread.sleep(DELAY_TIME_MS)
+ assertStoragePermissionState(granted = false)
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/TvPermissionTest.java b/tests/cts/permission/src/android/permission/cts/TvPermissionTest.java
new file mode 100644
index 000000000..e61b2667d
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/TvPermissionTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2014 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.permission.cts;
+
+import android.content.ContentValues;
+import android.content.pm.PackageManager;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+
+/**
+ * Tests for TV API related permissions.
+ */
+public class TvPermissionTest extends AndroidTestCase {
+ private static final String DUMMY_INPUT_ID = "dummy";
+
+ private boolean mHasTvInputFramework;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mHasTvInputFramework = getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_LIVE_TV);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void verifyInsert(Uri uri, String tableName) throws Exception {
+ try {
+ ContentValues values = new ContentValues();
+ getContext().getContentResolver().insert(uri, values);
+ fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
+ } catch (SecurityException e) {
+ // Expected exception
+ } catch (IllegalArgumentException e) {
+ // TvProvider is not visable for instant app
+ }
+ }
+
+ public void verifyUpdate(Uri uri, String tableName) throws Exception {
+ try {
+ ContentValues values = new ContentValues();
+ getContext().getContentResolver().update(uri, values, null, null);
+ fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
+ } catch (SecurityException e) {
+ // Expected exception
+ } catch (IllegalArgumentException e) {
+ // TvProvider is not visable for instant app
+ }
+ }
+
+ public void verifyDelete(Uri uri, String tableName) throws Exception {
+ try {
+ getContext().getContentResolver().delete(uri, null, null);
+ fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
+ } catch (SecurityException e) {
+ // Expected exception
+ } catch (IllegalArgumentException e) {
+ // TvProvider is not visable for instant app
+ }
+ }
+
+ @AppModeFull
+ public void testInsertChannels() throws Exception {
+ if (!mHasTvInputFramework) return;
+ verifyInsert(TvContract.Channels.CONTENT_URI, "channels");
+ }
+
+ @AppModeFull
+ public void testUpdateChannels() throws Exception {
+ if (!mHasTvInputFramework) return;
+ verifyUpdate(TvContract.Channels.CONTENT_URI, "channels");
+ }
+
+ @AppModeFull
+ public void testDeleteChannels() throws Exception {
+ if (!mHasTvInputFramework) return;
+ verifyDelete(TvContract.Channels.CONTENT_URI, "channels");
+ }
+
+ @AppModeFull
+ public void testInsertPrograms() throws Exception {
+ if (!mHasTvInputFramework) return;
+ verifyInsert(TvContract.Programs.CONTENT_URI, "programs");
+ }
+
+ @AppModeFull
+ public void testUpdatePrograms() throws Exception {
+ if (!mHasTvInputFramework) return;
+ verifyUpdate(TvContract.Programs.CONTENT_URI, "programs");
+ }
+
+ @AppModeFull
+ public void testDeletePrograms() throws Exception {
+ if (!mHasTvInputFramework) return;
+ verifyDelete(TvContract.Programs.CONTENT_URI, "programs");
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/UndefinedGroupPermissionTest.kt b/tests/cts/permission/src/android/permission/cts/UndefinedGroupPermissionTest.kt
new file mode 100644
index 000000000..5f0e15703
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/UndefinedGroupPermissionTest.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2020 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.permission.cts
+
+import android.Manifest.permission.CAMERA
+import android.Manifest.permission.RECORD_AUDIO
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Process
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.UiObjectNotFoundException
+import com.android.compatibility.common.util.SystemUtil
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject
+import java.util.regex.Pattern
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Tests that the permissioncontroller behaves normally when an app defines a permission in the
+ * android.permission-group.UNDEFINED group
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+class UndefinedGroupPermissionTest {
+ private var mInstrumentation: Instrumentation? = null
+ private var mUiDevice: UiDevice? = null
+ private var mContext: Context? = null
+ private var mPm: PackageManager? = null
+ private var mAllowButtonText: Pattern? = null
+ private var mDenyButtonText: Pattern? = null
+
+ @Before
+ fun install() {
+ SystemUtil.runShellCommand("pm uninstall $APP_PKG_NAME")
+ SystemUtil.runShellCommand("pm install -r " +
+ TEST_APP_DEFINES_UNDEFINED_PERMISSION_GROUP_ELEMENT_APK)
+ }
+
+ @Before
+ fun setup() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation()
+ mUiDevice = UiDevice.getInstance(mInstrumentation!!)
+ mContext = mInstrumentation?.targetContext
+ mPm = mContext?.packageManager
+ val permissionControllerResources = mContext?.createPackageContext(
+ mContext?.packageManager?.permissionControllerPackageName, 0)?.resources
+ mAllowButtonText = Pattern.compile(
+ Pattern.quote(requireNotNull(permissionControllerResources?.getString(
+ permissionControllerResources.getIdentifier(
+ "grant_dialog_button_allow", "string",
+ "com.android.permissioncontroller")))),
+ Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE)
+ mDenyButtonText = Pattern.compile(
+ Pattern.quote(requireNotNull(permissionControllerResources?.getString(
+ permissionControllerResources.getIdentifier(
+ "grant_dialog_button_deny", "string",
+ "com.android.permissioncontroller")))),
+ Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE)
+ }
+
+ @Before
+ fun wakeUpScreenAndUnlock() {
+ SystemUtil.runShellCommand("input keyevent KEYCODE_WAKEUP")
+ SystemUtil.runShellCommand("input keyevent KEYCODE_MENU")
+ }
+
+ @Test
+ fun testOtherGroupPermissionsNotGranted_1() {
+ testOtherGroupPermissionsNotGranted(CAMERA, RECORD_AUDIO)
+ }
+
+ @Test
+ fun testOtherGroupPermissionsNotGranted_2() {
+ testOtherGroupPermissionsNotGranted(TEST, RECORD_AUDIO)
+ }
+
+ @Test
+ fun testOtherGroupPermissionsNotGranted_3() {
+ testOtherGroupPermissionsNotGranted(CAMERA, TEST)
+ }
+
+ /**
+ * When the custom permission is granted nothing else gets granted as a byproduct.
+ */
+ @Test
+ fun testCustomPermissionGrantedAlone() {
+ Assert.assertEquals(PackageManager.PERMISSION_DENIED,
+ mPm!!.checkPermission(CAMERA, APP_PKG_NAME))
+ Assert.assertEquals(PackageManager.PERMISSION_DENIED,
+ mPm!!.checkPermission(RECORD_AUDIO, APP_PKG_NAME))
+ Assert.assertEquals(PackageManager.PERMISSION_DENIED,
+ mPm!!.checkPermission(TEST, APP_PKG_NAME))
+ eventually {
+ startRequestActivity(arrayOf(TEST))
+ mUiDevice!!.waitForIdle()
+ Thread.sleep(2000)
+ findAllowButton().click()
+ }
+ eventually {
+ Assert.assertEquals(PackageManager.PERMISSION_DENIED,
+ mPm!!.checkPermission(CAMERA, APP_PKG_NAME))
+ Assert.assertEquals(PackageManager.PERMISSION_DENIED,
+ mPm!!.checkPermission(RECORD_AUDIO, APP_PKG_NAME))
+ Assert.assertEquals(PackageManager.PERMISSION_GRANTED,
+ mPm!!.checkPermission(TEST, APP_PKG_NAME))
+ }
+ }
+
+ @After
+ fun uninstall() {
+ SystemUtil.runShellCommand("pm uninstall $APP_PKG_NAME")
+ }
+
+ fun findAllowButton(): UiObject2 {
+ return if (mContext?.packageManager
+ ?.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) == true) {
+ waitFindObject(By.text(mAllowButtonText!!), 2000)
+ } else {
+ waitFindObject(By.res(
+ "com.android.permissioncontroller:id/permission_allow_button"),
+ 2000)
+ }
+ }
+
+ /**
+ * If app has one permission granted, then it can't grant itself another permission for free.
+ */
+ fun testOtherGroupPermissionsNotGranted(grantedPerm: String, targetPermission: String) {
+ // Grant the permission in the background
+ SystemUtil.runWithShellPermissionIdentity {
+ mPm!!.grantRuntimePermission(
+ APP_PKG_NAME, grantedPerm, Process.myUserHandle())
+ }
+ Assert.assertEquals("$grantedPerm not granted.", PackageManager.PERMISSION_GRANTED,
+ mPm!!.checkPermission(grantedPerm, APP_PKG_NAME))
+
+ // If the dialog shows, success. If not then either the UI is broken or the permission was
+ // granted in the background.
+ eventually {
+ startRequestActivity(arrayOf(targetPermission))
+ mUiDevice!!.waitForIdle()
+ try {
+ if (mContext?.packageManager
+ ?.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) == true) {
+ waitFindObject(By.text(mDenyButtonText!!), 2000)
+ } else {
+ waitFindObject(By.res("com.android.permissioncontroller:id/grant_dialog"), 2000)
+ }
+ } catch (e: UiObjectNotFoundException) {
+ Assert.assertEquals("grant dialog never showed.",
+ PackageManager.PERMISSION_GRANTED,
+ mPm!!.checkPermission(targetPermission, APP_PKG_NAME))
+ }
+ }
+ Assert.assertEquals(PackageManager.PERMISSION_DENIED,
+ mPm!!.checkPermission(targetPermission, APP_PKG_NAME))
+ }
+
+ private fun startRequestActivity(permissions: Array<String>) {
+ mContext!!.startActivity(Intent()
+ .setComponent(
+ ComponentName(APP_PKG_NAME, "$APP_PKG_NAME.RequestPermissions"))
+ .putExtra(EXTRA_PERMISSIONS, permissions)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+ }
+
+ companion object {
+ private const val TEST_APP_DEFINES_UNDEFINED_PERMISSION_GROUP_ELEMENT_APK =
+ "/data/local/tmp/cts/permissions/AppThatDefinesUndefinedPermissionGroupElement.apk"
+ private const val APP_PKG_NAME = "android.permission.cts.appthatrequestpermission"
+ private const val EXTRA_PERMISSIONS =
+ "android.permission.cts.appthatrequestpermission.extra.PERMISSIONS"
+ const val TEST = "android.permission.cts.appthatrequestpermission.TEST"
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl b/tests/cts/permission/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl
new file mode 100644
index 000000000..be92ed160
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 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.permission.cts.appthataccesseslocation;
+
+interface IAccessLocationOnCommand {
+ /** Access location on command */
+ void accessLocation();
+} \ No newline at end of file
diff --git a/tests/cts/permission/telephony/Android.bp b/tests/cts/permission/telephony/Android.bp
new file mode 100644
index 000000000..bbfe06c55
--- /dev/null
+++ b/tests/cts/permission/telephony/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2018 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: "CtsPermissionTestCasesTelephony",
+ defaults: ["cts_defaults"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+ // Include both the 32 and 64 bit versions
+ compile_multilib: "both",
+ static_libs: [
+ "ctstestrunner-axt",
+ "compatibility-device-util-axt",
+ ],
+ srcs: ["src/**/*.java"],
+ sdk_version: "test_current",
+}
diff --git a/tests/cts/permission/telephony/AndroidManifest.xml b/tests/cts/permission/telephony/AndroidManifest.xml
new file mode 100644
index 000000000..0349880e2
--- /dev/null
+++ b/tests/cts/permission/telephony/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.permission.cts.telephony" android:targetSandboxVersion="2">
+
+ <!--
+ The CTS stubs package cannot be used as the target application here,
+ since that requires many permissions to be set. Instead, specify this
+ package itself as the target and include any stub activities needed.
+
+ This test package uses the default InstrumentationTestRunner, because
+ the InstrumentationCtsTestRunner is only available in the stubs
+ package. That runner cannot be added to this package either, since it
+ relies on hidden APIs.
+ -->
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.permission.cts.telephony"
+ android:label="CTS tests of android.permission">
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/cts/permission/telephony/AndroidTest.xml b/tests/cts/permission/telephony/AndroidTest.xml
new file mode 100644
index 000000000..13ee78bcc
--- /dev/null
+++ b/tests/cts/permission/telephony/AndroidTest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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 Telephony Permission test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+
+ <!-- Telephony permission tests do not use any permission not available to instant apps. -->
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+ <option name="config-descriptor:metadata" key="parameter" value="run_on_sdk_sandbox" />
+
+ <!-- Disable package verifier before installing APKs -->
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+ <option name="restore-settings" value="true" />
+ <option name="force-skip-system-props" value="true" />
+ </target_preparer>
+
+ <!-- Install main test suite apk -->
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsPermissionTestCasesTelephony.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.permission.cts.telephony" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
diff --git a/tests/cts/permission/telephony/OWNERS b/tests/cts/permission/telephony/OWNERS
new file mode 100644
index 000000000..446cf1a00
--- /dev/null
+++ b/tests/cts/permission/telephony/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+# Bug component: 20868
+include platform/cts:/tests/tests/telephony/OWNERS \ No newline at end of file
diff --git a/tests/cts/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java b/tests/cts/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java
new file mode 100644
index 000000000..3605be21a
--- /dev/null
+++ b/tests/cts/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2009 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.permission.cts.telephony;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Test the non-location-related functionality of TelephonyManager.
+ */
+@RunWith(AndroidJUnit4.class)
+public class TelephonyManagerPermissionTest {
+
+ private boolean mHasTelephony;
+ TelephonyManager mTelephonyManager = null;
+ private AudioManager mAudioManager;
+
+ @Before
+ public void setUp() throws Exception {
+ mHasTelephony = getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY);
+ mTelephonyManager =
+ (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
+ assertNotNull(mTelephonyManager);
+ mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+ assertNotNull(mAudioManager);
+ }
+
+ /**
+ * Verify that TelephonyManager.getDeviceId requires Permission.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE}.
+ */
+ @Test
+ public void testGetDeviceId() {
+ if (!mHasTelephony) {
+ return;
+ }
+
+ try {
+ String id = mTelephonyManager.getDeviceId();
+ fail("Got device ID: " + id);
+ } catch (SecurityException e) {
+ // expected
+ }
+ try {
+ String id = mTelephonyManager.getDeviceId(0);
+ fail("Got device ID: " + id);
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that TelephonyManager.getLine1Number requires Permission.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE}.
+ */
+ @Test
+ public void testGetLine1Number() {
+ if (!mHasTelephony) {
+ return;
+ }
+
+ try {
+ String nmbr = mTelephonyManager.getLine1Number();
+ fail("Got line 1 number: " + nmbr);
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that TelephonyManager.getSimSerialNumber requires Permission.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE}.
+ */
+ @Test
+ public void testGetSimSerialNumber() {
+ if (!mHasTelephony) {
+ return;
+ }
+
+ try {
+ String nmbr = mTelephonyManager.getSimSerialNumber();
+ fail("Got SIM serial number: " + nmbr);
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that TelephonyManager.getSubscriberId requires Permission.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE}.
+ */
+ @Test
+ public void testGetSubscriberId() {
+ if (!mHasTelephony) {
+ return;
+ }
+
+ try {
+ String sid = mTelephonyManager.getSubscriberId();
+ fail("Got subscriber id: " + sid);
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that TelephonyManager.getVoiceMailNumber requires Permission.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE}.
+ */
+ @Test
+ public void testVoiceMailNumber() {
+ if (!mHasTelephony) {
+ return;
+ }
+
+ try {
+ String vmnum = mTelephonyManager.getVoiceMailNumber();
+ fail("Got voicemail number: " + vmnum);
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+ /**
+ * Verify that AudioManager.setMode requires Permission.
+ * <p>
+ * Requires Permissions:
+ * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} and
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} for
+ * {@link AudioManager#MODE_IN_CALL}.
+ */
+ @Test
+ public void testSetMode() {
+ if (!mHasTelephony) {
+ return;
+ }
+ int audioMode = mAudioManager.getMode();
+ mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+ assertEquals(audioMode, mAudioManager.getMode());
+ }
+
+ /**
+ * Tests that isManualNetworkSelectionAllowed requires permission
+ * Expects a security exception since the caller does not have carrier privileges.
+ */
+ @Test
+ public void testIsManualNetworkSelectionAllowedWithoutPermission() {
+ if (!mHasTelephony) {
+ return;
+ }
+ try {
+ mTelephonyManager.isManualNetworkSelectionAllowed();
+ fail("Expected SecurityException. App does not have carrier privileges.");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ /**
+ * Tests that getManualNetworkSelectionPlmn requires permission
+ * Expects a security exception since the caller does not have carrier privileges.
+ */
+ @Test
+ public void testGetManualNetworkSelectionPlmnWithoutPermission() {
+ if (!mHasTelephony) {
+ return;
+ }
+ try {
+ mTelephonyManager.getManualNetworkSelectionPlmn();
+ fail("Expected SecurityException. App does not have carrier privileges.");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ /**
+ * Verify that Telephony related broadcasts are protected.
+ */
+ @Test
+ public void testProtectedBroadcasts() {
+ if (!mHasTelephony) {
+ return;
+ }
+ try {
+ Intent intent = new Intent("android.intent.action.SIM_STATE_CHANGED");
+ getContext().sendBroadcast(intent);
+ fail("SecurityException expected!");
+ } catch (SecurityException e) {}
+ try {
+ Intent intent = new Intent("android.intent.action.SERVICE_STATE");
+ getContext().sendBroadcast(intent);
+ fail("SecurityException expected!");
+ } catch (SecurityException e) {}
+ try {
+ Intent intent = new Intent("android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED");
+ getContext().sendBroadcast(intent);
+ fail("SecurityException expected!");
+ } catch (SecurityException e) {}
+ try {
+ Intent intent = new Intent(
+ "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED");
+ getContext().sendBroadcast(intent);
+ fail("SecurityException expected!");
+ } catch (SecurityException e) {}
+ try {
+ Intent intent = new Intent(
+ "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED");
+ getContext().sendBroadcast(intent);
+ fail("SecurityException expected!");
+ } catch (SecurityException e) {}
+ try {
+ Intent intent = new Intent(
+ "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED");
+ getContext().sendBroadcast(intent);
+ fail("SecurityException expected!");
+ } catch (SecurityException e) {}
+ try {
+ Intent intent = new Intent("android.intent.action.SIG_STR");
+ getContext().sendBroadcast(intent);
+ fail("SecurityException expected!");
+ } catch (SecurityException e) {}
+ try {
+ Intent intent = new Intent("android.provider.Telephony.SECRET_CODE");
+ getContext().sendBroadcast(intent);
+ fail("SecurityException expected!");
+ } catch (SecurityException e) {}
+ }
+
+ /**
+ * Verify that TelephonyManager.getImei requires Permission.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE}.
+ */
+ @Test
+ public void testGetImei() {
+ if (!mHasTelephony) {
+ return;
+ }
+
+ try {
+ String imei = mTelephonyManager.getImei();
+ fail("Got IMEI: " + imei);
+ } catch (SecurityException e) {
+ // expected
+ }
+ try {
+ String imei = mTelephonyManager.getImei(0);
+ fail("Got IMEI: " + imei);
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that getNetworkType and getDataNetworkType requires Permission.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE}.
+ */
+ @Test
+ public void testGetNetworkType() {
+ if (!mHasTelephony) {
+ return;
+ }
+
+ try {
+ mTelephonyManager.getNetworkType();
+ fail("getNetworkType did not throw a SecurityException");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ try {
+ mTelephonyManager.getDataNetworkType();
+ fail("getDataNetworkType did not throw a SecurityException");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Tests that getNetworkSelectionMode requires permission
+ * Expects a security exception since the caller does not have carrier privileges.
+ */
+ @Test
+ public void testGetNetworkSelectionModeWithoutPermission() {
+ if (!mHasTelephony) {
+ return;
+ }
+ assertThrowsSecurityException(() -> mTelephonyManager.getNetworkSelectionMode(),
+ "Expected SecurityException. App does not have carrier privileges.");
+ }
+
+ /**
+ * Tests that setNetworkSelectionModeAutomatic requires permission
+ * Expects a security exception since the caller does not have carrier privileges.
+ */
+ @Test
+ public void testSetNetworkSelectionModeAutomaticWithoutPermission() {
+ if (!mHasTelephony) {
+ return;
+ }
+ assertThrowsSecurityException(() -> mTelephonyManager.setNetworkSelectionModeAutomatic(),
+ "Expected SecurityException. App does not have carrier privileges.");
+ }
+
+ /**
+ * Verify that setForbiddenPlmns requires Permission.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ */
+ @Test
+ public void testSetForbiddenPlmns() {
+ if (!mHasTelephony) {
+ return;
+ }
+
+ try {
+ mTelephonyManager.setForbiddenPlmns(new ArrayList<String>());
+ fail("SetForbiddenPlmns did not throw a SecurityException");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ static final int PHONE_STATE_PERMISSION_MASK =
+ PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
+ | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
+ | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST;
+
+ static final int PRECISE_PHONE_STATE_PERMISSION_MASK =
+ PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE
+ | PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES
+ | PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES
+ | PhoneStateListener.LISTEN_REGISTRATION_FAILURE
+ | PhoneStateListener.LISTEN_BARRING_INFO;
+
+ static final int PHONE_PERMISSIONS_MASK =
+ PHONE_STATE_PERMISSION_MASK | PRECISE_PHONE_STATE_PERMISSION_MASK;
+
+ /**
+ * Verify the documented permissions for PhoneStateListener.
+ */
+ @Test
+ public void testListen() {
+ PhoneStateListener psl = new PhoneStateListener((Runnable r) -> { });
+
+ try {
+ for (int i = 1; i != 0; i = i << 1) {
+ if ((i & PHONE_PERMISSIONS_MASK) == 0) continue;
+ final int listenBit = i;
+ assertThrowsSecurityException(() -> mTelephonyManager.listen(psl, listenBit),
+ "Expected a security exception for " + Integer.toHexString(i));
+ }
+ } finally {
+ mTelephonyManager.listen(psl, PhoneStateListener.LISTEN_NONE);
+ }
+ }
+
+ private static Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ // An actual version of assertThrows() was added in JUnit5
+ private static <T extends Throwable> void assertThrows(Class<T> clazz, Runnable r,
+ String message) {
+ try {
+ r.run();
+ } catch (Exception expected) {
+ assertTrue(clazz.isAssignableFrom(expected.getClass()));
+ return;
+ }
+ fail(message);
+ }
+
+ private static void assertThrowsSecurityException(Runnable r, String message) {
+ assertThrows(SecurityException.class, r, message);
+ }
+
+ /**
+ * Verify that TelephonyManager.getPrimaryImei requires Permission.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}.
+ */
+ @Test
+ public void testGetPrimaryImei() {
+ if (!mHasTelephony) {
+ return;
+ }
+
+ try {
+ String primaryImei = mTelephonyManager.getPrimaryImei();
+ fail("Received Primary Imei value: " + primaryImei);
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that getCarrierRestrictionStatus requires permission
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE}.
+ */
+ @Test
+ public void getCarrierRestrictionStatus_Exception() {
+ if (!mHasTelephony) {
+ return;
+ }
+ LinkedBlockingQueue<Integer> carrierRestrictionStatusResult = new LinkedBlockingQueue<>(1);
+ try {
+ mTelephonyManager.getCarrierRestrictionStatus(getContext().getMainExecutor(),
+ carrierRestrictionStatusResult::offer);
+ // Test case fail, if the API don't catch the security exception.
+ fail();
+ } catch (SecurityException ex) {
+ // expecting the security exception.
+ } catch (Exception ex) {
+ // The test case should fail if other than security exception comes.
+ fail();
+ }
+ }
+}
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp
new file mode 100644
index 000000000..6f0bcf111
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 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: "CtsAdversarialPermissionDefinerApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+ certificate: ":cts-testkey1",
+}
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/AndroidManifest.xml b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/AndroidManifest.xml
new file mode 100644
index 000000000..d28fba87c
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.permission.cts.revokepermissionwhenremoved.AdversarialPermissionDefinerApp">
+
+ <permission android:name="android.permission.cts.revokepermissionwhenremoved.TestPermission"
+ android:protectionLevel="dangerous"
+ android:label="TestPermission"
+ android:description="@string/test_permission" />
+
+ <application>
+ </application>
+</manifest>
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/res/values/strings.xml b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/res/values/strings.xml
new file mode 100644
index 000000000..bfb3e1e2b
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="test_permission">Test Permission</string>
+</resources>
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp
new file mode 100644
index 000000000..41f01e981
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 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: "CtsAdversarialPermissionUserApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+ certificate: ":cts-testkey2",
+}
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/AndroidManifest.xml b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/AndroidManifest.xml
new file mode 100644
index 000000000..f514d549a
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.permission.cts.revokepermissionwhenremoved.userapp">
+
+ <uses-permission android:name="android.permission.cts.revokepermissionwhenremoved.TestPermission" />
+
+ <application>
+ </application>
+</manifest>
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/Android.bp b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/Android.bp
new file mode 100644
index 000000000..a3e1f13d2
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 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: "CtsInstallPermissionDefinerApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+ certificate: ":cts-testkey1",
+}
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/AndroidManifest.xml b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/AndroidManifest.xml
new file mode 100644
index 000000000..2df67431b
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.permission.cts.revokepermissionwhenremoved.installpermissiondefinerapp">
+
+ <permission
+ android:name="android.permission.cts.revokepermissionwhenremoved.TestInstallPermission"
+ android:protectionLevel="normal" />
+
+ <application />
+</manifest>
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/Android.bp b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/Android.bp
new file mode 100644
index 000000000..caaa8e898
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 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: "CtsInstallPermissionEscalatorApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+ certificate: ":cts-testkey1",
+}
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/AndroidManifest.xml b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/AndroidManifest.xml
new file mode 100644
index 000000000..6320618c5
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.permission.cts.revokepermissionwhenremoved.installpermissionescalatorapp">
+
+ <permission
+ android:name="android.permission.cts.revokepermissionwhenremoved.TestInstallPermission"
+ android:permissionGroup="android.permission-group.PHONE"
+ android:protectionLevel="dangerous" />
+
+ <application />
+</manifest>
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/Android.bp b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/Android.bp
new file mode 100644
index 000000000..d9ca4c8ea
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 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: "CtsInstallPermissionUserApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+ certificate: ":cts-testkey2",
+}
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/AndroidManifest.xml b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/AndroidManifest.xml
new file mode 100644
index 000000000..acfafa986
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.permission.cts.revokepermissionwhenremoved.installpermissionuserapp">
+
+ <uses-permission android:name="android.permission.cts.revokepermissionwhenremoved.TestInstallPermission" />
+
+ <application />
+</manifest>
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionUserApp/AndroidManifest.xml b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionUserApp/AndroidManifest.xml
new file mode 100644
index 000000000..7a0e40554
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionUserApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.permission.cts.revokepermissionwhenremoved.installtimepermissionuserapp">
+
+ <uses-permission android:name="android.permission.cts.revokepermissionwhenremoved.TestInstalltimePermission" />
+
+ <application>
+ </application>
+</manifest>
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp
new file mode 100644
index 000000000..b4ea30ada
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 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: "CtsRuntimePermissionDefinerApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+ certificate: ":cts-testkey1",
+}
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/AndroidManifest.xml b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/AndroidManifest.xml
new file mode 100644
index 000000000..d3cf6d0aa
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.permission.cts.revokepermissionwhenremoved.runtimepermissiondefinerapp">
+
+ <permission android:name="android.permission.cts.revokepermissionwhenremoved.TestRuntimePermission"
+ android:protectionLevel="dangerous"
+ android:label="TestPermission"
+ android:description="@string/test_permission" />
+
+ <application>
+ </application>
+</manifest>
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/res/values/strings.xml b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/res/values/strings.xml
new file mode 100644
index 000000000..bfb3e1e2b
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="test_permission">Test Permission</string>
+</resources>
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp
new file mode 100644
index 000000000..22c6ccd6f
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 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: "CtsRuntimePermissionUserApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ ],
+ certificate: ":cts-testkey2",
+}
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/AndroidManifest.xml b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/AndroidManifest.xml
new file mode 100644
index 000000000..d977e46e4
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.permission.cts.revokepermissionwhenremoved.runtimepermissionuserapp">
+
+ <uses-permission android:name="android.permission.cts.revokepermissionwhenremoved.TestRuntimePermission" />
+
+ <application>
+ </application>
+</manifest>
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp
new file mode 100644
index 000000000..5627a3d36
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 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: "CtsVictimPermissionDefinerApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ ],
+ certificate: ":cts-testkey1",
+}
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/AndroidManifest.xml b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/AndroidManifest.xml
new file mode 100644
index 000000000..3fb0abd29
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.permission.cts.revokepermissionwhenremoved.VictimPermissionDefinerApp">
+ <permission android:name="android.permission.cts.revokepermissionwhenremoved.TestPermission"
+ android:protectionLevel="signature"
+ android:label="Test Permission"
+ android:description="@string/test_permission" />
+ <application>
+ </application>
+</manifest>
diff --git a/tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/res/values/strings.xml b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/res/values/strings.xml
new file mode 100644
index 000000000..bfb3e1e2b
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="test_permission">Test Permission</string>
+</resources>
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..10847c7c6
--- /dev/null
+++ b/tests/cts/permissionmultiuser/AndroidTest.xml
@@ -0,0 +1,69 @@
+<?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>
+
+ <!-- Create place to store apks -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/permissionmultiuser" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+ </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)
+ }
+ }
+}
diff --git a/tests/cts/permissionpolicy/Android.bp b/tests/cts/permissionpolicy/Android.bp
new file mode 100644
index 000000000..5a92e890a
--- /dev/null
+++ b/tests/cts/permissionpolicy/Android.bp
@@ -0,0 +1,66 @@
+//
+// Copyright (C) 2009 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: "CtsPermissionPolicyTestCases",
+ defaults: ["cts_defaults"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+ libs: ["android.test.base"],
+ static_libs: [
+ "androidx.test.core",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "guava",
+ "androidx.test.ext.junit-nodeps",
+ "truth-prebuilt",
+ "permission-test-util-lib",
+ ],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ sdk_version: "test_current",
+ data: [
+ ":CtsLocationPermissionsUserSdk22",
+ ":CtsLocationPermissionsUserSdk29",
+ ":CtsSMSCallLogPermissionsUserSdk22",
+ ":CtsSMSCallLogPermissionsUserSdk29",
+ ":CtsStoragePermissionsUserDefaultSdk22",
+ ":CtsStoragePermissionsUserDefaultSdk28",
+ ":CtsStoragePermissionsUserDefaultSdk29",
+ ":CtsStoragePermissionsUserOptInSdk22",
+ ":CtsStoragePermissionsUserOptInSdk28",
+ ":CtsStoragePermissionsUserOptOutSdk29",
+ ":CtsStoragePermissionsPreservedUserOptOutSdk30",
+ ":CtsLegacyStorageNotIsolatedWithSharedUid",
+ ":CtsLegacyStorageIsolatedWithSharedUid",
+ ":CtsLegacyStorageRestrictedWithSharedUid",
+ ":CtsLegacyStorageRestrictedSdk28WithSharedUid",
+ ":CtsStoragePermissionsUserOptOutSdk30",
+ ":CtsSMSRestrictedWithSharedUid",
+ ":CtsSMSNotRestrictedWithSharedUid",
+ ":CtsProcessOutgoingCalls",
+ ],
+}
diff --git a/tests/cts/permissionpolicy/AndroidManifest.xml b/tests/cts/permissionpolicy/AndroidManifest.xml
new file mode 100755
index 000000000..53ddc59e5
--- /dev/null
+++ b/tests/cts/permissionpolicy/AndroidManifest.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2009 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.permissionpolicy.cts" android:targetSandboxVersion="2">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!--
+ This app contains tests to verify specialized permissions, that require the app to have
+ some permissions.
+ -->
+
+ <!-- need ability to send sms, to test that SMS's cannot be received -->
+ <uses-permission android:name="android.permission.SEND_SMS"/>
+
+ <!-- needs read phone numbers to get current phone number for R+ -->
+ <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
+
+ <!-- needs read phone status to get current phone subscription info for R+ -->
+ <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+
+ <!-- need app that has WRITE_SETTINGS but not WRITE_SECURE_SETTINGS -->
+ <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+
+ <!-- need app that has CALL_PHONE but not PROCESS_OUTGOING_CALL -->
+ <uses-permission android:name="android.permission.CALL_PHONE"/>
+
+ <!-- need app that has RECORD_AUDIO but not CAPTURE_AUDIO_OUTPUT -->
+ <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+
+ <!-- need app that has READ_CONTACTS but not READ_PROFILE -->
+ <uses-permission android:name="android.permission.READ_CONTACTS"/>
+
+ <!-- need app that has WRITE_CONTACTS but not WRITE_PROFILE -->
+ <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+
+ <!-- need a permission that would ordinarily be granted, but has a maxSdkVersion that
+ causes it to be withheld under the current API level -->
+ <uses-permission
+ android:name="android.permission.INTERNET"
+ android:maxSdkVersion="18"/>
+
+
+ <!-- need a permission that will be granted -->
+ <uses-permission
+ android:name="android.permission.ACCESS_NETWORK_STATE"
+ android:maxSdkVersion="9000"/>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.permissionpolicy.cts"
+ android:label="More CTS tests for permissions">
+ </instrumentation>
+
+</manifest>
+
diff --git a/tests/cts/permissionpolicy/AndroidTest.xml b/tests/cts/permissionpolicy/AndroidTest.xml
new file mode 100644
index 000000000..ac27a0366
--- /dev/null
+++ b/tests/cts/permissionpolicy/AndroidTest.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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 Permission Policy test cases">
+
+ <option name="test-suite-tag" value="cts" />
+ <option name="not-shardable" value="true" />
+
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
+ <option name="config-descriptor:metadata" key="parameter" value="run_on_sdk_sandbox" />
+ <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
+
+ <!-- TODO(b/245579250): update to Sdk34 once sdk finalized -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsPermissionPolicyTestCases.apk" />
+ </target_preparer>
+
+ <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.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="CtsLocationPermissionsUserSdk22.apk->/data/local/tmp/cts/permissions2/CtsLocationPermissionsUserSdk22.apk" />
+ <option name="push" value="CtsLocationPermissionsUserSdk29.apk->/data/local/tmp/cts/permissions2/CtsLocationPermissionsUserSdk29.apk" />
+ <option name="push" value="CtsSMSCallLogPermissionsUserSdk22.apk->/data/local/tmp/cts/permissions2/CtsSMSCallLogPermissionsUserSdk22.apk" />
+ <option name="push" value="CtsSMSCallLogPermissionsUserSdk29.apk->/data/local/tmp/cts/permissions2/CtsSMSCallLogPermissionsUserSdk29.apk" />
+ <option name="push" value="CtsStoragePermissionsUserDefaultSdk22.apk->/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserDefaultSdk22.apk" />
+ <option name="push" value="CtsStoragePermissionsUserDefaultSdk28.apk->/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserDefaultSdk28.apk" />
+ <option name="push" value="CtsStoragePermissionsUserDefaultSdk29.apk->/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserDefaultSdk29.apk" />
+ <option name="push" value="CtsStoragePermissionsUserOptInSdk22.apk->/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserOptInSdk22.apk" />
+ <option name="push" value="CtsStoragePermissionsUserOptInSdk28.apk->/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserOptInSdk28.apk" />
+ <option name="push" value="CtsStoragePermissionsUserOptOutSdk29.apk->/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserOptOutSdk29.apk" />
+ <option name="push" value="CtsLegacyStorageNotIsolatedWithSharedUid.apk->/data/local/tmp/cts/permissions2/CtsLegacyStorageNotIsolatedWithSharedUid.apk" />
+ <option name="push" value="CtsLegacyStorageIsolatedWithSharedUid.apk->/data/local/tmp/cts/permissions2/CtsLegacyStorageIsolatedWithSharedUid.apk" />
+ <option name="push" value="CtsLegacyStorageRestrictedWithSharedUid.apk->/data/local/tmp/cts/permissions2/CtsLegacyStorageRestrictedWithSharedUid.apk" />
+ <option name="push" value="CtsLegacyStorageRestrictedSdk28WithSharedUid.apk->/data/local/tmp/cts/permissions2/CtsLegacyStorageRestrictedSdk28WithSharedUid.apk" />
+ <option name="push" value="CtsStoragePermissionsUserOptOutSdk30.apk->/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserOptOutSdk30.apk" />
+ <option name="push" value="CtsStoragePermissionsPreservedUserOptOutSdk30.apk->/data/local/tmp/cts/permissions2/CtsStoragePermissionsPreservedUserOptOutSdk30.apk" />
+ <option name="push" value="CtsSMSRestrictedWithSharedUid.apk->/data/local/tmp/cts/permissions2/CtsSMSRestrictedWithSharedUid.apk" />
+ <option name="push" value="CtsSMSNotRestrictedWithSharedUid.apk->/data/local/tmp/cts/permissions2/CtsSMSNotRestrictedWithSharedUid.apk" />
+ <option name="push" value="CtsProcessOutgoingCalls.apk->/data/local/tmp/cts/permissions2/CtsProcessOutgoingCalls.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- 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" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.permissionpolicy.cts" />
+ <option name="runtime-hint" value="2m" />
+ </test>
+
+</configuration>
diff --git a/tests/cts/permissionpolicy/CtsLegacyStorageIsolatedWithSharedUid/Android.bp b/tests/cts/permissionpolicy/CtsLegacyStorageIsolatedWithSharedUid/Android.bp
new file mode 100644
index 000000000..146bdceae
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsLegacyStorageIsolatedWithSharedUid/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2019 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: "CtsLegacyStorageIsolatedWithSharedUid",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+}
diff --git a/tests/cts/permissionpolicy/CtsLegacyStorageIsolatedWithSharedUid/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsLegacyStorageIsolatedWithSharedUid/AndroidManifest.xml
new file mode 100644
index 000000000..d221b1284
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsLegacyStorageIsolatedWithSharedUid/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.legacystoragewithshareduid.isolated"
+ android:versionCode="1"
+ android:sharedUserId="android.permissionpolicy.cts.restrictedpermissionuser.shareduid">
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+ <application android:label="CtsLegacyStorageIsolatedWithSharedUid" />
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp b/tests/cts/permissionpolicy/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp
new file mode 100644
index 000000000..c4bc761ce
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2019 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: "CtsLegacyStorageNotIsolatedWithSharedUid",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+}
diff --git a/tests/cts/permissionpolicy/CtsLegacyStorageNotIsolatedWithSharedUid/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsLegacyStorageNotIsolatedWithSharedUid/AndroidManifest.xml
new file mode 100644
index 000000000..69188d392
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsLegacyStorageNotIsolatedWithSharedUid/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.legacystoragewithshareduid.notisolated"
+ android:versionCode="1"
+ android:sharedUserId="android.permissionpolicy.cts.restrictedpermissionuser.shareduid">
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
+ <application android:label="CtsLegacyStorageNotIsolatedWithSharedUid"
+ android:requestLegacyExternalStorage="true" />
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp b/tests/cts/permissionpolicy/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp
new file mode 100644
index 000000000..2373fc1e2
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2019 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: "CtsLegacyStorageRestrictedSdk28WithSharedUid",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+}
diff --git a/tests/cts/permissionpolicy/CtsLegacyStorageRestrictedSdk28WithSharedUid/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsLegacyStorageRestrictedSdk28WithSharedUid/AndroidManifest.xml
new file mode 100644
index 000000000..cf09f33a3
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsLegacyStorageRestrictedSdk28WithSharedUid/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.legacystoragewithshareduid.restrictedsdk28"
+ android:versionCode="1"
+ android:sharedUserId="android.permissionpolicy.cts.restrictedpermissionuser.shareduid">
+
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+ <application android:label="CtsLegacyStorageRestrictedSdk28WithSharedUid" />
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsLegacyStorageRestrictedWithSharedUid/Android.bp b/tests/cts/permissionpolicy/CtsLegacyStorageRestrictedWithSharedUid/Android.bp
new file mode 100644
index 000000000..523f74224
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsLegacyStorageRestrictedWithSharedUid/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2019 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: "CtsLegacyStorageRestrictedWithSharedUid",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+}
diff --git a/tests/cts/permissionpolicy/CtsLegacyStorageRestrictedWithSharedUid/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsLegacyStorageRestrictedWithSharedUid/AndroidManifest.xml
new file mode 100644
index 000000000..3e9acc9aa
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsLegacyStorageRestrictedWithSharedUid/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.legacystoragewithshareduid.restricted"
+ android:versionCode="1"
+ android:sharedUserId="android.permissionpolicy.cts.restrictedpermissionuser.shareduid">
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+ <application android:label="CtsLegacyStorageRestrictedWithSharedUid" />
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk22/Android.bp b/tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk22/Android.bp
new file mode 100644
index 000000000..787cbaebe
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk22/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2019 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: "CtsLocationPermissionsUserSdk22",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+
+ // TODO: Uncomment when uncommenting the test
+ // srcs: ["src/**/*.java"]
+
+}
diff --git a/tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk22/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk22/AndroidManifest.xml
new file mode 100644
index 000000000..f0732755f
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk22/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.restrictedpermissionuser"
+ android:versionCode="1">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22"/>
+
+ <application android:label="CtsLocationPermissionsUserSdk22">
+ </application>
+
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk29/Android.bp b/tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk29/Android.bp
new file mode 100644
index 000000000..93c8b72b3
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk29/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2019 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: "CtsLocationPermissionsUserSdk29",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+
+ // TODO: Uncomment when uncommenting the test
+ // srcs: ["src/**/*.java"]
+
+}
diff --git a/tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk29/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk29/AndroidManifest.xml
new file mode 100644
index 000000000..21c73cc07
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsLocationPermissionsUserSdk29/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.restrictedpermissionuser"
+ android:versionCode="1">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+ <application android:label="CtsLocationPermissionsUserSdk29">
+ </application>
+
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsProcessOutgoingCalls/Android.bp b/tests/cts/permissionpolicy/CtsProcessOutgoingCalls/Android.bp
new file mode 100644
index 000000000..ef6f44b5a
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsProcessOutgoingCalls/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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: "CtsProcessOutgoingCalls",
+ defaults: ["cts_defaults"],
+ sdk_version: "test_current",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+}
diff --git a/tests/cts/permissionpolicy/CtsProcessOutgoingCalls/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsProcessOutgoingCalls/AndroidManifest.xml
new file mode 100644
index 000000000..254d7d9ce
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsProcessOutgoingCalls/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.permissionpolicy.cts.receivecallbroadcast">
+
+ <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
+
+ <application>
+ <activity android:name=".ProcessOutgoingCallReceiver$BaseActivity" android:exported="true"/>
+ <receiver android:name=".ProcessOutgoingCallReceiver" android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsProcessOutgoingCalls/src/android/permissionpolicy/cts/receivecallbroadcast/ProcessOutgoingCallReceiver.kt b/tests/cts/permissionpolicy/CtsProcessOutgoingCalls/src/android/permissionpolicy/cts/receivecallbroadcast/ProcessOutgoingCallReceiver.kt
new file mode 100644
index 000000000..fbab4a2d7
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsProcessOutgoingCalls/src/android/permissionpolicy/cts/receivecallbroadcast/ProcessOutgoingCallReceiver.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.permissionpolicy.cts.receivecallbroadcast
+
+import android.app.Activity
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+
+class ProcessOutgoingCallReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ context!!.sendBroadcast(Intent(ACTION_TEST_APP_RECEIVED_OUTGOING_CALL).setPackage(
+ TEST_CLASS_PKG_NAME))
+ }
+
+ class BaseActivity : Activity()
+}
+
+const val TEST_CLASS_PKG_NAME = "android.permissionpolicy.cts"
+const val ACTION_TEST_APP_RECEIVED_OUTGOING_CALL =
+ "android.permissionpolicy.cts.TEST_APP_RECEIVED_CALL"
diff --git a/tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk22/Android.bp b/tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk22/Android.bp
new file mode 100644
index 000000000..b864a4e26
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk22/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2019 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: "CtsSMSCallLogPermissionsUserSdk22",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+
+ // TODO: Uncomment when uncommenting the test
+ // srcs: ["src/**/*.java"]
+
+}
diff --git a/tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk22/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk22/AndroidManifest.xml
new file mode 100644
index 000000000..3d1207360
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk22/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.restrictedpermissionuser"
+ android:versionCode="1">
+
+ <!-- SMS -->
+ <uses-permission android:name="android.permission.SEND_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+ <uses-permission android:name="android.permission.READ_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
+ <uses-permission android:name="android.permission.RECEIVE_MMS" />
+ <uses-permission android:name="android.permission.READ_CELL_BROADCASTS" />
+
+ <!-- CallLog -->
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+ <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22"/>
+
+ <application android:label="CtsSMSCallLogPermissionsUserSdk22">
+ </application>
+
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk29/Android.bp b/tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk29/Android.bp
new file mode 100644
index 000000000..d3a2e30f0
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk29/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2019 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: "CtsSMSCallLogPermissionsUserSdk29",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+
+ // TODO: Uncomment when uncommenting the test
+ // srcs: ["src/**/*.java"]
+
+}
diff --git a/tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk29/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk29/AndroidManifest.xml
new file mode 100644
index 000000000..035e2e495
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsSMSCallLogPermissionsUserSdk29/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.restrictedpermissionuser"
+ android:versionCode="1">
+
+ <!-- SMS -->
+ <uses-permission android:name="android.permission.SEND_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+ <uses-permission android:name="android.permission.READ_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
+ <uses-permission android:name="android.permission.RECEIVE_MMS" />
+ <uses-permission android:name="android.permission.READ_CELL_BROADCASTS" />
+
+ <!-- CallLog -->
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+ <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
+
+ <application android:label="CtsSMSCallLogPermissionsUserSdk29">
+ </application>
+
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsSMSNotRestrictedWithSharedUid/Android.bp b/tests/cts/permissionpolicy/CtsSMSNotRestrictedWithSharedUid/Android.bp
new file mode 100644
index 000000000..ab90dba35
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsSMSNotRestrictedWithSharedUid/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2019 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: "CtsSMSNotRestrictedWithSharedUid",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+}
diff --git a/tests/cts/permissionpolicy/CtsSMSNotRestrictedWithSharedUid/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsSMSNotRestrictedWithSharedUid/AndroidManifest.xml
new file mode 100644
index 000000000..109a9e8df
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsSMSNotRestrictedWithSharedUid/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.smswithshareduid.notrestricted"
+ android:versionCode="1"
+ android:sharedUserId="android.permissionpolicy.cts.restrictedpermissionuser.shareduid">
+
+ <uses-permission android:name="android.permission.READ_SMS" />
+
+ <application android:label="CtsSMSNotRestrictedWithSharedUid" />
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsSMSRestrictedWithSharedUid/Android.bp b/tests/cts/permissionpolicy/CtsSMSRestrictedWithSharedUid/Android.bp
new file mode 100644
index 000000000..8820ab776
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsSMSRestrictedWithSharedUid/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2019 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: "CtsSMSRestrictedWithSharedUid",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+}
diff --git a/tests/cts/permissionpolicy/CtsSMSRestrictedWithSharedUid/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsSMSRestrictedWithSharedUid/AndroidManifest.xml
new file mode 100644
index 000000000..cb44afdd1
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsSMSRestrictedWithSharedUid/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.smswithshareduid.restricted"
+ android:versionCode="1"
+ android:sharedUserId="android.permissionpolicy.cts.restrictedpermissionuser.shareduid">
+
+ <uses-permission android:name="android.permission.READ_SMS" />
+
+ <application android:label="CtsSMSRestrictedWithSharedUid" />
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsPreservedUserOptOutSdk30/Android.bp b/tests/cts/permissionpolicy/CtsStoragePermissionsPreservedUserOptOutSdk30/Android.bp
new file mode 100644
index 000000000..6c76c7485
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsPreservedUserOptOutSdk30/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2020 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: "CtsStoragePermissionsPreservedUserOptOutSdk30",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+}
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsPreservedUserOptOutSdk30/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsStoragePermissionsPreservedUserOptOutSdk30/AndroidManifest.xml
new file mode 100644
index 000000000..48c4f37c9
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsPreservedUserOptOutSdk30/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.permissionpolicy.cts.restrictedpermissionuser"
+ android:versionCode="1">
+
+ <!-- Storage -->
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <uses-sdk android:targetSdkVersion="30"/>
+
+ <application android:label="CtsStoragePermissionsPreservedUserOptOutSdk30"
+ android:preserveLegacyExternalStorage="true"/>
+
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk22/Android.bp b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk22/Android.bp
new file mode 100644
index 000000000..bb817f780
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk22/Android.bp
@@ -0,0 +1,24 @@
+//
+// Copyright (C) 2019 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: "CtsStoragePermissionsUserDefaultSdk22",
+ defaults: ["cts_defaults"],
+}
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk22/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk22/AndroidManifest.xml
new file mode 100644
index 000000000..543b9ebb8
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk22/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.restrictedpermissionuser"
+ android:versionCode="1">
+
+ <!-- Storage -->
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22"/>
+
+ <application android:label="CtsStoragePermissionsUserDefaultSdk22" />
+
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk28/Android.bp b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk28/Android.bp
new file mode 100644
index 000000000..60cb316f6
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk28/Android.bp
@@ -0,0 +1,24 @@
+//
+// Copyright (C) 2019 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: "CtsStoragePermissionsUserDefaultSdk28",
+ defaults: ["cts_defaults"],
+}
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk28/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk28/AndroidManifest.xml
new file mode 100644
index 000000000..77920de15
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk28/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.restrictedpermissionuser"
+ android:versionCode="1">
+
+ <!-- Storage -->
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
+
+ <application android:label="CtsStoragePermissionsUserDefaultSdk28" />
+
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk29/Android.bp b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk29/Android.bp
new file mode 100644
index 000000000..d3d016a63
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk29/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2019 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: "CtsStoragePermissionsUserDefaultSdk29",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "29",
+}
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk29/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk29/AndroidManifest.xml
new file mode 100644
index 000000000..e96452fd2
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk29/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.restrictedpermissionuser"
+ android:versionCode="1">
+
+ <!-- Storage -->
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <uses-sdk android:targetSdkVersion="29"/>
+
+ <application android:label="CtsStoragePermissionsUserDefaultSdk29" />
+
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk30/Android.bp b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk30/Android.bp
new file mode 100644
index 000000000..751342e2f
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk30/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2019 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: "CtsStoragePermissionsUserOptOutSdk30",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+}
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk30/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk30/AndroidManifest.xml
new file mode 100644
index 000000000..b5c71b373
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserDefaultSdk30/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.restrictedpermissionuser"
+ android:versionCode="1">
+
+ <!-- Storage -->
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
+ <uses-sdk android:targetSdkVersion="30"/>
+
+ <application android:label="CtsStoragePermissionsUserOptOutSdk30"
+ android:requestLegacyExternalStorage="true"/>
+
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk22/Android.bp b/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk22/Android.bp
new file mode 100644
index 000000000..21e6aceef
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk22/Android.bp
@@ -0,0 +1,24 @@
+//
+// Copyright (C) 2019 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: "CtsStoragePermissionsUserOptInSdk22",
+ defaults: ["cts_defaults"],
+}
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk22/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk22/AndroidManifest.xml
new file mode 100644
index 000000000..466d60157
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk22/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.restrictedpermissionuser"
+ android:versionCode="1">
+
+ <!-- Storage -->
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22"/>
+
+ <application android:label="CtsStoragePermissionsUserOptInSdk22"
+ android:requestLegacyExternalStorage="false"/>
+
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk28/Android.bp b/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk28/Android.bp
new file mode 100644
index 000000000..c7f30a8b6
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk28/Android.bp
@@ -0,0 +1,24 @@
+//
+// Copyright (C) 2019 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: "CtsStoragePermissionsUserOptInSdk28",
+ defaults: ["cts_defaults"],
+}
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk28/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk28/AndroidManifest.xml
new file mode 100644
index 000000000..151bbbfea
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptInSdk28/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.restrictedpermissionuser"
+ android:versionCode="1">
+
+ <!-- Storage -->
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
+
+ <application android:label="CtsStoragePermissionsUserOptInSdk28"
+ android:requestLegacyExternalStorage="false"/>
+
+</manifest>
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptOutSdk29/Android.bp b/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptOutSdk29/Android.bp
new file mode 100644
index 000000000..8ad13f798
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptOutSdk29/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2019 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: "CtsStoragePermissionsUserOptOutSdk29",
+ defaults: ["cts_defaults"],
+
+ sdk_version: "current",
+}
diff --git a/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptOutSdk29/AndroidManifest.xml b/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptOutSdk29/AndroidManifest.xml
new file mode 100644
index 000000000..e41ee7759
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsStoragePermissionsUserOptOutSdk29/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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.permissionpolicy.cts.restrictedpermissionuser"
+ android:versionCode="1">
+
+ <!-- Storage -->
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29"/>
+
+ <application android:label="CtsStoragePermissionsUserOptOutSdk29"
+ android:requestLegacyExternalStorage="true"/>
+
+</manifest>
diff --git a/tests/cts/permissionpolicy/OWNERS b/tests/cts/permissionpolicy/OWNERS
new file mode 100644
index 000000000..395269207
--- /dev/null
+++ b/tests/cts/permissionpolicy/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 137825
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
+
+per-file NoLocationPermissionTest.java = tgunn@google.com
+per-file RestrictedStoragePermissionSharedUidTest.java = nandana@google.com
+per-file RestrictedStoragePermissionTest.java = nandana@google.com
diff --git a/tests/cts/permissionpolicy/res/raw/OWNERS b/tests/cts/permissionpolicy/res/raw/OWNERS
new file mode 100644
index 000000000..80ca21cf5
--- /dev/null
+++ b/tests/cts/permissionpolicy/res/raw/OWNERS
@@ -0,0 +1,7 @@
+hackbod@google.com
+patb@google.com
+yamasani@google.com
+michaelwr@google.com
+narayan@google.com
+roosa@google.com
+per-file automotive_android_manifest.xml = sgurun@google.com,keunyoung@google.com,felipeal@google.com,skeys@google.com
diff --git a/tests/cts/permissionpolicy/res/raw/android_manifest.xml b/tests/cts/permissionpolicy/res/raw/android_manifest.xml
new file mode 100644
index 000000000..eb663acbd
--- /dev/null
+++ b/tests/cts/permissionpolicy/res/raw/android_manifest.xml
@@ -0,0 +1,8033 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/AndroidManifest.xml
+**
+** Copyright 2006, 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" coreApp="true" android:sharedUserId="android.uid.system"
+ android:sharedUserLabel="@string/android_system_label">
+
+ <!-- ================================================ -->
+ <!-- Special broadcasts that only the system can send -->
+ <!-- ================================================ -->
+ <eat-comment />
+
+ <protected-broadcast android:name="android.intent.action.SCREEN_OFF" />
+ <protected-broadcast android:name="android.intent.action.SCREEN_ON" />
+ <protected-broadcast android:name="android.intent.action.USER_PRESENT" />
+ <protected-broadcast android:name="android.intent.action.TIME_SET" />
+ <protected-broadcast android:name="android.intent.action.TIME_TICK" />
+ <protected-broadcast android:name="android.intent.action.TIMEZONE_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.DATE_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.PRE_BOOT_COMPLETED" />
+ <protected-broadcast android:name="android.intent.action.BOOT_COMPLETED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_INSTALL" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_ADDED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_REPLACED" />
+ <protected-broadcast android:name="android.intent.action.MY_PACKAGE_REPLACED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_REMOVED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_REMOVED_INTERNAL" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_LOADED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" />
+ <protected-broadcast android:name="android.intent.action.CANCEL_ENABLE_ROLLBACK" />
+ <protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_RESTARTED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_NEEDS_INTEGRITY_VERIFICATION" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_VERIFIED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENDED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGES_UNSUSPENDED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENSION_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY" />
+ <protected-broadcast android:name="android.intent.action.DISTRACTING_PACKAGES_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.UID_REMOVED" />
+ <protected-broadcast android:name="android.intent.action.QUERY_PACKAGE_RESTART" />
+ <protected-broadcast android:name="android.intent.action.CONFIGURATION_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.SPLIT_CONFIGURATION_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.LOCALE_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.APPLICATION_LOCALE_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.BATTERY_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.BATTERY_LEVEL_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.BATTERY_LOW" />
+ <protected-broadcast android:name="android.intent.action.BATTERY_OKAY" />
+ <protected-broadcast android:name="android.intent.action.ACTION_POWER_CONNECTED" />
+ <protected-broadcast android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
+ <protected-broadcast android:name="android.intent.action.ACTION_SHUTDOWN" />
+ <protected-broadcast android:name="android.intent.action.CHARGING" />
+ <protected-broadcast android:name="android.intent.action.DISCHARGING" />
+ <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_LOW" />
+ <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_OK" />
+ <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_FULL" />
+ <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_NOT_FULL" />
+ <protected-broadcast android:name="android.intent.action.NEW_OUTGOING_CALL" />
+ <protected-broadcast android:name="android.intent.action.REBOOT" />
+ <protected-broadcast android:name="android.intent.action.DOCK_EVENT" />
+ <protected-broadcast android:name="android.intent.action.THERMAL_EVENT" />
+ <protected-broadcast android:name="android.intent.action.MASTER_CLEAR_NOTIFICATION" />
+ <protected-broadcast android:name="android.intent.action.USER_ADDED" />
+ <protected-broadcast android:name="android.intent.action.USER_REMOVED" />
+ <protected-broadcast android:name="android.intent.action.USER_STARTING" />
+ <protected-broadcast android:name="android.intent.action.USER_STARTED" />
+ <protected-broadcast android:name="android.intent.action.USER_STOPPING" />
+ <protected-broadcast android:name="android.intent.action.USER_STOPPED" />
+ <protected-broadcast android:name="android.intent.action.USER_BACKGROUND" />
+ <protected-broadcast android:name="android.intent.action.USER_FOREGROUND" />
+ <protected-broadcast android:name="android.intent.action.USER_SWITCHED" />
+ <protected-broadcast android:name="android.intent.action.USER_INITIALIZE" />
+ <protected-broadcast android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION" />
+ <protected-broadcast android:name="android.intent.action.DOMAINS_NEED_VERIFICATION" />
+ <protected-broadcast android:name="android.intent.action.OVERLAY_ADDED" />
+ <protected-broadcast android:name="android.intent.action.OVERLAY_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.OVERLAY_REMOVED" />
+ <protected-broadcast android:name="android.intent.action.OVERLAY_PRIORITY_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.MY_PACKAGE_SUSPENDED" />
+ <protected-broadcast android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" />
+
+ <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
+ <protected-broadcast android:name="android.os.action.DEVICE_IDLE_MODE_CHANGED" />
+ <protected-broadcast android:name="android.os.action.POWER_SAVE_WHITELIST_CHANGED" />
+ <protected-broadcast android:name="android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED" />
+ <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL" />
+ <protected-broadcast android:name="android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED" />
+ <protected-broadcast android:name="android.os.action.LOW_POWER_STANDBY_POLICY_CHANGED" />
+ <protected-broadcast android:name="android.os.action.LOW_POWER_STANDBY_PORTS_CHANGED" />
+ <protected-broadcast android:name="android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED" />
+
+ <!-- @deprecated This is rarely used and will be phased out soon. -->
+ <protected-broadcast android:name="android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED" />
+
+ <protected-broadcast android:name="android.app.action.CLOSE_NOTIFICATION_HANDLER_PANEL" />
+
+ <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" />
+ <protected-broadcast android:name="android.app.action.EXIT_CAR_MODE" />
+ <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE_PRIORITIZED" />
+ <protected-broadcast android:name="android.app.action.EXIT_CAR_MODE_PRIORITIZED" />
+ <protected-broadcast android:name="android.app.action.ENTER_DESK_MODE" />
+ <protected-broadcast android:name="android.app.action.EXIT_DESK_MODE" />
+ <protected-broadcast android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
+
+ <protected-broadcast android:name="android.app.action.USER_ADDED" />
+ <protected-broadcast android:name="android.app.action.USER_REMOVED" />
+ <protected-broadcast android:name="android.app.action.USER_STARTED" />
+ <protected-broadcast android:name="android.app.action.USER_STOPPED" />
+ <protected-broadcast android:name="android.app.action.USER_SWITCHED" />
+
+ <protected-broadcast android:name="android.app.action.BUGREPORT_SHARING_DECLINED" />
+ <protected-broadcast android:name="android.app.action.BUGREPORT_FAILED" />
+ <protected-broadcast android:name="android.app.action.BUGREPORT_SHARE" />
+ <protected-broadcast android:name="android.app.action.SHOW_DEVICE_MONITORING_DIALOG" />
+ <protected-broadcast android:name="android.intent.action.PENDING_INCIDENT_REPORTS_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.INCIDENT_REPORT_READY" />
+
+ <protected-broadcast android:name="android.appwidget.action.APPWIDGET_UPDATE_OPTIONS" />
+ <protected-broadcast android:name="android.appwidget.action.APPWIDGET_DELETED" />
+ <protected-broadcast android:name="android.appwidget.action.APPWIDGET_DISABLED" />
+ <protected-broadcast android:name="android.appwidget.action.APPWIDGET_ENABLED" />
+ <protected-broadcast android:name="android.appwidget.action.APPWIDGET_HOST_RESTORED" />
+ <protected-broadcast android:name="android.appwidget.action.APPWIDGET_RESTORED" />
+
+ <protected-broadcast android:name="android.os.action.SETTING_RESTORED" />
+
+ <protected-broadcast android:name="android.app.backup.intent.CLEAR" />
+ <protected-broadcast android:name="android.app.backup.intent.INIT" />
+
+ <protected-broadcast android:name="android.bluetooth.intent.DISCOVERABLE_TIMEOUT" />
+ <protected-broadcast android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.adapter.action.SCAN_MODE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.adapter.action.DISCOVERY_STARTED" />
+ <protected-broadcast android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED" />
+ <protected-broadcast android:name="android.bluetooth.adapter.action.LOCAL_NAME_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.adapter.action.BLUETOOTH_ADDRESS_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.device.action.UUID" />
+ <protected-broadcast android:name="android.bluetooth.device.action.MAS_INSTANCE" />
+ <protected-broadcast android:name="android.bluetooth.device.action.ALIAS_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.device.action.FOUND" />
+ <protected-broadcast android:name="android.bluetooth.device.action.CLASS_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.device.action.ACL_CONNECTED" />
+ <protected-broadcast android:name="android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED" />
+ <protected-broadcast android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
+ <protected-broadcast android:name="android.bluetooth.device.action.NAME_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.device.action.BOND_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.device.action.NAME_FAILED" />
+ <protected-broadcast android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
+ <protected-broadcast android:name="android.bluetooth.device.action.PAIRING_CANCEL" />
+ <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REPLY" />
+ <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" />
+ <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" />
+ <protected-broadcast android:name="android.bluetooth.device.action.SDP_RECORD" />
+ <protected-broadcast android:name="android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.devicepicker.action.LAUNCH" />
+ <protected-broadcast android:name="android.bluetooth.devicepicker.action.DEVICE_SELECTED" />
+ <protected-broadcast
+ android:name="android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT" />
+ <protected-broadcast
+ android:name="android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.headsetclient.profile.action.AG_EVENT" />
+ <protected-broadcast
+ android:name="android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.headsetclient.profile.action.RESULT" />
+ <protected-broadcast
+ android:name="android.bluetooth.headsetclient.profile.action.LAST_VTAG" />
+ <protected-broadcast
+ android:name="android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.CSIS_DEVICE_AVAILABLE" />
+ <protected-broadcast android:name="android.bluetooth.action.CSIS_SET_MEMBER_AVAILABLE" />
+ <protected-broadcast
+ android:name="android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST" />
+ <protected-broadcast
+ android:name="android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT" />
+ <protected-broadcast
+ android:name="android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.input.profile.action.IDLE_TIME_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS" />
+ <protected-broadcast
+ android:name="android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED" />
+ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY" />
+ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY" />
+ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED" />
+ <protected-broadcast
+ android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT" />
+ <protected-broadcast
+ android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
+ <protected-broadcast
+ android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_CONF_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_GROUP_NODE_STATUS_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_GROUP_STATUS_CHANGED" />
+ <protected-broadcast
+ android:name="android.bluetooth.action.TETHERING_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.btopp.intent.action.INCOMING_FILE_NOTIFICATION" />
+ <protected-broadcast android:name="android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT" />
+ <protected-broadcast android:name="android.btopp.intent.action.LIST" />
+ <protected-broadcast android:name="android.btopp.intent.action.OPEN_OUTBOUND" />
+ <protected-broadcast android:name="android.btopp.intent.action.HIDE_COMPLETE" />
+ <protected-broadcast android:name="android.btopp.intent.action.CONFIRM" />
+ <protected-broadcast android:name="android.btopp.intent.action.HIDE" />
+ <protected-broadcast android:name="android.btopp.intent.action.RETRY" />
+ <protected-broadcast android:name="android.btopp.intent.action.OPEN" />
+ <protected-broadcast android:name="android.btopp.intent.action.OPEN_INBOUND" />
+ <protected-broadcast android:name="android.btopp.intent.action.TRANSFER_COMPLETE" />
+ <protected-broadcast android:name="android.btopp.intent.action.ACCEPT" />
+ <protected-broadcast android:name="android.btopp.intent.action.DECLINE" />
+ <protected-broadcast android:name="com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN" />
+ <protected-broadcast android:name="com.android.bluetooth.pbap.authchall" />
+ <protected-broadcast android:name="com.android.bluetooth.pbap.userconfirmtimeout" />
+ <protected-broadcast android:name="com.android.bluetooth.pbap.authresponse" />
+ <protected-broadcast android:name="com.android.bluetooth.pbap.authcancelled" />
+ <protected-broadcast android:name="com.android.bluetooth.sap.USER_CONFIRM_TIMEOUT" />
+ <protected-broadcast android:name="com.android.bluetooth.sap.action.DISCONNECT_ACTION" />
+
+ <protected-broadcast android:name="android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED" />
+
+ <protected-broadcast android:name="android.hardware.usb.action.USB_STATE" />
+ <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" />
+ <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
+ <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_DETACHED" />
+ <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE" />
+ <protected-broadcast android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+ <protected-broadcast android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
+
+ <protected-broadcast android:name="android.intent.action.HEADSET_PLUG" />
+ <protected-broadcast android:name="android.media.action.HDMI_AUDIO_PLUG" />
+ <protected-broadcast android:name="android.media.action.MICROPHONE_MUTE_CHANGED" />
+ <protected-broadcast android:name="android.media.action.SPEAKERPHONE_STATE_CHANGED" />
+
+ <protected-broadcast android:name="android.media.AUDIO_BECOMING_NOISY" />
+ <protected-broadcast android:name="android.media.RINGER_MODE_CHANGED" />
+ <protected-broadcast android:name="android.media.VIBRATE_SETTING_CHANGED" />
+ <protected-broadcast android:name="android.media.VOLUME_CHANGED_ACTION" />
+ <protected-broadcast android:name="android.media.MASTER_VOLUME_CHANGED_ACTION" />
+ <protected-broadcast android:name="android.media.MASTER_MUTE_CHANGED_ACTION" />
+ <protected-broadcast android:name="android.media.MASTER_MONO_CHANGED_ACTION" />
+ <protected-broadcast android:name="android.media.MASTER_BALANCE_CHANGED_ACTION" />
+ <protected-broadcast android:name="android.media.SCO_AUDIO_STATE_CHANGED" />
+ <protected-broadcast android:name="android.media.ACTION_SCO_AUDIO_STATE_UPDATED" />
+
+ <protected-broadcast android:name="android.intent.action.MEDIA_REMOVED" />
+ <protected-broadcast android:name="android.intent.action.MEDIA_UNMOUNTED" />
+ <protected-broadcast android:name="android.intent.action.MEDIA_CHECKING" />
+ <protected-broadcast android:name="android.intent.action.MEDIA_NOFS" />
+ <protected-broadcast android:name="android.intent.action.MEDIA_MOUNTED" />
+ <protected-broadcast android:name="android.intent.action.MEDIA_SHARED" />
+ <protected-broadcast android:name="android.intent.action.MEDIA_UNSHARED" />
+ <protected-broadcast android:name="android.intent.action.MEDIA_BAD_REMOVAL" />
+ <protected-broadcast android:name="android.intent.action.MEDIA_UNMOUNTABLE" />
+ <protected-broadcast android:name="android.intent.action.MEDIA_EJECT" />
+
+ <protected-broadcast android:name="android.net.conn.CAPTIVE_PORTAL" />
+ <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+ <!-- @deprecated. Only {@link android.net.ConnectivityManager.CONNECTIVITY_ACTION} is sent. -->
+ <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE" />
+ <protected-broadcast android:name="android.net.conn.DATA_ACTIVITY_CHANGE" />
+ <protected-broadcast android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED" />
+ <protected-broadcast android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED" />
+ <protected-broadcast android:name="android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED" />
+
+ <protected-broadcast android:name="android.net.nsd.STATE_CHANGED" />
+
+ <!-- For OMAPI -->
+ <protected-broadcast android:name="android.se.omapi.action.SECURE_ELEMENT_STATE_CHANGED" />
+
+ <protected-broadcast android:name="android.nfc.action.ADAPTER_STATE_CHANGED" />
+ <protected-broadcast android:name="android.nfc.action.PREFERRED_PAYMENT_CHANGED" />
+ <protected-broadcast android:name="android.nfc.action.TRANSACTION_DETECTED" />
+ <protected-broadcast android:name="android.nfc.action.REQUIRE_UNLOCK_FOR_NFC" />
+ <protected-broadcast android:name="com.android.nfc.action.LLCP_UP" />
+ <protected-broadcast android:name="com.android.nfc.action.LLCP_DOWN" />
+ <protected-broadcast android:name="com.android.nfc.cardemulation.action.CLOSE_TAP_DIALOG" />
+ <protected-broadcast android:name="com.android.nfc.handover.action.ALLOW_CONNECT" />
+ <protected-broadcast android:name="com.android.nfc.handover.action.DENY_CONNECT" />
+ <protected-broadcast android:name="com.android.nfc.handover.action.TIMEOUT_CONNECT" />
+ <protected-broadcast android:name="com.android.nfc_extras.action.RF_FIELD_ON_DETECTED" />
+ <protected-broadcast android:name="com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED" />
+ <protected-broadcast android:name="com.android.nfc_extras.action.AID_SELECTED" />
+ <!-- For NFC to BT handover -->
+ <protected-broadcast android:name="android.btopp.intent.action.WHITELIST_DEVICE" />
+ <protected-broadcast android:name="android.btopp.intent.action.STOP_HANDOVER_TRANSFER" />
+ <protected-broadcast android:name="android.nfc.handover.intent.action.HANDOVER_SEND" />
+ <protected-broadcast android:name="android.nfc.handover.intent.action.HANDOVER_SEND_MULTIPLE" />
+ <protected-broadcast android:name="com.android.nfc.handover.action.CANCEL_HANDOVER_TRANSFER" />
+
+ <protected-broadcast android:name="android.net.action.CLEAR_DNS_CACHE" />
+ <protected-broadcast android:name="android.intent.action.PROXY_CHANGE" />
+
+ <protected-broadcast android:name="android.os.UpdateLock.UPDATE_LOCK_CHANGED" />
+
+ <protected-broadcast android:name="android.intent.action.DREAMING_STARTED" />
+ <protected-broadcast android:name="android.intent.action.DREAMING_STOPPED" />
+ <protected-broadcast android:name="android.intent.action.ANY_DATA_STATE" />
+
+ <protected-broadcast android:name="com.android.server.stats.action.TRIGGER_COLLECTION" />
+
+ <protected-broadcast android:name="com.android.server.WifiManager.action.START_SCAN" />
+ <protected-broadcast android:name="com.android.server.WifiManager.action.START_PNO" />
+ <protected-broadcast android:name="com.android.server.WifiManager.action.DELAYED_DRIVER_STOP" />
+ <protected-broadcast android:name="com.android.server.WifiManager.action.DEVICE_IDLE" />
+ <protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_ACCEPTED" />
+ <protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_DECLINED" />
+ <protected-broadcast android:name="com.android.internal.action.EUICC_FACTORY_RESET" />
+ <protected-broadcast android:name="com.android.server.usb.ACTION_OPEN_IN_APPS" />
+ <protected-broadcast android:name="com.android.server.am.DELETE_DUMPHEAP" />
+ <protected-broadcast android:name="com.android.server.net.action.SNOOZE_WARNING" />
+ <protected-broadcast android:name="com.android.server.net.action.SNOOZE_RAPID" />
+ <protected-broadcast android:name="com.android.server.wifi.ACTION_SHOW_SET_RANDOMIZATION_DETAILS" />
+ <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP" />
+ <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP" />
+ <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED" />
+ <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_ALLOWED_CARRIER" />
+ <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_DISALLOWED_CARRIER" />
+ <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_DISMISSED" />
+ <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION" />
+ <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" />
+ <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" />
+ <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_NETWORK_AFTER_FAILURE" />
+ <protected-broadcast android:name="com.android.server.wifi.wakeup.DISMISS_NOTIFICATION" />
+ <protected-broadcast android:name="com.android.server.wifi.wakeup.OPEN_WIFI_PREFERENCES" />
+ <protected-broadcast android:name="com.android.server.wifi.wakeup.OPEN_WIFI_SETTINGS" />
+ <protected-broadcast android:name="com.android.server.wifi.wakeup.TURN_OFF_WIFI_WAKE" />
+ <protected-broadcast android:name="android.net.wifi.WIFI_STATE_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.SCAN_RESULTS" />
+ <protected-broadcast android:name="android.net.wifi.RSSI_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
+ <protected-broadcast android:name="android.net.wifi.LINK_CONFIGURATION_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" />
+ <protected-broadcast android:name="android.net.wifi.action.NETWORK_SETTINGS_RESET" />
+ <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT" />
+ <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_ICON" />
+ <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" />
+ <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION" />
+ <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW" />
+ <protected-broadcast android:name="android.net.wifi.action.REFRESH_USER_PROVISIONING" />
+ <protected-broadcast android:name="android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION" />
+ <protected-broadcast android:name="android.net.wifi.action.WIFI_SCAN_AVAILABILITY_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />
+ <protected-broadcast android:name="android.net.wifi.supplicant.STATE_CHANGE" />
+ <protected-broadcast android:name="android.net.wifi.p2p.STATE_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.p2p.DISCOVERY_STATE_CHANGE" />
+ <protected-broadcast android:name="android.net.wifi.p2p.THIS_DEVICE_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.p2p.PEERS_CHANGED" />
+ <protected-broadcast android:name="android.net.wifi.p2p.CONNECTION_STATE_CHANGE" />
+ <protected-broadcast android:name="android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED" />
+ <protected-broadcast android:name="android.net.conn.TETHER_STATE_CHANGED" />
+ <protected-broadcast android:name="android.net.conn.INET_CONDITION_ACTION" />
+ <!-- This broadcast is no longer sent in S but it should stay protected to avoid third party
+ apps broadcasting this and confusing old system apps that may not have been updated. -->
+ <protected-broadcast android:name="android.net.conn.NETWORK_CONDITIONS_MEASURED" />
+ <protected-broadcast
+ android:name="android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED" />
+ <protected-broadcast android:name="android.net.scoring.SCORE_NETWORKS" />
+ <protected-broadcast android:name="android.net.scoring.SCORER_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE" />
+ <protected-broadcast android:name="android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE" />
+ <protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />
+ <protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" />
+ <protected-broadcast android:name="android.intent.action.APPLICATION_RESTRICTIONS_CHANGED" />
+ <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES" />
+ <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT" />
+ <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_STATUS" />
+
+ <!-- Legacy -->
+ <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
+ <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" />
+
+ <protected-broadcast android:name="com.android.server.ACTION_TRIGGER_IDLE" />
+
+ <protected-broadcast android:name="android.intent.action.HDMI_PLUGGED" />
+
+ <protected-broadcast android:name="android.intent.action.PHONE_STATE" />
+
+ <protected-broadcast android:name="android.intent.action.SUB_DEFAULT_CHANGED" />
+
+ <protected-broadcast android:name="android.location.PROVIDERS_CHANGED" />
+ <protected-broadcast android:name="android.location.MODE_CHANGED" />
+ <protected-broadcast android:name="android.location.action.GNSS_CAPABILITIES_CHANGED" />
+
+ <protected-broadcast android:name="android.net.proxy.PAC_REFRESH" />
+
+ <protected-broadcast android:name="android.telecom.action.DEFAULT_DIALER_CHANGED" />
+ <protected-broadcast android:name="android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED" />
+ <protected-broadcast android:name="android.provider.action.SMS_MMS_DB_CREATED" />
+ <protected-broadcast android:name="android.provider.action.SMS_MMS_DB_LOST" />
+ <protected-broadcast android:name="android.intent.action.CONTENT_CHANGED" />
+ <protected-broadcast android:name="android.provider.Telephony.MMS_DOWNLOADED" />
+
+ <protected-broadcast
+ android:name="com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION" />
+
+ <!-- Defined in RestrictionsManager -->
+ <protected-broadcast
+ android:name="android.intent.action.PERMISSION_RESPONSE_RECEIVED" />
+ <!-- Defined in RestrictionsManager -->
+
+ <protected-broadcast android:name="android.intent.action.REQUEST_PERMISSION" />
+ <protected-broadcast android:name="android.nfc.handover.intent.action.HANDOVER_STARTED" />
+ <protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_DONE" />
+ <protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_PROGRESS" />
+ <protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_DONE" />
+
+ <protected-broadcast android:name="android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED" />
+
+ <protected-broadcast android:name="android.intent.action.ACTION_SET_RADIO_CAPABILITY_DONE" />
+ <protected-broadcast android:name="android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED" />
+
+ <protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
+ <protected-broadcast android:name="android.app.action.SYSTEM_UPDATE_POLICY_CHANGED" />
+ <protected-broadcast android:name="android.app.action.RESET_PROTECTION_POLICY_CHANGED" />
+ <protected-broadcast android:name="android.app.action.DEVICE_OWNER_CHANGED" />
+ <protected-broadcast android:name="android.app.action.MANAGED_USER_CREATED" />
+
+ <!-- Added in N -->
+ <protected-broadcast android:name="android.intent.action.ANR" />
+ <protected-broadcast android:name="android.intent.action.CALL" />
+ <protected-broadcast android:name="android.intent.action.CALL_PRIVILEGED" />
+ <protected-broadcast android:name="android.intent.action.DROPBOX_ENTRY_ADDED" />
+ <protected-broadcast android:name="android.intent.action.INPUT_METHOD_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.internal_sim_state_changed" />
+ <protected-broadcast android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+ <protected-broadcast android:name="android.intent.action.PRECISE_CALL_STATE" />
+ <protected-broadcast android:name="android.intent.action.SUBSCRIPTION_PHONE_STATE" />
+ <protected-broadcast android:name="android.intent.action.USER_INFO_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.USER_UNLOCKED" />
+ <protected-broadcast android:name="android.intent.action.WALLPAPER_CHANGED" />
+
+ <protected-broadcast android:name="android.app.action.DEVICE_POLICY_MANAGER_STATE_CHANGED" />
+ <protected-broadcast android:name="android.app.action.CHOOSE_PRIVATE_KEY_ALIAS" />
+ <protected-broadcast android:name="android.app.action.DEVICE_ADMIN_DISABLED" />
+ <protected-broadcast android:name="android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED" />
+ <protected-broadcast android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+ <protected-broadcast android:name="android.app.action.LOCK_TASK_ENTERING" />
+ <protected-broadcast android:name="android.app.action.LOCK_TASK_EXITING" />
+ <protected-broadcast android:name="android.app.action.NOTIFY_PENDING_SYSTEM_UPDATE" />
+ <protected-broadcast android:name="android.app.action.ACTION_PASSWORD_CHANGED" />
+ <protected-broadcast android:name="android.app.action.ACTION_PASSWORD_EXPIRING" />
+ <protected-broadcast android:name="android.app.action.ACTION_PASSWORD_FAILED" />
+ <protected-broadcast android:name="android.app.action.ACTION_PASSWORD_SUCCEEDED" />
+ <protected-broadcast android:name="com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION" />
+ <protected-broadcast android:name="com.android.server.ACTION_PROFILE_OFF_DEADLINE" />
+ <protected-broadcast android:name="com.android.server.ACTION_TURN_PROFILE_ON_NOTIFICATION" />
+
+ <protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_ADDED" />
+ <protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_UNLOCKED" />
+ <protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_REMOVED" />
+
+ <protected-broadcast android:name="android.bluetooth.adapter.action.BLE_STATE_CHANGED" />
+ <protected-broadcast android:name="com.android.bluetooth.map.USER_CONFIRM_TIMEOUT" />
+ <protected-broadcast android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT" />
+ <protected-broadcast android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
+ <protected-broadcast android:name="android.content.jobscheduler.JOB_DELAY_EXPIRED" />
+ <protected-broadcast android:name="android.content.syncmanager.SYNC_ALARM" />
+ <protected-broadcast android:name="android.media.INTERNAL_RINGER_MODE_CHANGED_ACTION" />
+ <protected-broadcast android:name="android.media.STREAM_DEVICES_CHANGED_ACTION" />
+ <protected-broadcast android:name="android.media.STREAM_MUTE_CHANGED_ACTION" />
+ <protected-broadcast android:name="android.net.sip.SIP_SERVICE_UP" />
+ <protected-broadcast android:name="android.nfc.action.ADAPTER_STATE_CHANGED" />
+ <protected-broadcast android:name="android.os.action.CHARGING" />
+ <protected-broadcast android:name="android.os.action.DISCHARGING" />
+ <protected-broadcast android:name="android.search.action.SEARCHABLES_CHANGED" />
+ <protected-broadcast android:name="android.security.STORAGE_CHANGED" />
+ <protected-broadcast android:name="android.security.action.TRUST_STORE_CHANGED" />
+ <protected-broadcast android:name="android.security.action.KEYCHAIN_CHANGED" />
+ <protected-broadcast android:name="android.security.action.KEY_ACCESS_CHANGED" />
+ <protected-broadcast android:name="android.telecom.action.NUISANCE_CALL_STATUS_CHANGED" />
+ <protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_REGISTERED" />
+ <protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_UNREGISTERED" />
+ <protected-broadcast android:name="android.telecom.action.POST_CALL" />
+ <protected-broadcast android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" />
+ <protected-broadcast android:name="android.telephony.action.CARRIER_CONFIG_CHANGED" />
+ <protected-broadcast android:name="android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED" />
+ <protected-broadcast android:name="android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED" />
+ <protected-broadcast android:name="android.telephony.action.SECRET_CODE" />
+ <protected-broadcast android:name="android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION" />
+ <protected-broadcast android:name="android.telephony.action.SUBSCRIPTION_PLANS_CHANGED" />
+
+ <protected-broadcast android:name="com.android.bluetooth.btservice.action.ALARM_WAKEUP" />
+ <protected-broadcast android:name="com.android.server.action.NETWORK_STATS_POLL" />
+ <protected-broadcast android:name="com.android.server.action.NETWORK_STATS_UPDATED" />
+ <protected-broadcast android:name="com.android.server.timedetector.NetworkTimeUpdateService.action.POLL" />
+ <protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
+ <protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
+ <protected-broadcast android:name="com.android.settings.bluetooth.ACTION_DISMISS_PAIRING" />
+ <protected-broadcast android:name="com.android.settings.network.DELETE_SUBSCRIPTION" />
+ <protected-broadcast android:name="com.android.settings.network.SWITCH_TO_SUBSCRIPTION" />
+ <protected-broadcast android:name="com.android.settings.wifi.action.NETWORK_REQUEST" />
+
+ <protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
+ <protected-broadcast android:name="NotificationHistoryDatabase.CLEANUP" />
+ <protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
+ <protected-broadcast android:name="EventConditionProvider.EVALUATE" />
+ <protected-broadcast android:name="SnoozeHelper.EVALUATE" />
+ <protected-broadcast android:name="wifi_scan_available" />
+
+ <protected-broadcast android:name="action.cne.started" />
+ <protected-broadcast android:name="android.content.jobscheduler.JOB_DEADLINE_EXPIRED" />
+ <protected-broadcast android:name="android.intent.action.ACTION_UNSOL_RESPONSE_OEM_HOOK_RAW" />
+ <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_SUPL" />
+ <protected-broadcast android:name="android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED" />
+ <protected-broadcast android:name="android.os.storage.action.VOLUME_STATE_CHANGED" />
+ <protected-broadcast android:name="android.os.storage.action.DISK_SCANNED" />
+ <protected-broadcast android:name="com.android.server.action.UPDATE_TWILIGHT_STATE" />
+ <protected-broadcast android:name="com.android.server.action.RESET_TWILIGHT_AUTO" />
+ <protected-broadcast android:name="com.android.server.device_idle.STEP_IDLE_STATE" />
+ <protected-broadcast android:name="com.android.server.device_idle.STEP_LIGHT_IDLE_STATE" />
+ <protected-broadcast android:name="com.android.server.Wifi.action.TOGGLE_PNO" />
+ <protected-broadcast android:name="intent.action.ACTION_RF_BAND_INFO" />
+ <protected-broadcast android:name="android.intent.action.MEDIA_RESOURCE_GRANTED" />
+ <protected-broadcast android:name="android.app.action.NETWORK_LOGS_AVAILABLE" />
+ <protected-broadcast android:name="android.app.action.SECURITY_LOGS_AVAILABLE" />
+ <protected-broadcast android:name="android.app.action.COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED" />
+
+ <protected-broadcast android:name="android.app.action.INTERRUPTION_FILTER_CHANGED" />
+ <protected-broadcast android:name="android.app.action.INTERRUPTION_FILTER_CHANGED_INTERNAL" />
+ <protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_CHANGED" />
+ <protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED" />
+ <protected-broadcast android:name="android.app.action.AUTOMATIC_ZEN_RULE_STATUS_CHANGED" />
+ <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
+ <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
+ <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
+ <protected-broadcast android:name="android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED" />
+ <protected-broadcast android:name="android.app.action.APP_BLOCK_STATE_CHANGED" />
+
+ <protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
+ <protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" />
+
+ <protected-broadcast android:name="android.intent.action.DYNAMIC_SENSOR_CHANGED" />
+
+ <protected-broadcast android:name="android.accounts.LOGIN_ACCOUNTS_CHANGED" />
+ <protected-broadcast android:name="android.accounts.action.ACCOUNT_REMOVED" />
+ <protected-broadcast android:name="android.accounts.action.VISIBLE_ACCOUNTS_CHANGED" />
+
+ <protected-broadcast android:name="com.android.sync.SYNC_CONN_STATUS_CHANGED" />
+
+ <protected-broadcast android:name="android.net.sip.action.SIP_INCOMING_CALL" />
+ <protected-broadcast android:name="com.android.phone.SIP_ADD_PHONE" />
+ <protected-broadcast android:name="android.net.sip.action.SIP_REMOVE_PROFILE" />
+ <protected-broadcast android:name="android.net.sip.action.SIP_SERVICE_UP" />
+ <protected-broadcast android:name="android.net.sip.action.SIP_CALL_OPTION_CHANGED" />
+ <protected-broadcast android:name="android.net.sip.action.START_SIP" />
+
+ <protected-broadcast android:name="android.bluetooth.adapter.action.BLE_ACL_CONNECTED" />
+ <protected-broadcast android:name="android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED" />
+
+ <protected-broadcast android:name="android.bluetooth.input.profile.action.HANDSHAKE" />
+ <protected-broadcast android:name="android.bluetooth.input.profile.action.REPORT" />
+
+ <protected-broadcast android:name="android.intent.action.TWILIGHT_CHANGED" />
+
+ <protected-broadcast android:name="com.android.server.fingerprint.ACTION_LOCKOUT_RESET" />
+ <protected-broadcast android:name="android.net.wifi.PASSPOINT_ICON_RECEIVED" />
+
+ <protected-broadcast android:name="com.android.server.notification.CountdownConditionProvider" />
+ <protected-broadcast android:name="android.server.notification.action.ENABLE_NAS" />
+ <protected-broadcast android:name="android.server.notification.action.DISABLE_NAS" />
+ <protected-broadcast android:name="android.server.notification.action.LEARNMORE_NAS" />
+
+ <protected-broadcast android:name="com.android.internal.location.ALARM_WAKEUP" />
+ <protected-broadcast android:name="com.android.internal.location.ALARM_TIMEOUT" />
+ <protected-broadcast android:name="android.intent.action.GLOBAL_BUTTON" />
+
+ <protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_AVAILABLE" />
+ <protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE" />
+ <protected-broadcast android:name="com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK" />
+
+ <protected-broadcast android:name="android.intent.action.PROFILE_ACCESSIBLE" />
+ <protected-broadcast android:name="android.intent.action.PROFILE_INACCESSIBLE" />
+
+ <protected-broadcast android:name="com.android.server.retaildemo.ACTION_RESET_DEMO" />
+
+ <protected-broadcast android:name="android.intent.action.DEVICE_LOCKED_CHANGED" />
+
+ <!-- Added in O -->
+ <protected-broadcast android:name="android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED" />
+ <protected-broadcast android:name="com.android.server.wm.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION" />
+ <protected-broadcast android:name="android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED" />
+
+ <protected-broadcast android:name="android.content.pm.action.SESSION_COMMITTED" />
+ <protected-broadcast android:name="android.os.action.USER_RESTRICTIONS_CHANGED" />
+ <protected-broadcast android:name="android.media.tv.action.PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT" />
+ <protected-broadcast android:name="android.media.tv.action.PREVIEW_PROGRAM_BROWSABLE_DISABLED" />
+ <protected-broadcast android:name="android.media.tv.action.WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED" />
+ <protected-broadcast android:name="android.media.tv.action.CHANNEL_BROWSABLE_REQUESTED" />
+ <protected-broadcast android:name="com.android.server.inputmethod.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER" />
+
+ <!-- Made protected in P (was introduced in JB-MR2) -->
+ <protected-broadcast android:name="android.intent.action.GET_RESTRICTION_ENTRIES" />
+ <protected-broadcast android:name="android.telephony.euicc.action.OTA_STATUS_CHANGED" />
+
+ <!-- Added in P -->
+ <protected-broadcast android:name="android.app.action.PROFILE_OWNER_CHANGED" />
+ <protected-broadcast android:name="android.app.action.TRANSFER_OWNERSHIP_COMPLETE" />
+ <protected-broadcast android:name="android.app.action.AFFILIATED_PROFILE_TRANSFER_OWNERSHIP_COMPLETE" />
+ <protected-broadcast android:name="android.app.action.STATSD_STARTED" />
+ <protected-broadcast android:name="com.android.server.biometrics.fingerprint.ACTION_LOCKOUT_RESET" />
+ <protected-broadcast android:name="com.android.server.biometrics.face.ACTION_LOCKOUT_RESET" />
+
+ <!-- For IdleController -->
+ <protected-broadcast android:name="android.intent.action.DOCK_IDLE" />
+ <protected-broadcast android:name="android.intent.action.DOCK_ACTIVE" />
+
+ <!-- Added in Q -->
+ <protected-broadcast android:name="android.content.pm.action.SESSION_UPDATED" />
+ <protected-broadcast android:name="android.settings.action.GRAYSCALE_CHANGED" />
+
+ <!-- For CarIdlenessTracker -->
+ <protected-broadcast android:name="com.android.server.jobscheduler.GARAGE_MODE_ON" />
+ <protected-broadcast android:name="com.android.server.jobscheduler.GARAGE_MODE_OFF" />
+ <protected-broadcast android:name="com.android.server.jobscheduler.FORCE_IDLE" />
+ <protected-broadcast android:name="com.android.server.jobscheduler.UNFORCE_IDLE" />
+
+ <protected-broadcast android:name="android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL" />
+
+ <protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" />
+
+ <!-- Added in R -->
+ <protected-broadcast android:name="android.app.action.RESET_PROTECTION_POLICY_CHANGED" />
+
+ <!-- For tether entitlement recheck-->
+ <protected-broadcast
+ android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />
+
+ <!-- Made protected in S (was added in R) -->
+ <protected-broadcast android:name="com.android.internal.intent.action.BUGREPORT_REQUESTED" />
+
+ <!-- Added in S -->
+ <protected-broadcast android:name="android.scheduling.action.REBOOT_READY" />
+ <protected-broadcast android:name="android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED" />
+ <protected-broadcast android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.app.action.SHOW_NEW_USER_DISCLAIMER" />
+
+ <!-- Moved from packages/services/Telephony in T -->
+ <protected-broadcast android:name="android.telecom.action.CURRENT_TTY_MODE_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.SERVICE_STATE" />
+ <protected-broadcast android:name="android.intent.action.RADIO_TECHNOLOGY" />
+ <protected-broadcast android:name="android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.EMERGENCY_CALL_STATE_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.SIG_STR" />
+ <protected-broadcast android:name="android.intent.action.ANY_DATA_STATE" />
+ <protected-broadcast android:name="android.intent.action.DATA_STALL_DETECTED" />
+ <protected-broadcast android:name="android.intent.action.SIM_STATE_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.USER_ACTIVITY_NOTIFICATION" />
+ <protected-broadcast android:name="android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS" />
+ <protected-broadcast android:name="android.intent.action.ACTION_MDN_STATE_CHANGED" />
+ <protected-broadcast android:name="android.telephony.action.SERVICE_PROVIDERS_UPDATED" />
+ <protected-broadcast android:name="android.provider.Telephony.SIM_FULL" />
+ <protected-broadcast android:name="com.android.internal.telephony.carrier_key_download_alarm" />
+ <protected-broadcast android:name="com.android.internal.telephony.data-restart-trysetup" />
+ <protected-broadcast android:name="com.android.internal.telephony.data-stall" />
+ <protected-broadcast android:name="com.android.internal.telephony.provisioning_apn_alarm" />
+ <protected-broadcast android:name="android.intent.action.DATA_SMS_RECEIVED" />
+ <protected-broadcast android:name="android.provider.Telephony.SMS_RECEIVED" />
+ <protected-broadcast android:name="android.provider.Telephony.SMS_DELIVER" />
+ <protected-broadcast android:name="android.provider.Telephony.SMS_REJECTED" />
+ <protected-broadcast android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+ <protected-broadcast android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />
+ <protected-broadcast android:name="android.provider.Telephony.SMS_CB_RECEIVED" />
+ <protected-broadcast android:name="android.provider.action.SMS_EMERGENCY_CB_RECEIVED" />
+ <protected-broadcast android:name="android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED" />
+ <protected-broadcast android:name="android.provider.Telephony.SECRET_CODE" />
+ <protected-broadcast android:name="com.android.internal.stk.command" />
+ <protected-broadcast android:name="com.android.internal.stk.session_end" />
+ <protected-broadcast android:name="com.android.internal.stk.icc_status_change" />
+ <protected-broadcast android:name="com.android.internal.stk.alpha_notify" />
+ <protected-broadcast android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" />
+ <protected-broadcast android:name="com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED" />
+ <protected-broadcast android:name="com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE" />
+ <protected-broadcast android:name="com.android.internal.telephony.CARRIER_SIGNAL_RESET" />
+ <protected-broadcast android:name="com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE" />
+ <protected-broadcast android:name="com.android.internal.telephony.PROVISION" />
+ <protected-broadcast android:name="com.android.internal.telephony.ACTION_LINE1_NUMBER_ERROR_DETECTED" />
+ <protected-broadcast android:name="com.android.internal.provider.action.VOICEMAIL_SMS_RECEIVED" />
+ <protected-broadcast android:name="com.android.intent.isim_refresh" />
+ <protected-broadcast android:name="com.android.ims.ACTION_RCS_SERVICE_AVAILABLE" />
+ <protected-broadcast android:name="com.android.ims.ACTION_RCS_SERVICE_UNAVAILABLE" />
+ <protected-broadcast android:name="com.android.ims.ACTION_RCS_SERVICE_DIED" />
+ <protected-broadcast android:name="com.android.ims.ACTION_PRESENCE_CHANGED" />
+ <protected-broadcast android:name="com.android.ims.ACTION_PUBLISH_STATUS_CHANGED" />
+ <protected-broadcast android:name="com.android.ims.IMS_SERVICE_UP" />
+ <protected-broadcast android:name="com.android.ims.IMS_SERVICE_DOWN" />
+ <protected-broadcast android:name="com.android.ims.IMS_INCOMING_CALL" />
+ <protected-broadcast android:name="com.android.ims.internal.uce.UCE_SERVICE_UP" />
+ <protected-broadcast android:name="com.android.ims.internal.uce.UCE_SERVICE_DOWN" />
+ <protected-broadcast android:name="com.android.imsconnection.DISCONNECTED" />
+ <protected-broadcast android:name="com.android.intent.action.IMS_FEATURE_CHANGED" />
+ <protected-broadcast android:name="com.android.intent.action.IMS_CONFIG_CHANGED" />
+ <protected-broadcast android:name="android.telephony.ims.action.WFC_IMS_REGISTRATION_ERROR" />
+ <protected-broadcast android:name="com.android.phone.vvm.omtp.sms.REQUEST_SENT" />
+ <protected-broadcast android:name="com.android.phone.vvm.ACTION_VISUAL_VOICEMAIL_SERVICE_EVENT" />
+ <protected-broadcast android:name="com.android.internal.telephony.CARRIER_VVM_PACKAGE_INSTALLED" />
+ <protected-broadcast android:name="com.android.cellbroadcastreceiver.GET_LATEST_CB_AREA_INFO" />
+ <protected-broadcast android:name="com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD" />
+ <protected-broadcast android:name="com.android.internal.telephony.action.COUNTRY_OVERRIDE" />
+ <protected-broadcast android:name="com.android.internal.telephony.OPEN_DEFAULT_SMS_APP" />
+ <protected-broadcast android:name="com.android.internal.telephony.ACTION_TEST_OVERRIDE_CARRIER_ID" />
+ <protected-broadcast android:name="android.telephony.action.SIM_CARD_STATE_CHANGED" />
+ <protected-broadcast android:name="android.telephony.action.SIM_APPLICATION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.telephony.action.SIM_SLOT_STATUS_CHANGED" />
+ <protected-broadcast android:name="android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED" />
+ <protected-broadcast android:name="android.telephony.action.SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED" />
+ <protected-broadcast android:name="android.telephony.action.TOGGLE_PROVISION" />
+ <protected-broadcast android:name="android.telephony.action.NETWORK_COUNTRY_CHANGED" />
+ <protected-broadcast android:name="android.telephony.action.PRIMARY_SUBSCRIPTION_LIST_CHANGED" />
+ <protected-broadcast android:name="android.telephony.action.MULTI_SIM_CONFIG_CHANGED" />
+ <protected-broadcast android:name="android.telephony.action.CARRIER_SIGNAL_RESET" />
+ <protected-broadcast android:name="android.telephony.action.CARRIER_SIGNAL_PCO_VALUE" />
+ <protected-broadcast android:name="android.telephony.action.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE" />
+ <protected-broadcast android:name="android.telephony.action.CARRIER_SIGNAL_REDIRECTED" />
+ <protected-broadcast android:name="android.telephony.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED" />
+ <protected-broadcast android:name="com.android.phone.settings.CARRIER_PROVISIONING" />
+ <protected-broadcast android:name="com.android.phone.settings.TRIGGER_CARRIER_PROVISIONING" />
+ <protected-broadcast android:name="android.telephony.action.ANOMALY_REPORTED" />
+ <protected-broadcast android:name="android.intent.action.SUBSCRIPTION_INFO_RECORD_ADDED" />
+ <protected-broadcast android:name="android.intent.action.ACTION_MANAGED_ROAMING_IND" />
+ <protected-broadcast android:name="android.telephony.ims.action.RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE" />
+
+ <!-- Added in T -->
+ <protected-broadcast android:name="android.app.action.LOST_MODE_LOCATION_UPDATE" />
+
+ <!-- ====================================================================== -->
+ <!-- RUNTIME PERMISSIONS -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Grouping for platform runtime permissions is not accessible to apps
+ @hide
+ @SystemApi
+ @TestApi
+ -->
+ <permission-group android:name="android.permission-group.UNDEFINED"
+ android:priority="100" />
+
+ <!-- ====================================================================== -->
+ <!-- Permissions for accessing user's contacts including personal profile -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Used for runtime permissions related to contacts and profiles on this
+ device. -->
+ <permission-group android:name="android.permission-group.CONTACTS"
+ android:icon="@drawable/perm_group_contacts"
+ android:label="@string/permgrouplab_contacts"
+ android:description="@string/permgroupdesc_contacts"
+ android:priority="100" />
+
+ <!-- Allows an application to read the user's contacts data.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.READ_CONTACTS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readContacts"
+ android:description="@string/permdesc_readContacts"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to write the user's contacts data.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.WRITE_CONTACTS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_writeContacts"
+ android:description="@string/permdesc_writeContacts"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to set default account for new contacts.
+ <p> This permission is only granted to system applications fulfilling the Contacts app role.
+ <p>Protection level: internal|role
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS"
+ android:protectionLevel="internal|role" />
+
+ <!-- ====================================================================== -->
+ <!-- Permissions for accessing user's calendar -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Used for runtime permissions related to user's calendar. -->
+ <permission-group android:name="android.permission-group.CALENDAR"
+ android:icon="@drawable/perm_group_calendar"
+ android:label="@string/permgrouplab_calendar"
+ android:description="@string/permgroupdesc_calendar"
+ android:priority="200" />
+
+ <!-- Allows an application to read the user's calendar data.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.READ_CALENDAR"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readCalendar"
+ android:description="@string/permdesc_readCalendar"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to write the user's calendar data.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.WRITE_CALENDAR"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_writeCalendar"
+ android:description="@string/permdesc_writeCalendar"
+ android:protectionLevel="dangerous" />
+
+ <!-- ====================================================================== -->
+ <!-- Permissions for accessing and modifying user's SMS messages -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Allows accessing the messages on ICC
+ @hide Used internally. -->
+ <permission android:name="android.permission.ACCESS_MESSAGES_ON_ICC"
+ android:protectionLevel="signature" />
+
+ <!-- Used for runtime permissions related to user's SMS messages. -->
+ <permission-group android:name="android.permission-group.SMS"
+ android:icon="@drawable/perm_group_sms"
+ android:label="@string/permgrouplab_sms"
+ android:description="@string/permgroupdesc_sms"
+ android:priority="300" />
+
+ <!-- Allows an application to send SMS messages.
+ <p>Protection level: dangerous
+
+ <p> This is a hard restricted permission which cannot be held by an app until
+ the installer on record whitelists the permission. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ -->
+ <permission android:name="android.permission.SEND_SMS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_sendSms"
+ android:description="@string/permdesc_sendSms"
+ android:permissionFlags="costsMoney|hardRestricted"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to receive SMS messages.
+ <p>Protection level: dangerous
+
+ <p> This is a hard restricted permission which cannot be held by an app until
+ the installer on record whitelists the permission. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ -->
+ <permission android:name="android.permission.RECEIVE_SMS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_receiveSms"
+ android:description="@string/permdesc_receiveSms"
+ android:permissionFlags="hardRestricted"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to read SMS messages.
+ <p>Protection level: dangerous
+
+ <p> This is a hard restricted permission which cannot be held by an app until
+ the installer on record whitelists the permission. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ -->
+ <permission android:name="android.permission.READ_SMS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readSms"
+ android:description="@string/permdesc_readSms"
+ android:permissionFlags="hardRestricted"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to receive WAP push messages.
+ <p>Protection level: dangerous
+
+ <p> This is a hard restricted permission which cannot be held by an app until
+ the installer on record whitelists the permission. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ -->
+ <permission android:name="android.permission.RECEIVE_WAP_PUSH"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_receiveWapPush"
+ android:description="@string/permdesc_receiveWapPush"
+ android:permissionFlags="hardRestricted"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to monitor incoming MMS messages.
+ <p>Protection level: dangerous
+
+ <p> This is a hard restricted permission which cannot be held by an app until
+ the installer on record whitelists the permission. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ -->
+ <permission android:name="android.permission.RECEIVE_MMS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_receiveMms"
+ android:description="@string/permdesc_receiveMms"
+ android:permissionFlags="hardRestricted"
+ android:protectionLevel="dangerous" />
+
+ <!-- @SystemApi @TestApi Allows an application to forward cell broadcast messages to the cell
+ broadcast module. This is required in order to bind to the cell broadcast service, and
+ ensures that only the system can forward messages to it.
+
+ <p>Protection level: signature
+
+ @hide -->
+ <permission android:name="android.permission.BIND_CELL_BROADCAST_SERVICE"
+ android:label="@string/permlab_bindCellBroadcastService"
+ android:description="@string/permdesc_bindCellBroadcastService"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @TestApi Allows an application to read previously received cell broadcast
+ messages and to register a content observer to get notifications when
+ a cell broadcast has been received and added to the database. For
+ emergency alerts, the database is updated immediately after the
+ alert dialog and notification sound/vibration/speech are presented.
+ The "read" column is then updated after the user dismisses the alert.
+ This enables supplementary emergency assistance apps to start loading
+ additional emergency information (if Internet access is available)
+ when the alert is first received, and to delay presenting the info
+ to the user until after the initial alert dialog is dismissed.
+ <p>Protection level: dangerous
+
+ <p> This is a hard restricted permission which cannot be held by an app until
+ the installer on record whitelists the permission. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+
+ @hide Pending API council approval -->
+ <permission android:name="android.permission.READ_CELL_BROADCASTS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readCellBroadcasts"
+ android:description="@string/permdesc_readCellBroadcasts"
+ android:permissionFlags="hardRestricted"
+ android:protectionLevel="dangerous" />
+
+ <!-- @SystemApi @hide Allows an application to communicate over satellite.
+ Only granted if the application is a system app.-->
+ <permission android:name="android.permission.SATELLITE_COMMUNICATION"
+ android:protectionLevel="role|signature|privileged" />
+
+ <!-- @SystemApi @hide Allows an application to bind with satellite service.
+ Only granted if the application is a system app.-->
+ <permission android:name="android.permission.BIND_SATELLITE_SERVICE"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+ <!-- @hide Allows an application to bind with satellite gateway service.
+ Only granted if the application is a system app.-->
+ <permission android:name="android.permission.BIND_SATELLITE_GATEWAY_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- ====================================================================== -->
+ <!-- Permissions for accessing external storage -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Used for runtime permissions related to the shared external storage. -->
+ <permission-group android:name="android.permission-group.STORAGE"
+ android:icon="@drawable/perm_group_storage"
+ android:label="@string/permgrouplab_storage"
+ android:description="@string/permgroupdesc_storage"
+ android:priority="900" />
+
+ <!-- Allows an application to read from external storage.
+ <p>Any app that declares the {@link #WRITE_EXTERNAL_STORAGE} permission is implicitly
+ granted this permission.</p>
+ <p>This permission is enforced starting in API level 19. Before API level 19, this
+ permission is not enforced and all apps still have access to read from external storage.
+ You can test your app with the permission enforced by enabling <em>Protect USB
+ storage</em> under Developer options in the Settings app on a device running Android 4.1 or
+ higher.</p>
+ <p>Also starting in API level 19, this permission is <em>not</em> required to
+ read/write files in your application-specific directories returned by
+ {@link android.content.Context#getExternalFilesDir} and
+ {@link android.content.Context#getExternalCacheDir}.
+ <p class="note"><strong>Note:</strong> If <em>both</em> your <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
+ minSdkVersion}</a> and <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> values are set to 3 or lower, the system implicitly
+ grants your app this permission. If you don't need this permission, be sure your <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> is 4 or higher.
+
+ <p> This is a soft restricted permission which cannot be held by an app it its
+ full form until the installer on record whitelists the permission.
+ Specifically, if the permission is allowlisted the holder app can access
+ external storage and the visual and aural media collections while if the
+ permission is not allowlisted the holder app can only access to the visual
+ and aural medial collections. Also the permission is immutably restricted
+ meaning that the allowlist state can be specified only at install time and
+ cannot change until the app is installed. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.READ_EXTERNAL_STORAGE"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_sdcardRead"
+ android:description="@string/permdesc_sdcardRead"
+ android:permissionFlags="softRestricted|immutablyRestricted"
+ android:protectionLevel="dangerous" />
+
+ <!-- Required to be able to read audio files from shared storage.
+ <p>Protection level: dangerous -->
+ <permission-group android:name="android.permission-group.READ_MEDIA_AURAL"
+ android:icon="@drawable/perm_group_read_media_aural"
+ android:label="@string/permgrouplab_readMediaAural"
+ android:description="@string/permgroupdesc_readMediaAural"
+ android:priority="950" />
+
+ <!-- Allows an application to read audio files from external storage.
+ <p>This permission is enforced starting in API level
+ {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+ For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
+ must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.READ_MEDIA_AUDIO"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readMediaAudio"
+ android:description="@string/permdesc_readMediaAudio"
+ android:protectionLevel="dangerous" />
+
+ <!-- Required to be able to read image and video files from shared storage.
+ <p>Protection level: dangerous -->
+ <permission-group android:name="android.permission-group.READ_MEDIA_VISUAL"
+ android:icon="@drawable/perm_group_read_media_visual"
+ android:label="@string/permgrouplab_readMediaVisual"
+ android:description="@string/permgroupdesc_readMediaVisual"
+ android:priority="1000" />
+
+ <!-- Allows an application to read audio files from external storage.
+ <p>This permission is enforced starting in API level
+ {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+ For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
+ must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.READ_MEDIA_VIDEO"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readMediaVideo"
+ android:description="@string/permdesc_readMediaVideo"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to read image files from external storage.
+ <p>This permission is enforced starting in API level
+ {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+ For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission
+ must not be used and the READ_EXTERNAL_STORAGE permission must be used instead.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.READ_MEDIA_IMAGES"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readMediaImage"
+ android:description="@string/permdesc_readMediaImage"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to read image or video files from external storage that a user has
+ selected via the permission prompt photo picker. Apps can check this permission to verify that
+ a user has decided to use the photo picker, instead of granting access to
+ {@link #READ_MEDIA_IMAGES or #READ_MEDIA_VIDEO}. It does not prevent apps from accessing the
+ standard photo picker manually.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readVisualUserSelect"
+ android:description="@string/permdesc_readVisualUserSelect"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to write to external storage.
+ <p class="note"><strong>Note:</strong> If <em>both</em> your <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
+ minSdkVersion}</a> and <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> values are set to 3 or lower, the system implicitly
+ grants your app this permission. If you don't need this permission, be sure your <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> is 4 or higher.
+ <p>Starting in API level 19, this permission is <em>not</em> required to
+ read/write files in your application-specific directories returned by
+ {@link android.content.Context#getExternalFilesDir} and
+ {@link android.content.Context#getExternalCacheDir}.
+ <p>If this permission is not allowlisted for an app that targets an API level before
+ {@link android.os.Build.VERSION_CODES#Q} this permission cannot be granted to apps.</p>
+ <p>Protection level: dangerous</p>
+ -->
+ <permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_sdcardWrite"
+ android:description="@string/permdesc_sdcardWrite"
+ android:permissionFlags="softRestricted|immutablyRestricted"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to access any geographic locations persisted in the
+ user's shared collection.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.ACCESS_MEDIA_LOCATION"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_mediaLocation"
+ android:description="@string/permdesc_mediaLocation"
+ android:protectionLevel="dangerous" />
+
+ <!-- @hide @SystemApi @TestApi
+ Allows an application to modify OBB files visible to other apps. -->
+ <permission android:name="android.permission.WRITE_OBB"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application a broad access to external storage in scoped storage.
+ Intended to be used by few apps that need to manage files on behalf of the users.
+ <p>Protection level: signature|appop|preinstalled -->
+ <permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:protectionLevel="signature|appop|preinstalled" />
+
+ <!-- Allows an application to modify and delete media files on this device or any connected
+ storage device without user confirmation. Applications must already be granted the
+ {@link #READ_EXTERNAL_STORAGE} or {@link #MANAGE_EXTERNAL_STORAGE}} permissions for this
+ permission to take effect.
+ <p>Even if applications are granted this permission, if applications want to modify or
+ delete media files, they also must get the access by calling
+ {@link android.provider.MediaStore#createWriteRequest(ContentResolver, Collection)},
+ {@link android.provider.MediaStore#createDeleteRequest(ContentResolver, Collection)}, or
+ {@link android.provider.MediaStore#createTrashRequest(ContentResolver, Collection, boolean)}.
+ <p>This permission doesn't give read or write access directly. It only prevents the user
+ confirmation dialog for these requests.
+ <p>If applications are not granted {@link #ACCESS_MEDIA_LOCATION}, the system also pops up
+ the user confirmation dialog for the write request.
+ <p>Protection level: signature|appop|preinstalled -->
+ <permission android:name="android.permission.MANAGE_MEDIA"
+ android:protectionLevel="signature|appop|preinstalled" />
+
+ <!-- ====================================================================== -->
+ <!-- Permissions for accessing the device location -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Used for permissions that allow accessing the device location. -->
+ <permission-group android:name="android.permission-group.LOCATION"
+ android:icon="@drawable/perm_group_location"
+ android:label="@string/permgrouplab_location"
+ android:description="@string/permgroupdesc_location"
+ android:priority="400" />
+
+ <!-- Allows an app to access precise location.
+ Alternatively, you might want {@link #ACCESS_COARSE_LOCATION}.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.ACCESS_FINE_LOCATION"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_accessFineLocation"
+ android:description="@string/permdesc_accessFineLocation"
+ android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION"
+ android:protectionLevel="dangerous|instant" />
+
+ <!-- Allows an app to access approximate location.
+ Alternatively, you might want {@link #ACCESS_FINE_LOCATION}.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.ACCESS_COARSE_LOCATION"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_accessCoarseLocation"
+ android:description="@string/permdesc_accessCoarseLocation"
+ android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION"
+ android:protectionLevel="dangerous|instant" />
+
+ <!-- Allows an app to access location in the background. If you're requesting this permission,
+ you must also request either {@link #ACCESS_COARSE_LOCATION} or
+ {@link #ACCESS_FINE_LOCATION}. Requesting this permission by itself doesn't give you
+ location access.
+ <p>Protection level: dangerous
+
+ <p> This is a hard restricted permission which cannot be held by an app until
+ the installer on record whitelists the permission. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ -->
+ <permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_accessBackgroundLocation"
+ android:permissionFlags="hardRestricted"
+ android:description="@string/permdesc_accessBackgroundLocation"
+ android:protectionLevel="dangerous|instant" />
+
+ <!-- Allows an application (emergency or advanced driver-assistance app) to bypass
+ location settings.
+ <p>Not for use by third-party applications.
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.LOCATION_BYPASS"
+ android:protectionLevel="signature|privileged"/>
+
+ <!-- ====================================================================== -->
+ <!-- Permissions for accessing the call log -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Used for permissions that are associated telephony features. -->
+ <permission-group android:name="android.permission-group.CALL_LOG"
+ android:icon="@drawable/perm_group_call_log"
+ android:label="@string/permgrouplab_calllog"
+ android:description="@string/permgroupdesc_calllog"
+ android:priority="450" />
+
+ <!-- Allows an application to access the IMS call service: making and
+ modifying a call
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.ACCESS_IMS_CALL_SERVICE"
+ android:label="@string/permlab_accessImsCallService"
+ android:description="@string/permdesc_accessImsCallService"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows an application to perform IMS Single Registration related actions.
+ Only granted if the application is a system app AND is in the Default SMS Role.
+ The permission is revoked when the app is taken out of the Default SMS Role.
+ <p>Protection level: internal|role
+ -->
+ <permission android:name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to read the user's call log.
+ <p class="note"><strong>Note:</strong> If your app uses the
+ {@link #READ_CONTACTS} permission and <em>both</em> your <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
+ minSdkVersion}</a> and <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> values are set to 15 or lower, the system implicitly
+ grants your app this permission. If you don't need this permission, be sure your <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> is 16 or higher.</p>
+ <p>Protection level: dangerous
+
+ <p> This is a hard restricted permission which cannot be held by an app until
+ the installer on record whitelists the permission. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ -->
+ <permission android:name="android.permission.READ_CALL_LOG"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readCallLog"
+ android:description="@string/permdesc_readCallLog"
+ android:permissionFlags="hardRestricted"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to write (but not read) the user's
+ call log data.
+ <p class="note"><strong>Note:</strong> If your app uses the
+ {@link #WRITE_CONTACTS} permission and <em>both</em> your <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
+ minSdkVersion}</a> and <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> values are set to 15 or lower, the system implicitly
+ grants your app this permission. If you don't need this permission, be sure your <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> is 16 or higher.</p>
+ <p>Protection level: dangerous
+
+ <p> This is a hard restricted permission which cannot be held by an app until
+ the installer on record whitelists the permission. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ -->
+ <permission android:name="android.permission.WRITE_CALL_LOG"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_writeCallLog"
+ android:description="@string/permdesc_writeCallLog"
+ android:permissionFlags="hardRestricted"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to see the number being dialed during an outgoing
+ call with the option to redirect the call to a different number or
+ abort the call altogether.
+ <p>Protection level: dangerous
+
+ <p> This is a hard restricted permission which cannot be held by an app until
+ the installer on record whitelists the permission. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+
+ @deprecated Applications should use {@link android.telecom.CallRedirectionService} instead
+ of the {@link android.content.Intent#ACTION_NEW_OUTGOING_CALL} broadcast.
+ -->
+ <permission android:name="android.permission.PROCESS_OUTGOING_CALLS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_processOutgoingCalls"
+ android:description="@string/permdesc_processOutgoingCalls"
+ android:permissionFlags="hardRestricted"
+ android:protectionLevel="dangerous" />
+
+ <!-- ====================================================================== -->
+ <!-- Permissions for accessing the device telephony -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Used for permissions that are associated telephony features. -->
+ <permission-group android:name="android.permission-group.PHONE"
+ android:icon="@drawable/perm_group_phone_calls"
+ android:label="@string/permgrouplab_phone"
+ android:description="@string/permgroupdesc_phone"
+ android:priority="500" />
+
+ <!-- Allows read only access to phone state, including the current cellular network information,
+ the status of any ongoing calls, and a list of any {@link android.telecom.PhoneAccount}s
+ registered on the device.
+ <p class="note"><strong>Note:</strong> If <em>both</em> your <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
+ minSdkVersion}</a> and <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> values are set to 3 or lower, the system implicitly
+ grants your app this permission. If you don't need this permission, be sure your <a
+ href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
+ targetSdkVersion}</a> is 4 or higher.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.READ_PHONE_STATE"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readPhoneState"
+ android:description="@string/permdesc_readPhoneState"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows read access to the device's phone number(s). This is a subset of the capabilities
+ granted by {@link #READ_PHONE_STATE} but is exposed to instant applications.
+ <p>Protection level: dangerous-->
+ <permission android:name="android.permission.READ_PHONE_NUMBERS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readPhoneNumbers"
+ android:description="@string/permdesc_readPhoneNumbers"
+ android:protectionLevel="dangerous|instant" />
+
+ <!-- Allows an application to initiate a phone call without going through
+ the Dialer user interface for the user to confirm the call.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.CALL_PHONE"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:permissionFlags="costsMoney"
+ android:label="@string/permlab_callPhone"
+ android:description="@string/permdesc_callPhone"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to add voicemails into the system.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_addVoicemail"
+ android:description="@string/permdesc_addVoicemail"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to use SIP service.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.USE_SIP"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_use_sip"
+ android:label="@string/permlab_use_sip"
+ android:protectionLevel="dangerous"/>
+
+ <!-- Allows the app to answer an incoming phone call.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.ANSWER_PHONE_CALLS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_answerPhoneCalls"
+ android:description="@string/permdesc_answerPhoneCalls"
+ android:protectionLevel="dangerous|runtime" />
+
+ <!-- Allows a calling application which manages its own calls through the self-managed
+ {@link android.telecom.ConnectionService} APIs. See
+ {@link android.telecom.PhoneAccount#CAPABILITY_SELF_MANAGED} for more information on the
+ self-managed ConnectionService APIs.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.MANAGE_OWN_CALLS"
+ android:label="@string/permlab_manageOwnCalls"
+ android:description="@string/permdesc_manageOwnCalls"
+ android:protectionLevel="normal" />
+
+ <!--Allows an app which implements the
+ {@link android.telecom.InCallService InCallService} API to be eligible to be enabled as a
+ calling companion app. This means that the Telecom framework will bind to the app's
+ InCallService implementation when there are calls active. The app can use the InCallService
+ API to view information about calls on the system and control these calls.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.CALL_COMPANION_APP"
+ android:label="@string/permlab_callCompanionApp"
+ android:description="@string/permdesc_callCompanionApp"
+ android:protectionLevel="normal" />
+
+ <!-- Exempt this uid from restrictions to background audio recoding
+ <p>Protection level: signature|privileged
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS"
+ android:label="@string/permlab_exemptFromAudioRecordRestrictions"
+ android:description="@string/permdesc_exemptFromAudioRecordRestrictions"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Allows a calling app to continue a call which was started in another app. An example is a
+ video calling app that wants to continue a voice call on the user's mobile network.<p>
+ When the handover of a call from one app to another takes place, there are two devices
+ which are involved in the handover; the initiating and receiving devices. The initiating
+ device is where the request to handover the call was started, and the receiving device is
+ where the handover request is confirmed by the other party.<p>
+ This permission protects access to the
+ {@link android.telecom.TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)} which
+ the receiving side of the handover uses to accept a handover.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.ACCEPT_HANDOVER"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android.label="@string/permlab_acceptHandover"
+ android:description="@string/permdesc_acceptHandovers"
+ android:protectionLevel="dangerous" />
+
+ <!-- ====================================================================== -->
+ <!-- Permissions for accessing the device microphone -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Used for permissions that are associated with accessing
+ microphone audio from the device. Note that phone calls also capture audio
+ but are in a separate (more visible) permission group. -->
+ <permission-group android:name="android.permission-group.MICROPHONE"
+ android:icon="@drawable/perm_group_microphone"
+ android:label="@string/permgrouplab_microphone"
+ android:description="@string/permgroupdesc_microphone"
+ android:priority="600" />
+
+ <!-- Allows an application to record audio.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.RECORD_AUDIO"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_recordAudio"
+ android:description="@string/permdesc_recordAudio"
+ android:backgroundPermission="android.permission.RECORD_BACKGROUND_AUDIO"
+ android:protectionLevel="dangerous|instant" />
+
+ <!-- @SystemApi @TestApi Allows an application to record audio while in the background.
+ This permission is not intended to be held by apps.
+ <p>Protection level: internal
+ @hide -->
+ <permission android:name="android.permission.RECORD_BACKGROUND_AUDIO"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_recordBackgroundAudio"
+ android:description="@string/permdesc_recordBackgroundAudio"
+ android:protectionLevel="internal|role" />
+
+ <!-- ====================================================================== -->
+ <!-- Permissions for activity recognition -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Used for permissions that are associated with activity recognition. -->
+ <permission-group android:name="android.permission-group.ACTIVITY_RECOGNITION"
+ android:icon="@drawable/perm_group_activity_recognition"
+ android:label="@string/permgrouplab_activityRecognition"
+ android:description="@string/permgroupdesc_activityRecognition"
+ android:priority="1000" />
+
+ <!-- Allows an application to recognize physical activity.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.ACTIVITY_RECOGNITION"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_activityRecognition"
+ android:description="@string/permdesc_activityRecognition"
+ android:protectionLevel="dangerous|instant" />
+
+ <!-- ====================================================================== -->
+ <!-- Permissions for accessing the vendor UCE Service -->
+ <!-- ====================================================================== -->
+
+ <!-- @hide Allows an application to Access UCE-Presence.
+ <p>Protection level: signature|privileged
+ @deprecated Framework should no longer use this permission to access the vendor UCE service
+ using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
+ -->
+ <permission android:name="android.permission.ACCESS_UCE_PRESENCE_SERVICE"
+ android:permissionGroup="android.permission-group.PHONE"
+ android:protectionLevel="signature|privileged"/>
+
+ <!-- @hide Allows an application to Access UCE-OPTIONS.
+ <p>Protection level: signature|privileged
+ @deprecated Framework should no longer use this permission to access the vendor UCE service
+ using AIDL, it is instead implemented by RcsCapabilityExchangeImplBase
+ -->
+ <permission android:name="android.permission.ACCESS_UCE_OPTIONS_SERVICE"
+ android:permissionGroup="android.permission-group.PHONE"
+ android:protectionLevel="signature|privileged"/>
+
+
+
+ <!-- ====================================================================== -->
+ <!-- Permissions for accessing the device camera -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Used for permissions that are associated with accessing
+ camera or capturing images/video from the device. -->
+ <permission-group android:name="android.permission-group.CAMERA"
+ android:icon="@drawable/perm_group_camera"
+ android:label="@string/permgrouplab_camera"
+ android:description="@string/permgroupdesc_camera"
+ android:priority="700" />
+
+ <!-- Required to be able to access the camera device.
+ <p>This will automatically enforce the
+ <a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">
+ uses-feature</a> manifest element for <em>all</em> camera features.
+ If you do not require all camera features or can properly operate if a camera
+ is not available, then you must modify your manifest as appropriate in order to
+ install on devices that don't support all camera features.</p>
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.CAMERA"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_camera"
+ android:description="@string/permdesc_camera"
+ android:backgroundPermission="android.permission.BACKGROUND_CAMERA"
+ android:protectionLevel="dangerous|instant" />
+
+ <!-- Required to be able to discover and connect to nearby Bluetooth devices.
+ <p>Protection level: dangerous -->
+ <permission-group android:name="android.permission-group.NEARBY_DEVICES"
+ android:icon="@drawable/perm_group_nearby_devices"
+ android:label="@string/permgrouplab_nearby_devices"
+ android:description="@string/permgroupdesc_nearby_devices"
+ android:priority="750" />
+
+ <!-- @SystemApi @TestApi Required to be able to access the camera device in the background.
+ This permission is not intended to be held by apps.
+ <p>Protection level: internal
+ @hide -->
+ <permission android:name="android.permission.BACKGROUND_CAMERA"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_backgroundCamera"
+ android:description="@string/permdesc_backgroundCamera"
+ android:protectionLevel="internal|role" />
+
+ <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
+ system only camera devices.
+ <p>Protection level: system|signature|role
+ @hide -->
+ <permission android:name="android.permission.SYSTEM_CAMERA"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_systemCamera"
+ android:description="@string/permdesc_systemCamera"
+ android:protectionLevel="system|signature|role" />
+
+ <!-- @SystemApi Allows receiving the camera service notifications when a camera is opened
+ (by a certain application package) or closed.
+ @hide -->
+ <permission android:name="android.permission.CAMERA_OPEN_CLOSE_LISTENER"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_cameraOpenCloseListener"
+ android:description="@string/permdesc_cameraOpenCloseListener"
+ android:protectionLevel="signature" />
+
+ <!-- ====================================================================== -->
+ <!-- Permissions for accessing the device sensors -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Used for permissions that are associated with accessing
+ body or environmental sensors. -->
+ <permission-group android:name="android.permission-group.SENSORS"
+ android:icon="@drawable/perm_group_sensors"
+ android:label="@string/permgrouplab_sensors"
+ android:description="@string/permgroupdesc_sensors"
+ android:priority="800" />
+
+ <!-- Allows an app to access sensor data with a sampling rate greater than 200 Hz.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS"
+ android:permissionGroup="android.permission-group.SENSORS"
+ android:label="@string/permlab_highSamplingRateSensors"
+ android:description="@string/permdesc_highSamplingRateSensors"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an application to access data from sensors that the user uses to
+ measure what is happening inside their body, such as heart rate.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.BODY_SENSORS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_bodySensors"
+ android:description="@string/permdesc_bodySensors"
+ android:backgroundPermission="android.permission.BODY_SENSORS_BACKGROUND"
+ android:protectionLevel="dangerous" />
+
+ <!-- Allows an application to access data from sensors that the user uses to measure what is
+ happening inside their body, such as heart rate. If you're requesting this permission, you
+ must also request {@link #BODY_SENSORS}. Requesting this permission by itself doesn't give
+ you Body sensors access.
+ <p>Protection level: dangerous
+
+ <p> This is a hard restricted permission which cannot be held by an app until
+ the installer on record allowlists the permission. For more details see
+ {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+ -->
+ <permission android:name="android.permission.BODY_SENSORS_BACKGROUND"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_bodySensors_background"
+ android:description="@string/permdesc_bodySensors_background"
+ android:protectionLevel="dangerous"
+ android:permissionFlags="hardRestricted" />
+
+ <!-- Allows an app to use fingerprint hardware.
+ <p>Protection level: normal
+ @deprecated Applications should request {@link
+ android.Manifest.permission#USE_BIOMETRIC} instead
+ -->
+ <permission android:name="android.permission.USE_FINGERPRINT"
+ android:permissionGroup="android.permission-group.SENSORS"
+ android:label="@string/permlab_useFingerprint"
+ android:description="@string/permdesc_useFingerprint"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an app to use device supported biometric modalities.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.USE_BIOMETRIC"
+ android:permissionGroup="android.permission-group.SENSORS"
+ android:label="@string/permlab_useBiometric"
+ android:description="@string/permdesc_useBiometric"
+ android:protectionLevel="normal" />
+
+ <!-- ======================================================================= -->
+ <!-- Permissions for posting notifications -->
+ <!-- ====================================================================== -->
+ <eat-comment />
+
+ <!-- Used for permissions that are associated with posting notifications
+ -->
+ <permission-group android:name="android.permission-group.NOTIFICATIONS"
+ android:icon="@drawable/ic_notifications_alerted"
+ android:label="@string/permgrouplab_notifications"
+ android:description="@string/permgroupdesc_notifications"
+ android:priority="850" />
+
+ <!-- Allows an app to post notifications
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.POST_NOTIFICATIONS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_postNotification"
+ android:description="@string/permdesc_postNotification"
+ android:protectionLevel="dangerous|instant" />
+
+ <!-- ====================================================================== -->
+ <!-- REMOVED PERMISSIONS -->
+ <!-- ====================================================================== -->
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="android.permission.READ_PROFILE"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="android.permission.WRITE_PROFILE"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="android.permission.READ_SOCIAL_STREAM"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="android.permission.WRITE_SOCIAL_STREAM"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="android.permission.READ_USER_DICTIONARY"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="android.permission.WRITE_USER_DICTIONARY"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="android.permission.WRITE_SMS"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="com.android.browser.permission.WRITE_HISTORY_BOOKMARKS"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="android.permission.MANAGE_ACCOUNTS"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="android.permission.USE_CREDENTIALS"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="android.permission.SUBSCRIBED_FEEDS_READ"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="android.permission.SUBSCRIBED_FEEDS_WRITE"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- @hide We need to keep this around for backwards compatibility -->
+ <permission android:name="android.permission.FLASHLIGHT"
+ android:protectionLevel="normal"
+ android:permissionFlags="removed"/>
+
+ <!-- ====================================================================== -->
+ <!-- INSTALL PERMISSIONS -->
+ <!-- ====================================================================== -->
+
+ <!-- ================================== -->
+ <!-- Permissions for accessing messages -->
+ <!-- ================================== -->
+ <eat-comment />
+
+ <!-- Allows an application (Phone) to send a request to other applications
+ to handle the respond-via-message action during incoming calls.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SEND_RESPOND_VIA_MESSAGE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to send SMS to premium shortcodes without user permission.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.SEND_SMS_NO_CONFIRMATION"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to filter carrier specific sms.
+ @hide -->
+ <permission android:name="android.permission.CARRIER_FILTER_SMS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to receive emergency cell broadcast messages,
+ to record or display them to the user.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.RECEIVE_EMERGENCY_BROADCAST"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to monitor incoming Bluetooth MAP messages, to record
+ or perform processing on them. -->
+ <!-- @hide -->
+ <permission android:name="android.permission.RECEIVE_BLUETOOTH_MAP"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows an application to execute contacts directory search.
+ This should only be used by ContactsProvider.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.BIND_DIRECTORY_SEARCH"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows an application to modify the cell broadcasts configuration
+ (i.e. enable or disable channels).
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MODIFY_CELL_BROADCASTS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- =============================================================== -->
+ <!-- Permissions for setting the device alarm -->
+ <!-- =============================================================== -->
+ <eat-comment />
+
+ <!-- Allows an application to broadcast an Intent to set an alarm for the user.
+ <p>Protection level: normal
+ -->
+ <permission android:name="com.android.alarm.permission.SET_ALARM"
+ android:label="@string/permlab_setAlarm"
+ android:description="@string/permdesc_setAlarm"
+ android:protectionLevel="normal" />
+
+ <!-- =============================================================== -->
+ <!-- Permissions for accessing the user voicemail -->
+ <!-- =============================================================== -->
+ <eat-comment />
+
+ <!-- Allows an application to modify and remove existing voicemails in the system.
+ <p>Protection level: signature|privileged|role
+ -->
+ <permission android:name="com.android.voicemail.permission.WRITE_VOICEMAIL"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Allows an application to read voicemails in the system.
+ <p>Protection level: signature|privileged|role
+ -->
+ <permission android:name="com.android.voicemail.permission.READ_VOICEMAIL"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- ======================================= -->
+ <!-- Permissions for accessing location info -->
+ <!-- ======================================= -->
+ <eat-comment />
+
+ <!-- Allows an application to access extra location provider commands.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"
+ android:label="@string/permlab_accessLocationExtraCommands"
+ android:description="@string/permdesc_accessLocationExtraCommands"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an application to install a location provider into the Location Manager.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.INSTALL_LOCATION_PROVIDER"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows an application to provide location-based time zone suggestions to
+ the system server. This is needed because the system server discovers time zone providers
+ by exposed intent actions and metadata, without it any app could potentially register
+ itself as time zone provider. The system server checks for this permission.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows an application to bind to a android.service.TimeZoneProviderService
+ for the purpose of detecting the device's time zone. This prevents arbitrary clients
+ connecting to the time zone provider service. The system server checks that the provider's
+ intent service explicitly sets this permission via the android:permission attribute of the
+ service.
+ This is only expected to be possessed by the system server outside of tests.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows HDMI-CEC service to access device and configuration files.
+ This should only be used by HDMI-CEC service.
+ -->
+ <permission android:name="android.permission.HDMI_CEC"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+ <!-- Allows an application to use location features in hardware,
+ such as the geofencing api.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.LOCATION_HARDWARE"
+ android:protectionLevel="signature|privileged|role" />
+ <uses-permission android:name="android.permission.LOCATION_HARDWARE"/>
+
+ <!-- @SystemApi Allows an application to use the Context Hub.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.ACCESS_CONTEXT_HUB"
+ android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB"/>
+
+ <!-- @SystemApi Allows an application to create mock location providers for testing.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.ACCESS_MOCK_LOCATION"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows automotive applications to control location
+ suspend state for power management use cases.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.CONTROL_AUTOMOTIVE_GNSS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- ======================================= -->
+ <!-- Permissions for accessing networks -->
+ <!-- ======================================= -->
+ <eat-comment />
+
+ <!-- Allows applications to open network sockets.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.INTERNET"
+ android:description="@string/permdesc_createNetworkSockets"
+ android:label="@string/permlab_createNetworkSockets"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows applications to access information about networks.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.ACCESS_NETWORK_STATE"
+ android:description="@string/permdesc_accessNetworkState"
+ android:label="@string/permlab_accessNetworkState"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows applications to access information about Wi-Fi networks.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.ACCESS_WIFI_STATE"
+ android:description="@string/permdesc_accessWifiState"
+ android:label="@string/permlab_accessWifiState"
+ android:protectionLevel="normal" />
+
+ <!-- Allows applications to change Wi-Fi connectivity state.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.CHANGE_WIFI_STATE"
+ android:description="@string/permdesc_changeWifiState"
+ android:label="@string/permlab_changeWifiState"
+ android:protectionLevel="normal" />
+
+ <!-- This permission is used to let OEMs grant their trusted app access to a subset of
+ privileged wifi APIs to improve wifi performance. Allows applications to manage
+ Wi-Fi network selection related features such as enable or disable global auto-join,
+ modify connectivity scan intervals, and approve Wi-Fi Direct connections.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_WIFI_NETWORK_SELECTION"
+ android:protectionLevel="signature|privileged|knownSigner"
+ android:knownCerts="@array/wifi_known_signers" />
+
+ <!-- Allows applications to get notified when a Wi-Fi interface request cannot
+ be satisfied without tearing down one or more other interfaces, and provide a decision
+ whether to approve the request or reject it.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_WIFI_INTERFACES"
+ android:protectionLevel="signature|privileged|knownSigner"
+ android:knownCerts="@array/wifi_known_signers" />
+
+ <!-- @SystemApi @hide Allows apps to create and manage IPsec tunnels.
+ <p>Only granted to applications that are currently bound by the
+ system for creating and managing IPsec-based interfaces.
+ -->
+ <permission android:name="android.permission.MANAGE_IPSEC_TUNNELS"
+ android:protectionLevel="signature|appop" />
+
+ <!-- @SystemApi @hide Allows apps to create and manage Test Networks.
+ <p>Granted only to shell. CTS tests will use
+ UiAutomation.AdoptShellPermissionIdentity() to gain access.
+ -->
+ <permission android:name="android.permission.MANAGE_TEST_NETWORKS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows applications to read Wi-Fi credential.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.READ_WIFI_CREDENTIAL"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows applications to change tether state and run
+ tether carrier provisioning.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.TETHER_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allow system apps to receive broadcast
+ when a wifi network credential is changed.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to modify any wifi configuration, even if created
+ by another application. Once reconfigured the original creator cannot make any further
+ modifications.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.OVERRIDE_WIFI_CONFIG"
+ android:protectionLevel="signature|privileged|knownSigner"
+ android:knownCerts="@array/wifi_known_signers" />
+
+ <!-- Allows applications to act as network scorers. @hide @SystemApi-->
+ <permission android:name="android.permission.SCORE_NETWORKS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows applications to request network
+ recommendations and scores from the NetworkScoreService.
+ @SystemApi
+ <p>Not for use by third-party applications. @hide -->
+ <permission android:name="android.permission.REQUEST_NETWORK_SCORES"
+ android:protectionLevel="signature|setup" />
+
+ <!-- Allows applications to restart the Wi-Fi subsystem.
+ @SystemApi
+ <p>Not for use by third-party applications. @hide -->
+ <permission android:name="android.permission.RESTART_WIFI_SUBSYSTEM"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows applications to toggle airplane mode.
+ <p>Not for use by third-party or privileged applications.
+ -->
+ <permission android:name="android.permission.NETWORK_AIRPLANE_MODE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows network stack services (Connectivity and Wifi) to coordinate
+ <p>Not for use by third-party or privileged applications.
+ @SystemApi @TestApi
+ @hide This should only be used by Connectivity and Wifi Services.
+ -->
+ <permission android:name="android.permission.NETWORK_STACK"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows an application to observe network policy changes. -->
+ <permission android:name="android.permission.OBSERVE_NETWORK_POLICY"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows applications to register network factory or agent -->
+ <permission android:name="android.permission.NETWORK_FACTORY"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi @hide Allows applications to access network stats provider -->
+ <permission android:name="android.permission.NETWORK_STATS_PROVIDER"
+ android:protectionLevel="signature" />
+
+ <!-- Allows Settings and SystemUI to call methods in Networking services
+ <p>Not for use by third-party or privileged applications.
+ @SystemApi @TestApi
+ @hide This should only be used by Settings and SystemUI.
+ -->
+ <permission android:name="android.permission.NETWORK_SETTINGS"
+ android:protectionLevel="signature" />
+
+ <!-- Allows holder to request bluetooth/wifi scan bypassing global "use location" setting and
+ location permissions.
+ <p>Not for use by third-party or privileged applications.
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"
+ android:protectionLevel="signature|companion" />
+
+ <!-- Allows SetupWizard to call methods in Networking services
+ <p>Not for use by any other third-party or privileged applications.
+ @SystemApi
+ @hide This should only be used by SetupWizard.
+ -->
+ <permission android:name="android.permission.NETWORK_SETUP_WIZARD"
+ android:protectionLevel="signature|setup" />
+
+ <!-- Allows Managed Provisioning to call methods in Networking services
+ <p>Not for use by any other third-party or privileged applications.
+ @SystemApi
+ @hide This should only be used by ManagedProvisioning app.
+ -->
+ <permission android:name="android.permission.NETWORK_MANAGED_PROVISIONING"
+ android:protectionLevel="signature|role" />
+
+ <!-- Allows Carrier Provisioning to call methods in Networking services
+ <p>Not for use by any other third-party or privileged applications.
+ @SystemApi
+ @hide This should only be used by CarrierProvisioning.
+ -->
+ <permission android:name="android.permission.NETWORK_CARRIER_PROVISIONING"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- #SystemApi @hide Allows applications to access information about LoWPAN interfaces.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.ACCESS_LOWPAN_STATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- #SystemApi @hide Allows applications to change LoWPAN connectivity state.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CHANGE_LOWPAN_STATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- #SystemApi @hide Allows applications to read LoWPAN credential.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.READ_LOWPAN_CREDENTIAL"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- #SystemApi @hide Allows a service to register or unregister
+ new LoWPAN interfaces.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_LOWPAN_INTERFACES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- #SystemApi @hide Allows an app to bypass Private DNS.
+ <p>Not for use by third-party applications.
+ TODO: publish as system API in next API release. -->
+ <permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows device mobility state to be set so that Wifi scan interval can
+ be increased when the device is stationary in order to save power.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WIFI_SET_DEVICE_MOBILITY_STATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows privileged system APK to update Wifi usability stats and score.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows applications to update Wifi/Cellular coex channels to avoid.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi @hide Allows applications to access Wifi/Cellular coex channels being avoided.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi @hide Allows system APK to manage country code.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_WIFI_COUNTRY_CODE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows an application to manage an automotive device's application network
+ preference as it relates to OEM_PAID and OEM_PRIVATE capable networks.
+ <p>Not for use by third-party or privileged applications. -->
+ <permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows an application to manage ethernet networks.
+ <p>Not for use by third-party or privileged applications. -->
+ <permission android:name="android.permission.MANAGE_ETHERNET_NETWORKS"
+ android:protectionLevel="signature" />
+
+ <!-- ======================================= -->
+ <!-- Permissions for short range, peripheral networks -->
+ <!-- ======================================= -->
+ <eat-comment />
+
+ <!-- Allows applications to connect to paired bluetooth devices.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.BLUETOOTH"
+ android:description="@string/permdesc_bluetooth"
+ android:label="@string/permlab_bluetooth"
+ android:protectionLevel="normal" />
+
+ <!-- Required to be able to discover and pair nearby Bluetooth devices.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.BLUETOOTH_SCAN"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_bluetooth_scan"
+ android:label="@string/permlab_bluetooth_scan"
+ android:protectionLevel="dangerous" />
+
+ <!-- Required to be able to connect to paired Bluetooth devices.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.BLUETOOTH_CONNECT"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_bluetooth_connect"
+ android:label="@string/permlab_bluetooth_connect"
+ android:protectionLevel="dangerous" />
+
+ <!-- Required to be able to advertise to nearby Bluetooth devices.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.BLUETOOTH_ADVERTISE"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_bluetooth_advertise"
+ android:label="@string/permlab_bluetooth_advertise"
+ android:protectionLevel="dangerous" />
+
+ <!-- Required to be able to range to devices using ultra-wideband.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.UWB_RANGING"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_uwb_ranging"
+ android:label="@string/permlab_uwb_ranging"
+ android:protectionLevel="dangerous" />
+
+ <!-- Required to be able to advertise and connect to nearby devices via Wi-Fi.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.NEARBY_WIFI_DEVICES"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_nearby_wifi_devices"
+ android:label="@string/permlab_nearby_wifi_devices"
+ android:protectionLevel="dangerous" />
+
+ <!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the
+ user from using them until they are unsuspended.
+ @hide
+ -->
+ <permission android:name="android.permission.SUSPEND_APPS"
+ android:protectionLevel="signature|role" />
+
+ <!-- Allows applications to discover and pair bluetooth devices.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.BLUETOOTH_ADMIN"
+ android:description="@string/permdesc_bluetoothAdmin"
+ android:label="@string/permlab_bluetoothAdmin"
+ android:protectionLevel="normal" />
+
+ <!-- Allows applications to pair bluetooth devices without user interaction, and to
+ allow or disallow phonebook access or message access.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.BLUETOOTH_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- SystemApi Control access to email providers exclusively for Bluetooth
+ @hide
+ -->
+ <permission android:name="android.permission.BLUETOOTH_MAP"
+ android:protectionLevel="signature|role" />
+
+ <!-- Allows bluetooth stack to access files
+ @hide This should only be used by Bluetooth apk.
+ -->
+ <permission android:name="android.permission.BLUETOOTH_STACK"
+ android:protectionLevel="signature|role" />
+
+ <!-- Allows uhid write access for creating virtual input devices
+ @hide
+ -->
+ <permission android:name="android.permission.VIRTUAL_INPUT_DEVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows applications to perform I/O operations over NFC.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.NFC"
+ android:description="@string/permdesc_nfc"
+ android:label="@string/permlab_nfc"
+ android:protectionLevel="normal" />
+
+ <!-- Allows applications to receive NFC transaction events.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.NFC_TRANSACTION_EVENT"
+ android:protectionLevel="normal" />
+
+ <!-- Allows applications to receive NFC preferred payment service information.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.NFC_PREFERRED_PAYMENT_INFO"
+ android:description="@string/permdesc_preferredPaymentInfo"
+ android:label="@string/permlab_preferredPaymentInfo"
+ android:protectionLevel="normal" />
+
+ <!-- @SystemApi Allows access to set NFC controller always on states.
+ <p>Protection level: signature|privileged
+ @hide -->
+ <permission android:name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an internal user to use privileged SecureElement APIs.
+ Applications holding this permission can access OMAPI reset system API
+ and bypass OMAPI AccessControlEnforcer.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @deprecated This permission used to allow too broad access to sensitive methods and all its
+ uses have been replaced by a more appropriate permission. Most uses have been replaced with
+ a NETWORK_STACK or NETWORK_SETTINGS check. Please look up the documentation of the
+ individual functions to figure out what permission now protects the individual function.
+ @SystemApi Allows an internal user to use privileged ConnectivityManager APIs.
+ @hide -->
+ <permission android:name="android.permission.CONNECTIVITY_INTERNAL"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an internal user to use restricted Networks.
+ @hide -->
+ <permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"
+ android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
+
+ <!-- @SystemApi Allows an internal user to set signal strength in NetworkRequest. This kind of
+ request will wake up device when signal strength meets the given value.
+ @hide -->
+ <permission android:name="android.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows a system application to access hardware packet offload capabilities.
+ @hide -->
+ <permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi
+ @hide -->
+ <permission android:name="android.permission.RECEIVE_DATA_ACTIVITY_CHANGE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows access to the loop radio (Android@Home mesh network) device.
+ @hide -->
+ <permission android:name="android.permission.LOOP_RADIO"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows sending and receiving handover transfer status from Wifi and Bluetooth
+ @hide -->
+ <permission android:name="android.permission.NFC_HANDOVER_STATUS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide Allows internal management of Bluetooth state when on wireless consent mode.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows the device to be reset, clearing all data and enables Test Harness Mode. -->
+ <permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows access to ultra wideband device.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.UWB_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- ================================== -->
+ <!-- Permissions for accessing accounts -->
+ <!-- ================================== -->
+ <eat-comment />
+
+ <!-- Allows access to the list of accounts in the Accounts Service.
+
+ <p class="note"><strong>Note:</strong> Beginning with Android 6.0 (API level
+ 23), if an app shares the signature of the authenticator that manages an
+ account, it does not need <code>"GET_ACCOUNTS"</code> permission to read
+ information about that account. On Android 5.1 and lower, all apps need
+ <code>"GET_ACCOUNTS"</code> permission to read information about any
+ account.</p>
+
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.GET_ACCOUNTS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:protectionLevel="dangerous"
+ android:description="@string/permdesc_getAccounts"
+ android:label="@string/permlab_getAccounts" />
+ <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
+
+ <!-- Allows applications to call into AccountAuthenticators.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.ACCOUNT_MANAGER"
+ android:protectionLevel="signature" />
+
+ <!-- ================================== -->
+ <!-- Permissions for accessing hardware that may effect battery life-->
+ <!-- ================================== -->
+ <eat-comment />
+
+ <!-- Allows applications to enter Wi-Fi Multicast mode.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"
+ android:description="@string/permdesc_changeWifiMulticastState"
+ android:label="@string/permlab_changeWifiMulticastState"
+ android:protectionLevel="normal" />
+
+ <!-- Allows access to the vibrator.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.VIBRATE"
+ android:label="@string/permlab_vibrate"
+ android:description="@string/permdesc_vibrate"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows access to the vibrator always-on settings.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.VIBRATE_ALWAYS_ON"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows access to the vibrator state.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.ACCESS_VIBRATOR_STATE"
+ android:label="@string/permdesc_vibrator_state"
+ android:description="@string/permdesc_vibrator_state"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows using PowerManager WakeLocks to keep processor from sleeping or screen
+ from dimming.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.WAKE_LOCK"
+ android:label="@string/permlab_wakeLock"
+ android:description="@string/permdesc_wakeLock"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows using the device's IR transmitter, if available.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.TRANSMIT_IR"
+ android:label="@string/permlab_transmitIr"
+ android:description="@string/permdesc_transmitIr"
+ android:protectionLevel="normal" />
+
+ <!-- ==================================================== -->
+ <!-- Permissions related to changing audio settings -->
+ <!-- ==================================================== -->
+ <eat-comment />
+
+ <!-- Allows an application to modify global audio settings.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"
+ android:label="@string/permlab_modifyAudioSettings"
+ android:description="@string/permdesc_modifyAudioSettings"
+ android:protectionLevel="normal" />
+
+ <!-- ==================================================== -->
+ <!-- Permissions related to screen capture -->
+ <!-- ==================================================== -->
+ <eat-comment />
+
+ <!-- Allows an application to capture screen content to perform a screenshot using the intent
+ action {@link android.content.Intent#ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE}.
+ <p>Protection level: internal|role
+ <p>Intended for use by ROLE_NOTES only.
+ -->
+ <permission android:name="android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to be notified whenever a screen capture is attempted.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.DETECT_SCREEN_CAPTURE"
+ android:label="@string/permlab_detectScreenCapture"
+ android:description="@string/permdesc_detectScreenCapture"
+ android:protectionLevel="normal" />
+
+ <!-- ======================================== -->
+ <!-- Permissions for factory reset protection -->
+ <!-- ======================================== -->
+ <eat-comment />
+
+ <!-- @SystemApi Allows an application to set a factory reset protection (FRP) policy.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_FACTORY_RESET_PROTECTION"
+ android:protectionLevel="signature|privileged"/>
+
+ <!-- ======================================== -->
+ <!-- Permissions for lost mode -->
+ <!-- ======================================== -->
+ <eat-comment />
+
+ <!-- @SystemApi Allows an application to trigger lost mode on an organization-owned device.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.TRIGGER_LOST_MODE"
+ android:protectionLevel="signature|role"/>
+
+ <!-- ================================== -->
+ <!-- Permissions for accessing hardware -->
+ <!-- ================================== -->
+ <eat-comment />
+
+ <!-- @SystemApi Allows an application to manage preferences and permissions for USB devices
+ @hide -->
+ <permission android:name="android.permission.MANAGE_USB"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to manage Android Debug Bridge settings.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_DEBUGGING"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to access the MTP USB kernel driver.
+ For use only by the device side MTP implementation.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_MTP"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows access to hardware peripherals. Intended only for hardware testing.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.HARDWARE_TEST"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to manage DynamicSystem image -->
+ <permission android:name="android.permission.MANAGE_DYNAMIC_SYSTEM"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to install a DynamicSystem image and get status updates.
+ @hide -->
+ <permission android:name="android.permission.INSTALL_DYNAMIC_SYSTEM"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows access to Broadcast Radio
+ @hide This is not a third-party API (intended for system apps).-->
+ <permission android:name="android.permission.ACCESS_BROADCAST_RADIO"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @deprecated @SystemApi Allows access to FM
+ @hide This is not a third-party API (intended for system apps).-->
+ <permission android:name="android.permission.ACCESS_FM_RADIO"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows access to configure network interfaces, configure/use IPSec, etc.
+ @hide -->
+ <permission android:name="android.permission.NET_ADMIN"
+ android:protectionLevel="signature|role" />
+
+ <!-- Allows registration for remote audio playback. @hide -->
+ <permission android:name="android.permission.REMOTE_AUDIO_PLAYBACK"
+ android:protectionLevel="signature" />
+
+ <!-- Allows TvInputService to access underlying TV input hardware such as
+ built-in tuners and HDMI-in's.
+ <p>This should only be used by OEM's TvInputService's.
+ @hide @SystemApi -->
+ <permission android:name="android.permission.TV_INPUT_HARDWARE"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+ <!-- Allows to capture a frame of TV input hardware such as
+ built-in tuners and HDMI-in's.
+ <p>Not for use by third-party applications.
+ @hide @SystemApi -->
+ <permission android:name="android.permission.CAPTURE_TV_INPUT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide Allows TvInputService to access DVB device.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.DVB_DEVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows reading and enabling/disabling the OEM unlock allowed by carrier state
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_CARRIER_OEM_UNLOCK_STATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows reading and enabling/disabling the OEM unlock allowed by user state
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows reading the OEM unlock state
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.READ_OEM_UNLOCK_STATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide Allows enabling/disabling OEM unlock
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.OEM_UNLOCK_STATE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows querying state of PersistentDataBlock
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.ACCESS_PDB_STATE"
+ android:protectionLevel="signature|role" />
+
+ <!-- Allows testing if a passwords is forbidden by the admins.
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.TEST_BLACKLISTED_PASSWORD"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows system update service to notify device owner about pending updates.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- =========================================== -->
+ <!-- Permissions associated with camera and image capture -->
+ <!-- =========================================== -->
+ <eat-comment />
+
+ <!-- @SystemApi Allows disabling the transmit-indicator LED that is normally on when
+ a camera is in use by an application.
+ @hide -->
+ <permission android:name="android.permission.CAMERA_DISABLE_TRANSMIT_LED"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows sending the camera service notifications about system-wide events.
+ @hide -->
+ <permission android:name="android.permission.CAMERA_SEND_SYSTEM_EVENTS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows injecting the external camera to replace the internal camera.
+ @hide -->
+ <permission android:name="android.permission.CAMERA_INJECT_EXTERNAL_CAMERA"
+ android:protectionLevel="signature" />
+
+ <!-- =========================================== -->
+ <!-- Permissions associated with telephony state -->
+ <!-- =========================================== -->
+ <eat-comment />
+
+ <!-- @SystemApi Allows granting runtime permissions to telephony related components.
+ @hide -->
+ <permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS"
+ android:protectionLevel="signature" />
+
+ <!-- Allows modification of the telephony state - power on, mmi, etc.
+ Does not include placing calls.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MODIFY_PHONE_STATE"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Allows read only access to precise phone state.
+ Allows reading of detailed information about phone state for special-use applications
+ such as dialers, carrier applications, or ims applications. -->
+ <permission android:name="android.permission.READ_PRECISE_PHONE_STATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @TestApi Allows read access to privileged phone state.
+ @hide Used internally. -->
+ <permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Allows to read device identifiers and use ICC based authentication like EAP-AKA.
+ Often required in authentication to access the carrier's server and manage services
+ of the subscriber.
+ <p>Protection level: signature|appop -->
+ <permission android:name="android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER"
+ android:protectionLevel="signature|appop" />
+
+ <!-- @SystemApi Allows read access to emergency number information for ongoing calls or SMS
+ sessions.
+ @hide Used internally. -->
+ <permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"
+ android:protectionLevel="signature" />
+
+ <!-- Allows listen permission to always reported system signal strength.
+ @hide Used internally. -->
+ <permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Protects the ability to register any PhoneAccount with
+ PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION. This capability indicates that the PhoneAccount
+ corresponds to a device SIM.
+ @hide -->
+ <permission android:name="android.permission.REGISTER_SIM_SUBSCRIPTION"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Protects the ability to register any PhoneAccount with
+ PhoneAccount#CAPABILITY_CALL_PROVIDER.
+ @hide -->
+ <permission android:name="android.permission.REGISTER_CALL_PROVIDER"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Protects the ability to register any PhoneAccount with
+ PhoneAccount#CAPABILITY_CONNECTION_MANAGER
+ @hide -->
+ <permission android:name="android.permission.REGISTER_CONNECTION_MANAGER"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by a {@link android.telecom.InCallService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature|privileged
+ -->
+ <permission android:name="android.permission.BIND_INCALL_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows to query ongoing call details and manage ongoing calls
+ <p>Protection level: signature|appop -->
+ <permission android:name="android.permission.MANAGE_ONGOING_CALLS"
+ android:protectionLevel="signature|appop"
+ android:label="@string/permlab_manageOngoingCalls"
+ android:description="@string/permdesc_manageOngoingCalls" />
+
+ <!-- Allows the app to request network scans from telephony.
+ <p>Not for use by third-party applications.
+ @SystemApi @hide-->
+ <permission android:name="android.permission.NETWORK_SCAN"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by a link {@link android.telephony.VisualVoicemailService} to ensure that
+ only the system can bind to it.
+ <p>Protection level: signature|privileged
+ -->
+ <permission
+ android:name="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"
+ android:protectionLevel="signature|privileged"/>
+
+ <!-- Must be required by a {@link android.telecom.CallScreeningService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature|privileged
+ -->
+ <permission android:name="android.permission.BIND_SCREENING_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by a {@link android.telecom.PhoneAccountSuggestionService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link android.telecom.CallDiagnosticService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_CALL_DIAGNOSTIC_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link android.telecom.CallRedirectionService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature|privileged
+ -->
+ <permission android:name="android.permission.BIND_CALL_REDIRECTION_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by a {@link android.telecom.CallStreamingService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ @SystemApi @hide-->
+ <permission android:name="android.permission.BIND_CALL_STREAMING_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link android.telecom.ConnectionService},
+ to ensure that only the system can bind to it.
+ @deprecated {@link android.telecom.ConnectionService}s should require
+ android.permission.BIND_TELECOM_CONNECTION_SERVICE instead.
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.BIND_CONNECTION_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by a
+ android.service.wallpapereffectsgeneration.WallpaperEffectsGenerationService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_WALLPAPER_EFFECTS_GENERATION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link android.telecom.ConnectionService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature|privileged
+ -->
+ <permission android:name="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to control the in-call experience.
+ @hide -->
+ <permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Allows an application to receive STK related commands.
+ @hide -->
+ <permission android:name="android.permission.RECEIVE_STK_COMMANDS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to send EMBMS download intents to apps
+ @hide -->
+ <permission android:name="android.permission.SEND_EMBMS_INTENTS"
+ android:protectionLevel="signature|privileged" />
+
+
+ <!-- Allows internal management of the sensor framework
+ @hide -->
+ <permission android:name="android.permission.MANAGE_SENSORS"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by an ImsService to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature|privileged|vendorPrivileged
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_IMS_SERVICE"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+ <!-- Must be required by a telephony data service to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_TELEPHONY_DATA_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a NetworkService to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_TELEPHONY_NETWORK_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to manage embedded subscriptions (those on a eUICC)
+ through EuiccManager APIs.
+ <p>Protection level: signature|privileged|development
+ @hide
+ -->
+ <permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- @SystemApi Must be required by an EuiccService to ensure that only the system can bind to
+ it.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_EUICC_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Required for reading information about carrier apps from SystemConfigManager.
+ <p>Protection level: signature
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.READ_CARRIER_APP_INFO"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by an GbaService to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_GBA_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Required for an Application to access APIs related to RCS User Capability Exchange.
+ <p> This permission is only granted to system applications fulfilling the SMS, Dialer, and
+ Contacts app roles.
+ <p>Protection level: internal|role
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"
+ android:protectionLevel="internal|role" />
+
+ <!-- ================================== -->
+ <!-- Permissions for sdcard interaction -->
+ <!-- ================================== -->
+ <eat-comment />
+
+ <!-- @SystemApi @TestApi Allows an application to write to internal media storage
+ @deprecated This permission is no longer honored in the system and no longer adds
+ the media_rw gid as a supplementary gid to the holder. Use the
+ android.permission.MANAGE_EXTERNAL_STORAGE instead.
+ @hide -->
+ <permission android:name="android.permission.WRITE_MEDIA_STORAGE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to manage access to documents, usually as part
+ of a document picker.
+ <p>This permission should <em>only</em> be requested by the platform
+ document management app. This permission cannot be granted to
+ third-party apps.
+ -->
+ <permission android:name="android.permission.MANAGE_DOCUMENTS"
+ android:protectionLevel="signature|role" />
+
+ <!-- Allows an application to manage access to crates, usually as part
+ of a crates picker.
+ <p>This permission should <em>only</em> be requested by the platform
+ management app. This permission cannot be granted to
+ third-party apps.
+ @hide
+ @TestApi
+ -->
+ <permission android:name="android.permission.MANAGE_CRATES"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to cache content.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.CACHE_CONTENT"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi @hide
+ Allows an application to aggressively allocate disk space.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.ALLOCATE_AGGRESSIVE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide
+ Allows an application to use reserved disk space.
+ <p>Not for use by third-party applications. Should only be requested by
+ apps that provide core system functionality, to ensure system stability
+ when disk is otherwise completely full.
+ -->
+ <permission android:name="android.permission.USE_RESERVED_DISK"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- ================================== -->
+ <!-- Permissions for screenlock -->
+ <!-- ================================== -->
+ <eat-comment />
+
+ <!-- Allows applications to disable the keyguard if it is not secure.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.DISABLE_KEYGUARD"
+ android:description="@string/permdesc_disableKeyguard"
+ android:label="@string/permlab_disableKeyguard"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an application to request the screen lock complexity and prompt users to update the
+ screen lock to a certain complexity level.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.REQUEST_PASSWORD_COMPLEXITY"
+ android:label="@string/permlab_requestPasswordComplexity"
+ android:description="@string/permdesc_requestPasswordComplexity"
+ android:protectionLevel="normal" />
+
+ <!-- ================================== -->
+ <!-- Permissions to access other installed applications -->
+ <!-- ================================== -->
+ <eat-comment />
+
+ <!-- @deprecated No longer enforced. -->
+ <permission android:name="android.permission.GET_TASKS"
+ android:label="@string/permlab_getTasks"
+ android:description="@string/permdesc_getTasks"
+ android:protectionLevel="normal" />
+
+ <!-- New version of GET_TASKS that apps can request, since GET_TASKS doesn't really
+ give access to task information. We need this new one because there are
+ many existing apps that use add libraries and such that have validation
+ code to ensure the app has requested the GET_TASKS permission by seeing
+ if it has been granted the permission... if it hasn't, it kills the app
+ with a message about being upset. So we need to have it continue to look
+ like the app is getting that permission, even though it will never be
+ checked, and new privileged apps can now request this one for real access.
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.REAL_GET_TASKS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to start a task from a ActivityManager#RecentTaskInfo.
+ @hide -->
+ <permission android:name="android.permission.START_TASKS_FROM_RECENTS"
+ android:protectionLevel="signature|privileged|recents" />
+
+ <!-- @SystemApi @hide Allows an application to call APIs that allow it to do interactions
+ across the users on the device, using singleton services and
+ user-targeted broadcasts. This permission is not available to
+ third party applications. -->
+ <permission android:name="android.permission.INTERACT_ACROSS_USERS"
+ android:protectionLevel="signature|privileged|development|role" />
+
+ <!-- @SystemApi Fuller form of {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
+ that removes restrictions on where broadcasts can be sent and allows other
+ types of interactions
+ @hide -->
+ <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
+ android:protectionLevel="signature|installer|role" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
+ <!-- Allows interaction across profiles in the same profile group. -->
+ <permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
+ android:protectionLevel="signature|appop" />
+
+ <!-- @SystemApi Allows configuring apps to have the INTERACT_ACROSS_PROFILES permission so that
+ they can interact across profiles in the same profile group.
+ @hide -->
+ <permission android:name="android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
+ <permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi @hide Allows an application to call APIs that allow it to query and manage
+ users on the device. This permission is not available to
+ third party applications. -->
+ <permission android:name="android.permission.MANAGE_USERS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows an application to create, remove users and get the list of
+ users on the device. Applications holding this permission can create users (including
+ normal, restricted, guest, managed, and demo users) and can optionally endow them with the
+ ephemeral property. For creating users with other kinds of properties,
+ {@link android.Manifest.permission#MANAGE_USERS} is needed.
+ This permission is not available to third party applications. -->
+ <permission android:name="android.permission.CREATE_USERS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows an application to set user association
+ with a certain subscription. Used by Enterprise to associate a
+ subscription with a work or personal profile. -->
+ <permission android:name="android.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows an application to call APIs that allow it to query users on the
+ device. -->
+ <permission android:name="android.permission.QUERY_USERS"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Allows an application to access data blobs across users. -->
+ <permission android:name="android.permission.ACCESS_BLOBS_ACROSS_USERS"
+ android:protectionLevel="signature|privileged|development|role" />
+
+ <!-- @SystemApi @hide Allows an application to set the profile owners and the device owner.
+ This permission is not available to third party applications.-->
+ <permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"
+ android:protectionLevel="signature|role"
+ android:label="@string/permlab_manageProfileAndDeviceOwners"
+ android:description="@string/permdesc_manageProfileAndDeviceOwners" />
+
+ <!-- @SystemApi @hide Allows an application to query device policies set by any admin on
+ the device.-->
+ <permission android:name="android.permission.QUERY_ADMIN_POLICY"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi @hide Allows an application to exempt apps from platform restrictions.-->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage device policy relating to time.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.-->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_TIME"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set the grant state of runtime permissions on packages.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage the identity of the managing organization.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set support messages for when a user action is affected by an
+ active policy.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage backup service policy.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_BACKUP_SERVICE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage lock task policy.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCK_TASK"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy regarding modifying applications.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage installing from unknown sources policy.
+ <p>MANAGE_SECURITY_CRITICAL_DEVICE_POLICY_ACROSS_USERS is required to call APIs protected
+ by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage application restrictions.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage calling policy.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_CALLS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage debugging features policy.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy preventing users from modifying users.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage safe boot policy.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to restricting a user's ability to use or
+ enable and disable the microphone.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MICROPHONE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to restricting a user's ability to use or
+ enable and disable the camera.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_CAMERA"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy related to keyguard.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_KEYGUARD"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to account management.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to hiding and suspending packages.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_PACKAGE_STATE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to force set a new device unlock password or a managed profile
+ challenge on current user.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to the status bar.-->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_STATUS_BAR"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to bluetooth.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_BLUETOOTH"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to fun.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_FUN"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to airplane mode.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_AIRPLANE_MODE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to mobile networks.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MOBILE_NETWORK"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to physical media.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to sms.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SMS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to usb file transfers.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to lock credentials.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to Wifi.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_WIFI"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to screen capture.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SCREEN_CAPTURE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to input methods.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_INPUT_METHODS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to restricting the user from configuring
+ private DNS.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to the default sms application.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_DEFAULT_SMS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to profiles.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_PROFILES"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to interacting with profiles (e.g. Disallowing
+ cross-profile copy and paste).
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTION"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to VPNs.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_VPN"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to audio output.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to the display.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_DISPLAY"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to location.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCATION"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to factory reset.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to the wallpaper.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_WALLPAPER"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to the usage of the contents of the screen.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SCREEN_CONTENT"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to system dialogs.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to users running in the background.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to printing.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_PRINTING"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to nearby communications (e.g. Beam and
+ nearby streaming).
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to windows.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_WINDOWS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to locale.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCALE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to autofill.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUTOFILL"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to users.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_USERS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to certificates.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_CERTIFICATES"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to override APNs.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_OVERRIDE_APN"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to security logging.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SECURITY_LOGGING"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to system updates.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to query system updates.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to private DNS.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_PRIVATE_DNS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to settings.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SETTINGS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to network logging.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_NETWORK_LOGGING"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to usb data signalling.-->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to suspending personal apps.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SUSPEND_PERSONAL_APPS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to keeping uninstalled packages.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
+ required to call APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_KEEP_UNINSTALLED_PACKAGES"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy related to accessibility.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACCESSIBILITY"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy related to common criteria mode.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy related to metered data.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_METERED_DATA"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set a network-independent global HTTP proxy.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_PROXY"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to request bugreports with user consent.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_BUGREPORT"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy related to application user data.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_USER_DATA"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to lock a profile or the device with the appropriate cross-user
+ permission.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCK"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy related to system apps.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_SYSTEM_APPS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy related to wiping data.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is required to call
+ APIs protected by this permission on users different to the calling user.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_WIPE_DATA"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy related to the Memory Tagging Extension (MTE).
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MTE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy related to device identifiers. -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_DEVICE_IDENTIFIERS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set device policies outside the current user
+ that are critical for securing data within the current user.
+ <p>Holding this permission allows the use of other held MANAGE_DEVICE_POLICY_*
+ permissions across all users on the device provided they are required for securing data
+ within the current user.-->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set device policies outside the current user
+ that are required for securing device ownership without accessing user data.
+ <p>Holding this permission allows the use of other held MANAGE_DEVICE_POLICY_*
+ permissions across all users on the device provided they do not grant access to user
+ data. -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set device policies outside the current user.
+ <p>Fuller form of {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS}
+ that removes the restriction on accessing user data.
+ <p>Holding this permission allows the use of any other held MANAGE_DEVICE_POLICY_*
+ permissions across all users on the device.-->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL"
+ android:protectionLevel="internal|role" />
+
+ <!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
+ <permission android:name="android.permission.PROVISION_DEMO_DEVICE"
+ android:protectionLevel="signature|setup|knownSigner"
+ android:knownCerts="@array/demo_device_provisioning_known_signers" />
+
+ <!-- @TestApi @hide Allows an application to reset the record of previous system update freeze
+ periods. -->
+ <permission android:name="android.permission.CLEAR_FREEZE_PERIOD"
+ android:protectionLevel="signature" />
+
+ <!-- @TestApi @hide Allows an application to force available DevicePolicyManager logs to
+ DPC. -->
+ <permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to write to the security log buffer in logd.
+ @hide -->
+ <permission android:name="android.permission.WRITE_SECURITY_LOG"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to get full detailed information about
+ recently running tasks, with full fidelity to the real state.
+ @hide -->
+ <permission android:name="android.permission.GET_DETAILED_TASKS"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to change the Z-order of tasks.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.REORDER_TASKS"
+ android:label="@string/permlab_reorderTasks"
+ android:description="@string/permdesc_reorderTasks"
+ android:protectionLevel="normal" />
+
+ <!-- @SystemApi @TestApi @hide Allows an application to change to remove/kill tasks -->
+ <permission android:name="android.permission.REMOVE_TASKS"
+ android:protectionLevel="signature|recents|role" />
+
+ <!-- @deprecated Use MANAGE_ACTIVITY_TASKS instead.
+ @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks -->
+ <permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @TestApi @hide Allows an application to create/manage/remove tasks -->
+ <permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"
+ android:protectionLevel="signature|recents" />
+
+ <!-- @SystemApi @TestApi @hide Allows an application to embed other activities -->
+ <permission android:name="android.permission.ACTIVITY_EMBEDDING"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to start any activity, regardless of permission
+ protection or exported state.
+ @hide -->
+ <permission android:name="android.permission.START_ANY_ACTIVITY"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows an application to start activities from background -->
+ <permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"
+ android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role" />
+
+ <!-- Allows an application to start foreground services from the background at any time.
+ <em>This permission is not for use by third-party applications</em>,
+ with the only exception being if the app is the default SMS app.
+ Otherwise, it's only usable by privileged apps, app verifier app, and apps with
+ any of the EMERGENCY or SYSTEM GALLERY roles.
+ -->
+ <permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"
+ android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role"/>
+
+ <!-- Allows an application to request interactive options when sending a broadcast.
+ @hide -->
+ <permission android:name="android.permission.BROADCAST_OPTION_INTERACTIVE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Must be required by activities that handle the intent action
+ {@link Intent#ACTION_SEND_SHOW_SUSPENDED_APP_DETAILS}. This is for use by apps that
+ hold {@link Manifest.permission#SUSPEND_APPS} to interact with the system.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS" />
+
+ <!-- Allows an application to start an activity as another app, provided that app has been
+ granted a permissionToken from the ActivityManagerService.
+ @hide -->
+ <permission android:name="android.permission.START_ACTIVITY_AS_CALLER"
+ android:protectionLevel="signature" />
+
+ <!-- @deprecated The {@link android.app.ActivityManager#restartPackage}
+ API is no longer supported. -->
+ <permission android:name="android.permission.RESTART_PACKAGES"
+ android:label="@string/permlab_killBackgroundProcesses"
+ android:description="@string/permdesc_killBackgroundProcesses"
+ android:protectionLevel="normal" />
+
+ <!-- @deprecated Allows an application to call
+ {@link android.app.ActivityManager#killBackgroundProcesses}.
+ <p>As of Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ the {@link android.app.ActivityManager#killBackgroundProcesses} is no longer available to
+ third party applications. For backwards compatibility, the background processes of the
+ caller's own package will still be killed when calling this API, meanwhile this permission
+ is not required anymore in this case.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"
+ android:label="@string/permlab_killBackgroundProcesses"
+ android:description="@string/permdesc_killBackgroundProcesses"
+ android:protectionLevel="normal" />
+
+ <!-- @SystemApi @hide Allows an application to call
+ {@link android.app.ActivityManager#killBackgroundProcesses}
+ to kill background processes of other apps.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.KILL_ALL_BACKGROUND_PROCESSES"
+ android:label="@string/permlab_killBackgroundProcesses"
+ android:description="@string/permdesc_killBackgroundProcesses"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows an application to query process states and current
+ OOM adjustment scores.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- Allows use of PendingIntent.getIntent(), .
+ @hide @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
+ -->
+ <permission android:name="android.permission.GET_INTENT_SENDER_INTENT"
+ android:protectionLevel="signature" />
+
+ <!-- ================================== -->
+ <!-- Permissions affecting the display of other applications -->
+ <!-- ================================== -->
+ <eat-comment />
+
+ <!-- Allows an app to create windows using the type
+ {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY},
+ shown on top of all other apps. Very few apps
+ should use this permission; these windows are intended for
+ system-level interaction with the user.
+
+ <p class="note"><strong>Note:</strong> If the app
+ targets API level 23 or higher, the app user must explicitly grant
+ this permission to the app through a permission management screen. The app requests
+ the user's approval by sending an intent with action
+ {@link android.provider.Settings#ACTION_MANAGE_OVERLAY_PERMISSION}.
+ The app can check whether it has this authorization by calling
+ {@link android.provider.Settings#canDrawOverlays
+ Settings.canDrawOverlays()}.
+ <p>Protection level: signature|setup|appop|installer|pre23|development -->
+ <permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
+ android:label="@string/permlab_systemAlertWindow"
+ android:description="@string/permdesc_systemAlertWindow"
+ android:protectionLevel="signature|setup|appop|installer|pre23|development" />
+
+ <!-- @SystemApi @hide Allows an application to create windows using the type
+ {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY},
+ shown on top of all other apps.
+
+ Allows an application to use
+ {@link android.view.WindowManager.LayoutsParams#setSystemApplicationOverlay(boolean)}
+ to create overlays that will stay visible, even if another window is requesting overlays to
+ be hidden through {@link android.view.Window#setHideOverlayWindows(boolean)}.
+
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY"
+ android:protectionLevel="signature|recents|role|installer"/>
+
+ <!-- @deprecated Use {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND}
+ @hide
+ -->
+ <permission android:name="android.permission.RUN_IN_BACKGROUND"
+ android:label="@string/permlab_runInBackground"
+ android:description="@string/permdesc_runInBackground"
+ android:protectionLevel="signature" />
+
+ <!-- @deprecated Use
+ {@link android.Manifest.permission#REQUEST_COMPANION_USE_DATA_IN_BACKGROUND}
+ @hide
+ -->
+ <permission android:name="android.permission.USE_DATA_IN_BACKGROUND"
+ android:label="@string/permlab_useDataInBackground"
+ android:description="@string/permdesc_useDataInBackground"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to set display offsets for the screen.
+ This permission is not available to third party applications. -->
+ <permission android:name="android.permission.SET_DISPLAY_OFFSET"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows a companion app to run in the background. This permission implies
+ {@link android.Manifest.permission#REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND},
+ and allows to start a foreground service from the background.
+ If an app does not have to run in the background, but only needs to start a foreground
+ service from the background, consider using
+ {@link android.Manifest.permission#REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND},
+ which is less powerful.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND"
+ android:label="@string/permlab_runInBackground"
+ android:description="@string/permdesc_runInBackground"
+ android:protectionLevel="normal" />
+
+ <!-- Allows a companion app to start a foreground service from the background.
+ {@see android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND}
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND"
+ android:protectionLevel="normal"/>
+
+ <!-- Allows a companion app to use data in the background.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND"
+ android:label="@string/permlab_useDataInBackground"
+ android:description="@string/permdesc_useDataInBackground"
+ android:protectionLevel="normal" />
+
+ <!-- Allows app to request to be associated with a device via
+ {@link android.companion.CompanionDeviceManager}
+ as a "watch"
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH"
+ android:protectionLevel="normal" />
+
+ <!-- Allows app to request to be associated with a device via
+ {@link android.companion.CompanionDeviceManager}
+ as "glasses"
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_GLASSES"
+ android:protectionLevel="normal" />
+
+ <!-- Allows application to request to be associated with a virtual display capable of streaming
+ Android applications
+ ({@link android.companion.AssociationRequest#DEVICE_PROFILE_APP_STREAMING})
+ by {@link android.companion.CompanionDeviceManager}.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows application to request to be associated with a virtual device associated to a
+ nearby device capable of rendering an entire OS
+ ({@link android.companion.AssociationRequest#DEVICE_PROFILE_NEARBY_DEVICE_STREAMING})
+ by {@link android.companion.CompanionDeviceManager}.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows application to request to be associated with a vehicle head unit capable of
+ automotive projection
+ ({@link android.companion.AssociationRequest#DEVICE_PROFILE_AUTOMOTIVE_PROJECTION})
+ by {@link android.companion.CompanionDeviceManager}.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows application to request to be associated with a computer to share functionality
+ and/or data with other devices, such as notifications, photos and media
+ ({@link android.companion.AssociationRequest#DEVICE_PROFILE_COMPUTER})
+ by {@link android.companion.CompanionDeviceManager}.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_COMPUTER"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to create a "self-managed" association.
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows a companion app to associate to Wi-Fi.
+ <p>Only for use by a single pre-approved app.
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an app to read and listen to projection state.
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.READ_PROJECTION_STATE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an app to set and release automotive projection.
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an app to prevent non-system-overlay windows from being drawn on top of it -->
+ <permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"
+ android:protectionLevel="normal" />
+
+ <!-- ================================== -->
+ <!-- Permissions affecting the system wallpaper -->
+ <!-- ================================== -->
+ <eat-comment />
+
+ <!-- Allows applications to set the wallpaper.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.SET_WALLPAPER"
+ android:label="@string/permlab_setWallpaper"
+ android:description="@string/permdesc_setWallpaper"
+ android:protectionLevel="normal" />
+
+ <!-- Allows applications to set the wallpaper hints.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.SET_WALLPAPER_HINTS"
+ android:label="@string/permlab_setWallpaperHints"
+ android:description="@string/permdesc_setWallpaperHints"
+ android:protectionLevel="normal" />
+
+ <!-- Allow the app to read the system wallpaper image without
+ holding the READ_EXTERNAL_STORAGE permission.
+ <p>Not for use by third-party applications.
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.READ_WALLPAPER_INTERNAL"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allow apps to always update wallpaper by sending data.
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.ALWAYS_UPDATE_WALLPAPER"
+ android:protectionLevel="internal|role" />
+
+ <!-- ===================================================== -->
+ <!-- Permissions for changing the system clock / time zone -->
+ <!-- ===================================================== -->
+ <eat-comment />
+
+ <!-- Allows applications to set the system time directly.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SET_TIME"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Allows applications to set the system time zone directly.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.SET_TIME_ZONE"
+ android:label="@string/permlab_setTimeZone"
+ android:description="@string/permdesc_setTimeZone"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Allows telephony to suggest the time / time zone.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows applications like settings to suggest the user's manually chosen time / time zone.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows system clock time suggestions from an external clock / time source to be made.
+ The nature of "external" could be highly form-factor specific. Example, times
+ obtained via the VHAL for Android Auto OS.
+ <p>Not for use by third-party applications.
+ @SystemApi @hide
+ -->
+ <permission android:name="android.permission.SUGGEST_EXTERNAL_TIME"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows applications like settings to manage configuration associated with automatic time
+ and time zone detection.
+ <p>Not for use by third-party applications.
+ @SystemApi @hide
+ -->
+ <permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- ==================================================== -->
+ <!-- Permissions related to changing status bar -->
+ <!-- ==================================================== -->
+ <eat-comment />
+
+ <!-- Allows an application to expand or collapse the status bar.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.EXPAND_STATUS_BAR"
+ android:label="@string/permlab_expandStatusBar"
+ android:description="@string/permdesc_expandStatusBar"
+ android:protectionLevel="normal" />
+
+ <!-- ============================================================== -->
+ <!-- Permissions related to adding/removing shortcuts from Launcher -->
+ <!-- ============================================================== -->
+ <eat-comment />
+
+ <!-- Allows an application to install a shortcut in Launcher.
+ <p>In Android O (API level 26) and higher, the <code>INSTALL_SHORTCUT</code> broadcast no
+ longer has any effect on your app because it's a private, implicit
+ broadcast. Instead, you should create an app shortcut by using the
+ {@link android.content.pm.ShortcutManager#requestPinShortcut requestPinShortcut()}
+ method from the {@link android.content.pm.ShortcutManager} class.
+ <p>Protection level: normal
+ -->
+ <permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
+ android:label="@string/permlab_install_shortcut"
+ android:description="@string/permdesc_install_shortcut"
+ android:protectionLevel="normal"/>
+
+ <!-- <p class="caution"><strong>Don't use this permission in your app.</strong><br>This
+ permission is no longer supported.
+ -->
+ <permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"
+ android:label="@string/permlab_uninstall_shortcut"
+ android:description="@string/permdesc_uninstall_shortcut"
+ android:protectionLevel="normal"/>
+
+ <!-- ==================================================== -->
+ <!-- Permissions related to accessing sync settings -->
+ <!-- ==================================================== -->
+ <eat-comment />
+
+ <!-- Allows applications to read the sync settings.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.READ_SYNC_SETTINGS"
+ android:description="@string/permdesc_readSyncSettings"
+ android:label="@string/permlab_readSyncSettings"
+ android:protectionLevel="normal" />
+
+ <!-- Allows applications to write the sync settings.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.WRITE_SYNC_SETTINGS"
+ android:description="@string/permdesc_writeSyncSettings"
+ android:label="@string/permlab_writeSyncSettings"
+ android:protectionLevel="normal" />
+
+ <!-- Allows applications to read the sync stats.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.READ_SYNC_STATS"
+ android:description="@string/permdesc_readSyncStats"
+ android:label="@string/permlab_readSyncStats"
+ android:protectionLevel="normal" />
+
+ <!-- ============================================ -->
+ <!-- Permissions for low-level system interaction -->
+ <!-- ============================================ -->
+ <eat-comment />
+
+ <!-- @SystemApi @hide Change the screen compatibility mode of applications -->
+ <permission android:name="android.permission.SET_SCREEN_COMPATIBILITY"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to modify the current configuration, such
+ as locale.
+ <p>Protection level: signature|privileged|development -->
+ <permission android:name="android.permission.CHANGE_CONFIGURATION"
+ android:protectionLevel="signature|privileged|development|role" />
+
+ <!-- Allows an application to read or write the system settings.
+
+ <p class="note"><strong>Note:</strong> If the app targets API level 23
+ or higher, the app user
+ must explicitly grant this permission to the app through a permission management screen.
+ The app requests the user's approval by sending an intent with action
+ {@link android.provider.Settings#ACTION_MANAGE_WRITE_SETTINGS}. The app
+ can check whether it has this authorization by calling {@link
+ android.provider.Settings.System#canWrite Settings.System.canWrite()}.
+
+ <p>Protection level: signature|preinstalled|appop|pre23
+ -->
+ <permission android:name="android.permission.WRITE_SETTINGS"
+ android:label="@string/permlab_writeSettings"
+ android:description="@string/permdesc_writeSettings"
+ android:protectionLevel="signature|preinstalled|appop|pre23|role" />
+
+ <!-- Allows an application to modify the Google service map.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WRITE_GSERVICES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @TestApi @hide Allows an application to modify config settings.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WRITE_DEVICE_CONFIG"
+ android:protectionLevel="signature|verifier|configurator"/>
+
+ <!-- @SystemApi @hide Allows an application to read config settings.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.READ_DEVICE_CONFIG"
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- @SystemApi @TestApi @hide Allows an application to modify only allowlisted settings.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG"
+ android:protectionLevel="signature|verifier|configurator"/>
+
+ <!-- @SystemApi @TestApi @hide Allows an application to read/write sync disabled mode config.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG"
+ android:protectionLevel="signature|verifier|configurator"/>
+
+ <!-- @SystemApi @hide Allows applications like settings to read system-owned
+ application-specific locale configs.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES"
+ android:protectionLevel="signature|installer" />
+
+ <!-- @hide Allows applications to set an application-specific {@link LocaleConfig}.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SET_APP_SPECIFIC_LOCALECONFIG"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to monitor {@link android.provider.Settings.Config} access.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"
+ android:protectionLevel="signature"/>
+
+ <!-- @SystemApi @TestApi Allows an application to call
+ {@link android.app.ActivityManager#forceStopPackage}.
+ @hide -->
+ <permission android:name="android.permission.FORCE_STOP_PACKAGES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows an application to retrieve the content of the active window
+ An active window is the window that has fired an accessibility event. -->
+ <permission android:name="android.permission.RETRIEVE_WINDOW_CONTENT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Modify the global animation scaling factor.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SET_ANIMATION_SCALE"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- @deprecated This functionality will be removed in the future; please do
+ not use. Allow an application to make its activities persistent. -->
+ <permission android:name="android.permission.PERSISTENT_ACTIVITY"
+ android:label="@string/permlab_persistentActivity"
+ android:description="@string/permdesc_persistentActivity"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an application to find out the space used by any package.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.GET_PACKAGE_SIZE"
+ android:label="@string/permlab_getPackageSize"
+ android:description="@string/permdesc_getPackageSize"
+ android:protectionLevel="normal" />
+
+ <!-- @deprecated No longer useful, see
+ {@link android.content.pm.PackageManager#addPackageToPreferred}
+ for details. -->
+ <permission android:name="android.permission.SET_PREFERRED_APPLICATIONS"
+ android:protectionLevel="signature|installer|verifier" />
+
+ <!-- Allows an application to receive the
+ {@link android.content.Intent#ACTION_BOOT_COMPLETED} that is
+ broadcast after the system finishes booting. If you don't
+ request this permission, you will not receive the broadcast at
+ that time. Though holding this permission does not have any
+ security implications, it can have a negative impact on the
+ user experience by increasing the amount of time it takes the
+ system to start and allowing applications to have themselves
+ running without the user being aware of them. As such, you must
+ explicitly declare your use of this facility to make that visible
+ to the user.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"
+ android:label="@string/permlab_receiveBootCompleted"
+ android:description="@string/permdesc_receiveBootCompleted"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an application to broadcast sticky intents. These are
+ broadcasts whose data is held by the system after being finished,
+ so that clients can quickly retrieve that data without having
+ to wait for the next broadcast.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.BROADCAST_STICKY"
+ android:label="@string/permlab_broadcastSticky"
+ android:description="@string/permdesc_broadcastSticky"
+ android:protectionLevel="normal" />
+
+ <!-- Allows mounting and unmounting file systems for removable storage.
+ <p>Not for use by third-party applications.-->
+ <permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows formatting file systems for removable storage.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide -->
+ <permission android:name="android.permission.STORAGE_INTERNAL"
+ android:protectionLevel="signature" />
+
+ <!-- Allows access to ASEC non-destructive API calls
+ @hide -->
+ <permission android:name="android.permission.ASEC_ACCESS"
+ android:protectionLevel="signature" />
+
+ <!-- Allows creation of ASEC volumes
+ @hide -->
+ <permission android:name="android.permission.ASEC_CREATE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows destruction of ASEC volumes
+ @hide -->
+ <permission android:name="android.permission.ASEC_DESTROY"
+ android:protectionLevel="signature" />
+
+ <!-- Allows mount / unmount of ASEC volumes
+ @hide -->
+ <permission android:name="android.permission.ASEC_MOUNT_UNMOUNT"
+ android:protectionLevel="signature" />
+
+ <!-- Allows rename of ASEC volumes
+ @hide -->
+ <permission android:name="android.permission.ASEC_RENAME"
+ android:protectionLevel="signature" />
+
+ <!-- Allows applications to write the apn settings and read sensitive fields of
+ an existing apn settings like user and password.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WRITE_APN_SETTINGS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows applications to change network connectivity state.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.CHANGE_NETWORK_STATE"
+ android:description="@string/permdesc_changeNetworkState"
+ android:label="@string/permlab_changeNetworkState"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an application to clear the caches of all installed
+ applications on the device.
+ <p>Protection level: signature|privileged
+ -->
+ <permission android:name="android.permission.CLEAR_APP_CACHE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to use any media decoder when decoding for playback
+ @hide -->
+ <permission android:name="android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to install and/or uninstall CA certificates on
+ behalf of the user.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_CA_CERTIFICATES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to do certain operations needed for
+ interacting with the recovery (system update) system.
+ @hide -->
+ <permission android:name="android.permission.RECOVERY"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to do certain operations needed for
+ resume on reboot feature.
+ @hide -->
+ <permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to read system update info.
+ @hide -->
+ <permission android:name="android.permission.READ_SYSTEM_UPDATE_INFO"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows the system to bind to an application's task services
+ @hide -->
+ <permission android:name="android.permission.BIND_JOB_SERVICE"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.BIND_JOB_SERVICE"/>
+
+ <!-- Allows an application to initiate configuration updates
+ <p>An application requesting this permission is responsible for
+ verifying the source and integrity of any update before passing
+ it off to the various individual installer components
+ @hide -->
+ <permission android:name="android.permission.UPDATE_CONFIG"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to query the current time zone rules state
+ on device.
+ @SystemApi @hide -->
+ <permission android:name="android.permission.QUERY_TIME_ZONE_RULES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows a time zone rule updater application to request
+ the system installs / uninstalls timezone rules.
+ <p>An application requesting this permission is responsible for
+ verifying the source and integrity of the update before passing
+ it off to the installer components.
+ @SystemApi @hide -->
+ <permission android:name="android.permission.UPDATE_TIME_ZONE_RULES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows the system to reset throttling in shortcut manager.
+ @hide -->
+ <permission android:name="android.permission.RESET_SHORTCUT_MANAGER_THROTTLING"
+ android:protectionLevel="signature" />
+
+ <!-- Allows the system to bind to the discovered Network Recommendation Service.
+ @SystemApi @hide -->
+ <permission android:name="android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"/>
+
+ <!-- Allows an application to enable, disable and change priority of
+ runtime resource overlays.
+ @hide -->
+ <permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to set, update and remove the credential management app.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP"
+ android:protectionLevel="signature" />
+
+ <!-- Allows a font updater application to request that the system installs/uninstalls/updates
+ font files. @SystemApi @hide -->
+ <permission android:name="android.permission.UPDATE_FONTS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to use the AttestationVerificationService.
+ @hide -->
+ <permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to export a AttestationVerificationService to verify attestations on
+ behalf of AttestationVerificationManager for system-defined attestation profiles.
+ @hide -->
+ <permission android:name="android.permission.VERIFY_ATTESTATION"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by any AttestationVerificationService to ensure that only the system can
+ bind to it.
+ @hide -->
+ <permission android:name="android.permission.BIND_ATTESTATION_VERIFICATION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows the caller to generate keymint keys with the INCLUDE_UNIQUE_ID tag, which
+ uniquely identifies the device via the attestation certificate.
+ @hide @TestApi -->
+ <permission android:name="android.permission.REQUEST_UNIQUE_ID_ATTESTATION"
+ android:protectionLevel="signature" />
+
+ <!-- ========================================= -->
+ <!-- Permissions for special development tools -->
+ <!-- ========================================= -->
+ <eat-comment />
+
+ <!-- Allows an application to read or write the secure system settings.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WRITE_SECURE_SETTINGS"
+ android:protectionLevel="signature|privileged|development|role|installer" />
+
+ <!-- Allows an application to retrieve state dump information from system services.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.DUMP"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- Allows an application to start tracing for InputMethod and WindowManager.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.CONTROL_UI_TRACING"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- Allows an application to read the low-level system log files.
+ <p>Not for use by third-party applications, because
+ Log entries can contain the user's private information. -->
+ <permission android:name="android.permission.READ_LOGS"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- Configure an application for debugging.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SET_DEBUG_APP"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- Allows an application to set the maximum number of (not needed)
+ application processes that can be running.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SET_PROCESS_LIMIT"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- Allows an application to control whether activities are immediately
+ finished when put in the background.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SET_ALWAYS_FINISH"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- Allow an application to request that a signal be sent to all persistent processes.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- @hide @SystemApi Must be required by a
+ {@link com.android.service.tracing.TraceReportService}, to ensure that only the system
+ can bind to it.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.BIND_TRACE_REPORT_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @hide @SystemApi @TestApi
+ Allow an application to approve incident and bug reports to be
+ shared off-device. There can be only one application installed on the
+ device with this permission, and since this is a privileged permission, it
+ must be in priv-app.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.APPROVE_INCIDENT_REPORTS"
+ android:protectionLevel="signature|incidentReportApprover" />
+
+ <!-- @hide Allow an application to approve an incident or bug report approval from
+ the system. -->
+ <permission android:name="android.permission.REQUEST_INCIDENT_REPORT_APPROVAL"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- ==================================== -->
+ <!-- Private permissions -->
+ <!-- ==================================== -->
+ <eat-comment />
+
+ <!-- Allows access to the list of accounts in the Accounts Service.
+ <p>Protection level: signature|privileged -->
+ <permission android:name="android.permission.GET_ACCOUNTS_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows but does not guarantee access to user passwords at the conclusion of add account
+ @hide -->
+ <permission android:name="android.permission.GET_PASSWORD"
+ android:protectionLevel="signature" />
+
+ <!-- Allows applications to RW to diagnostic resources.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.DIAGNOSTIC"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to open, close, or disable the status bar
+ and its icons.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.STATUS_BAR"
+ android:protectionLevel="signature|privileged|recents" />
+
+ <!-- Allows an application to trigger bugreport via shell using the bugreport API.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.TRIGGER_SHELL_BUGREPORT"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to trigger profcollect report upload via shell.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.TRIGGER_SHELL_PROFCOLLECT_UPLOAD"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to be the status bar. Currently used only by SystemUI.apk
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.STATUS_BAR_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to bind to third party quick settings tiles.
+ <p>Should only be requested by the System, should be required by
+ TileService declarations.-->
+ <permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE"
+ android:protectionLevel="signature|recents" />
+
+ <!-- Allows SystemUI to request third party controls.
+ <p>Should only be requested by the System and required by
+ {@link android.service.controls.ControlsProviderService} declarations.
+ -->
+ <permission android:name="android.permission.BIND_CONTROLS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to force a BACK operation on whatever is the
+ top activity.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.FORCE_BACK"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to update device statistics.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.UPDATE_DEVICE_STATS"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi @hide Allows an application to collect application operation statistics.
+ Not for use by third party apps. -->
+ <permission android:name="android.permission.GET_APP_OPS_STATS"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- @SystemApi @hide Allows an application to collect historical application operation
+ statistics.
+ <p>Not for use by third party applications.
+ -->
+ <permission android:name="android.permission.GET_HISTORICAL_APP_OPS_STATS"
+ android:protectionLevel="internal|role" />
+
+ <!-- @SystemApi Allows an application to update application operation statistics. Not for
+ use by third party apps.
+ @hide -->
+ <permission android:name="android.permission.UPDATE_APP_OPS_STATS"
+ android:protectionLevel="signature|privileged|installer|role" />
+
+ <!-- @SystemApi Allows an application to update the user app op restrictions.
+ Not for use by third party apps.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_APP_OPS_RESTRICTIONS"
+ android:protectionLevel="signature|installer" />
+
+ <!-- Allows an application to update the user app op modes.
+ Not for use by third party apps.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_APP_OPS_MODES"
+ android:protectionLevel="signature|installer|verifier|role" />
+
+ <!-- @SystemApi Allows an application to open windows that are for use by parts
+ of the system user interface.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
+ android:protectionLevel="signature|module" />
+
+ <!-- Allows an application to avoid all toast rate limiting restrictions.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.UNLIMITED_TOASTS"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.UNLIMITED_TOASTS" />
+
+ <!-- @SystemApi Allows an application to use
+ {@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
+ to hide non-system-overlay windows.
+ <p>Not for use by third-party applications.
+ @deprecated Use {@link android.Manifest.permission#HIDE_OVERLAY_WINDOWS} instead
+ @hide
+ -->
+ <permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- @SystemApi Allows an application to manage (create, destroy,
+ Z-order) application tokens in the window manager.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_APP_TOKENS"
+ android:protectionLevel="signature" />
+
+ <!-- Allows System UI to register listeners for events from Window Manager.
+ @hide -->
+ <permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows the application to temporarily freeze the screen for a
+ full-screen transition. -->
+ <permission android:name="android.permission.FREEZE_SCREEN"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to inject user events (keys, touch, trackball)
+ into the event stream and deliver them to ANY window. Without this
+ permission, you can only deliver events to windows in your own process.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.INJECT_EVENTS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to register an input filter which filters the stream
+ of user events (keys, touch, trackball) before they are dispatched to any window. -->
+ <permission android:name="android.permission.FILTER_EVENTS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to retrieve the window token from the accessibility manager. -->
+ <permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to modify accessibility information from another app. -->
+ <permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to perform accessibility operations (e.g. send events) on
+ behalf of another package. -->
+ <permission android:name="android.permission.ACT_AS_PACKAGE_FOR_ACCESSIBILITY"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to change the accessibility volume. -->
+ <permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to collect frame statistics -->
+ <permission android:name="android.permission.FRAME_STATS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to temporary enable accessibility on the device. -->
+ <permission android:name="android.permission.TEMPORARY_ENABLE_ACCESSIBILITY"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to launch detail settings activity of a particular
+ accessibility service.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.OPEN_ACCESSIBILITY_DETAILS_SETTINGS"
+ android:protectionLevel="signature|installer" />
+
+ <!-- @SystemApi Allows an application to watch and control how activities are
+ started globally in the system. Only for is in debugging
+ (usually the monkey command).
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.SET_ACTIVITY_WATCHER"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to call the activity manager shutdown() API
+ to put the higher-level system there into a shutdown state.
+ @hide -->
+ <permission android:name="android.permission.SHUTDOWN"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi Allows an application to tell the activity manager to temporarily
+ stop application switches, putting it into a special mode that
+ prevents applications from immediately switching away from some
+ critical UI such as the home screen.
+ @hide -->
+ <permission android:name="android.permission.STOP_APP_SWITCHES"
+ android:protectionLevel="signature|privileged|recents" />
+
+ <!-- @SystemApi Allows an application to retrieve private information about
+ the current top activity, such as any assist context it can provide.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"
+ android:protectionLevel="signature|recents" />
+
+ <!-- @SystemApi Allows an application to set the system audio caption and its UI
+ enabled state.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION"
+ android:protectionLevel="signature|role" />
+
+ <!-- Allows an application to retrieve the current state of keys and
+ switches.
+ <p>Not for use by third-party applications.
+ @deprecated The API that used this permission has been removed. -->
+ <permission android:name="android.permission.READ_INPUT_STATE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by an {@link android.inputmethodservice.InputMethodService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_INPUT_METHOD"
+ android:protectionLevel="signature" />
+
+ <!-- Allows access to Test APIs defined in {@link android.view.inputmethod.InputMethodManager}.
+ @hide -->
+ <permission android:name="android.permission.TEST_INPUT_METHOD"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by an {@link android.media.midi.MidiDeviceService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_MIDI_DEVICE_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by an {@link android.accessibilityservice.AccessibilityService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link android.printservice.PrintService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_PRINT_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link android.printservice.recommendation.RecommendationService},
+ to ensure that only the system can bind to it.
+ @hide
+ @SystemApi
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_PRINT_RECOMMENDATION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows applications to get the installed and enabled print services.
+ @hide
+ @SystemApi
+ <p>Protection level: signature|preinstalled
+ -->
+ <permission android:name="android.permission.READ_PRINT_SERVICES"
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- Allows applications to get the currently recommended print services for printers.
+ @hide
+ @SystemApi
+ <p>Protection level: signature|preinstalled
+ -->
+ <permission android:name="android.permission.READ_PRINT_SERVICE_RECOMMENDATIONS"
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- Must be required by a {@link android.nfc.cardemulation.HostApduService}
+ or {@link android.nfc.cardemulation.OffHostApduService} to ensure that only
+ the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_NFC_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link android.service.quickaccesswallet.QuickAccessWalletService}
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by the PrintSpooler to ensure that only the system can bind to it.
+ @hide -->
+ <permission android:name="android.permission.BIND_PRINT_SPOOLER_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by the CompanionDeviceManager to ensure that only the system can bind to it.
+ @hide -->
+ <permission android:name="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by any
+ {@link android.companion.CompanionDeviceService}s
+ to ensure that only the system can bind to it. -->
+ <permission android:name="android.permission.BIND_COMPANION_DEVICE_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Must be required by the RuntimePermissionPresenterService to ensure
+ that only the system can bind to it.
+ @hide -->
+ <permission android:name="android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a TextService (e.g. SpellCheckerService)
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_TEXT_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Must be required by a AttentionService
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_ATTENTION_SERVICE"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.BIND_ATTENTION_SERVICE" />
+
+ <!-- @SystemApi Must be required by a RotationResolverService
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_ROTATION_RESOLVER_SERVICE"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.BIND_ROTATION_RESOLVER_SERVICE" />
+
+ <!-- @SystemApi Allows an application to access ambient context service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"
+ android:protectionLevel="signature|privileged|role"/>
+
+ <!-- @SystemApi Required by a AmbientContextEventDetectionService
+ to ensure that only the service with this permission can bind to it.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"
+ android:protectionLevel="signature"/>
+
+ <!-- @SystemApi Required by a WearableSensingService to
+ ensure that only the caller with this permission can bind to it.
+ <p> Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_WEARABLE_SENSING_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an app to manage the wearable sensing service.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by a {@link android.net.VpnService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_VPN_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link android.service.wallpaper.WallpaperService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature|privileged
+ -->
+ <permission android:name="android.permission.BIND_WALLPAPER"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by a game service to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_GAME_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link android.service.voice.VoiceInteractionService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_VOICE_INTERACTION"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Must be required by a {@link android.service.voice.HotwordDetectionService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Must be required by a {@link android.service.voice.visualQueryDetection},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE"
+ android:protectionLevel="signature" />
+
+
+ <!-- @SystemApi Allows an application to manage hotword detection and visual query detection
+ on the device.
+ <p>Protection level: internal|preinstalled
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.MANAGE_HOTWORD_DETECTION"
+ android:protectionLevel="internal|preinstalled" />
+
+ <!-- Allows an application to subscribe to keyguard locked (i.e., showing) state.
+ <p>Protection level: signature|role
+ <p>Intended for use by ROLE_ASSISTANT and signature apps only.
+ -->
+ <permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE"
+ android:protectionLevel="signature|role"/>
+
+ <!-- Must be required by a {@link android.service.credentials.CredentialProviderService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_CREDENTIAL_PROVIDER_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link android.service.autofill.AutofillService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_AUTOFILL_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a
+ {@link android.service.assist.classification.FieldClassificationService},
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_FIELD_CLASSIFICATION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Alternative version of android.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE.
+ This permission was renamed during the O previews but it was supported on the final O
+ release, so we need to carry it over.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_AUTOFILL"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by an {@link android.service.autofill.AutofillFieldClassificationService}
+ to ensure that only the system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by an {@link android.service.autofill.InlineSuggestionRenderService}
+ to ensure that only the system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_INLINE_SUGGESTION_RENDER_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a android.service.textclassifier.TextClassifierService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_TEXTCLASSIFIER_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a
+ {@link android.service.remotelockscreenvalidation.RemoteLockscreenValidationService}
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a android.service.selectiontoolbar.SelectionToolbarRenderService,
+ to ensure that only the system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_SELECTION_TOOLBAR_RENDER_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a android.service.contentcapture.ContentCaptureService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a android.service.translation.TranslationService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_TRANSLATION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows apps to use ui translation functions.
+ <p>Protection level: signature|privileged
+ @hide Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.MANAGE_UI_TRANSLATION"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Must be required by a android.service.contentsuggestions.ContentSuggestionsService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be declared by a android.service.musicrecognition.MusicRecognitionService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_MUSIC_RECOGNITION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a android.service.autofill.augmented.AugmentedAutofillService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link android.service.voice.VoiceInteractionService} implementation
+ to enroll its own sound models. This is a more restrictive permission than the higher-level
+ permission KEYPHRASE_ENROLLMENT_APPLICATION. For the caller to enroll sound models with
+ this permission, it must hold the permission and be the active VoiceInteractionService in
+ the system.
+ {@see Settings.Secure.VOICE_INTERACTION_SERVICE}
+ @hide -->
+ <permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by a keyphrase enrollment application, to enroll sound models. This is
+ treated as a higher-level permission to MANAGE_VOICE_KEYPHRASES as a caller can enroll
+ sound models at any time. This permission should be reserved for system enrollment
+ applications detected by {@link android.hardware.soundtrigger.KeyphraseEnrollmentInfo}
+ only.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.KEYPHRASE_ENROLLMENT_APPLICATION"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider},
+ to ensure that only the system can bind to it.
+ @hide -->
+ <permission android:name="android.permission.BIND_REMOTE_DISPLAY"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link android.media.tv.TvInputService}
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature|privileged
+ -->
+ <permission android:name="android.permission.BIND_TV_INPUT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by a {@link android.media.tv.interactive.TvInteractiveAppService}
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature|privileged
+ -->
+ <permission android:name="android.permission.BIND_TV_INTERACTIVE_APP"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi
+ Must be required by a {@link com.android.media.tv.remoteprovider.TvRemoteProvider}
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature|privileged
+ <p>Not for use by third-party applications. </p>
+ @hide -->
+ <permission android:name="android.permission.BIND_TV_REMOTE_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows TV input apps and TV apps to use TIS extension interfaces for
+ domain-specific features.
+ <p>Protection level: signature|privileged|vendorPrivileged
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.TIS_EXTENSION_INTERFACE"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+ <!-- @SystemApi
+ Must be required for a virtual remote controller for TV.
+ <p>Protection level: signature|privileged
+ <p>Not for use by third-party applications. </p>
+ @hide -->
+ <permission android:name="android.permission.TV_VIRTUAL_REMOTE_CONTROLLER"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to change HDMI CEC active source.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to modify parental controls
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MODIFY_PARENTAL_CONTROLS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to read TvContentRatingSystemInfo
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.READ_CONTENT_RATING_SYSTEMS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to notify TV inputs by sending broadcasts.
+ <p>Protection level: signature|privileged
+ <p>Not for use by third-party applications.
+ @hide @SystemApi -->
+ <permission android:name="android.permission.NOTIFY_TV_INPUTS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- This permission is required among systems services when accessing
+ tuner resource management related APIs or information.
+ <p>Protection level: signature|privileged|vendorPrivileged
+ <p>This should only be used by the OEM TvInputService.
+ @hide -->
+ <permission android:name="android.permission.TUNER_RESOURCE_ACCESS"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+ <!-- @SystemApi This permission is required by Media Resource Manager Service when
+ system services create MediaCodecs on behalf of other processes and apps.
+ <p>Protection level: signature
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+ <uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" />
+
+ <!-- This permission is required by Media Resource Observer Service when
+ accessing its registerObserver Api.
+ <p>Protection level: signature|privileged
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by a {@link android.media.routing.MediaRouteService}
+ to ensure that only the system can interact with it.
+ @hide -->
+ <permission android:name="android.permission.BIND_ROUTE_PROVIDER"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by device administration receiver, to ensure that only the
+ system can interact with it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_DEVICE_ADMIN"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Required to add or remove another application as a device admin.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_DEVICE_ADMINS"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an app to reset the device password.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.RESET_PASSWORD"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an app to lock the device.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.LOCK_DEVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows low-level access to setting the orientation (actually
+ rotation) of the screen.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.SET_ORIENTATION"
+ android:protectionLevel="signature|recents" />
+
+ <!-- @SystemApi Allows low-level access to setting the pointer speed.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.SET_POINTER_SPEED"
+ android:protectionLevel="signature" />
+
+ <!-- Allows low-level access to setting input device calibration.
+ <p>Not for use by normal applications.
+ @hide -->
+ <permission android:name="android.permission.SET_INPUT_CALIBRATION"
+ android:protectionLevel="signature" />
+
+ <!-- Allows low-level access to setting the keyboard layout.
+ <p>Not for use by third-party applications.
+ @hide
+ @TestApi -->
+ <permission android:name="android.permission.SET_KEYBOARD_LAYOUT"
+ android:protectionLevel="signature" />
+
+ <!-- Allows low-level access for re-mapping modifier keys.
+ <p>Not for use by third-party applications.
+ @hide
+ @TestApi -->
+ <permission android:name="android.permission.REMAP_MODIFIER_KEYS"
+ android:protectionLevel="signature" />
+
+ <!-- Allows low-level access for monitoring keyboard backlight changes.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an app to schedule a prioritized alarm that can be used to perform
+ background work even when the device is in doze.
+ <p>Not for use by third-party applications.
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.SCHEDULE_PRIORITIZED_ALARM"
+ android:protectionLevel="signature|privileged"/>
+
+ <!-- Allows applications to use exact alarm APIs.
+ <p>Exact alarms should only be used for user-facing features.
+ For more details, see <a
+ href="{@docRoot}about/versions/12/behavior-changes-12#exact-alarm-permission">
+ Exact alarm permission</a>.
+ <p>Apps who hold this permission and target API level 31 or above, always stay in the
+ {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_WORKING_SET WORKING_SET} or
+ lower standby bucket.
+ Applications targeting API level 30 or below do not need this permission to use
+ exact alarm APIs.
+ -->
+ <permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+ android:protectionLevel="signature|privileged|appop"/>
+
+ <!-- Allows apps to use exact alarms just like with SCHEDULE_EXACT_ALARM but without needing
+ to request this permission from the user.
+ <p><b>This is only for apps that rely on exact alarms for their core functionality.</b>
+ App stores may enforce policies to audit and review the use of this permission. Any app that
+ requests this but is found to not require exact alarms for its primary function may be
+ removed from the app store.
+ -->
+ <permission android:name="android.permission.USE_EXACT_ALARM"
+ android:protectionLevel="normal"/>
+
+ <!-- Allows an application to query tablet mode state and monitor changes
+ in it.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.TABLET_MODE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to request installing packages. Apps
+ targeting APIs greater than 25 must hold this permission in
+ order to use {@link android.content.Intent#ACTION_INSTALL_PACKAGE}.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"
+ android:label="@string/permlab_requestInstallPackages"
+ android:description="@string/permdesc_requestInstallPackages"
+ android:protectionLevel="signature|appop" />
+
+ <!-- Allows an application to request deleting packages. Apps
+ targeting APIs {@link android.os.Build.VERSION_CODES#P} or greater must hold this
+ permission in order to use {@link android.content.Intent#ACTION_UNINSTALL_PACKAGE} or
+ {@link android.content.pm.PackageInstaller#uninstall}.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.REQUEST_DELETE_PACKAGES"
+ android:label="@string/permlab_requestDeletePackages"
+ android:description="@string/permdesc_requestDeletePackages"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an application to install packages.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.INSTALL_PACKAGES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to install self updates. This is a limited version
+ of {@link android.Manifest.permission#INSTALL_PACKAGES}.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.INSTALL_SELF_UPDATES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to install updates. This is a limited version
+ of {@link android.Manifest.permission#INSTALL_PACKAGES}.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.INSTALL_PACKAGE_UPDATES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to install existing system packages. This is a limited
+ version of {@link android.Manifest.permission#INSTALL_PACKAGES}.
+ <p>Not for use by third-party applications.
+ TODO(b/80204953): remove this permission once we have a long-term solution.
+ @hide
+ -->
+ <permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Allows an application to use the package installer v2 APIs.
+ <p>The package installer v2 APIs are still a work in progress and we're
+ currently validating they work in all scenarios.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="com.android.permission.USE_INSTALLER_V2"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @TestApi Allows a testOnly application to get installed.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.INSTALL_TEST_ONLY_PACKAGE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to install DPCs only, an application is
+ considered a DPC if it has a {@link android.app.admin.DeviceAdminReceiver}
+ protected by {@link android.Manifest.permission#BIND_DEVICE_ADMIN).
+ This is a limited version of
+ {@link android.Manifest.permission#INSTALL_PACKAGES}.
+ @hide
+ -->
+ <permission android:name="android.permission.INSTALL_DPC_PACKAGES"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an application to read resolved paths to the APKs (Base and any splits)
+ of a session based install.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.READ_INSTALLED_SESSION_PATHS"
+ android:protectionLevel="signature|installer" />
+ <uses-permission android:name="android.permission.READ_INSTALLED_SESSION_PATHS" />
+
+ <!-- Allows an application to use System Data Loaders.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="com.android.permission.USE_SYSTEM_DATA_LOADERS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @TestApi Allows an application to clear user data.
+ <p>Not for use by third-party applications
+ @hide
+ -->
+ <permission android:name="android.permission.CLEAR_APP_USER_DATA"
+ android:protectionLevel="signature|installer" />
+
+ <!-- @hide Allows an application to get the URI permissions
+ granted to another application.
+ <p>Not for use by third-party applications
+ -->
+ <permission android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to clear the URI permissions
+ granted to another application.
+ <p>Not for use by third-party applications
+ -->
+ <permission
+ android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide
+ Allows an application to change the status of Scoped Access Directory requests granted or
+ rejected by the user.
+ <p>This permission should <em>only</em> be requested by the platform
+ settings app. This permission cannot be granted to third-party apps.
+ <p>Protection level: signature
+ -->
+ <permission
+ android:name="android.permission.MANAGE_SCOPED_ACCESS_DIRECTORY_PERMISSIONS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide
+ Allows an application to change the status of a persistable URI permission granted
+ to another application.
+ <p>This permission should <em>only</em> be requested by the platform
+ settings app. This permission cannot be granted to third-party apps.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.FORCE_PERSISTABLE_URI_PERMISSIONS"
+ android:protectionLevel="signature" />
+
+ <!-- Old permission for deleting an app's cache files, no longer used,
+ but signals for us to quietly ignore calls instead of throwing an exception.
+ <p>Protection level: signature|privileged -->
+ <permission android:name="android.permission.DELETE_CACHE_FILES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to delete cache files.
+ @hide -->
+ <permission android:name="android.permission.INTERNAL_DELETE_CACHE_FILES"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to delete packages.
+ <p>Not for use by third-party applications.
+ <p>Starting in {@link android.os.Build.VERSION_CODES#N}, user confirmation is requested
+ when the application deleting the package is not the same application that installed the
+ package. -->
+ <permission android:name="android.permission.DELETE_PACKAGES"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi Allows an application to move location of installed package.
+ @hide -->
+ <permission android:name="android.permission.MOVE_PACKAGE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @TestApi Allows an application to keep uninstalled packages as apks.
+ @hide -->
+ <permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to change whether an application component (other than its own) is
+ enabled or not.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi Allows an application to grant specific permissions.
+ @hide -->
+ <permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS"
+ android:protectionLevel="signature|installer|verifier" />
+
+ <!-- @SystemApi Allows an application to launch the settings page which manages various
+ permissions.
+ @hide -->
+ <permission android:name="android.permission.LAUNCH_PERMISSION_SETTINGS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an app that has this permission and the permissions to install packages
+ to request certain runtime permissions to be granted at installation.
+ @hide -->
+ <permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"
+ android:protectionLevel="signature|installer|verifier" />
+
+ <!-- @SystemApi Allows an application to revoke specific permissions.
+ @hide -->
+ <permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS"
+ android:protectionLevel="signature|installer|verifier" />
+
+ <!-- @TestApi Allows an application to revoke the POST_NOTIFICATIONS permission from an app
+ without killing the app. Only granted to the shell.
+ @hide -->
+ <permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows the system to read runtime permission state.
+ @hide -->
+ <permission android:name="android.permission.GET_RUNTIME_PERMISSIONS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows the system to restore runtime permission state. This might grant
+ permissions, hence this is a more scoped, less powerful variant of GRANT_RUNTIME_PERMISSIONS.
+ Among other restrictions this cannot override user choices.
+ @hide -->
+ <permission android:name="android.permission.RESTORE_RUNTIME_PERMISSIONS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to change policy_fixed permissions.
+ @hide -->
+ <permission android:name="android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"
+ android:protectionLevel="signature|installer" />
+
+ <!-- @SystemApi @TestApi Allows an application to upgrade runtime permissions.
+ @hide -->
+ <permission android:name="android.permission.UPGRADE_RUNTIME_PERMISSIONS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to allowlist restricted permissions
+ on any of the whitelists.
+ @hide -->
+ <permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS"
+ android:protectionLevel="signature|installer" />
+
+ <!-- @SystemApi Allows an application to an exempt an app from having its permission be
+ auto-revoked when unused for an extended period of time.
+ @hide -->
+ <permission android:name="android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS"
+ android:protectionLevel="signature|installer" />
+
+ <!-- @hide Allows an application to observe permission changes. -->
+ <permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to start and stop one time permission sessions
+ @hide -->
+ <permission android:name="android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS"
+ android:protectionLevel="signature|installer" />
+
+ <!-- @SystemApi Allows an application to manage the holders of a role.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
+ android:protectionLevel="signature|installer" />
+
+ <!-- @SystemApi Allows an application to manage the holders of roles associated with default
+ applications.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_DEFAULT_APPLICATIONS"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an application to bypass role qualification. This allows switching role
+ holders to otherwise non eligible holders. Only the shell is allowed to do this, the
+ qualification for the shell role itself cannot be bypassed, and each role needs to
+ explicitly allow bypassing qualification in its definition. The bypass state will not be
+ persisted across reboot.
+ @hide -->
+ <permission android:name="android.permission.BYPASS_ROLE_QUALIFICATION"
+ android:protectionLevel="internal|role" />
+
+ <!-- @SystemApi Allows an application to observe role holder changes.
+ @hide -->
+ <permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"
+ android:protectionLevel="signature|installer" />
+
+ <!-- Allows an application to manage the companion devices.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_COMPANION_DEVICES"
+ android:protectionLevel="signature|role|module" />
+
+ <!-- Allows an application to subscribe to notifications about the presence status change
+ of their associated companion device
+ -->
+ <permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an application to deliver companion messages to system
+ -->
+ <permission android:name="android.permission.DELIVER_COMPANION_MESSAGES"
+ android:protectionLevel="normal" />
+
+ <!-- @SystemApi Allows an application to send and receive messages via CDM transports.
+ @hide -->
+ <permission android:name="android.permission.USE_COMPANION_TRANSPORTS"
+ android:protectionLevel="signature|module" />
+
+ <!-- Allows an application to create new companion device associations.
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.ASSOCIATE_COMPANION_DEVICES"
+ android:protectionLevel="internal|role" />
+
+ <!-- @SystemApi Allows an application to use SurfaceFlinger's low level features.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.ACCESS_SURFACE_FLINGER"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to rotate a surface by arbitrary degree.
+ This is a sub-feature of ACCESS_SURFACE_FLINGER and can be granted in a more concrete way.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.ROTATE_SURFACE_FLINGER"
+ android:protectionLevel="signature|recents" />
+
+ <!-- @SystemApi Allows an application to provide hints to SurfaceFlinger that can influence
+ its wakes up time to compose the next frame. This is a subset of the capabilities granted
+ by {@link #ACCESS_SURFACE_FLINGER}.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.WAKEUP_SURFACE_FLINGER"
+ android:protectionLevel="signature|recents" />
+
+ <!-- Allows an application to take screen shots and more generally
+ get access to the frame buffer data.
+ <p>Not for use by third-party applications.
+ @hide
+ @removed -->
+ <permission android:name="android.permission.READ_FRAME_BUFFER"
+ android:protectionLevel="signature|recents" />
+
+ <!-- Allows an application to use InputFlinger's low level features.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_INPUT_FLINGER"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to disable/enable input devices.
+ Could be used to prevent unwanted touch events
+ on a touchscreen, for example during swimming or rain.
+ @hide -->
+ <permission android:name="android.permission.DISABLE_INPUT_DEVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to configure and connect to Wifi displays
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY"
+ android:protectionLevel="signature|knownSigner"
+ android:knownCerts="@array/wifi_known_signers" />
+
+ <!-- Allows an application to control low-level features of Wifi displays
+ such as opening an RTSP socket. This permission should only be used
+ by the display manager.
+ @hide -->
+ <permission android:name="android.permission.CONTROL_WIFI_DISPLAY"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to control the color modes set for displays system-wide.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to control the lights on the device.
+ @hide
+ @SystemApi
+ @TestApi -->
+ <permission android:name="android.permission.CONTROL_DEVICE_LIGHTS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to control the color saturation of the display.
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.CONTROL_DISPLAY_SATURATION"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to control display color transformations.
+ <p>Not for use by third-party applications.</p>
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to collect usage infomation about brightness slider changes.
+ <p>Not for use by third-party applications.</p>
+ @hide
+ @SystemApi
+ @TestApi -->
+ <permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- Allows an application to collect ambient light stats.
+ <p>Not for use by third party applications.</p>
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.ACCESS_AMBIENT_LIGHT_STATS"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- Allows an application to modify the display brightness configuration
+ @hide
+ @SystemApi
+ @TestApi -->
+ <permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- Allows an application to control the system's display brightness
+ @hide -->
+ <permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to override the display mode requests
+ so the app requested mode will be selected and user settings and display
+ policies will be ignored.
+ @hide
+ @TestApi -->
+ <permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to modify the refresh rate switching type. This
+ matches Setting.Secure.MATCH_CONTENT_FRAME_RATE.
+ @hide
+ @TestApi -->
+ <permission android:name="android.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to modify the user preferred display mode.
+ @hide
+ @TestApi -->
+ <permission android:name="android.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to modify the HDR conversion mode.
+ @hide
+ @TestApi -->
+ <permission android:name="android.permission.MODIFY_HDR_CONVERSION_MODE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to control VPN.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.CONTROL_VPN"
+ android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.CONTROL_VPN" />
+
+ <!-- Allows an application to access and modify always-on VPN configuration.
+ <p>Not for use by third-party or privileged applications.
+ @hide -->
+ <permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to capture the audio from tuner input devices types,
+ such as FM_TUNER.
+
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.CAPTURE_TUNER_AUDIO_INPUT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to capture audio output.
+ Use the {@code CAPTURE_MEDIA_OUTPUT} permission if only the {@code USAGE_UNKNOWN}),
+ {@code USAGE_MEDIA}) or {@code USAGE_GAME}) usages are intended to be captured.
+ <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi Allows an application to capture the audio played by other apps
+ that have set an allow capture policy of
+ {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM}.
+
+ Without this permission, only audio with an allow capture policy of
+ {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_ALL} can be used.
+
+ There are strong restriction listed at
+ {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM}
+ on what an app can do with the captured audio.
+
+ See {@code CAPTURE_AUDIO_OUTPUT} for capturing audio use cases other than media playback.
+
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.CAPTURE_MEDIA_OUTPUT"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi Allows an application to capture the audio played by other apps
+ with the {@code USAGE_VOICE_COMMUNICATION} usage.
+
+ The application may opt out of capturing by setting an allow capture policy of
+ {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_NONE}.
+
+ There are strong restriction listed at
+ {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM}
+ on what an app can do with the captured audio.
+
+ See {@code CAPTURE_AUDIO_OUTPUT} and {@code CAPTURE_MEDIA_OUTPUT} for capturing
+ audio use cases other than voice communication playback.
+
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi Allows an application to capture audio for hotword detection.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.CAPTURE_AUDIO_HOTWORD"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi Allows an application to access the ultrasound content.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.ACCESS_ULTRASOUND"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Puts an application in the chain of trust for sound trigger
+ operations. Being in the chain of trust allows an application to
+ delegate an identity of a separate entity to the sound trigger system
+ and vouch for the authenticity of this identity.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.SOUNDTRIGGER_DELEGATE_IDENTITY"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to modify audio routing and override policy decisions.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.MODIFY_AUDIO_ROUTING"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi Allows an application to access the uplink and downlink audio of an ongoing
+ call.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.CALL_AUDIO_INTERCEPTION"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!--@SystemApi Allows an application to modify system audio settings that shouldn't be
+ controllable by external apps, such as volume settings or volume behaviors for audio
+ devices, regardless of their connection status.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @TestApi Allows an application to query audio related state.
+ @hide -->
+ <permission android:name="android.permission.QUERY_AUDIO_STATE"
+ android:protectionLevel="signature|role" />
+
+ <!-- Allows an application to modify what effects are applied to all audio
+ (matching certain criteria) from any application.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to disable system sound effects when the user exits one of
+ the application's activities.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.DISABLE_SYSTEM_SOUND_EFFECTS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to provide remote displays.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.REMOTE_DISPLAY_PROVIDER"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to capture video output.
+ <p>Not for use by third-party applications.</p>
+ @hide
+ @removed -->
+ <permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to capture secure video output.
+ <p>Not for use by third-party applications.</p>
+ @hide
+ @removed -->
+ <permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to know what content is playing and control its playback.
+ <p>Not for use by third-party applications due to privacy of media consumption</p> -->
+ <permission android:name="android.permission.MEDIA_CONTENT_CONTROL"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows an application to set the volume key long-press listener.
+ <p>When it's set, the application will receive the volume key long-press event
+ instead of changing volume.</p>
+ <p>Not for use by third-party applications</p> -->
+ <permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- @SystemApi @hide Allows an application to set media key event listener.
+ <p>When it's set, the application will receive the media key event before
+ any other media sessions. If the event is handled by the listener, other sessions
+ cannot get the event.</p>
+ <p>Not for use by third-party applications</p> -->
+ <permission android:name="android.permission.SET_MEDIA_KEY_LISTENER"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- @SystemApi Required to be able to disable the device (very dangerous!).
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.BRICK"
+ android:protectionLevel="signature" />
+
+ <!-- Required to be able to reboot the device.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.REBOOT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows low-level access to power management.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.DEVICE_POWER"
+ android:protectionLevel="signature|role" />
+
+ <!-- Allows toggling battery saver on the system.
+ Superseded by DEVICE_POWER permission. @hide @SystemApi
+ -->
+ <permission android:name="android.permission.POWER_SAVER"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows providing the system with battery predictions.
+ Superseded by DEVICE_POWER permission. @hide @SystemApi
+ -->
+ <permission android:name="android.permission.BATTERY_PREDICTION"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows access to the PowerManager.userActivity function.
+ <p>Not for use by third-party applications. @hide @SystemApi -->
+ <permission android:name="android.permission.USER_ACTIVITY"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide @SystemApi Allows an application to manage Low Power Standby settings.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_LOW_POWER_STANDBY"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide @SystemApi Allows an application to request ports to remain open during
+ Low Power Standby.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SET_LOW_POWER_STANDBY_PORTS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide Allows low-level access to tun tap driver -->
+ <permission android:name="android.permission.NET_TUNNELING"
+ android:protectionLevel="signature|role" />
+
+ <!-- Run as a manufacturer test application, running as the root user.
+ Only available when the device is running in manufacturer test mode.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.FACTORY_TEST"
+ android:protectionLevel="signature" />
+
+ <!-- @hide @TestApi @SystemApi Allows an application to broadcast the intent {@link
+ android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS}.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"
+ android:protectionLevel="signature|privileged|recents" />
+ <uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" />
+
+ <!-- Allows an application to broadcast a notification that an application
+ package has been removed.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.BROADCAST_PACKAGE_REMOVED"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to broadcast an SMS receipt notification.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.BROADCAST_SMS"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to broadcast a WAP PUSH receipt notification.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.BROADCAST_WAP_PUSH"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to broadcast privileged networking requests.
+ <p>Not for use by third-party applications.
+ @hide
+ @deprecated Use {@link android.Manifest.permission#REQUEST_NETWORK_SCORES} instead
+ -->
+ <permission android:name="android.permission.BROADCAST_NETWORK_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Not for use by third-party applications. -->
+ <permission android:name="android.permission.MASTER_CLEAR"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Allows an application to call any phone number, including emergency
+ numbers, without going through the Dialer user interface for the user
+ to confirm the call being placed.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CALL_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to perform CDMA OTA provisioning @hide -->
+ <permission android:name="android.permission.PERFORM_CDMA_PROVISIONING"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi Allows an application to perform SIM Activation @hide -->
+ <permission android:name="android.permission.PERFORM_SIM_ACTIVATION"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows enabling/disabling location update notifications from
+ the radio.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CONTROL_LOCATION_UPDATES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows read/write access to the "properties" table in the checkin
+ database, to change values that get uploaded.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to collect component usage
+ statistics
+ <p>Declaring the permission implies intention to use the API and the user of the
+ device can grant permission through the Settings application.
+ <p>Protection level: signature|privileged|development|appop|retailDemo -->
+ <permission android:name="android.permission.PACKAGE_USAGE_STATS"
+ android:protectionLevel="signature|privileged|development|appop|retailDemo" />
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+
+ <!-- Allows an application to query broadcast response stats (see
+ {@link android.app.usage.BroadcastResponseStats}).
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.ACCESS_BROADCAST_RESPONSE_STATS"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- Allows a data loader to read a package's access logs. The access logs contain the
+ set of pages referenced over time.
+ <p>Declaring the permission implies intention to use the API and the user of the
+ device can grant permission through the Settings application.
+ <p>Protection level: signature|privileged|appop
+ <p>A data loader has to be the one which provides data to install an app.
+ <p>A data loader has to have both permission:LOADER_USAGE_STATS AND
+ appop:LOADER_USAGE_STATS allowed to be able to access the read logs. -->
+ <permission android:name="android.permission.LOADER_USAGE_STATS"
+ android:protectionLevel="signature|privileged|appop" />
+ <uses-permission android:name="android.permission.LOADER_USAGE_STATS" />
+
+ <!-- @hide @SystemApi Allows an application to observe usage time of apps. The app can register
+ for callbacks when apps reach a certain usage time limit, etc. -->
+ <permission android:name="android.permission.OBSERVE_APP_USAGE"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @hide @TestApi @SystemApi Allows an application to change the app idle state of an app.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide @TestApi @SystemApi Allows an application to change the estimated launch time
+ of an app.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide @SystemApi Allows an application to temporarily allowlist an inactive app to
+ access the network and acquire wakelocks.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Permission an application must hold in order to use
+ {@link android.provider.Settings#ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS}.
+ <p>Protection level: normal -->
+ <permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"
+ android:label="@string/permlab_requestIgnoreBatteryOptimizations"
+ android:description="@string/permdesc_requestIgnoreBatteryOptimizations"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an application to collect battery statistics
+ <p>Protection level: signature|privileged|development -->
+ <permission android:name="android.permission.BATTERY_STATS"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!--Allows an application to manage statscompanion.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.STATSCOMPANION"
+ android:protectionLevel="signature" />
+
+ <!--@SystemApi @hide Allows an application to register stats pull atom callbacks.
+ <p>Not for use by third-party applications.-->
+ <permission android:name="android.permission.REGISTER_STATS_PULL_ATOM"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows an application to read restricted stats from statsd.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.READ_RESTRICTED_STATS"
+ android:protectionLevel="internal|privileged" />
+
+ <!-- @SystemApi Allows an application to control the backup and restore process.
+ <p>Not for use by third-party applications.
+ @hide pending API council -->
+ <permission android:name="android.permission.BACKUP"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to make modifications to device settings such that these
+ modifications will be overridden by settings restore..
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE"
+ android:protectionLevel="signature|setup" />
+
+ <!-- @SystemApi Allows application to manage
+ {@link android.security.keystore.recovery.RecoveryController}.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.RECOVER_KEYSTORE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows application to verify lockscreen credentials provided by a remote device.
+ @hide -->
+ <permission android:name="android.permission.CHECK_REMOTE_LOCKSCREEN"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows a package to launch the secure full-backup confirmation UI.
+ ONLY the system process may hold this permission.
+ @hide -->
+ <permission android:name="android.permission.CONFIRM_FULL_BACKUP"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link android.widget.RemoteViewsService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature|privileged -->
+ <permission android:name="android.permission.BIND_REMOTEVIEWS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to tell the AppWidget service which application
+ can access AppWidget's data. The normal user flow is that a user
+ picks an AppWidget to go into a particular host, thereby giving that
+ host application access to the private data from the AppWidget app.
+ An application that has this permission should honor that contract.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.BIND_APPWIDGET"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide Allows sysui to manage user grants of slice permissions. -->
+ <permission android:name="android.permission.MANAGE_SLICE_PERMISSIONS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Private permission, to restrict who can bring up a dialog to add a new
+ keyguard widget
+ @hide -->
+ <permission android:name="android.permission.BIND_KEYGUARD_APPWIDGET"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Internal permission allowing an application to query/set which
+ applications can bind AppWidgets.
+ @hide -->
+ <permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows applications to change the background data setting.
+ <p>Not for use by third-party applications.
+ @hide pending API council -->
+ <permission android:name="android.permission.CHANGE_BACKGROUND_DATA_SETTING"
+ android:protectionLevel="signature" />
+
+ <!-- This permission can be used on content providers to allow the global
+ search system to access their data. Typically it used when the
+ provider has some permissions protecting it (which global search
+ would not be expected to hold), and added as a read-only permission
+ to the path in the provider where global search queries are
+ performed. This permission can not be held by regular applications;
+ it is used by applications to protect themselves from everyone else
+ besides global search.
+ <p>Protection level: signature|privileged -->
+ <permission android:name="android.permission.GLOBAL_SEARCH"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Internal permission protecting access to the global search
+ system: ensures that only the system can access the provider
+ to perform queries (since this otherwise provides unrestricted
+ access to a variety of content providers), and to write the
+ search statistics (to keep applications from gaming the source
+ ranking).
+ @hide -->
+ <permission android:name="android.permission.GLOBAL_SEARCH_CONTROL"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Internal permission to allows an application to read indexable data.
+ @hide -->
+ <permission android:name="android.permission.READ_SEARCH_INDEXABLES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Internal permission to allows an application to bind to suggestion service.
+ @hide -->
+ <permission android:name="android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Internal permission to allows an application to access card content provider. -->
+ <permission android:name="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- An application needs this permission for
+ {@link android.provider.Settings#ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY} to show its
+ {@link android.app.Activity} embedded in Settings app. -->
+ <permission android:name="android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK"
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- @SystemApi {@link android.app.Activity} should require this permission to ensure that only
+ the settings app can embed it in a multi pane window.
+ @hide -->
+ <permission android:name="android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows applications to set a live wallpaper.
+ @hide XXX Change to signature once the picker is moved to its
+ own apk as Ghod Intended. -->
+ <permission android:name="android.permission.SET_WALLPAPER_COMPONENT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows applications to set wallpaper dim amount.
+ @hide -->
+ <permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows applications to read dream settings and dream state.
+ @hide -->
+ <permission android:name="android.permission.READ_DREAM_STATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows applications to write dream settings, and start or stop dreaming.
+ @hide -->
+ <permission android:name="android.permission.WRITE_DREAM_STATE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide Allows applications to read whether ambient display is suppressed. -->
+ <permission android:name="android.permission.READ_DREAM_SUPPRESSION"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allow an application to read and write the cache partition.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by default container service so that only
+ the system can bind to it and use it to copy
+ protected data to secure containers or files
+ accessible to the system.
+ @hide -->
+ <permission android:name="android.permission.COPY_PROTECTED_DATA"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Internal permission protecting access to the encryption methods
+ @hide
+ -->
+ <permission android:name="android.permission.CRYPT_KEEPER"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi Allows an application to read historical network usage for
+ specific networks and applications. @hide -->
+ <permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to manage network policies (such as warning and disable
+ limits) and to define application-specific rules. @hide -->
+ <permission android:name="android.permission.MANAGE_NETWORK_POLICY"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide @deprecated use UPDATE_DEVICE_STATS instead -->
+ <permission android:name="android.permission.MODIFY_NETWORK_ACCOUNTING"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi @hide Allows an application to manage carrier subscription plans. -->
+ <permission android:name="android.permission.MANAGE_SUBSCRIPTION_PLANS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- C2DM permission.
+ @hide Used internally.
+ -->
+ <permission android:name="android.intent.category.MASTER_CLEAR.permission.C2D_MESSAGE"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.intent.category.MASTER_CLEAR.permission.C2D_MESSAGE"/>
+
+ <!-- @SystemApi @hide Package verifier needs to have this permission before the PackageManager will
+ trust it to verify packages.
+ -->
+ <permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by package verifier receiver, to ensure that only the
+ system can interact with it.
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_PACKAGE_VERIFIER"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Rollback manager needs to have this permission before the PackageManager will
+ trust it to enable rollback.
+ -->
+ <permission android:name="android.permission.PACKAGE_ROLLBACK_AGENT"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @TestApi @hide Allows managing apk level rollbacks. -->
+ <permission android:name="android.permission.MANAGE_ROLLBACKS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @TestApi @hide Allows testing apk level rollbacks. -->
+ <permission android:name="android.permission.TEST_MANAGE_ROLLBACKS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows an application to mark other applications as harmful -->
+ <permission android:name="android.permission.SET_HARMFUL_APP_WARNINGS"
+ android:protectionLevel="signature|verifier" />
+
+ <!-- @SystemApi @hide Intent filter verifier needs to have this permission before the
+ PackageManager will trust it to verify intent filters.
+ -->
+ <permission android:name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by intent filter verifier rintent-filtereceiver, to ensure that only the
+ system can interact with it.
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_INTENT_FILTER_VERIFIER"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Domain verification agent package needs to have this permission before the
+ system will trust it to verify domains.
+
+ TODO(159952358): STOPSHIP: This must be updated to the new "internal" protectionLevel
+ -->
+ <permission android:name="android.permission.DOMAIN_VERIFICATION_AGENT"
+ android:protectionLevel="internal|privileged" />
+
+ <!-- @SystemApi @hide Must be required by the domain verification agent's intent
+ BroadcastReceiver, to ensure that only the system can interact with it.
+ -->
+ <permission android:name="android.permission.BIND_DOMAIN_VERIFICATION_AGENT"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows an app like Settings to update the user's grants to what domains
+ an app is allowed to automatically open.
+ -->
+ <permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows applications to access serial ports via the SerialManager.
+ @hide -->
+ <permission android:name="android.permission.SERIAL_PORT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows the holder to access content providers from outside an ApplicationThread.
+ This permission is enforced by the ActivityManagerService on the corresponding APIs,
+ in particular ActivityManagerService#getContentProviderExternal(String) and
+ ActivityManagerService#removeContentProviderExternal(String).
+ @hide
+ -->
+ <permission android:name="android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to hold an UpdateLock, recommending that a headless
+ OTA reboot *not* occur while the lock is held.
+ @hide -->
+ <permission android:name="android.permission.UPDATE_LOCK"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application the opportunity to become a
+ {@link android.service.notification.NotificationAssistantService}.
+ User permission is still required before access is granted.
+ @hide -->
+ <permission android:name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi @TestApi Allows an application to read the current set of notifications, including
+ any metadata and intents attached.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_NOTIFICATIONS"
+ android:protectionLevel="signature|privileged|appop" />
+
+ <!-- Marker permission for applications that wish to access notification policy. This permission
+ is not supported on managed profiles.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"
+ android:description="@string/permdesc_access_notification_policy"
+ android:label="@string/permlab_access_notification_policy"
+ android:protectionLevel="normal" />
+
+ <!-- Allows modification of do not disturb rules and policies. Only allowed for system
+ processes.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_NOTIFICATIONS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @TestApi Allows adding/removing enabled notification listener components.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS"
+ android:protectionLevel="signature|installer" />
+ <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
+
+ <!-- @SystemApi Allows notifications to be colorized
+ <p>Not for use by third-party applications. @hide -->
+ <permission android:name="android.permission.USE_COLORIZED_NOTIFICATIONS"
+ android:protectionLevel="signature|setup|role" />
+
+ <!-- Allows access to keyguard secure storage. Only allowed for system processes.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"
+ android:protectionLevel="signature|setup" />
+
+ <!-- Allows applications to set the initial lockscreen state.
+ <p>Not for use by third-party applications. @hide -->
+ <permission android:name="android.permission.SET_INITIAL_LOCK"
+ android:protectionLevel="signature|setup"/>
+
+ <!-- @TestApi Allows applications to set and verify lockscreen credentials.
+ @hide -->
+ <permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS"
+ android:protectionLevel="signature"/>
+
+ <!-- Allows managing (adding, removing) fingerprint templates. Reserved for the system. @hide -->
+ <permission android:name="android.permission.MANAGE_FINGERPRINT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows managing (adding, removing) face templates. Reserved for the system. @hide -->
+ <permission android:name="android.permission.MANAGE_FACE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an app to reset fingerprint attempt counter. Reserved for the system. @hide -->
+ <permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT"
+ android:protectionLevel="signature" />
+
+ <!-- Allows access to TestApis for various components in the biometric stack, including
+ FingerprintService, FaceService, BiometricService. Used by com.android.server.biometrics
+ CTS tests. @hide @TestApi -->
+ <permission android:name="android.permission.TEST_BIOMETRIC"
+ android:protectionLevel="signature" />
+
+ <!-- Allows direct access to the <Biometric>Service interfaces. Reserved for the system. @hide -->
+ <permission android:name="android.permission.MANAGE_BIOMETRIC"
+ android:protectionLevel="signature" />
+
+ <!-- Allows direct access to the <Biometric>Service authentication methods. Reserved for the system. @hide -->
+ <permission android:name="android.permission.USE_BIOMETRIC_INTERNAL"
+ android:protectionLevel="signature" />
+
+ <!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide -->
+ <permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to control keyguard. Only allowed for system processes.
+ @hide -->
+ <permission android:name="android.permission.CONTROL_KEYGUARD"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to control keyguard features like secure notifications.
+ @hide -->
+ <permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to manage weak escrow token on the device. This permission
+ is not available to third party applications.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_WEAK_ESCROW_TOKEN"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to listen to trust changes. Only allowed for system processes.
+ @hide -->
+ <permission android:name="android.permission.TRUST_LISTENER"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to provide a trust agent.
+ @hide For security reasons, this is a platform-only permission. -->
+ <permission android:name="android.permission.PROVIDE_TRUST_AGENT"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to show a message
+ on the keyguard when asking to dismiss it.
+ @hide For security reasons, this is a platform-only permission. -->
+ <permission android:name="android.permission.SHOW_KEYGUARD_MESSAGE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to launch the trust agent settings activity.
+ @hide -->
+ <permission android:name="android.permission.LAUNCH_TRUST_AGENT_SETTINGS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Must be required by an {@link
+ android.service.trust.TrustAgentService},
+ to ensure that only the system can bind to it.
+ @hide -->
+ <permission android:name="android.permission.BIND_TRUST_AGENT"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by an {@link
+ android.service.notification.NotificationListenerService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Must be required by an {@link
+ android.service.notification.NotificationAssistantService} to ensure that only the system
+ can bind to it.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link
+ android.service.chooser.ChooserTargetService}, to ensure that
+ only the system can bind to it.
+ <p>Protection level: signature
+
+ @deprecated For publishing direct share targets, please follow the instructions in
+ https://developer.android.com/training/sharing/receive.html#providing-direct-share-targets
+ instead.
+ -->
+ <permission android:name="android.permission.BIND_CHOOSER_TARGET_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Must be held by services that extend
+ {@link android.service.resolver.ResolverRankerService}.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Must be required by services that extend
+ {@link android.service.resolver.ResolverRankerService}, to ensure that only the system can
+ bind to them.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by a {@link
+ android.service.notification.ConditionProviderService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_CONDITION_PROVIDER_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by an {@link android.service.dreams.DreamService},
+ to ensure that only the system can bind to it.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_DREAM_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by an {@link android.app.usage.CacheQuotaService} to ensure that only the
+ system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_CACHE_QUOTA_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to call into a carrier setup flow. It is up to the
+ carrier setup application to enforce that this permission is required
+ @hide This is not a third-party API (intended for OEMs and system apps). -->
+ <permission android:name="android.permission.INVOKE_CARRIER_SETUP"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to listen for network condition observations.
+ @hide This is not a third-party API (intended for system apps). -->
+ <permission android:name="android.permission.ACCESS_NETWORK_CONDITIONS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to provision and access DRM certificates
+ @hide This is not a third-party API (intended for system apps). -->
+ <permission android:name="android.permission.ACCESS_DRM_CERTIFICATES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Api Allows an application to manage media projection sessions.
+ @hide This is not a third-party API (intended for system apps). -->
+ <permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to read install sessions
+ @hide This is not a third-party API (intended for system apps). -->
+ <permission android:name="android.permission.READ_INSTALL_SESSIONS"
+ android:label="@string/permlab_readInstallSessions"
+ android:description="@string/permdesc_readInstallSessions"
+ android:protectionLevel="normal"/>
+
+ <!-- @SystemApi Allows an application to remove DRM certificates
+ @hide This is not a third-party API (intended for system apps). -->
+ <permission android:name="android.permission.REMOVE_DRM_CERTIFICATES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @deprecated Use {@link android.Manifest.permission#BIND_CARRIER_SERVICES} instead -->
+ <permission android:name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to interact with the currently active
+ {@link android.service.voice.VoiceInteractionService}.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- The system process that is allowed to bind to services in carrier apps will
+ have this permission. Carrier apps should use this permission to protect
+ their services that only the system is allowed to bind to.
+ <p>Protection level: signature|privileged
+ -->
+ <permission android:name="android.permission.BIND_CARRIER_SERVICES"
+ android:label="@string/permlab_bindCarrierServices"
+ android:description="@string/permdesc_bindCarrierServices"
+ android:protectionLevel="signature|privileged" />
+
+ <!--
+ Allows the holder to start the permission usage screen for an app.
+ <p>Protection level: signature|installer
+ -->
+ <permission android:name="android.permission.START_VIEW_PERMISSION_USAGE"
+ android:label="@string/permlab_startViewPermissionUsage"
+ android:description="@string/permdesc_startViewPermissionUsage"
+ android:protectionLevel="signature|installer|module" />
+
+ <!--
+ @SystemApi
+ Allows the holder to start the screen to review permission decisions.
+ <p>Protection level: signature|installer
+ @hide -->
+ <permission android:name="android.permission.START_REVIEW_PERMISSION_DECISIONS"
+ android:label="@string/permlab_startReviewPermissionDecisions"
+ android:description="@string/permdesc_startReviewPermissionDecisions"
+ android:protectionLevel="signature|installer" />
+
+ <!--
+ Allows the holder to start the screen with a list of app features.
+ <p>Protection level: signature|installer
+ -->
+ <permission android:name="android.permission.START_VIEW_APP_FEATURES"
+ android:label="@string/permlab_startViewAppFeatures"
+ android:description="@string/permdesc_startViewAppFeatures"
+ android:protectionLevel="signature|installer" />
+
+ <!-- Allows an application to query whether DO_NOT_ASK_CREDENTIALS_ON_BOOT
+ flag is set.
+ @hide -->
+ <permission android:name="android.permission.QUERY_DO_NOT_ASK_CREDENTIALS_ON_BOOT"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows applications to kill UIDs.
+ <p>This permission can be granted to the SYSTEM_SUPERVISOR role used for parental
+ controls.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.KILL_UID"
+ android:protectionLevel="signature|installer|role" />
+
+ <!-- @SystemApi Allows applications to read the local WiFi and Bluetooth MAC address.
+ @hide -->
+ <permission android:name="android.permission.LOCAL_MAC_ADDRESS"
+ android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS"/>
+
+ <!-- @SystemApi Allows access to MAC addresses of WiFi and Bluetooth peer devices.
+ @hide -->
+ <permission android:name="android.permission.PEERS_MAC_ADDRESS"
+ android:protectionLevel="signature|setup|role" />
+
+ <!-- Allows the Nfc stack to dispatch Nfc messages to applications. Applications
+ can use this permission to ensure incoming Nfc messages are from the Nfc stack
+ and not simulated by another application.
+ @hide -->
+ <permission android:name="android.permission.DISPATCH_NFC_MESSAGE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows changing day / night mode when system is configured with
+ config_lockDayNightMode set to true. If requesting app does not have permission,
+ it will be ignored.
+ @hide -->
+ <permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi Allows entering or exiting car mode using a specified priority.
+ This permission is required to use UiModeManager while specifying a priority for the calling
+ app. A device manufacturer uses this permission to prioritize the apps which can
+ potentially request to enter car-mode on a device to help establish the correct behavior
+ where multiple such apps are active at the same time.
+ @hide -->
+ <permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Required to receive ACTION_ENTER_CAR_MODE_PRIVILEGED or
+ ACTION_EXIT_CAR_MODE_PRIVILEGED.
+ @hide -->
+ <permission android:name="android.permission.HANDLE_CAR_MODE_CHANGES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows the holder to send category_car notifications.
+ @hide -->
+ <permission
+ android:name="android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- The system process is explicitly the only one allowed to launch the
+ confirmation UI for full backup/restore -->
+ <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
+
+ <!-- @SystemApi Allows the holder to access and manage instant applications on the device.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_INSTANT_APPS"
+ android:protectionLevel="signature|installer|verifier|role" />
+ <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS"/>
+
+ <!-- Allows the holder to view the instant applications on the device.
+ @hide -->
+ <permission android:name="android.permission.VIEW_INSTANT_APPS"
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- Allows the holder to manage whether the system can bind to services
+ provided by instant apps. This permission is intended to protect
+ test/development fucntionality and should be used only in such cases.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Allows receiving the usage of media resource e.g. video/audio codec and
+ graphic memory.
+ @hide -->
+ <permission android:name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by system/priv apps when accessing the sound trigger
+ APIs given by {@link SoundTriggerManager}.
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Must be required by system/priv apps to run sound trigger recognition sessions while in
+ battery saver mode.
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by system/priv apps implementing sound trigger detection services
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows trusted applications to dispatch managed provisioning message to Managed
+ Provisioning app. If requesting app does not have permission, it will be ignored.
+ @hide -->
+ <permission android:name="android.permission.DISPATCH_PROVISIONING_MESSAGE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows the holder to read blocked numbers. See
+ {@link android.provider.BlockedNumberContract}.
+ @hide -->
+ <permission android:name="android.permission.READ_BLOCKED_NUMBERS"
+ android:protectionLevel="signature" />
+
+ <!-- Allows the holder to write blocked numbers. See
+ {@link android.provider.BlockedNumberContract}.
+ @hide -->
+ <permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by an {@link android.service.vr.VrListenerService}, to ensure that only
+ the system can bind to it.
+ <p>Protection level: signature -->
+ <permission android:name="android.permission.BIND_VR_LISTENER_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by system apps when accessing restricted VR APIs.
+ @hide
+ @SystemApi
+ <p>Protection level: signature -->
+ <permission android:name="android.permission.RESTRICTED_VR_ACCESS"
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- Required to make calls to {@link android.service.vr.IVrManager}.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_VR_MANAGER"
+ android:protectionLevel="signature" />
+
+ <!-- Required to access VR-Mode state and state change events via {android.app.VrStateCallback}
+ @hide -->
+ <permission android:name="android.permission.ACCESS_VR_STATE"
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- Allows an application to allowlist tasks during lock task mode
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES"
+ android:protectionLevel="signature|setup" />
+
+ <!-- @SystemApi Allows an application to replace the app name displayed alongside notifications
+ in the N-release and later.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to show notifications before the device is provisioned.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.NOTIFICATION_DURING_SETUP"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to manage auto-fill sessions.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_AUTO_FILL"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to manage the content capture service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_CONTENT_CAPTURE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to manager the rotation resolver service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_ROTATION_RESOLVER"
+ android:protectionLevel="signature"/>
+
+ <!-- @SystemApi Allows an application to manage the music recognition service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @SystemApi Allows an application to manage speech recognition service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_SPEECH_RECOGNITION"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to manage the content suggestions service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to manage the app predictions service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_APP_PREDICTIONS"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an application to manage the search ui service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_SEARCH_UI"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an application to manage the smartspace service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_SMARTSPACE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to manage the wallpaper effects
+ generation service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to manage the cloudsearch service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_CLOUDSEARCH"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Allows an app to set the theme overlay in /vendor/overlay
+ being used.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MODIFY_THEME_OVERLAY"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an instant app to create foreground services.
+ <p>Protection level: signature|development|instant|appop -->
+ <permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
+ android:protectionLevel="signature|development|instant|appop" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground}.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE"
+ android:description="@string/permdesc_foregroundService"
+ android:label="@string/permlab_foregroundService"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "camera".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "connectedDevice".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "dataSync".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "location".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "mediaPlayback".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "mediaProjection".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "microphone".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "phoneCall".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "health".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "remoteMessaging".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "systemExempted".
+ Apps are allowed to use this type only in the use cases listed in
+ {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED}.
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "fileManagement".
+ <p>Protection level: normal|instant
+ @hide
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT"
+ android:description="@string/permdesc_foregroundServiceFileManagement"
+ android:label="@string/permlab_foregroundServiceFileManagement"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "specialUse".
+ <p>Protection level: normal|appop|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
+ android:protectionLevel="normal|appop|instant" />
+
+ <!-- @SystemApi Allows to access all app shortcuts.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_SHORTCUTS"
+ android:protectionLevel="signature|role|recents" />
+
+ <!-- @SystemApi Allows unlimited calls to shortcut mutation APIs.
+ @hide -->
+ <permission android:name="android.permission.UNLIMITED_SHORTCUTS_API_CALLS"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an application to read the runtime profiles of other apps.
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.READ_RUNTIME_PROFILES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide Allows audio policy management. -->
+ <permission android:name="android.permission.MANAGE_AUDIO_POLICY"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to turn on / off quiet mode.
+ @hide -->
+ <permission android:name="android.permission.MODIFY_QUIET_MODE"
+ android:protectionLevel="signature|privileged|development|role" />
+
+ <!-- Allows internal management of the camera framework
+ @hide -->
+ <permission android:name="android.permission.MANAGE_CAMERA"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to control remote animations. See
+ {@link ActivityOptions#makeRemoteAnimation}
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to watch changes and/or active state of app ops.
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WATCH_APPOPS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows hidden API checks to be disabled when starting a process.
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.DISABLE_HIDDEN_API_CHECKS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Permission that protects the
+ {@link android.provider.Telephony.Intents#ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL}
+ broadcast -->
+ <permission android:name="android.permission.MONITOR_DEFAULT_SMS_PACKAGE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- A subclass of {@link android.service.carrier.CarrierMessagingClientService} must be protected with this permission.
+ <p>Protection level: signature -->
+ <permission android:name="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- Must be required by an {@link android.service.watchdog.ExplicitHealthCheckService} to
+ ensure that only the system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Must be required by an {@link android.service.storage.ExternalStorageService} to
+ ensure that only the system can bind to it.
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_EXTERNAL_STORAGE_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Permission that allows configuring appops.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_APPOPS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Permission that allows background clipboard access.
+ @hide Not for use by third-party applications. -->
+ <permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Permission that allows apps to disable the clipboard access notifications.
+ @hide
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION"
+ android:protectionLevel="signature|installer" />
+
+ <!-- @hide Permission that suppresses the notification when the clipboard is accessed.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows modifying accessibility state.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_ACCESSIBILITY"
+ android:protectionLevel="signature|setup|recents|role" />
+
+ <!-- @SystemApi Allows an app to grant a profile owner access to device identifiers.
+ <p>Not for use by third-party applications.
+ @deprecated
+ @hide -->
+ <permission android:name="android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an app to mark a profile owner as managing an organization-owned device.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MARK_DEVICE_ORGANIZATION_OWNED"
+ android:protectionLevel="signature|role" />
+
+ <!-- Allows financial apps to read filtered sms messages.
+ Protection level: signature|appop
+ @deprecated The API that used this permission is no longer functional. -->
+ <permission android:name="android.permission.SMS_FINANCIAL_TRANSACTIONS"
+ android:protectionLevel="signature|appop" />
+
+ <!-- Required for apps targeting {@link android.os.Build.VERSION_CODES#Q} that want to use
+ {@link android.app.Notification.Builder#setFullScreenIntent notification full screen
+ intents}.
+ <p>Protection level: normal -->
+ <permission android:name="android.permission.USE_FULL_SCREEN_INTENT"
+ android:label="@string/permlab_fullScreenIntent"
+ android:description="@string/permdesc_fullScreenIntent"
+ android:protectionLevel="normal|appop" />
+
+ <!-- @SystemApi Allows requesting the framework broadcast the
+ {@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY} intent.
+ @hide -->
+ <permission android:name="android.permission.SEND_DEVICE_CUSTOMIZATION_READY"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Permission that protects the {@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY}
+ intent.
+ @hide -->
+ <permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY"
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- @SystemApi Allows wallpaper to be rendered in ambient mode.
+ @hide -->
+ <permission android:name="android.permission.AMBIENT_WALLPAPER"
+ android:protectionLevel="signature|preinstalled" />
+
+ <!-- @SystemApi Allows sensor privacy to be modified.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_SENSOR_PRIVACY"
+ android:protectionLevel="internal|role|installer" />
+
+ <!-- @SystemApi Allows sensor privacy changes to be observed.
+ @hide -->
+ <permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY"
+ android:protectionLevel="internal|role|installer" />
+
+ <!-- @SystemApi Permission that protects the {@link Intent#ACTION_REVIEW_ACCESSIBILITY_SERVICES}
+ intent.
+ @hide -->
+ <permission android:name="android.permission.REVIEW_ACCESSIBILITY_SERVICES"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an activity to replace the app name and icon displayed in share targets
+ in the sharesheet for the Q-release and later.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to access shared libraries.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES"
+ android:protectionLevel="signature|installer" />
+
+ <!-- Allows an app to log compat change usage.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.LOG_COMPAT_CHANGE"
+ android:protectionLevel="signature|privileged" />
+ <!-- Allows an app to read compat change config.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"
+ android:protectionLevel="signature|privileged" />
+ <!-- Allows an app to override compat change config.
+ This permission only allows to override config on debuggable builds or test-apks and is
+ therefore a less powerful version of OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"
+ android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an app to override compat change config on release builds.
+ Only ChangeIds that are annotated as @Overridable can be overridden on release builds.
+ @hide -->
+ <permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows input events to be monitored. Very dangerous! @hide -->
+ <permission android:name="android.permission.MONITOR_INPUT"
+ android:protectionLevel="signature|recents" />
+ <!-- Allows the use of FLAG_SLIPPERY, which permits touch events to slip from the current
+ window to the window where the touch currently is on top of. @hide -->
+ <permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES"
+ android:protectionLevel="signature|privileged|recents|role" />
+ <!-- Allows the caller to change the associations between input devices and displays.
+ Very dangerous! @hide -->
+ <permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY"
+ android:protectionLevel="signature" />
+
+ <!-- Allows query of any normal app on the device, regardless of manifest declarations.
+ <p>Protection level: normal -->
+ <permission android:name="android.permission.QUERY_ALL_PACKAGES"
+ android:label="@string/permlab_queryAllPackages"
+ android:description="@string/permdesc_queryAllPackages"
+ android:protectionLevel="normal" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
+ <!-- @hide Allow the caller to collect debugging data from processes that otherwise
+ would require USAGE_STATS. Before sharing this data with other apps, holders
+ of this permission are REQUIRED to themselves check that the caller has
+ PACKAGE_USAGE_STATS and OP_GET_USAGE_STATS. -->
+ <permission android:name="android.permission.PEEK_DROPBOX_DATA"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an application to access TV tuner HAL
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_TV_TUNER"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+ <!-- @SystemApi Allows an application to access descrambler of TV tuner HAL
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_TV_DESCRAMBLER"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+ <!-- @SystemApi Allows an application to access shared filter of TV tuner HAL
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_TV_SHARED_FILTER"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+ <!-- Allows an application to create trusted displays. @hide @SystemApi -->
+ <permission android:name="android.permission.ADD_TRUSTED_DISPLAY"
+ android:protectionLevel="signature|role" />
+
+ <!-- Allows an application to create always-unlocked displays. @hide @SystemApi -->
+ <permission android:name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY"
+ android:protectionLevel="signature|role"/>
+
+ <!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. -->
+ <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
+ android:protectionLevel="signature|role" />
+
+ <!-- @hide @SystemApi Allows an application to manage app hibernation state. -->
+ <permission android:name="android.permission.MANAGE_APP_HIBERNATION"
+ android:protectionLevel="signature|installer" />
+
+ <!-- @hide @TestApi Allows apps to reset the state of {@link com.android.server.am.AppErrors}.
+ <p>CTS tests will use UiAutomation.adoptShellPermissionIdentity() to gain access. -->
+ <permission android:name="android.permission.RESET_APP_ERRORS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to create/destroy input consumer. -->
+ <permission android:name="android.permission.INPUT_CONSUMER"
+ android:protectionLevel="signature" />
+
+ <!-- @hide @TestApi Allows an application to control the system's device state managed by the
+ {@link android.service.devicestate.DeviceStateManagerService}. For example, on foldable
+ devices this would grant access to toggle between the folded and unfolded states. -->
+ <permission android:name="android.permission.CONTROL_DEVICE_STATE"
+ android:protectionLevel="signature" />
+
+ <!-- @hide @SystemApi Must be required by a
+ {@link android.service.displayhash.DisplayHashingService}
+ to ensure that only the system can bind to it.
+ This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.BIND_DISPLAY_HASHING_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @hide @TestApi Allows an application to enable/disable toast rate limiting.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows managing the Game Mode
+ @hide -->
+ <permission android:name="android.permission.MANAGE_GAME_MODE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows accessing the frame rate per second of a given application
+ @hide -->
+ <permission android:name="android.permission.ACCESS_FPS_COUNTER"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows managing the GameService APIs
+ @hide -->
+ <permission android:name="android.permission.MANAGE_GAME_ACTIVITY"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows managing the Game service
+ @hide @TestApi Used only for testing. -->
+ <permission android:name="android.permission.SET_GAME_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager
+ when they are performing reboot-blocking work.
+ @hide -->
+ <permission android:name="android.permission.SIGNAL_REBOOT_READINESS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows an application to change the touch mode state.
+ @hide -->
+ <permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to get a People Tile preview for a given shortcut. -->
+ <permission android:name="android.permission.GET_PEOPLE_TILE_PREVIEW"
+ android:protectionLevel="signature|recents" />
+
+ <!-- @hide @SystemApi Allows an application to retrieve whether shortcut is backed by a
+ Conversation.
+ TODO(b/180412052): STOPSHIP: Define a role so it can be granted to Shell and AiAi. -->
+ <permission android:name="android.permission.READ_PEOPLE_DATA"
+ android:protectionLevel="signature|recents|role"/>
+
+ <!-- @hide @SystemApi Allows a logical component within an application to
+ temporarily renounce a set of otherwise granted permissions. -->
+ <permission android:name="android.permission.RENOUNCE_PERMISSIONS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to read nearby streaming policy. The policy controls
+ whether to allow the device to stream its notifications and apps to nearby devices. -->
+ <permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY"
+ android:protectionLevel="normal" />
+
+ <!-- @SystemApi Allows the holder to set the source of the data when setting a clip on the
+ clipboard.
+ @hide -->
+ <permission android:name="android.permission.SET_CLIP_SOURCE"
+ android:protectionLevel="signature|recents" />
+
+ <!-- @SystemApi Allows an application to access TV tuned info
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_TUNED_INFO"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+ <!-- Allows an application to indicate via
+ {@link android.content.pm.PackageInstaller.SessionParams#setRequireUserAction(int)}
+ that user action should not be required for an app update.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION"
+ android:label="@string/permlab_updatePackagesWithoutUserAction"
+ android:description="@string/permdesc_updatePackagesWithoutUserAction"
+ android:protectionLevel="normal" />
+ <uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION"/>
+
+ <!-- Allows an application to indicate via {@link
+ android.content.pm.PackageInstaller.SessionParams#setRequestUpdateOwnership}
+ that it has the intention of becoming the update owner.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.ENFORCE_UPDATE_OWNERSHIP"
+ android:protectionLevel="normal" />
+ <uses-permission android:name="android.permission.ENFORCE_UPDATE_OWNERSHIP" />
+
+ <!-- Allows an application to take screenshots of layers that normally would be blacked out when
+ a screenshot is taken. Specifically, layers that have the flag
+ {@link android.view.SurfaceControl#SECURE} will be screenshot if the caller requests to
+ capture secure layers. Normally those layers will be rendered black.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT"
+ android:protectionLevel="signature" />
+
+ <!-- Allows read only access to phone state with a non dangerous permission,
+ including the information like cellular network type, software version. -->
+ <permission android:name="android.permission.READ_BASIC_PHONE_STATE"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readBasicPhoneState"
+ android:description="@string/permdesc_readBasicPhoneState"
+ android:protectionLevel="normal" />
+
+ <!-- @SystemApi Allows an application to query over global data in AppSearch.
+ @hide -->
+ <permission android:name="android.permission.READ_GLOBAL_APP_SEARCH_DATA"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to query over global data in AppSearch that's visible to the
+ ASSISTANT role. -->
+ <permission android:name="android.permission.READ_ASSISTANT_APP_SEARCH_DATA"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to query over global data in AppSearch that's visible to the
+ HOME role. -->
+ <permission android:name="android.permission.READ_HOME_APP_SEARCH_DATA"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an assistive application to perform actions on behalf of users inside of
+ applications.
+ <p>For now, this permission is only granted to the Assistant application selected by
+ the user.
+ <p>Protection level: internal|role
+ -->
+ <permission android:name="android.permission.EXECUTE_APP_ACTION"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to display its suggestions using the autofill framework.
+ <p>For now, this permission is only granted to the Browser application.
+ <p>Protection level: internal|role
+ -->
+ <permission android:name="android.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS"
+ android:protectionLevel="internal|role" />
+
+ <!-- @SystemApi Allows an application to create virtual devices in VirtualDeviceManager.
+ @hide -->
+ <permission android:name="android.permission.CREATE_VIRTUAL_DEVICE"
+ android:protectionLevel="internal|role" />
+
+ <!-- @SystemApi Must be required by a safety source to send an update using the
+ {@link android.safetycenter.SafetyCenterManager}.
+ <p>Protection level: internal|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE"
+ android:protectionLevel="internal|privileged" />
+
+ <!-- @SystemApi Allows an application to launch device manager setup screens.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.LAUNCH_DEVICE_MANAGER_SETUP"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an application to update certain device management related system
+ resources.
+ @hide -->
+ <permission android:name="android.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an app to read whether SafetyCenter is enabled/disabled.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.READ_SAFETY_CENTER_STATUS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Required to access the safety center internal APIs using the
+ {@link android.safetycenter.SafetyCenterManager}.
+ <p>Protection level: internal|installer|role
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_SAFETY_CENTER"
+ android:protectionLevel="internal|installer|role" />
+
+ <!-- @SystemApi Allows an app to set keep-clear areas without restrictions on the size or
+ number of keep-clear areas (see {@link android.view.View#setPreferKeepClearRects}).
+ When the system arranges floating windows onscreen, it might decide to ignore keep-clear
+ areas from windows, whose owner does not have this permission.
+ @hide
+ -->
+ <permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an app to set gesture exclusion without restrictions on the vertical extent of the
+ exclusions (see {@link android.view.View#setSystemGestureExclusionRects}).
+ @hide
+ -->
+ <permission android:name="android.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION"
+ android:protectionLevel="signature|privileged|recents" />
+
+ <!-- Allows an UID to be visible to the application based on an interaction between the
+ two apps. This permission is not intended to be held by apps.
+ @hide @TestApi -->
+ <permission android:name="android.permission.MAKE_UID_VISIBLE"
+ android:protectionLevel="signature" />
+
+ <!-- Limits the system as the only handler of the QUERY_PACKAGE_RESTART broadcast
+ @hide -->
+ <permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
+
+ <!-- Allows financed device kiosk apps to perform actions on the Device Lock service
+ @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+ <permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an app to turn on the screen on, e.g. with
+ {@link android.os.PowerManager#ACQUIRE_CAUSES_WAKEUP}.
+ <p>Intended to only be used by home automation apps.
+ -->
+ <permission android:name="android.permission.TURN_SCREEN_ON"
+ android:label="@string/permlab_turnScreenOn"
+ android:description="@string/permdesc_turnScreenOn"
+ android:protectionLevel="signature|privileged|appop" />
+
+ <!-- Allows applications to use the user-initiated jobs API. For more details
+ see {@link android.app.job.JobInfo.Builder#setUserInitiated}.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.RUN_USER_INITIATED_JOBS"
+ android:protectionLevel="normal"/>
+
+ <!-- Allows a browser to invoke the set of credential candidate query apis.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS"
+ android:protectionLevel="normal" />
+
+ <!-- Allows browsers to call on behalf of another app by passing in a custom origin.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ORIGIN"
+ android:protectionLevel="normal"/>
+
+ <!-- Allows specifying candidate credential providers to be queried in Credential Manager
+ get flows, or to be preferred as a default in the Credential Manager create flows.
+ <p>Protection level: normal -->
+ <permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS"
+ android:protectionLevel="normal"/>
+
+ <!-- Allows permission to use Credential Manager UI for providing and saving credentials
+ @hide -->
+ <permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"
+ android:protectionLevel="signature" />
+
+ <!-- Allows an app to list Credential Manager providers.
+ @hide
+ -->
+ <permission android:name="android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS"
+ android:protectionLevel="signature|privileged"/>
+
+ <!-- Allows a system application to be registered with credential manager without
+ having to be enabled by the user.
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to be able to store and retrieve credentials from a remote
+ device. -->
+ <permission android:name="android.permission.PROVIDE_REMOTE_CREDENTIALS"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- Allows an app access to the installer provided app metadata.
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.GET_APP_METADATA"
+ android:protectionLevel="signature|installer" />
+
+ <!-- @hide @SystemApi Allows an application to stage HealthConnect's remote data so that
+ HealthConnect can later integrate it. -->
+ <permission android:name="android.permission.STAGE_HEALTH_CONNECT_REMOTE_DATA"
+ android:protectionLevel="signature|knownSigner"
+ android:knownCerts="@array/config_healthConnectRestoreKnownSigners"/>
+
+ <!-- @hide @TestApi Allows an application to clear HealthConnect's staged remote data for
+ testing only. For security reasons, this is a platform-only permission. -->
+ <permission android:name="android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows the holder to call health connect migration APIs.
+ @hide -->
+ <permission android:name="android.permission.MIGRATE_HEALTH_CONNECT_DATA"
+ android:protectionLevel="signature|knownSigner"
+ android:knownCerts="@array/config_healthConnectMigrationKnownSigners" />
+
+ <!-- @SystemApi Allows an app to query apps in clone profile. The permission is
+ bidirectional in nature, i.e. cloned apps would be able to query apps in root user.
+ The permission is not meant for 3P apps as of now.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.QUERY_CLONED_APPS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide @SystemApi
+ Allows applications to capture bugreport directly without consent dialog when using the
+ bugreporting API on userdebug/eng build.
+ <p>The application still needs to hold {@link android.permission.DUMP} permission and be
+ bugreport-whitelisted to be able to capture a bugreport using the bugreporting API in the
+ first place. Then, when the corresponding app op of this permission is ALLOWED, the
+ bugreport can be captured directly without going through the consent dialog.
+ <p>Protection level: internal|appop
+ <p>Intended to only be used on userdebug/eng build.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD"
+ android:protectionLevel="internal|appop" />
+
+ <!-- @SystemApi Allows to call APIs that log process lifecycle events
+ @hide -->
+ <permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE"
+ android:protectionLevel="signature|module" />
+
+ <!-- @hide Allows the settings app to access GPU service APIs".
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.ACCESS_GPU_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows an application to get type of any provider uri.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.GET_ANY_PROVIDER_TYPE"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows internal applications to read and synchronize non-core flags.
+ Apps without this permission can only read a subset of flags specifically intended
+ for use in "core", (i.e. third party apps). Apps with this permission can define their
+ own flags, and federate those values with other system-level apps.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.SYNC_FLAGS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows internal applications to override flags in the FeatureFlags service.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.WRITE_FLAGS"
+ android:protectionLevel="signature" />
+
+ <!-- Attribution for Geofencing service. -->
+ <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
+ <!-- Attribution for Country Detector. -->
+ <attribution android:tag="CountryDetector" android:label="@string/country_detector"/>
+ <!-- Attribution for Location service. -->
+ <attribution android:tag="LocationService" android:label="@string/location_service"/>
+ <!-- Attribution for Gnss service. -->
+ <attribution android:tag="GnssService" android:label="@string/gnss_service"/>
+ <!-- Attribution for Sensor Notification service. -->
+ <attribution android:tag="SensorNotificationService"
+ android:label="@string/sensor_notification_service"/>
+ <!-- Attribution for Twilight service. -->
+ <attribution android:tag="TwilightService" android:label="@string/twilight_service"/>
+ <!-- Attribution for Gnss Time Update service. -->
+ <attribution android:tag="GnssTimeUpdateService"
+ android:label="@string/gnss_time_update_service"/>
+ <!-- Attribution for MusicRecognitionManagerService.
+ <p>Not for use by third-party applications.</p> -->
+ <attribution android:tag="MusicRecognitionManagerService"
+ android:label="@string/music_recognition_manager_service"/>
+
+ <application android:process="system"
+ android:persistent="true"
+ android:hasCode="false"
+ android:label="@string/android_system_label"
+ android:allowClearUserData="false"
+ android:backupAgent="com.android.server.backup.SystemBackupAgent"
+ android:killAfterRestore="false"
+ android:icon="@drawable/ic_launcher_android"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.DeviceDefault.Light.DarkActionBar"
+ android:defaultToDeviceProtectedStorage="true"
+ android:forceQueryable="true"
+ android:directBootAware="true">
+ <activity android:name="com.android.internal.app.ChooserActivity"
+ android:theme="@style/Theme.DeviceDefault.Chooser"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true"
+ android:documentLaunchMode="never"
+ android:relinquishTaskIdentity="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:process=":ui"
+ android:exported="true"
+ android:visibleToInstantApps="true">
+ <intent-filter android:priority="100">
+ <action android:name="android.intent.action.CHOOSER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.VOICE" />
+ </intent-filter>
+ </activity>
+ <activity android:name="com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity"
+ android:exported="false"
+ android:theme="@style/Theme.DeviceDefault.Dialog.Alert.DayNight"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true"
+ android:documentLaunchMode="never"
+ android:relinquishTaskIdentity="true"
+ android:process=":ui"
+ android:visibleToInstantApps="true">
+ <intent-filter>
+ <action android:name="com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity android:name="com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity"
+ android:exported="false"
+ android:theme="@style/Theme.DeviceDefault.Resolver"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true"
+ android:documentLaunchMode="never"
+ android:relinquishTaskIdentity="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:process=":ui"
+ android:visibleToInstantApps="true">
+ <intent-filter>
+ <action android:name="com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity android:name="com.android.internal.app.IntentForwarderActivity"
+ android:finishOnCloseSystemDialogs="true"
+ android:theme="@style/Theme.Translucent.NoTitleBar"
+ android:excludeFromRecents="true"
+ android:label="@string/user_owner_label"
+ android:exported="true"
+ android:visibleToInstantApps="true"
+ >
+ </activity>
+ <activity-alias android:name="com.android.internal.app.ForwardIntentToParent"
+ android:targetActivity="com.android.internal.app.IntentForwarderActivity"
+ android:exported="true"
+ android:label="@string/user_owner_label">
+ </activity-alias>
+ <activity-alias android:name="com.android.internal.app.ForwardIntentToManagedProfile"
+ android:targetActivity="com.android.internal.app.IntentForwarderActivity"
+ android:icon="@drawable/ic_corp_badge"
+ android:exported="true"
+ android:label="@string/managed_profile_label">
+ </activity-alias>
+ <activity android:name="com.android.internal.app.HeavyWeightSwitcherActivity"
+ android:theme="@style/Theme.DeviceDefault.System.Dialog.Alert"
+ android:label="@string/heavy_weight_switcher_title"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true"
+ android:process=":ui">
+ </activity>
+ <activity android:name="com.android.internal.app.PlatLogoActivity"
+ android:theme="@style/Theme.DeviceDefault.Wallpaper.NoTitleBar"
+ android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
+ android:icon="@drawable/platlogo"
+ android:process=":ui">
+ </activity>
+ <activity android:name="com.android.internal.app.DisableCarModeActivity"
+ android:theme="@style/Theme.NoDisplay"
+ android:excludeFromRecents="true"
+ android:process=":ui">
+ </activity>
+
+ <activity android:name="android.accounts.ChooseAccountActivity"
+ android:excludeFromRecents="true"
+ android:exported="true"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog"
+ android:label="@string/choose_account_label"
+ android:process=":ui"
+ android:visibleToInstantApps="true">
+ </activity>
+
+ <activity android:name="android.accounts.ChooseTypeAndAccountActivity"
+ android:excludeFromRecents="true"
+ android:exported="true"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog"
+ android:label="@string/choose_account_label"
+ android:process=":ui"
+ android:visibleToInstantApps="true">
+ </activity>
+
+ <activity android:name="android.accounts.ChooseAccountTypeActivity"
+ android:excludeFromRecents="true"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog"
+ android:label="@string/choose_account_label"
+ android:process=":ui"
+ android:visibleToInstantApps="true">
+ </activity>
+
+ <activity android:name="android.accounts.CantAddAccountActivity"
+ android:excludeFromRecents="true"
+ android:exported="true"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog.NoActionBar"
+ android:process=":ui">
+ </activity>
+
+ <activity android:name="android.accounts.GrantCredentialsPermissionActivity"
+ android:excludeFromRecents="true"
+ android:exported="true"
+ android:theme="@style/Theme.DeviceDefault.Light.DialogWhenLarge"
+ android:process=":ui"
+ android:visibleToInstantApps="true">
+ </activity>
+
+ <activity android:name="android.content.SyncActivityTooManyDeletes"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog"
+ android:label="@string/sync_too_many_deletes"
+ android:process=":ui">
+ </activity>
+
+ <activity android:name="com.android.internal.app.ShutdownActivity"
+ android:permission="android.permission.SHUTDOWN"
+ android:theme="@style/Theme.NoDisplay"
+ android:exported="true"
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name="com.android.internal.intent.action.REQUEST_SHUTDOWN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.REBOOT" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="com.android.internal.app.NetInitiatedActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true"
+ android:process=":ui">
+ </activity>
+
+ <activity android:name="com.android.internal.app.SystemUserHomeActivity"
+ android:enabled="false"
+ android:process=":ui"
+ android:systemUserOnly="true"
+ android:exported="true"
+ android:theme="@style/Theme.Translucent.NoTitleBar">
+ <intent-filter android:priority="-100">
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ </activity>
+
+ <!-- Activity to prompt user if it's ok to create a new user sandbox for a
+ specified account. -->
+ <activity android:name="com.android.internal.app.ConfirmUserCreationActivity"
+ android:excludeFromRecents="true"
+ android:process=":ui"
+ android:exported="true"
+ android:theme="@style/Theme.Dialog.Confirmation">
+ <intent-filter android:priority="1000">
+ <action android:name="android.os.action.CREATE_USER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="com.android.internal.app.SuspendedAppActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true"
+ android:process=":ui">
+ </activity>
+
+ <activity android:name="com.android.internal.app.UnlaunchableAppActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true"
+ android:process=":ui">
+ </activity>
+
+ <activity android:name="com.android.internal.app.BlockedAppActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true"
+ android:lockTaskMode="always"
+ android:process=":ui">
+ </activity>
+
+ <activity android:name="com.android.internal.app.LaunchAfterAuthenticationActivity"
+ android:theme="@style/Theme.Translucent.NoTitleBar"
+ android:excludeFromRecents="true"
+ android:process=":ui">
+ </activity>
+
+ <activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true">
+ </activity>
+
+ <activity android:name="com.android.internal.app.HarmfulAppWarningActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true"
+ android:process=":ui"
+ android:label="@string/harmful_app_warning_title"
+ android:exported="false">
+ </activity>
+
+ <activity android:name="com.android.server.notification.NASLearnMoreActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true"
+ android:exported="false">
+ </activity>
+
+ <receiver android:name="com.android.server.BootReceiver"
+ android:exported="true"
+ android:systemUserOnly="true">
+ <intent-filter android:priority="1000">
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.updates.CertPinInstallReceiver"
+ android:exported="true"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_PINS" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.updates.IntentFirewallInstallReceiver"
+ android:exported="true"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_INTENT_FIREWALL" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.updates.SmsShortCodesInstallReceiver"
+ android:exported="true"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_SMS_SHORT_CODES" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.updates.NetworkWatchlistInstallReceiver"
+ android:exported="true"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_NETWORK_WATCHLIST" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.updates.ApnDbInstallReceiver"
+ android:exported="true"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="com.android.internal.intent.action.UPDATE_APN_DB" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.updates.CarrierProvisioningUrlsInstallReceiver"
+ android:exported="true"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.updates.CertificateTransparencyLogInstallReceiver"
+ android:exported="true"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_CT_LOGS" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.updates.LangIdInstallReceiver"
+ android:exported="true"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_LANG_ID" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.updates.SmartSelectionInstallReceiver"
+ android:exported="true"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_SMART_SELECTION" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.updates.ConversationActionsInstallReceiver"
+ android:exported="true"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_CONVERSATION_ACTIONS" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.updates.CarrierIdInstallReceiver"
+ android:exported="true"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.os.action.UPDATE_CARRIER_ID_DB" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.updates.EmergencyNumberDbInstallReceiver"
+ android:exported="true"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.os.action.UPDATE_EMERGENCY_NUMBER_DB" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.MasterClearReceiver"
+ android:exported="true"
+ android:permission="android.permission.MASTER_CLEAR">
+ <intent-filter
+ android:priority="100" >
+ <!-- For Checkin, Settings, etc.: action=FACTORY_RESET -->
+ <action android:name="android.intent.action.FACTORY_RESET" />
+ <!-- As above until all the references to the deprecated MASTER_CLEAR get updated to
+ FACTORY_RESET. -->
+ <action android:name="android.intent.action.MASTER_CLEAR" />
+
+ <!-- MCS always uses REMOTE_INTENT: category=MASTER_CLEAR -->
+ <action android:name="com.google.android.c2dm.intent.RECEIVE" />
+ <category android:name="android.intent.category.MASTER_CLEAR" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="com.android.server.WallpaperUpdateReceiver"
+ android:exported="true"
+ android:permission="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY">
+ <intent-filter>
+ <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
+ </intent-filter>
+ </receiver>
+
+ <service android:name="android.hardware.location.GeofenceHardwareService"
+ android:permission="android.permission.LOCATION_HARDWARE"
+ android:exported="false" />
+
+ <service android:name="com.android.server.MountServiceIdler"
+ android:exported="true"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.ZramWriteback"
+ android:exported="false"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.backup.FullBackupJob"
+ android:exported="true"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.backup.KeyValueBackupJob"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.content.SyncJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.pm.BackgroundDexOptJobService"
+ android:exported="true"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
+ <service android:name="com.android.server.pm.DynamicCodeLoggingService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
+ <service android:name="com.android.server.PruneInstantAppsJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.storage.DiskStatsLoggingService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.PreloadsFileCacheExpirationJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.camera.CameraStatsJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.usage.UsageStatsIdleService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.net.watchlist.ReportWatchlistJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.display.BrightnessIdleJob"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.people.data.DataMaintenanceService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.profcollect.ProfcollectForwardingService$ProfcollectBGJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service
+ android:name="com.android.server.autofill.AutofillCompatAccessibilityService"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+ android:visibleToInstantApps="true"
+ android:exported="true">
+ <meta-data
+ android:name="android.accessibilityservice"
+ android:resource="@xml/autofill_compat_accessibility_service" />
+ </service>
+
+ <service android:name="com.android.server.blob.BlobStoreIdleJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
+ <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.intent.action.LOAD_DATA"/>
+ </intent-filter>
+ </service>
+
+ <provider
+ android:name="com.android.server.textclassifier.IconsContentProvider"
+ android:authorities="com.android.textclassifier.icons"
+ android:singleUser="true"
+ android:enabled="true"
+ android:exported="true">
+ </provider>
+
+ </application>
+
+</manifest>
diff --git a/tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml b/tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml
new file mode 100644
index 000000000..8a6aa1980
--- /dev/null
+++ b/tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml
@@ -0,0 +1,573 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" >
+
+ <!--
+ This file contains the permissions defined by CarService-Builtin(com.android.car)
+ and CarService-updatable(com.[google.]?android.car.updatable). As this is only a
+ resource file, permissions from both packages can be added here.
+ -->
+ <permission-group android:name="android.car.permission-group.CAR_MONITORING"
+ android:icon="@drawable/perm_group_car"
+ android:description="@string/car_permission_desc"
+ android:label="@string/car_permission_label"/>
+ <permission android:name="android.car.permission.CAR_ENERGY"
+ android:permissionGroup="android.car.permission-group.CAR_MONITORING"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_energy"
+ android:description="@string/car_permission_desc_energy"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_ENERGY"
+ android:permissionGroup="android.car.permission-group.CAR_MONITORING"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_energy"
+ android:description="@string/car_permission_desc_control_car_energy"/>
+ <permission android:name="android.car.permission.READ_DRIVER_MONITORING_SETTINGS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_read_driver_monitoring_settings"
+ android:description="@string/car_permission_desc_read_driver_monitoring_settings"/>
+ <permission android:name="android.car.permission.CONTROL_DRIVER_MONITORING_SETTINGS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_driver_monitoring_settings"
+ android:description="@string/car_permission_desc_control_driver_monitoring_settings"/>
+ <permission android:name="android.car.permission.READ_DRIVER_MONITORING_STATES"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_read_driver_monitoring_states"
+ android:description="@string/car_permission_desc_read_driver_monitoring_states"/>
+ <permission android:name="android.car.permission.ADJUST_RANGE_REMAINING"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_adjust_range_remaining"
+ android:description="@string/car_permission_desc_adjust_range_remaining"/>
+ <permission android:name="android.car.permission.CAR_IDENTIFICATION"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_car_identification"
+ android:description="@string/car_permission_desc_car_identification"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_CLIMATE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_hvac"
+ android:description="@string/car_permission_desc_hvac"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_DOORS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_doors"
+ android:description="@string/car_permission_desc_control_car_doors"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_WINDOWS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_windows"
+ android:description="@string/car_permission_desc_control_car_windows"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_MIRRORS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_mirrors"
+ android:description="@string/car_permission_desc_control_car_mirrors"/>
+ <permission android:name="android.car.permission.CONTROL_GLOVE_BOX"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_glove_box"
+ android:description="@string/car_permission_desc_control_glove_box"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_SEATS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_seats"
+ android:description="@string/car_permission_desc_control_car_seats"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_AIRBAGS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_airbags"
+ android:description="@string/car_permission_desc_control_car_airbags"/>
+ <permission android:name="android.car.permission.CAR_MILEAGE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_mileage"
+ android:description="@string/car_permission_desc_mileage"/>
+ <permission android:name="android.car.permission.CAR_TIRES"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_car_tires"
+ android:description="@string/car_permission_desc_car_tires"/>
+ <permission android:name="android.car.permission.READ_CAR_STEERING"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_car_steering"
+ android:description="@string/car_permission_desc_car_steering"/>
+ <permission android:name="android.car.permission.READ_CAR_DISPLAY_UNITS"
+ android:protectionLevel="normal"
+ android:label="@string/car_permission_label_read_car_display_units"
+ android:description="@string/car_permission_desc_read_car_display_units"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_DISPLAY_UNITS"
+ android:protectionLevel="normal"
+ android:label="@string/car_permission_label_control_car_display_units"
+ android:description="@string/car_permission_desc_control_car_display_units"/>
+ <permission android:name="android.car.permission.CAR_SPEED"
+ android:permissionGroup="android.permission-group.LOCATION"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_speed"
+ android:description="@string/car_permission_desc_speed"/>
+ <permission android:name="android.car.permission.CAR_ENERGY_PORTS"
+ android:protectionLevel="normal"
+ android:label="@string/car_permission_label_car_energy_ports"
+ android:description="@string/car_permission_desc_car_energy_ports"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_ENERGY_PORTS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_energy_ports"
+ android:description="@string/car_permission_desc_control_car_energy_ports"/>
+ <permission android:name="android.car.permission.CAR_ENGINE_DETAILED"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_car_engine_detailed"
+ android:description="@string/car_permission_desc_car_engine_detailed"/>
+ <permission android:name="android.car.permission.CAR_DYNAMICS_STATE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_vehicle_dynamics_state"
+ android:description="@string/car_permission_desc_vehicle_dynamics_state"/>
+ <permission android:name="android.car.permission.CAR_VENDOR_EXTENSION"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_vendor_extension"
+ android:description="@string/car_permission_desc_vendor_extension"/>
+ <permission android:name="android.car.permission.CAR_PROJECTION"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_projection"
+ android:description="@string/car_permission_desc_projection"/>
+ <permission android:name="android.car.permission.ACCESS_CAR_PROJECTION_STATUS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_access_projection_status"
+ android:description="@string/car_permission_desc_access_projection_status"/>
+ <permission android:name="android.car.permission.BIND_PROJECTION_SERVICE"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_bind_projection_service"
+ android:description="@string/car_permission_desc_bind_projection_service"/>
+ <permission android:name="android.car.permission.CAR_MOCK_VEHICLE_HAL"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_mock_vehicle_hal"
+ android:description="@string/car_permission_desc_mock_vehicle_hal"/>
+ <permission android:name="android.car.permission.CAR_INFO"
+ android:protectionLevel="normal"
+ android:label="@string/car_permission_label_car_info"
+ android:description="@string/car_permission_desc_car_info"/>
+ <permission android:name="android.car.permission.PRIVILEGED_CAR_INFO"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_privileged_car_info"
+ android:description="@string/car_permission_desc_privileged_car_info"/>
+ <permission android:name="android.car.permission.READ_CAR_VENDOR_PERMISSION_INFO"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_vendor_permission_info"
+ android:description="@string/car_permission_desc_vendor_permission_info"/>
+ <permission android:name="android.car.permission.MANAGE_REMOTE_DEVICE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_manage_remote_device"
+ android:description="@string/car_permission_desc_manage_remote_device"/>
+ <permission android:name="android.car.permission.MANAGE_OCCUPANT_CONNECTION"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_manage_occupant_connection"
+ android:description="@string/car_permission_desc_manage_occupant_connection"/>
+
+ <!-- Allows an application to read the vehicle exterior environment information. For example,
+ it allows an application to read the vehicle exterior temperature and night mode status.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.car.permission.CAR_EXTERIOR_ENVIRONMENT"
+ android:protectionLevel="normal"
+ android:label="@string/car_permission_label_car_exterior_environment"
+ android:description="@string/car_permission_desc_car_exterior_environment"/>
+ <permission android:name="android.car.permission.CAR_EPOCH_TIME"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_car_epoch_time"
+ android:description="@string/car_permission_desc_car_epoch_time"/>
+ <permission android:name="android.car.permission.CAR_EXTERIOR_LIGHTS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_car_exterior_lights"
+ android:description="@string/car_permission_desc_car_exterior_lights"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_EXTERIOR_LIGHTS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_exterior_lights"
+ android:description="@string/car_permission_desc_control_car_exterior_lights"/>
+ <permission android:name="android.car.permission.READ_CAR_INTERIOR_LIGHTS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_car_interior_lights"
+ android:description="@string/car_permission_desc_car_interior_lights"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_INTERIOR_LIGHTS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_interior_lights"
+ android:description="@string/car_permission_desc_control_car_interior_lights"/>
+ <permission android:name="android.car.permission.CAR_POWER"
+ android:protectionLevel="signature|privileged|vendorPrivileged"
+ android:label="@string/car_permission_label_car_power"
+ android:description="@string/car_permission_desc_car_power"/>
+ <permission android:name="android.car.permission.CAR_POWERTRAIN"
+ android:protectionLevel="normal"
+ android:label="@string/car_permission_label_car_powertrain"
+ android:description="@string/car_permission_desc_car_powertrain"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_POWERTRAIN"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_powertrain"
+ android:description="@string/car_permission_desc_control_car_powertrain"/>
+ <permission android:name="android.car.permission.CAR_NAVIGATION_MANAGER"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_car_navigation_manager"
+ android:description="@string/car_permission_desc_car_navigation_manager"/>
+ <permission android:name="android.car.permission.CAR_DIAGNOSTICS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_diag_read"
+ android:description="@string/car_permission_desc_diag_read"/>
+ <permission android:name="android.car.permission.CLEAR_CAR_DIAGNOSTICS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_diag_clear"
+ android:description="@string/car_permission_desc_diag_clear"/>
+ <permission android:name="android.car.permission.BIND_VMS_CLIENT"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_bind_vms_client"
+ android:description="@string/car_permission_desc_bind_vms_client"/>
+ <permission android:name="android.car.permission.VMS_PUBLISHER"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_vms_publisher"
+ android:description="@string/car_permission_desc_vms_publisher"/>
+ <permission android:name="android.car.permission.VMS_SUBSCRIBER"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_vms_subscriber"
+ android:description="@string/car_permission_desc_vms_subscriber"/>
+ <permission android:name="android.car.permission.CAR_DRIVING_STATE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_driving_state"
+ android:description="@string/car_permission_desc_driving_state"/>
+ <permission android:name="android.car.permission.USE_CAR_TELEMETRY_SERVICE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_use_telemetry_service"
+ android:description="@string/car_permission_desc_use_telemetry_service"/>
+ <permission android:name="android.car.permission.REQUEST_CAR_EVS_ACTIVITY"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_request_evs_activity"
+ android:description="@string/car_permission_desc_request_evs_activity"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_EVS_ACTIVITY"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_evs_activity"
+ android:description="@string/car_permission_desc_control_evs_activity"/>
+ <permission android:name="android.car.permission.USE_CAR_EVS_CAMERA"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_use_evs_camera"
+ android:description="@string/car_permission_desc_use_evs_camera"/>
+ <permission android:name="android.car.permission.MONITOR_CAR_EVS_STATUS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_monitor_evs_status"
+ android:description="@string/car_permission_desc_monitor_evs_status"/>
+ <permission android:name="android.car.permission.CONTROL_APP_BLOCKING"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_app_blocking"
+ android:description="@string/car_permission_desc_control_app_blocking"/>
+ <permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_audio_volume"
+ android:description="@string/car_permission_desc_audio_volume"/>
+ <permission android:name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_audio_settings"
+ android:description="@string/car_permission_desc_audio_settings"/>
+ <permission android:name="android.car.permission.RECEIVE_CAR_AUDIO_DUCKING_EVENTS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_receive_ducking"
+ android:description="@string/car_permission_desc_receive_ducking"/>
+ <permission android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_bind_instrument_cluster_rendering"
+ android:description="@string/car_permission_desc_bind_instrument_cluster_rendering"/>
+ <permission android:name="android.car.permission.BIND_CAR_INPUT_SERVICE"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_bind_input_service"
+ android:description="@string/car_permission_desc_bind_input_service"/>
+ <permission android:name="android.car.permission.CAR_DISPLAY_IN_CLUSTER"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_car_display_in_cluster"
+ android:description="@string/car_permission_desc_car_display_in_cluster"/>
+ <permission android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_car_cluster_control"
+ android:description="@string/car_permission_desc_car_cluster_control"/>
+ <permission android:name="android.car.permission.CAR_MONITOR_CLUSTER_NAVIGATION_STATE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_car_monitor_cluster_navigation_state"
+ android:description="@string/car_permission_desc_car_monitor_cluster_navigation_state"/>
+ <permission android:name="android.car.permission.CAR_HANDLE_USB_AOAP_DEVICE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_car_handle_usb_aoap_device"
+ android:description="@string/car_permission_desc_car_handle_usb_aoap_device"/>
+ <permission android:name="android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_car_ux_restrictions_configuration"
+ android:description="@string/car_permission_desc_car_ux_restrictions_configuration"/>
+ <permission android:name="android.car.permission.READ_CAR_OCCUPANT_AWARENESS_STATE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_read_car_occupant_awareness_state"
+ android:description="@string/car_permission_desc_read_car_occupant_awareness_state"/>
+ <permission android:name="android.car.permission.ACCESS_PRIVATE_DISPLAY_ID"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_access_private_display_id"
+ android:description="@string/car_permission_desc_access_private_display_id"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_OCCUPANT_AWARENESS_SYSTEM"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_occupant_awareness_system"
+ android:description="@string/car_permission_desc_control_car_occupant_awareness_system"/>
+ <permission android:name="android.car.permission.STORAGE_MONITORING"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_storage_monitoring"
+ android:description="@string/car_permission_desc_storage_monitoring"/>
+ <permission android:name="android.car.permission.CAR_ENROLL_TRUST"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_enroll_trust"
+ android:description="@string/car_permission_desc_enroll_trust"/>
+ <permission android:name="android.car.permission.CAR_TEST_SERVICE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_car_test_service"
+ android:description="@string/car_permission_desc_car_test_service"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_FEATURES"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_features"
+ android:description="@string/car_permission_desc_control_car_features"/>
+ <permission android:name="android.car.permission.USE_CAR_WATCHDOG"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_use_car_watchdog"
+ android:description="@string/car_permission_desc_use_car_watchdog"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_WATCHDOG_CONFIG"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_watchdog_config"
+ android:description="@string/car_permission_desc_control_car_watchdog_config"/>
+ <permission android:name="android.car.permission.COLLECT_CAR_WATCHDOG_METRICS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_collect_car_watchdog_metrics"
+ android:description="@string/car_permission_desc_collect_car_watchdog_metrics"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_WINDOW"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_window"
+ android:description="@string/car_permission_desc_get_car_vendor_category_window"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_WINDOW"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_window"
+ android:description="@string/car_permission_desc_set_car_vendor_category_window"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_DOOR"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_door"
+ android:description="@string/car_permission_desc_get_car_vendor_category_door"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_DOOR"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_door"
+ android:description="@string/car_permission_desc_set_car_vendor_category_door"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_SEAT"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_seat"
+ android:description="@string/car_permission_desc_get_car_vendor_category_seat"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_SEAT"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_seat"
+ android:description="@string/car_permission_desc_set_car_vendor_category_seat"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_MIRROR"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_mirror"
+ android:description="@string/car_permission_desc_get_car_vendor_category_mirror"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_MIRROR"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_mirror"
+ android:description="@string/car_permission_desc_set_car_vendor_category_mirror"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_INFO"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_info"
+ android:description="@string/car_permission_desc_get_car_vendor_category_info"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_INFO"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_info"
+ android:description="@string/car_permission_desc_set_car_vendor_category_info"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_ENGINE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_engine"
+ android:description="@string/car_permission_desc_get_car_vendor_category_engine"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_ENGINE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_engine"
+ android:description="@string/car_permission_desc_set_car_vendor_category_engine"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_HVAC"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_hvac"
+ android:description="@string/car_permission_desc_get_car_vendor_category_hvac"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_HVAC"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_hvac"
+ android:description="@string/car_permission_desc_set_car_vendor_category_hvac"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_LIGHT"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_light"
+ android:description="@string/car_permission_desc_get_car_vendor_category_light"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_LIGHT"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_light"
+ android:description="@string/car_permission_desc_set_car_vendor_category_light"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_1"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_1"
+ android:description="@string/car_permission_desc_get_car_vendor_category_1"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_1"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_1"
+ android:description="@string/car_permission_desc_set_car_vendor_category_1"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_2"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_2"
+ android:description="@string/car_permission_desc_get_car_vendor_category_2"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_2"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_2"
+ android:description="@string/car_permission_desc_set_car_vendor_category_2"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_3"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_3"
+ android:description="@string/car_permission_desc_get_car_vendor_category_3"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_3"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_3"
+ android:description="@string/car_permission_desc_set_car_vendor_category_3"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_4"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_4"
+ android:description="@string/car_permission_desc_get_car_vendor_category_4"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_4"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_4"
+ android:description="@string/car_permission_desc_set_car_vendor_category_4"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_5"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_5"
+ android:description="@string/car_permission_desc_get_car_vendor_category_5"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_5"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_5"
+ android:description="@string/car_permission_desc_set_car_vendor_category_5"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_6"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_6"
+ android:description="@string/car_permission_desc_get_car_vendor_category_6"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_6"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_6"
+ android:description="@string/car_permission_desc_set_car_vendor_category_6"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_7"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_7"
+ android:description="@string/car_permission_desc_get_car_vendor_category_7"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_7"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_7"
+ android:description="@string/car_permission_desc_set_car_vendor_category_7"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_8"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_8"
+ android:description="@string/car_permission_desc_get_car_vendor_category_8"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_8"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_8"
+ android:description="@string/car_permission_desc_set_car_vendor_category_8"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_9"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_9"
+ android:description="@string/car_permission_desc_get_car_vendor_category_9"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_9"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_9"
+ android:description="@string/car_permission_desc_set_car_vendor_category_9"/>
+ <permission android:name="android.car.permission.GET_CAR_VENDOR_CATEGORY_10"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_get_car_vendor_category_10"
+ android:description="@string/car_permission_desc_get_car_vendor_category_10"/>
+ <permission android:name="android.car.permission.SET_CAR_VENDOR_CATEGORY_10"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_set_car_vendor_category_10"
+ android:description="@string/car_permission_desc_set_car_vendor_category_10"/>
+ <permission android:name="android.car.permission.CAR_MONITOR_INPUT"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_monitor_input"
+ android:description="@string/car_permission_desc_monitor_input"/>
+ <permission android:name="android.car.permission.READ_CAR_POWER_POLICY"
+ android:protectionLevel="normal"
+ android:label="@string/car_permission_label_read_car_power_policy"
+ android:description="@string/car_permission_desc_read_car_power_policy"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_POWER_POLICY"
+ android:protectionLevel="signature|privileged|vendorPrivileged"
+ android:label="@string/car_permission_label_control_car_power_policy"
+ android:description="@string/car_permission_desc_control_car_power_policy"/>
+ <permission android:name="android.car.permission.CONTROL_SHUTDOWN_PROCESS"
+ android:protectionLevel="signature|privileged|vendorPrivileged"
+ android:label="@string/car_permission_label_adjust_shutdown_process"
+ android:description="@string/car_permission_desc_adjust_shutdown_process"/>
+ <permission android:name="android.car.permission.TEMPLATE_RENDERER"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_template_renderer"
+ android:description="@string/car_permission_desc_template_renderer"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_APP_LAUNCH"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_app_launch"
+ android:description="@string/car_permission_desc_control_car_app_launch"/>
+ <permission android:name="android.car.permission.MANAGE_THREAD_PRIORITY"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_manage_thread_priority"
+ android:description="@string/car_permission_desc_manage_thread_priority"/>
+ <permission android:name="android.car.permission.BIND_OEM_CAR_SERVICE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_bind_oem_car_service"
+ android:description="@string/car_permission_desc_bind_oem_car_service"/>
+ <permission android:name="android.car.permission.MANAGE_OCCUPANT_ZONE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_manage_occupant_zone"
+ android:description="@string/car_permission_desc_manage_occupant_zone"/>
+ <permission android:name="android.car.permission.CONTROL_STEERING_WHEEL"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_steering_wheel"
+ android:description="@string/car_permission_desc_control_steering_wheel"/>
+ <permission android:name="android.car.permission.USE_REMOTE_ACCESS"
+ android:protectionLevel="normal"
+ android:label="@string/car_permission_label_use_remote_access"
+ android:description="@string/car_permission_desc_use_remote_access"/>
+ <permission android:name="android.car.permission.CONTROL_REMOTE_ACCESS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_remote_access"
+ android:description="@string/car_permission_desc_control_remote_access"/>
+ <permission android:name="android.car.permission.READ_ADAS_SETTINGS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_read_adas_settings"
+ android:description="@string/car_permission_desc_read_adas_settings"/>
+ <permission android:name="android.car.permission.CONTROL_ADAS_SETTINGS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_adas_settings"
+ android:description="@string/car_permission_desc_control_adas_settings"/>
+ <permission android:name="android.car.permission.READ_ADAS_STATES"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_read_adas_states"
+ android:description="@string/car_permission_desc_read_adas_states"/>
+ <permission android:name="android.car.permission.CONTROL_ADAS_STATES"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_adas_states"
+ android:description="@string/car_permission_desc_control_adas_states"/>
+ <permission android:name="android.car.permission.ACCESS_MIRRORED_SURFACE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_access_mirrored_surface"
+ android:description="@string/car_permission_desc_access_mirrored_surface"/>
+ <permission android:name="android.car.permission.MIRROR_DISPLAY"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_mirror_display"
+ android:description="@string/car_permission_desc_mirror_display"/>
+ <permission android:name="android.car.permission.REGISTER_CAR_SYSTEM_UI_PROXY"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_register_car_system_ui_proxy"
+ android:description="@string/car_permission_desc_register_car_system_ui_proxy"/>
+ <permission android:name="android.car.permission.MANAGE_CAR_SYSTEM_UI"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_manage_car_system_ui"
+ android:description="@string/car_permission_desc_manage_car_system_ui"/>
+ <permission android:name="android.car.permission.READ_WINDSHIELD_WIPERS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_read_windshield_wipers"
+ android:description="@string/car_permission_desc_read_windshield_wipers"/>
+ <permission android:name="android.car.permission.CONTROL_WINDSHIELD_WIPERS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_windshield_wipers"
+ android:description="@string/car_permission_desc_control_windshield_wipers"/>
+</manifest>
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/CommandBroadcastReceiver.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/CommandBroadcastReceiver.java
new file mode 100644
index 000000000..02138f36f
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/CommandBroadcastReceiver.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 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 andf
+ * limitations under the License.
+ */
+
+package android.permissionpolicy.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import javax.annotation.Nullable;
+
+public class CommandBroadcastReceiver extends BroadcastReceiver {
+
+ private static @Nullable OnCommandResultListener sOnCommandResultListener;
+
+ public interface OnCommandResultListener {
+ void onCommandResult(@Nullable Intent result);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final OnCommandResultListener listener;
+ synchronized (CommandBroadcastReceiver.class) {
+ listener = sOnCommandResultListener;
+ }
+ if (listener != null) {
+ listener.onCommandResult(intent);
+ }
+ }
+
+ public static void setOnCommandResultListener(@Nullable OnCommandResultListener listener) {
+ synchronized (CommandBroadcastReceiver.class) {
+ sOnCommandResultListener = listener;
+ }
+ }
+}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ContactsProviderTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ContactsProviderTest.java
new file mode 100644
index 000000000..d8dc11a14
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ContactsProviderTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 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.permissionpolicy.cts;
+
+import android.content.ContentValues;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.ContactsContract;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Verify that deprecated contacts permissions are not enforced.
+ */
+@AppModeFull(reason = "Instant apps cannot get the READ_CONTACTS/WRITE_CONTACTS permissions")
+public class ContactsProviderTest extends AndroidTestCase {
+
+ /**
+ * Verifies that query(ContactsContract.Contacts.CONTENT_URI) only requires
+ * permission {@link android.Manifest.permission#READ_CONTACTS}.
+ */
+ @SmallTest
+ public void testQueryContacts() {
+ getContext().getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
+ null, null, null, null);
+ }
+
+ /**
+ * Verifies that insert(ContactsContract.Contacts.CONTENT_URI) only requires
+ * permission {@link android.Manifest.permission#WRITE_CONTACTS}.
+ */
+ @SmallTest
+ public void testInsertContacts() {
+ try {
+ getContext().getContentResolver().insert(ContactsContract.Contacts.CONTENT_URI,
+ new ContentValues());
+ } catch (SecurityException e) {
+ fail("insert(ContactsContract.Contacts.CONTENT_URI) threw SecurityException");
+ } catch (UnsupportedOperationException e) {
+ // It is okay for this fail in this manner.
+ }
+ }
+
+ /**
+ * Verifies that query(ContactsContract.Profile.CONTENT_URI) only requires
+ * permission {@link android.Manifest.permission#READ_CONTACTS}.
+ */
+ @SmallTest
+ public void testQueryProfile() {
+ getContext().getContentResolver().query(ContactsContract.Profile.CONTENT_URI,
+ null, null, null, null);
+ }
+
+ /**
+ * Verifies that insert(ContactsContract.Profile.CONTENT_URI) only requires
+ * permission {@link android.Manifest.permission#WRITE_CONTACTS}. The provider won't
+ * actually let us execute this. But at least it shouldn't throw a security exception.
+ */
+ @SmallTest
+ public void testInsertProfile() {
+ try {
+ getContext().getContentResolver().insert(ContactsContract.Profile.CONTENT_URI,
+ new ContentValues(0));
+ } catch (SecurityException e) {
+ fail("insert(ContactsContract.Profile.CONTENT_URI) threw SecurityException");
+ } catch (UnsupportedOperationException e) {
+ // It is okay for this fail in this manner.
+ }
+ }
+
+ /**
+ * Verifies that update(ContactsContract.Profile.CONTENT_URI) only requires
+ * permission {@link android.Manifest.permission#WRITE_CONTACTS}.
+ */
+ @SmallTest
+ public void testUpdateProfile() {
+ getContext().getContentResolver().update(ContactsContract.Profile.CONTENT_URI,
+ new ContentValues(0), null, null);
+ }
+}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/IntelligenceRolesPolicyTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/IntelligenceRolesPolicyTest.java
new file mode 100644
index 000000000..e2cfe86ca
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/IntelligenceRolesPolicyTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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 android.permissionpolicy.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.R;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import com.google.common.base.Strings;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+@AppModeFull(reason = "Instant apps cannot read the system servers permission")
+@RunWith(Parameterized.class)
+public class IntelligenceRolesPolicyTest {
+ private final int mConfigKey;
+
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection intelligenceRoles() {
+ return Arrays.asList(new Object[][]{
+ {"systemUiIntelligence", R.string.config_systemUiIntelligence},
+ {"systemAmbientAudioIntelligence", R.string.config_systemAmbientAudioIntelligence},
+ {"systemAudioIntelligence", R.string.config_systemAudioIntelligence},
+ {"systemNotificationIntelligence", R.string.config_systemNotificationIntelligence},
+ {"systemTextIntelligence", R.string.config_systemTextIntelligence},
+ {"systemVisualIntelligence", R.string.config_systemVisualIntelligence},
+ });
+ }
+
+ public IntelligenceRolesPolicyTest(String unusedName, int configKey) {
+ mConfigKey = configKey;
+ }
+
+ @Test
+ public void testNoInternetPermissionRequested() throws Exception {
+ assumeTrue(ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S));
+
+ String packageName = sContext.getResources().getString(mConfigKey);
+ assumeTrue(!Strings.isNullOrEmpty(packageName));
+
+ List<String> requestedPermissions;
+
+ try {
+ requestedPermissions = getRequestedPermissions(sContext, packageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ // A package is not found, despite overlay config pointing to it. Strictly speaking that
+ // means that the policy for being an intelligence role is fulfilled.
+ return;
+ }
+
+ assertWithMessage("Package " + packageName + "MUST NOT request INTERNET permission. "
+ + "Instead packages MUST access the internet through well-defined APIs in an open "
+ + "source project.")
+ .that(requestedPermissions)
+ .doesNotContain(android.Manifest.permission.INTERNET);
+ }
+
+ private static List<String> getRequestedPermissions(Context context, String pkg)
+ throws PackageManager.NameNotFoundException {
+ PackageInfo packageInfo = context.getPackageManager()
+ .getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
+
+ return Arrays.asList(packageInfo.requestedPermissions);
+ }
+}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoCaptureAudioOutputPermissionTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoCaptureAudioOutputPermissionTest.java
new file mode 100644
index 000000000..0f7638694
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoCaptureAudioOutputPermissionTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013 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.permissionpolicy.cts;
+
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder.AudioSource;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Verify the capture system video output permission requirements.
+ */
+public class NoCaptureAudioOutputPermissionTest extends AndroidTestCase {
+ /**
+ * Verify that the AudioRecord constructor fails to create a recording object
+ * when the app does not have permission to capture audio output.
+ * For the purposes of this test, the app must already have the normal audio
+ * record permission, just not the capture audio output permission.
+ * <p>Requires permission:
+ * {@link android.Manifest.permission#RECORD_AUDIO} and
+ * {@link android.Manifest.permission#CAPTURE_VIDEO_OUTPUT}.
+ */
+ @SmallTest
+ public void testCreateAudioRecord() {
+ int bufferSize = AudioRecord.getMinBufferSize(44100,
+ AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
+
+ if (bufferSize <= 0)
+ {
+ // getMinBufferSize() returns an invalid buffer size.
+ // That could be because there is no microphone. In that case,
+ // use this buffer size to test AudioRecord creation.
+ PackageManager packageManager = mContext.getPackageManager();
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
+ bufferSize = 44100;
+ }
+ }
+
+ // The attempt to create the AudioRecord object succeeds even if the
+ // app does not have permission, but the object is not usable.
+ // The API should probably throw SecurityException but it was not originally
+ // designed to do that and it's not clear we can change it now.
+ AudioRecord record = new AudioRecord(AudioSource.REMOTE_SUBMIX, 44100,
+ AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
+ try {
+ assertTrue("AudioRecord state should not be INITIALIZED because the application"
+ + "does not have permission to access the remote submix source",
+ record.getState() != AudioRecord.STATE_INITIALIZED);
+ } finally {
+ record.release();
+ }
+ }
+}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoProcessOutgoingCallPermissionTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoProcessOutgoingCallPermissionTest.java
new file mode 100644
index 000000000..dc4f18744
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoProcessOutgoingCallPermissionTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2009 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.permissionpolicy.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.telecom.TelecomManager;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Verify that processing outgoing calls requires Permission.
+ */
+@AppModeFull(reason = "Instant apps cannot hold PROCESS_OUTGOING_CALL")
+public class NoProcessOutgoingCallPermissionTest {
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ // Time to wait for call to be placed.
+ private static final int CALL_START_WAIT_TIME_SEC = 30;
+ // Time to wait for a broadcast to be received after we verify that the test app which has
+ // the proper permission got the broadcast
+ private static final int POST_CALL_START_WAIT_TIME_SEC = 5;
+
+ private static final String APK_INSTALL_LOCATION =
+ "/data/local/tmp/cts/permissions2/CtsProcessOutgoingCalls.apk";
+ private static final String LOG_TAG = "NoProcessOutgoingCallPermissionTest";
+
+ private static final String ACTION_TEST_APP_RECEIVED_CALL =
+ "android.permissionpolicy.cts.TEST_APP_RECEIVED_CALL";
+ private static final String TEST_PKG_NAME = "android.permissionpolicy.cts.receivecallbroadcast";
+
+ private final CountDownLatch mTestAppBroadcastLatch = new CountDownLatch(1);
+ private final CountDownLatch mSystemBroadcastLatch = new CountDownLatch(1);
+
+ private void callPhone() {
+ Uri uri = Uri.parse("tel:123456");
+ Intent intent = new Intent(Intent.ACTION_CALL, uri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ Log.i(LOG_TAG, "Called phone: " + uri.toString());
+ }
+
+ /**
+ * Verify that to process an outgoing call requires Permission.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#PROCESS_OUTGOING_CALLS}
+ */
+ // TODO: add back to LargeTest when test can cancel initiated call
+ @Test
+ public void testProcessOutgoingCall() throws InterruptedException {
+ final PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) ||
+ !pm.hasSystemFeature(PackageManager.FEATURE_SIP_VOIP)) {
+ return;
+ }
+
+ OutgoingCallBroadcastReceiver rcvr = new OutgoingCallBroadcastReceiver();
+ IntentFilter filter = new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL);
+ filter.addAction(ACTION_TEST_APP_RECEIVED_CALL);
+ mContext.registerReceiver(rcvr, filter, Context.RECEIVER_EXPORTED);
+ // start the test app, so that it can receive the broadcast
+ mContext.startActivity(new Intent().setComponent(new ComponentName(TEST_PKG_NAME,
+ TEST_PKG_NAME + ".ProcessOutgoingCallReceiver$BaseActivity"))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+
+ callPhone();
+
+ boolean testAppGotBroadcast =
+ mTestAppBroadcastLatch.await(CALL_START_WAIT_TIME_SEC, TimeUnit.SECONDS);
+ Assert.assertTrue("Expected test app to receive broadcast within "
+ + CALL_START_WAIT_TIME_SEC + " seconds", testAppGotBroadcast);
+ boolean testClassGotBroadcast =
+ mSystemBroadcastLatch.await(POST_CALL_START_WAIT_TIME_SEC, TimeUnit.SECONDS);
+ Assert.assertFalse("Outgoing call processed without proper permissions",
+ testClassGotBroadcast);
+ }
+
+ @Before
+ public void installApp() {
+ String installResult = runShellCommand("pm install -g " + APK_INSTALL_LOCATION);
+ assertThat(installResult.trim()).isEqualTo("Success");
+ }
+
+ @After
+ public void endCall() {
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ mContext.getSystemService(TelecomManager.class).endCall();
+ });
+ runShellCommand("pm uninstall " + TEST_PKG_NAME);
+ }
+
+ public class OutgoingCallBroadcastReceiver extends BroadcastReceiver {
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_TEST_APP_RECEIVED_CALL.equals(intent.getAction())) {
+ mTestAppBroadcastLatch.countDown();
+ return;
+ }
+ Bundle xtrs = intent.getExtras();
+ Log.e(LOG_TAG, xtrs.toString());
+ mSystemBroadcastLatch.countDown();
+ }
+ }
+
+}
+
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoReceiveSmsPermissionTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoReceiveSmsPermissionTest.java
new file mode 100644
index 000000000..a2d9c32ea
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoReceiveSmsPermissionTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2009 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.permissionpolicy.cts;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.SystemUserOnly;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Verify Sms and Mms cannot be received without required permissions.
+ * Uses {@link android.telephony.SmsManager}.
+ */
+@AppModeFull(reason = "Instant apps cannot get the SEND_SMS permission")
+@SystemUserOnly(reason = "Secondary users have the DISALLOW_SMS user restriction")
+public class NoReceiveSmsPermissionTest extends AndroidTestCase {
+
+ private static final String TELEPHONY_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
+ private static final String MESSAGE_STATUS_RECEIVED_ACTION =
+ "com.android.cts.permission.sms.MESSAGE_STATUS_RECEIVED_ACTION";
+ private static final String MESSAGE_SENT_ACTION =
+ "com.android.cts.permission.sms.MESSAGE_SENT";
+ private static final String APP_SPECIFIC_SMS_RECEIVED_ACTION =
+ "com.android.cts.permission.sms.APP_SPECIFIC_SMS_RECEIVED";
+
+
+ private static final String LOG_TAG = "NoReceiveSmsPermissionTest";
+
+ private Semaphore mSemaphore = new Semaphore(0);
+
+ /**
+ * Verify that SmsManager.sendTextMessage requires permissions.
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#SEND_SMS}.
+ *
+ * Note: this test requires that the device under test reports a valid phone number
+ */
+ public void testReceiveTextMessage() {
+ PackageManager packageManager = mContext.getPackageManager();
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ // register our test receiver to receive SMSs. This won't throw a SecurityException,
+ // so test needs to wait to determine if it actual receives an SMS
+ // admittedly, this is a weak verification
+ // this test should be used in conjunction with a test that verifies an SMS can be
+ // received successfully using the same logic if all permissions are in place
+ IllegalSmsReceiver receiver = new IllegalSmsReceiver();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(TELEPHONY_SMS_RECEIVED);
+ filter.addAction(MESSAGE_SENT_ACTION);
+ filter.addAction(MESSAGE_STATUS_RECEIVED_ACTION);
+
+ getContext().registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
+ sendSMSToSelf("test");
+
+ waitForForEvents(mSemaphore, 1);
+ assertTrue("[RERUN] Sms not sent successfully. Check signal.",
+ receiver.isMessageSent());
+ assertFalse("Sms received without proper permissions", receiver.isSmsReceived());
+ }
+
+ /**
+ * Verify that without {@link android.Manifest.permission#RECEIVE_SMS} that an SMS sent
+ * containing a nonce from {@link SmsManager#createAppSpecificSmsToken} is delivered
+ * to the app.
+ */
+ public void testAppSpecificSmsToken() {
+ PackageManager packageManager = mContext.getPackageManager();
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ return;
+ }
+
+ AppSpecificSmsReceiver receiver = new AppSpecificSmsReceiver();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(TELEPHONY_SMS_RECEIVED);
+ filter.addAction(MESSAGE_SENT_ACTION);
+ filter.addAction(MESSAGE_STATUS_RECEIVED_ACTION);
+ filter.addAction(APP_SPECIFIC_SMS_RECEIVED_ACTION);
+ getContext().registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
+
+ PendingIntent receivedIntent = PendingIntent.getBroadcast(getContext(), 0,
+ new Intent(APP_SPECIFIC_SMS_RECEIVED_ACTION)
+ .setPackage(getContext().getPackageName()),
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
+
+ String token = SmsManager.getDefault().createAppSpecificSmsToken(receivedIntent);
+ String message = "test message, token=" + token;
+ sendSMSToSelf(message);
+
+ waitForForEvents(mSemaphore, 1);
+ assertTrue("[RERUN] Sms not sent successfully. Check signal.",
+ receiver.isMessageSent());
+ assertFalse("Sms received without proper permissions", receiver.isSmsReceived());
+ waitForForEvents(mSemaphore, 1);
+ assertTrue("App specific SMS intent not triggered", receiver.isAppSpecificSmsReceived());
+ }
+
+ private boolean waitForForEvents(Semaphore semaphore, int expectedNumberOfEvents) {
+ for (int i = 0; i < expectedNumberOfEvents; i++) {
+ try {
+ if (!semaphore.tryAcquire(2000, TimeUnit.MILLISECONDS)) {
+ return false;
+ }
+ } catch (Exception ex) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void sendSMSToSelf(String message) {
+ PendingIntent sentIntent = PendingIntent.getBroadcast(getContext(), 0,
+ new Intent(MESSAGE_SENT_ACTION).setPackage(getContext().getPackageName()),
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
+ PendingIntent deliveryIntent = PendingIntent.getBroadcast(getContext(), 0,
+ new Intent(MESSAGE_STATUS_RECEIVED_ACTION)
+ .setPackage(getContext().getPackageName()),
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
+
+ SubscriptionManager subscription = (SubscriptionManager)
+ getContext().getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ int subscriptionId = subscription.getActiveDataSubscriptionId();
+
+ assertFalse("[RERUN] No active telephony subscription. Check there is one enabled.",
+ subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+ // get current phone number
+ String currentNumber = subscription.getPhoneNumber(subscriptionId);
+
+ // fallback to getActiveSubscriptionInfo if number is empty
+ if (TextUtils.isEmpty(currentNumber)) {
+ SubscriptionInfo subInfo = subscription.getActiveSubscriptionInfo(subscriptionId);
+
+ assertTrue("[RERUN] No info for the active telephony subscription.",
+ subInfo != null);
+ currentNumber = subInfo.getNumber();
+ }
+ assertFalse("[RERUN] SIM card does not provide phone number. Use a suitable SIM Card.",
+ TextUtils.isEmpty(currentNumber));
+
+ Log.i(LOG_TAG, String.format("Sending SMS to self: %s", currentNumber));
+ sendSms(currentNumber, message, sentIntent, deliveryIntent);
+ }
+
+ protected void sendSms(String currentNumber, String text, PendingIntent sentIntent,
+ PendingIntent deliveryIntent) {
+ SmsManager.getDefault().sendTextMessage(currentNumber, null, text, sentIntent,
+ deliveryIntent);
+ }
+
+ /**
+ * A receiver that tracks if message was sent and received
+ */
+ public class IllegalSmsReceiver extends BroadcastReceiver {
+
+ private boolean mIsSmsReceived = false;
+ private boolean mIsMessageSent = false;
+
+ public void onReceive(Context context, Intent intent) {
+ if (TELEPHONY_SMS_RECEIVED.equals(intent.getAction())) {
+ // this is bad, received sms without having SMS permission
+ setSmsReceived();
+ } else if (MESSAGE_STATUS_RECEIVED_ACTION.equals(intent.getAction())) {
+ handleResultCode(getResultCode(), "delivery");
+ } else if (MESSAGE_SENT_ACTION.equals(intent.getAction())) {
+ handleResultCode(getResultCode(), "sent");
+ } else {
+ Log.w(LOG_TAG, String.format("unknown intent received: %s", intent.getAction()));
+ }
+
+ }
+
+ public boolean isSmsReceived() {
+ return mIsSmsReceived;
+ }
+
+ private synchronized void setSmsReceived() {
+ mIsSmsReceived = true;
+ notify();
+ }
+
+ public boolean isMessageSent() {
+ return mIsMessageSent;
+ }
+
+ private void handleResultCode(int resultCode, String action) {
+ if (resultCode == Activity.RESULT_OK) {
+ Log.i(LOG_TAG, String.format("message %1$s successful", action));
+ setMessageSentSuccess();
+ } else {
+ setMessageSentFailure();
+ String reason = getErrorReason(resultCode);
+ Log.e(LOG_TAG, String.format("message %1$s failed: %2$s", action, reason));
+ }
+ }
+
+ private synchronized void setMessageSentSuccess() {
+ mIsMessageSent = true;
+ // set this to true, but don't notify receiver since we don't know if message received
+ // yet
+ }
+
+ private synchronized void setMessageSentFailure() {
+ mIsMessageSent = false;
+ // test environment failure, notify observer so it can stop listening
+ // TODO: should test retry?
+ notify();
+ }
+
+ private String getErrorReason(int resultCode) {
+ switch (resultCode) {
+ case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
+ return "generic failure";
+ case SmsManager.RESULT_ERROR_NO_SERVICE:
+ return "no service";
+ case SmsManager.RESULT_ERROR_NULL_PDU:
+ return "null pdu";
+ case SmsManager.RESULT_ERROR_RADIO_OFF:
+ return "Radio off";
+ }
+ return "unknown";
+ }
+ }
+
+ public class AppSpecificSmsReceiver extends IllegalSmsReceiver {
+ private boolean mAppSpecificSmsReceived = false;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (APP_SPECIFIC_SMS_RECEIVED_ACTION.equals(intent.getAction())) {
+ mAppSpecificSmsReceived = true;
+ } else {
+ super.onReceive(context, intent);
+ }
+ try {
+ mSemaphore.release();
+ } catch (Exception ex) {
+ Log.e(LOG_TAG, "mSemaphore: Got exception in releasing semaphore, ex=" + ex);
+ }
+ }
+
+ public boolean isAppSpecificSmsReceived() {
+ return mAppSpecificSmsReceived;
+ }
+ }
+}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoWriteSecureSettingsPermissionTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoWriteSecureSettingsPermissionTest.java
new file mode 100644
index 000000000..2e4a806b6
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoWriteSecureSettingsPermissionTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2009 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.permissionpolicy.cts;
+
+import android.Manifest;
+import android.content.ContentValues;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+
+/**
+ * Verify secure settings cannot be written to without required permissions.
+ */
+public class NoWriteSecureSettingsPermissionTest extends AndroidTestCase {
+
+ /**
+ * Verify that write to secure settings requires permissions.
+ * This test app must have WRITE_SETTINGS permission but not WRITE_SECURE_SETTINGS
+ * <p>Tests Permission:
+ * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}
+ */
+ public void testWriteSecureSettings() {
+ try {
+ ContentValues values = new ContentValues();
+ values.put(Settings.Secure.NAME, Settings.Secure.ACCESSIBILITY_ENABLED);
+ values.put(Settings.Secure.VALUE, Boolean.TRUE);
+ getContext().getContentResolver().insert(Settings.Secure.CONTENT_URI, values);
+ fail("expected SecurityException requiring "
+ + Manifest.permission.WRITE_SECURE_SETTINGS);
+ } catch (SecurityException expected) {
+ /* do nothing */
+ }
+ }
+}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionMaxSdkVersionTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionMaxSdkVersionTest.java
new file mode 100644
index 000000000..b02b32f22
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionMaxSdkVersionTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 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.permission.cts;
+
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Verify permission behaviors with android:maxSdkVersion
+ */
+public class PermissionMaxSdkVersionTest extends AndroidTestCase {
+ // These two permission names must match the corresponding <uses-permission>
+ // declarations in the test app manifest.
+ static final String UNGRANTABLE_PERMISSION = "android.permission.INTERNET";
+ static final String GRANTABLE_PERMISSION = "android.permission.ACCESS_NETWORK_STATE";
+
+ /**
+ * Verify that with android:maxSdkVersion set to a previous API level,
+ * the permission is not being granted.
+ */
+ @SmallTest
+ public void testMaxSdkInPast() {
+ int result = mContext.checkPermission(UNGRANTABLE_PERMISSION,
+ Process.myPid(), Process.myUid());
+ assertEquals("Permissions with maxSdkVersion in the past should not be granted",
+ result,
+ PackageManager.PERMISSION_DENIED);
+ }
+
+ /**
+ * Verify that with android:maxSdkVersion set to a future API level,
+ * the permission is being granted.
+ */
+ @SmallTest
+ public void testMaxSdkInFuture() {
+ int result = mContext.checkPermission(GRANTABLE_PERMISSION,
+ Process.myPid(), Process.myUid());
+ assertEquals("Permissions with maxSdkVersion in the future should be granted",
+ result,
+ PackageManager.PERMISSION_GRANTED);
+ }
+}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionPolicyTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionPolicyTest.java
new file mode 100644
index 000000000..94bd2be1b
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionPolicyTest.java
@@ -0,0 +1,533 @@
+/*
+* Copyright (C) 2015 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.permissionpolicy.cts;
+
+import static android.content.pm.PermissionInfo.FLAG_INSTALLED;
+import static android.content.pm.PermissionInfo.PROTECTION_MASK_BASE;
+import static android.os.Build.VERSION.SECURITY_PATCH;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.platform.test.annotations.AppModeFull;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.InputStream;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Tests for permission policy on the platform.
+ */
+@AppModeFull(reason = "Instant apps cannot read the system servers permission")
+@RunWith(AndroidJUnit4.class)
+public class PermissionPolicyTest {
+ private static final Date HIDE_NON_SYSTEM_OVERLAY_WINDOWS_PATCH_DATE = parseDate("2017-11-01");
+ private static final String HIDE_NON_SYSTEM_OVERLAY_WINDOWS_PERMISSION
+ = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS";
+
+ private static final Date MANAGE_COMPANION_DEVICES_PATCH_DATE = parseDate("2020-07-01");
+ private static final String MANAGE_COMPANION_DEVICES_PERMISSION
+ = "android.permission.MANAGE_COMPANION_DEVICES";
+
+ private static final String LOG_TAG = "PermissionProtectionTest";
+
+ private static final String PLATFORM_PACKAGE_NAME = "android";
+
+ private static final String PLATFORM_ROOT_NAMESPACE = "android.";
+
+ private static final String TAG_PERMISSION = "permission";
+ private static final String TAG_PERMISSION_GROUP = "permission-group";
+
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_PERMISSION_GROUP = "permissionGroup";
+ private static final String ATTR_PERMISSION_FLAGS = "permissionFlags";
+ private static final String ATTR_PROTECTION_LEVEL = "protectionLevel";
+ private static final String ATTR_BACKGROUND_PERMISSION = "backgroundPermission";
+
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ @Test
+ public void shellIsOnlySystemAppThatRequestsRevokePostNotificationsWithoutKill() {
+ List<PackageInfo> pkgs = sContext.getPackageManager().getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(
+ PackageManager.GET_PERMISSIONS | PackageManager.MATCH_ALL));
+ int shellUid = Process.myUserHandle().getUid(Process.SHELL_UID);
+ for (PackageInfo pkg : pkgs) {
+ Assert.assertFalse(pkg.applicationInfo.uid != shellUid
+ && hasRevokeNotificationNoKillPermission(pkg));
+ }
+ }
+
+ @Test
+ public void platformPermissionPolicyIsUnaltered() throws Exception {
+ Map<String, PermissionInfo> declaredPermissionsMap =
+ getPermissionsForPackage(sContext, PLATFORM_PACKAGE_NAME);
+
+ List<String> offendingList = new ArrayList<>();
+
+ List<PermissionGroupInfo> declaredGroups = sContext.getPackageManager()
+ .getAllPermissionGroups(0);
+ Set<String> declaredGroupsSet = new ArraySet<>();
+ for (PermissionGroupInfo declaredGroup : declaredGroups) {
+ declaredGroupsSet.add(declaredGroup.name);
+ }
+
+ Set<String> expectedPermissionGroups = loadExpectedPermissionGroupNames(
+ R.raw.android_manifest);
+ List<ExpectedPermissionInfo> expectedPermissions = loadExpectedPermissions(
+ R.raw.android_manifest);
+
+ if (sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ expectedPermissions.addAll(loadExpectedPermissions(R.raw.automotive_android_manifest));
+ String carServicePackageName = SystemProperties.get("ro.android.car.carservice.package",
+ null);
+
+ assertWithMessage("Car service package not defined").that(
+ carServicePackageName).isNotNull();
+
+ declaredPermissionsMap.putAll(
+ getPermissionsForPackage(sContext, carServicePackageName));
+
+ // Load signature permission declared in CarService-builtin
+ String carServiceBuiltInPackageName = "com.android.car";
+ Map<String, PermissionInfo> carServiceBuiltInPermissionsMap = getPermissionsForPackage(
+ sContext, carServiceBuiltInPackageName);
+ // carServiceBuiltInPermissionsMap should only have signature permissions and those
+ // permissions should not be defined in car service updatable.
+ for (Map.Entry<String, PermissionInfo> permissionData : carServiceBuiltInPermissionsMap
+ .entrySet()) {
+ PermissionInfo carServiceBuiltInDeclaredPermission = permissionData.getValue();
+ String carServiceBuiltInDeclaredPermissionName = permissionData.getKey();
+
+ // Signature only permission should be defined in built-in car service
+ if ((carServiceBuiltInDeclaredPermission
+ .getProtection() != PermissionInfo.PROTECTION_SIGNATURE)
+ || (carServiceBuiltInDeclaredPermission.getProtectionFlags() != 0)) {
+ offendingList.add("Permission " + carServiceBuiltInDeclaredPermissionName
+ + " should be signature only permission to be declared in"
+ + " carServiceBuiltIn package.");
+ continue;
+ }
+
+ if (declaredPermissionsMap.get(carServiceBuiltInDeclaredPermissionName) != null) {
+ offendingList.add("Permission " + carServiceBuiltInDeclaredPermissionName
+ + " from car service builtin is already declared in other packages.");
+ continue;
+ }
+ }
+ declaredPermissionsMap.putAll(carServiceBuiltInPermissionsMap);
+ }
+
+ for (ExpectedPermissionInfo expectedPermission : expectedPermissions) {
+ String expectedPermissionName = expectedPermission.name;
+ if (shouldSkipPermission(expectedPermissionName)) {
+ // This permission doesn't need to exist yet, but will exist in
+ // a future SPL. It is acceptable to declare the permission
+ // even in an earlier SPL, so we remove it here so it doesn't
+ // trigger a failure after the loop.
+ declaredPermissionsMap.remove(expectedPermissionName);
+ continue;
+ }
+
+ // OEMs cannot remove permissions
+ PermissionInfo declaredPermission = declaredPermissionsMap.get(expectedPermissionName);
+ if (declaredPermission == null) {
+ offendingList.add("Permission " + expectedPermissionName + " must be declared");
+ continue;
+ }
+
+ // We want to end up with OEM defined permissions and groups to check their namespace
+ declaredPermissionsMap.remove(expectedPermissionName);
+
+ // OEMs cannot change permission protection
+ final int expectedProtection = expectedPermission.protectionLevel
+ & PROTECTION_MASK_BASE;
+ final int declaredProtection = declaredPermission.protectionLevel
+ & PROTECTION_MASK_BASE;
+ if (expectedProtection != declaredProtection) {
+ offendingList.add(
+ String.format(
+ "Permission %s invalid protection level %x, expected %x",
+ expectedPermissionName, declaredProtection, expectedProtection));
+ }
+
+ // OEMs cannot change permission flags
+ final int expectedFlags = expectedPermission.flags;
+ final int declaredFlags = (declaredPermission.flags & ~FLAG_INSTALLED);
+ if (expectedFlags != declaredFlags) {
+ offendingList.add(
+ String.format(
+ "Permission %s invalid flags %x, expected %x",
+ expectedPermissionName,
+ declaredFlags,
+ expectedFlags));
+ }
+
+ // OEMs cannot change permission protection flags
+ final int expectedProtectionFlags =
+ expectedPermission.protectionLevel & ~PROTECTION_MASK_BASE;
+ final int declaredProtectionFlags = declaredPermission.getProtectionFlags();
+ if (expectedProtectionFlags != declaredProtectionFlags) {
+ offendingList.add(
+ String.format(
+ "Permission %s invalid enforced protection %x, expected %x",
+ expectedPermissionName,
+ declaredProtectionFlags,
+ expectedProtectionFlags));
+ }
+
+ // OEMs cannot change permission grouping
+ if ((declaredPermission.protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) != 0) {
+ if (!Objects.equals(expectedPermission.group, declaredPermission.group)) {
+ offendingList.add(
+ "Permission " + expectedPermissionName + " not in correct group "
+ + "(expected=" + expectedPermission.group + " actual="
+ + declaredPermission.group);
+ }
+
+ if (declaredPermission.group != null
+ && !declaredGroupsSet.contains(declaredPermission.group)) {
+ offendingList.add(
+ "Permission group " + expectedPermission.group + " must be defined");
+ }
+ }
+
+ // OEMs cannot change background permission mapping
+ if (!Objects.equals(expectedPermission.backgroundPermission,
+ declaredPermission.backgroundPermission)) {
+ offendingList.add(
+ String.format(
+ "Permission %s invalid background permission %s, expected %s",
+ expectedPermissionName,
+ declaredPermission.backgroundPermission,
+ expectedPermission.backgroundPermission));
+ }
+ }
+
+ // OEMs cannot define permissions in the platform namespace
+ for (String permission : declaredPermissionsMap.keySet()) {
+ if (permission.startsWith(PLATFORM_ROOT_NAMESPACE)) {
+ final PermissionInfo permInfo = declaredPermissionsMap.get(permission);
+ offendingList.add(
+ "Cannot define permission " + permission
+ + ", package " + permInfo.packageName
+ + " in android namespace");
+ }
+ }
+
+ // OEMs cannot define groups in the platform namespace
+ for (PermissionGroupInfo declaredGroup : declaredGroups) {
+ if (!expectedPermissionGroups.contains(declaredGroup.name)) {
+ if (declaredGroup.name != null) {
+ if (declaredGroup.packageName.equals(PLATFORM_PACKAGE_NAME)
+ && declaredGroup.name.startsWith(PLATFORM_ROOT_NAMESPACE)) {
+ offendingList.add(
+ "Cannot define group " + declaredGroup.name
+ + ", package " + declaredGroup.packageName
+ + " in android namespace");
+ }
+ }
+ }
+ }
+
+ // OEMs cannot define new ephemeral permissions
+ for (String permission : declaredPermissionsMap.keySet()) {
+ PermissionInfo info = declaredPermissionsMap.get(permission);
+ if ((info.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0) {
+ offendingList.add("Cannot define new instant permission " + permission);
+ }
+ }
+
+ // Fail on any offending item
+ assertWithMessage("list of offending permissions").that(offendingList).isEmpty();
+ }
+
+ private boolean hasRevokeNotificationNoKillPermission(PackageInfo info) {
+ if (info.requestedPermissions == null) {
+ return false;
+ }
+
+ for (int i = 0; i < info.requestedPermissions.length; i++) {
+ if (Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL.equals(
+ info.requestedPermissions[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private List<ExpectedPermissionInfo> loadExpectedPermissions(int resourceId) throws Exception {
+ List<ExpectedPermissionInfo> permissions = new ArrayList<>();
+ try (InputStream in = sContext.getResources().openRawResource(resourceId)) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (TAG_PERMISSION.equals(parser.getName())) {
+ ExpectedPermissionInfo permissionInfo = new ExpectedPermissionInfo(
+ parser.getAttributeValue(null, ATTR_NAME),
+ parser.getAttributeValue(null, ATTR_PERMISSION_GROUP),
+ parser.getAttributeValue(null, ATTR_BACKGROUND_PERMISSION),
+ parsePermissionFlags(
+ parser.getAttributeValue(null, ATTR_PERMISSION_FLAGS)),
+ parseProtectionLevel(
+ parser.getAttributeValue(null, ATTR_PROTECTION_LEVEL)));
+ permissions.add(permissionInfo);
+ } else {
+ Log.e(LOG_TAG, "Unknown tag " + parser.getName());
+ }
+ }
+ }
+
+ return permissions;
+ }
+
+ private Set<String> loadExpectedPermissionGroupNames(int resourceId) throws Exception {
+ ArraySet<String> permissionGroups = new ArraySet<>();
+ try (InputStream in = sContext.getResources().openRawResource(resourceId)) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (TAG_PERMISSION_GROUP.equals(parser.getName())) {
+ permissionGroups.add(parser.getAttributeValue(null, ATTR_NAME));
+ } else {
+ Log.e(LOG_TAG, "Unknown tag " + parser.getName());
+ }
+ }
+ }
+ return permissionGroups;
+ }
+
+ private static int parsePermissionFlags(@Nullable String permissionFlagsString) {
+ if (permissionFlagsString == null) {
+ return 0;
+ }
+
+ int protectionFlags = 0;
+ String[] fragments = permissionFlagsString.split("\\|");
+ for (String fragment : fragments) {
+ switch (fragment.trim()) {
+ case "removed": {
+ protectionFlags |= PermissionInfo.FLAG_REMOVED;
+ } break;
+ case "costsMoney": {
+ protectionFlags |= PermissionInfo.FLAG_COSTS_MONEY;
+ } break;
+ case "hardRestricted": {
+ protectionFlags |= PermissionInfo.FLAG_HARD_RESTRICTED;
+ } break;
+ case "immutablyRestricted": {
+ protectionFlags |= PermissionInfo.FLAG_IMMUTABLY_RESTRICTED;
+ } break;
+ case "softRestricted": {
+ protectionFlags |= PermissionInfo.FLAG_SOFT_RESTRICTED;
+ } break;
+ }
+ }
+ return protectionFlags;
+ }
+
+ private static int parseProtectionLevel(String protectionLevelString) {
+ int protectionLevel = 0;
+ String[] fragments = protectionLevelString.split("\\|");
+ for (String fragment : fragments) {
+ switch (fragment.trim()) {
+ case "normal": {
+ protectionLevel |= PermissionInfo.PROTECTION_NORMAL;
+ } break;
+ case "dangerous": {
+ protectionLevel |= PermissionInfo.PROTECTION_DANGEROUS;
+ } break;
+ case "signature": {
+ protectionLevel |= PermissionInfo.PROTECTION_SIGNATURE;
+ } break;
+ case "signatureOrSystem": {
+ protectionLevel |= PermissionInfo.PROTECTION_SIGNATURE;
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM;
+ } break;
+ case "internal": {
+ protectionLevel |= PermissionInfo.PROTECTION_INTERNAL;
+ } break;
+ case "system": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM;
+ } break;
+ case "installer": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_INSTALLER;
+ } break;
+ case "verifier": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_VERIFIER;
+ } break;
+ case "preinstalled": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_PREINSTALLED;
+ } break;
+ case "pre23": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_PRE23;
+ } break;
+ case "appop": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_APPOP;
+ } break;
+ case "development": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_DEVELOPMENT;
+ } break;
+ case "privileged": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_PRIVILEGED;
+ } break;
+ case "oem": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_OEM;
+ } break;
+ case "vendorPrivileged": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED;
+ } break;
+ case "setup": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_SETUP;
+ } break;
+ case "textClassifier": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER;
+ } break;
+ case "configurator": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_CONFIGURATOR;
+ } break;
+ case "incidentReportApprover": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER;
+ } break;
+ case "appPredictor": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR;
+ } break;
+ case "instant": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_INSTANT;
+ } break;
+ case "runtime": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY;
+ } break;
+ case "companion": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_COMPANION;
+ } break;
+ case "retailDemo": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO;
+ } break;
+ case "recents": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_RECENTS;
+ } break;
+ case "role": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_ROLE;
+ } break;
+ case "knownSigner": {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER;
+ } break;
+ case "module" : {
+ protectionLevel |= PermissionInfo.PROTECTION_FLAG_MODULE;
+ } break;
+ }
+ }
+ return protectionLevel;
+ }
+
+ private static Map<String, PermissionInfo> getPermissionsForPackage(Context context, String pkg)
+ throws NameNotFoundException {
+ PackageInfo packageInfo = context.getPackageManager()
+ .getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
+ Map<String, PermissionInfo> declaredPermissionsMap = new ArrayMap<>();
+
+ for (PermissionInfo declaredPermission : packageInfo.permissions) {
+ declaredPermissionsMap.put(declaredPermission.name, declaredPermission);
+ }
+ return declaredPermissionsMap;
+ }
+
+ private static Date parseDate(String date) {
+ Date patchDate = new Date();
+ try {
+ SimpleDateFormat template = new SimpleDateFormat("yyyy-MM-dd");
+ patchDate = template.parse(date);
+ } catch (ParseException e) {
+ }
+
+ return patchDate;
+ }
+
+ private boolean shouldSkipPermission(String permissionName) {
+ switch (permissionName) {
+ case HIDE_NON_SYSTEM_OVERLAY_WINDOWS_PERMISSION:
+ return parseDate(SECURITY_PATCH).before(HIDE_NON_SYSTEM_OVERLAY_WINDOWS_PATCH_DATE);
+ case MANAGE_COMPANION_DEVICES_PERMISSION:
+ return parseDate(SECURITY_PATCH).before(MANAGE_COMPANION_DEVICES_PATCH_DATE);
+ default:
+ return false;
+ }
+ }
+
+ private class ExpectedPermissionInfo {
+ final @NonNull String name;
+ final @Nullable String group;
+ final @Nullable String backgroundPermission;
+ final int flags;
+ final int protectionLevel;
+
+ private ExpectedPermissionInfo(@NonNull String name, @Nullable String group,
+ @Nullable String backgroundPermission, int flags, int protectionLevel) {
+ this.name = name;
+ this.group = group;
+ this.backgroundPermission = backgroundPermission;
+ this.flags = flags;
+ this.protectionLevel = protectionLevel;
+ }
+ }
+}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PrivappPermissionsTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PrivappPermissionsTest.java
new file mode 100644
index 000000000..f33e8a6e6
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PrivappPermissionsTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2016 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.permissionpolicy.cts;
+
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
+import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
+
+import static com.google.common.collect.Maps.filterValues;
+import static com.google.common.collect.Sets.difference;
+import static com.google.common.collect.Sets.intersection;
+import static com.google.common.collect.Sets.newHashSet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.platform.test.annotations.AppModeFull;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PropertyUtil;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Tests enforcement of signature|privileged permission whitelist:
+ * <ul>
+ * <li>Report what is granted into the CTS log
+ * <li>Ensure all priv permissions are exclusively granted to applications declared in
+ * &lt;privapp-permissions&gt;
+ * </ul>
+ */
+@AppModeFull(reason = "This test test platform properties, not capabilities of an apps")
+@RunWith(AndroidJUnit4.class)
+public class PrivappPermissionsTest {
+
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = "PrivappPermissionsTest";
+
+ private static final String PLATFORM_PACKAGE_NAME = "android";
+
+ @Test
+ public void privappPermissionsMustBeEnforced() {
+ assertEquals("ro.control_privapp_permissions is not set to enforce",
+ "enforce", PropertyUtil.getProperty("ro.control_privapp_permissions"));
+ }
+
+ @Test
+ public void privappPermissionsNeedToBeWhitelisted() throws Exception {
+ Set<String> platformPrivPermissions = new HashSet<>();
+ PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+ PackageInfo platformPackage = pm.getPackageInfo(PLATFORM_PACKAGE_NAME,
+ PackageManager.GET_PERMISSIONS);
+
+ for (PermissionInfo permission : platformPackage.permissions) {
+ int protectionLevel = permission.protectionLevel;
+ if ((protectionLevel & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) {
+ platformPrivPermissions.add(permission.name);
+ }
+ }
+
+ List<PackageInfo> installedPackages = pm
+ .getInstalledPackages(MATCH_UNINSTALLED_PACKAGES | GET_PERMISSIONS);
+ installedPackages.sort(Comparator.comparing(p -> p.packageName));
+
+ Map<String, Set<String>> packagesGrantedNotInWhitelist = new HashMap<>();
+ Map<String, Set<String>> packagesNotGrantedNotRemovedNotInDenylist = new HashMap<>();
+ for (PackageInfo pkg : installedPackages) {
+ String packageName = pkg.packageName;
+ if (!pkg.applicationInfo.isPrivilegedApp()
+ || PLATFORM_PACKAGE_NAME.equals(packageName)) {
+ continue;
+ }
+
+ PackageInfo factoryPkg = pm
+ .getPackageInfo(packageName, MATCH_FACTORY_ONLY | GET_PERMISSIONS
+ | MATCH_UNINSTALLED_PACKAGES);
+
+ assertNotNull("No system image version found for " + packageName, factoryPkg);
+
+ Set<String> factoryRequestedPrivPermissions;
+ if (factoryPkg.requestedPermissions == null) {
+ factoryRequestedPrivPermissions = Collections.emptySet();
+ } else {
+ factoryRequestedPrivPermissions = intersection(
+ newHashSet(factoryPkg.requestedPermissions), platformPrivPermissions);
+ }
+
+ Map<String, Boolean> requestedPrivPermissions = new ArrayMap<>();
+ if (pkg.requestedPermissions != null) {
+ for (int i = 0; i < pkg.requestedPermissions.length; i++) {
+ String permission = pkg.requestedPermissions[i];
+ if (platformPrivPermissions.contains(permission)) {
+ requestedPrivPermissions.put(permission,
+ (pkg.requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED)
+ != 0);
+ }
+ }
+ }
+
+ // If an app is requesting any privileged permissions, log the details and verify
+ // that granted permissions are whitelisted
+ if (!factoryRequestedPrivPermissions.isEmpty() && !requestedPrivPermissions.isEmpty()) {
+ Set<String> granted = filterValues(requestedPrivPermissions,
+ isGranted -> isGranted).keySet();
+
+ Set<String> factoryNotGranted = difference(factoryRequestedPrivPermissions,
+ granted);
+
+ // priv permissions that the system package requested, but the current package not
+ // anymore
+ Set<String> removed = difference(factoryRequestedPrivPermissions,
+ requestedPrivPermissions.keySet());
+
+ Set<String> whitelist = getPrivAppPermissions(packageName);
+ Set<String> denylist = getPrivAppDenyPermissions(packageName);
+
+ if (DEBUG) {
+ String msg = "Application " + packageName + "\n"
+ + " Factory requested permissions:\n"
+ + getPrintableSet(" ", factoryRequestedPrivPermissions)
+ + " Granted:\n"
+ + getPrintableSet(" ", granted)
+ + " Removed:\n"
+ + getPrintableSet(" ", removed)
+ + " Whitelisted:\n"
+ + getPrintableSet(" ", whitelist)
+ + " Denylisted:\n"
+ + getPrintableSet(" ", denylist)
+ + " Factory not granted:\n"
+ + getPrintableSet(" ", factoryNotGranted);
+
+ for (String line : msg.split("\n")) {
+ Log.i(TAG, line);
+
+ // Prevent log from truncating output
+ Thread.sleep(10);
+ }
+ }
+
+ Set<String> grantedNotInWhitelist = difference(granted, whitelist);
+ Set<String> factoryNotGrantedNotRemovedNotInDenylist = difference(difference(
+ factoryNotGranted, removed), denylist);
+
+ if (!grantedNotInWhitelist.isEmpty()) {
+ packagesGrantedNotInWhitelist.put(packageName, grantedNotInWhitelist);
+ }
+
+ if (!factoryNotGrantedNotRemovedNotInDenylist.isEmpty()) {
+ packagesNotGrantedNotRemovedNotInDenylist.put(packageName,
+ factoryNotGrantedNotRemovedNotInDenylist);
+ }
+ }
+ }
+ StringBuilder message = new StringBuilder();
+ if (!packagesGrantedNotInWhitelist.isEmpty()) {
+ message.append("Not whitelisted permissions are granted: "
+ + packagesGrantedNotInWhitelist.toString());
+ }
+ if (!packagesNotGrantedNotRemovedNotInDenylist.isEmpty()) {
+ if (message.length() != 0) {
+ message.append(", ");
+ }
+ message.append("Requested permissions not granted: "
+ + packagesNotGrantedNotRemovedNotInDenylist.toString());
+ }
+ if (!packagesGrantedNotInWhitelist.isEmpty()
+ || !packagesNotGrantedNotRemovedNotInDenylist.isEmpty()) {
+ fail(message.toString());
+ }
+ }
+
+ private <T> String getPrintableSet(String indendation, Set<T> set) {
+ if (set.isEmpty()) {
+ return "";
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ for (T e : new TreeSet<>(set)) {
+ if (!TextUtils.isEmpty(e.toString().trim())) {
+ sb.append(indendation);
+ sb.append(e);
+ sb.append("\n");
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private Set<String> getPrivAppPermissions(String packageName) throws IOException {
+ String output = SystemUtil.runShellCommand(
+ InstrumentationRegistry.getInstrumentation(),
+ "cmd package get-privapp-permissions " + packageName).trim();
+ if (output.startsWith("{") && output.endsWith("}")) {
+ String[] split = output.substring(1, output.length() - 1).split("\\s*,\\s*");
+ return new LinkedHashSet<>(Arrays.asList(split));
+ }
+ return Collections.emptySet();
+ }
+
+ private Set<String> getPrivAppDenyPermissions(String packageName) throws IOException {
+ String output = SystemUtil.runShellCommand(
+ InstrumentationRegistry.getInstrumentation(),
+ "cmd package get-privapp-deny-permissions " + packageName).trim();
+ if (output.startsWith("{") && output.endsWith("}")) {
+ String[] split = output.substring(1, output.length() - 1).split("\\s*,\\s*");
+ return new LinkedHashSet<>(Arrays.asList(split));
+ }
+ return Collections.emptySet();
+ }
+
+}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ProtectedBroadcastsTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ProtectedBroadcastsTest.java
new file mode 100644
index 000000000..71c990441
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ProtectedBroadcastsTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2009 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.permissionpolicy.cts;
+
+import android.content.Intent;
+import android.content.RestrictionsManager;
+import android.content.pm.PackageManager;
+import android.test.AndroidTestCase;
+
+/**
+ * Verify that applications can not send protected broadcasts.
+ */
+public class ProtectedBroadcastsTest extends AndroidTestCase {
+ private static final String BROADCASTS[] = new String[] {
+ Intent.ACTION_SCREEN_OFF,
+ Intent.ACTION_SCREEN_ON,
+ Intent.ACTION_USER_PRESENT,
+ Intent.ACTION_TIME_TICK,
+ Intent.ACTION_TIMEZONE_CHANGED,
+ Intent.ACTION_BOOT_COMPLETED,
+ Intent.ACTION_PACKAGE_INSTALL,
+ Intent.ACTION_PACKAGE_ADDED,
+ Intent.ACTION_PACKAGE_REPLACED,
+ Intent.ACTION_PACKAGE_REMOVED,
+ Intent.ACTION_PACKAGE_CHANGED,
+ Intent.ACTION_PACKAGE_RESTARTED,
+ Intent.ACTION_PACKAGE_DATA_CLEARED,
+ Intent.ACTION_UID_REMOVED,
+ Intent.ACTION_CONFIGURATION_CHANGED,
+ Intent.ACTION_BATTERY_CHANGED,
+ Intent.ACTION_BATTERY_LOW,
+ Intent.ACTION_BATTERY_OKAY,
+ Intent.ACTION_POWER_CONNECTED,
+ Intent.ACTION_POWER_DISCONNECTED,
+ Intent.ACTION_SHUTDOWN,
+ Intent.ACTION_DEVICE_STORAGE_LOW,
+ Intent.ACTION_DEVICE_STORAGE_OK,
+ Intent.ACTION_REBOOT,
+ "com.android.server.WifiManager.action.START_SCAN",
+ "com.android.server.WifiManager.action.DELAYED_DRIVER_STOP",
+ "android.net.wifi.WIFI_STATE_CHANGED",
+ "android.net.wifi.WIFI_AP_STATE_CHANGED",
+ "android.net.wifi.SCAN_RESULTS",
+ "android.net.wifi.RSSI_CHANGED",
+ "android.net.wifi.STATE_CHANGE",
+ "android.net.wifi.LINK_CONFIGURATION_CHANGED",
+ "android.net.wifi.CONFIGURED_NETWORKS_CHANGE",
+ "android.net.wifi.supplicant.CONNECTION_CHANGE",
+ "android.net.wifi.supplicant.STATE_CHANGE",
+ "android.net.wifi.p2p.STATE_CHANGED",
+ "android.net.wifi.p2p.DISCOVERY_STATE_CHANGE",
+ "android.net.wifi.p2p.THIS_DEVICE_CHANGED",
+ "android.net.wifi.p2p.PEERS_CHANGED",
+ "android.net.wifi.p2p.CONNECTION_STATE_CHANGE",
+ "android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED",
+ "android.net.conn.TETHER_STATE_CHANGED",
+ "android.net.conn.INET_CONDITION_ACTION",
+ "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED",
+ RestrictionsManager.ACTION_PERMISSION_RESPONSE_RECEIVED,
+ RestrictionsManager.ACTION_REQUEST_PERMISSION
+ };
+
+ private static final String BROADCASTS_TELEPHONY[] = new String[] {
+ Intent.ACTION_NEW_OUTGOING_CALL,
+ "android.intent.action.SERVICE_STATE",
+ "android.intent.action.SIG_STR",
+ "android.intent.action.RADIO_TECHNOLOGY",
+ "android.intent.action.ANY_DATA_STATE",
+ "android.intent.action.ACTION_MDN_STATE_CHANGED",
+ "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED",
+ "android.intent.action.SIM_STATE_CHANGED",
+ "android.telephony.action.SERVICE_PROVIDERS_UPDATED",
+ "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED",
+ "android.telephony.action.SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED",
+ "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS",
+ };
+
+ /**
+ * Verify that protected broadcast actions can't be sent.
+ */
+ public void testSendProtectedBroadcasts() {
+ for (String action : BROADCASTS) {
+ try {
+ Intent intent = new Intent(action);
+ getContext().sendBroadcast(intent);
+ fail("expected security exception broadcasting action: " + action);
+ } catch (SecurityException expected) {
+ assertNotNull("security exception's error message.", expected.getMessage());
+ }
+ }
+ }
+
+ public void testSendProtectedTelephonyBroadcasts() {
+ if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ return;
+ }
+ for (String action : BROADCASTS_TELEPHONY) {
+ try {
+ Intent intent = new Intent(action);
+ getContext().sendBroadcast(intent);
+ fail("expected security exception broadcasting telephony action: " + action);
+ } catch (SecurityException expected) {
+ assertNotNull("security exception's error message.", expected.getMessage());
+ }
+ }
+ }
+}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedPermissionsTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedPermissionsTest.java
new file mode 100644
index 000000000..95e428f1f
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedPermissionsTest.java
@@ -0,0 +1,744 @@
+/*
+ * Copyright (C) 2019 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 andf
+ * limitations under the License.
+ */
+
+package android.permissionpolicy.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.READ_SMS;
+import static android.permission.cts.PermissionUtils.isGranted;
+import static android.permission.cts.PermissionUtils.isPermissionGranted;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.Manifest.permission;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.Session;
+import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.SystemUserOnly;
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ThrowingRunnable;
+import com.android.modules.utils.build.SdkLevel;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nullable;
+
+/**
+ * Tests for restricted permission behaviors.
+ */
+public class RestrictedPermissionsTest {
+ private static final String APK_USES_LOCATION_22 =
+ "/data/local/tmp/cts/permissions2/CtsLocationPermissionsUserSdk22.apk";
+
+ private static final String APK_USES_LOCATION_29 =
+ "/data/local/tmp/cts/permissions2/CtsLocationPermissionsUserSdk29.apk";
+
+ private static final String APK_USES_SMS_CALL_LOG_22 =
+ "/data/local/tmp/cts/permissions2/CtsSMSCallLogPermissionsUserSdk22.apk";
+
+ private static final String APK_NAME_USES_SMS_CALL_LOG_29 =
+ "CtsSMSCallLogPermissionsUserSdk29.apk";
+
+ private static final String APK_USES_SMS_CALL_LOG_29 =
+ "/data/local/tmp/cts/permissions2/CtsSMSCallLogPermissionsUserSdk29.apk";
+
+ private static final String APK_USES_STORAGE_DEFAULT_29 =
+ "/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserDefaultSdk29.apk";
+
+ private static final String PKG = "android.permissionpolicy.cts.restrictedpermissionuser";
+
+ private static final String APK_USES_SMS_RESTRICTED_SHARED_UID =
+ "/data/local/tmp/cts/permissions2/CtsSMSRestrictedWithSharedUid.apk";
+
+ private static final String PKG_USES_SMS_RESTRICTED_SHARED_UID =
+ "android.permissionpolicy.cts.smswithshareduid.restricted";
+
+ private static final String APK_USES_SMS_NOT_RESTRICTED_SHARED_UID =
+ "/data/local/tmp/cts/permissions2/CtsSMSNotRestrictedWithSharedUid.apk";
+
+ private static final String PKG_USES_SMS_NOT_RESTRICTED_SHARED_UID =
+ "android.permissionpolicy.cts.smswithshareduid.notrestricted";
+
+ private static final long UI_TIMEOUT = 5000L;
+
+ private static @NonNull BroadcastReceiver sCommandReceiver;
+
+ @BeforeClass
+ public static void setUpOnce() {
+ sCommandReceiver = new CommandBroadcastReceiver();
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction("installRestrictedPermissionUserApp");
+ intentFilter.addAction("uninstallApp");
+ getContext().registerReceiver(sCommandReceiver, intentFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ getContext().unregisterReceiver(sCommandReceiver);
+ }
+
+ @Test
+ @AppModeFull
+ public void testDefaultAllRestrictedPermissionsWhitelistedAtInstall29() throws Exception {
+ // Install with no changes to whitelisted permissions, not attempting to grant.
+ installRestrictedPermissionUserApp(null /*whitelistedPermissions*/,
+ Collections.EMPTY_SET /*grantedPermissions*/);
+
+ // All restricted permission should be whitelisted.
+ assertAllRestrictedPermissionWhitelisted();
+
+ // No restricted permission should be granted.
+ assertNoRestrictedPermissionGranted();
+ }
+
+ @Test
+ @AppModeFull
+ public void testSomeRestrictedPermissionsWhitelistedAtInstall29() throws Exception {
+ // Whitelist only these permissions.
+ final Set<String> whitelistedPermissions = new ArraySet<>(2);
+ whitelistedPermissions.add(Manifest.permission.SEND_SMS);
+ whitelistedPermissions.add(Manifest.permission.READ_CALL_LOG);
+
+ // Install with some whitelisted permissions, not attempting to grant.
+ installRestrictedPermissionUserApp(whitelistedPermissions,
+ Collections.EMPTY_SET /*grantedPermissions*/);
+
+ // Some restricted permission should be whitelisted.
+ assertRestrictedPermissionWhitelisted(whitelistedPermissions);
+
+ // No restricted permission should be granted.
+ assertNoRestrictedPermissionGranted();
+ }
+
+ @Test
+ @AppModeFull
+ public void testNoneRestrictedPermissionWhitelistedAtInstall29() throws Exception {
+ // Install with all whitelisted permissions, not attempting to grant.
+ installRestrictedPermissionUserApp(Collections.emptySet(),
+ Collections.EMPTY_SET /*grantedPermissions*/);
+
+ // No restricted permission should be whitelisted.
+ assertNoRestrictedPermissionWhitelisted();
+
+ // No restricted permission should be granted.
+ assertNoRestrictedPermissionGranted();
+ }
+
+ @Test
+ @AppModeFull
+ @SystemUserOnly(reason = "Secondary users have the DISALLOW_SMS user restriction")
+ public void testDefaultAllRestrictedPermissionsWhitelistedAtInstall22() throws Exception {
+ Assume.assumeTrue("Secondary users have the DISALLOW_SMS user restriction",
+ UserHandle.SYSTEM.equals(Process.myUserHandle()));
+
+ String bypassLowTargetSdkFlag = "";
+ if (SdkLevel.isAtLeastU()) {
+ bypassLowTargetSdkFlag = " --bypass-low-target-sdk-block";
+ }
+
+ // Install with no changes to whitelisted permissions
+ runShellCommand("pm install" + bypassLowTargetSdkFlag
+ + " -g --force-queryable " + APK_USES_SMS_CALL_LOG_22);
+
+ // All restricted permission should be whitelisted.
+ assertAllRestrictedPermissionWhitelisted();
+ }
+
+ @Test
+ @AppModeFull
+ @SystemUserOnly(reason = "Secondary users have the DISALLOW_OUTGOING_CALLS user restriction")
+ public void testSomeRestrictedPermissionsWhitelistedAtInstall22() throws Exception {
+ Assume.assumeTrue("Secondary users have the DISALLOW_OUTGOING_CALLS user restriction",
+ UserHandle.SYSTEM.equals(Process.myUserHandle()));
+
+ // Whitelist only these permissions.
+ final Set<String> whitelistedPermissions = new ArraySet<>(2);
+ whitelistedPermissions.add(Manifest.permission.SEND_SMS);
+ whitelistedPermissions.add(Manifest.permission.READ_CALL_LOG);
+
+ // Install with some whitelisted permissions
+ installApp(APK_USES_SMS_CALL_LOG_22, whitelistedPermissions, null /*grantedPermissions*/);
+
+ // Some restricted permission should be whitelisted.
+ assertRestrictedPermissionWhitelisted(whitelistedPermissions);
+ }
+
+ @Test
+ @AppModeFull
+ public void testNoneRestrictedPermissionWhitelistedAtInstall22() throws Exception {
+ // Install with all whitelisted permissions
+ installApp(APK_USES_SMS_CALL_LOG_22, Collections.emptySet(),
+ null /*grantedPermissions*/);
+
+ // No restricted permission should be whitelisted.
+ assertNoRestrictedPermissionWhitelisted();
+ }
+
+ @Test
+ @AppModeFull
+ public void testLocationBackgroundPermissionWhitelistedAtInstall29() throws Exception {
+ installApp(APK_USES_LOCATION_29, null, new ArraySet<>(Arrays.asList(ACCESS_FINE_LOCATION,
+ ACCESS_BACKGROUND_LOCATION)));
+ assertAllRestrictedPermissionWhitelisted();
+ }
+
+ @Test
+ @AppModeFull
+ public void testLocationBackgroundPermissionNotWhitelistedAtInstall29() throws Exception {
+ installApp(APK_USES_LOCATION_29, Collections.emptySet(),
+ Collections.singleton(ACCESS_FINE_LOCATION));
+ assertNoRestrictedPermissionWhitelisted();
+ }
+
+ @Test
+ @AppModeFull
+ public void testLocationBackgroundPermissionWhitelistedAtInstall22() throws Exception {
+ installApp(APK_USES_LOCATION_22, null, new ArraySet<>(Arrays.asList(ACCESS_FINE_LOCATION,
+ ACCESS_BACKGROUND_LOCATION)));
+ assertAllRestrictedPermissionWhitelisted();
+ }
+
+ @Test
+ @AppModeFull
+ public void testLocationBackgroundPermissionNotWhitelistedAtInstall22() throws Exception {
+ installApp(APK_USES_LOCATION_22, Collections.emptySet(),
+ Collections.singleton(ACCESS_FINE_LOCATION));
+ assertNoRestrictedPermissionWhitelisted();
+ }
+
+ @Test
+ @AppModeFull
+ @SystemUserOnly(reason = "Secondary users have the DISALLOW_OUTGOING_CALLS user restriction")
+ public void testSomeRestrictedPermissionsGrantedAtInstall() throws Exception {
+ Assume.assumeTrue("Secondary users have the DISALLOW_OUTGOING_CALLS user restriction",
+ UserHandle.SYSTEM.equals(Process.myUserHandle()));
+
+ // Grant only these permissions.
+ final Set<String> grantedPermissions = new ArraySet<>(1);
+ grantedPermissions.add(Manifest.permission.SEND_SMS);
+ grantedPermissions.add(Manifest.permission.READ_CALL_LOG);
+
+ // Install with no whitelisted permissions attempting to grant.
+ installRestrictedPermissionUserApp(null /*whitelistedPermissions*/, grantedPermissions);
+
+ // All restricted permission should be whitelisted.
+ assertAllRestrictedPermissionWhitelisted();
+
+ // Some restricted permission should be granted.
+ assertRestrictedPermissionGranted(grantedPermissions);
+ }
+
+ @Test
+ @AppModeFull
+ public void testCanGrantSoftRestrictedNotWhitelistedPermissions() throws Exception {
+ try {
+ final Set<String> grantedPermissions = new ArraySet<>();
+ grantedPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
+ grantedPermissions.add(permission.WRITE_EXTERNAL_STORAGE);
+
+ installApp(APK_USES_STORAGE_DEFAULT_29, Collections.emptySet(), grantedPermissions);
+
+ assertRestrictedPermissionGranted(grantedPermissions);
+ } finally {
+ uninstallApp();
+ }
+ }
+
+ @Test
+ @AppModeFull
+ @SystemUserOnly(reason = "Secondary users have the DISALLOW_SMS user restriction")
+ public void testAllRestrictedPermissionsGrantedAtInstall() throws Exception {
+ Assume.assumeTrue("Secondary users have the DISALLOW_SMS user restriction",
+ UserHandle.SYSTEM.equals(Process.myUserHandle()));
+
+ // Install with whitelisted permissions attempting to grant.
+ installRestrictedPermissionUserApp(null /*whitelistedPermissions*/,
+ null);
+
+ // All restricted permission should be whitelisted.
+ assertAllRestrictedPermissionWhitelisted();
+
+ // Some restricted permission should be granted.
+ assertAllRestrictedPermissionGranted();
+ }
+
+ @Test
+ @AppModeFull
+ public void testWhitelistAccessControl() throws Exception {
+ // Install with no whitelisted permissions not attempting to grant.
+ installRestrictedPermissionUserApp(Collections.emptySet(), null);
+
+ assertWeCannotReadOrWriteWhileShellCanReadAndWrite(
+ PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM);
+
+ assertWeCannotReadOrWriteWhileShellCanReadAndWrite(
+ PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE);
+
+ assertWeCannotReadOrWriteWhileShellCanReadAndWrite(
+ PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+ }
+
+ @Test
+ @AppModeFull
+ public void onSideLoadRestrictedPermissionsWhitelistingDefault() throws Exception {
+ installRestrictedPermissionUserApp(new SessionParams(SessionParams.MODE_FULL_INSTALL));
+
+ // All restricted permissions whitelisted on side-load by default
+ assertAllRestrictedPermissionWhitelisted();
+ }
+
+ @Test
+ @AppModeFull
+ public void onSideLoadAllRestrictedPermissionsWhitelisted() throws Exception {
+ SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+ params.setWhitelistedRestrictedPermissions(SessionParams.RESTRICTED_PERMISSIONS_ALL);
+
+ installRestrictedPermissionUserApp(params);
+
+ assertAllRestrictedPermissionWhitelisted();
+ }
+
+ @Test
+ @AppModeFull
+ @FlakyTest
+ public void onSideLoadWhitelistSomePermissions() throws Exception {
+ Set<String> whitelistedPermissions = new ArraySet<>();
+ whitelistedPermissions.add(Manifest.permission.SEND_SMS);
+ whitelistedPermissions.add(Manifest.permission.READ_CALL_LOG);
+
+ SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+ params.setWhitelistedRestrictedPermissions(whitelistedPermissions);
+
+ installRestrictedPermissionUserApp(params);
+
+ assertRestrictedPermissionWhitelisted(whitelistedPermissions);
+ }
+
+ @Test
+ @AppModeFull
+ @FlakyTest
+ public void onSideLoadWhitelistNoPermissions() throws Exception {
+ SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+ params.setWhitelistedRestrictedPermissions(Collections.emptySet());
+
+ installRestrictedPermissionUserApp(params);
+
+ assertNoRestrictedPermissionWhitelisted();
+ }
+
+ @Test
+ @AppModeFull
+ @SystemUserOnly(reason = "Secondary users have the DISALLOW_SMS user restriction")
+ public void shareUidBetweenRestrictedAndNotRestrictedApp() throws Exception {
+ Assume.assumeTrue("Secondary users have the DISALLOW_SMS user restriction",
+ UserHandle.SYSTEM.equals(Process.myUserHandle()));
+
+ runShellCommand(
+ "pm install -g --force-queryable --restrict-permissions "
+ + APK_USES_SMS_RESTRICTED_SHARED_UID);
+ runShellCommand("pm install -g --force-queryable "
+ + APK_USES_SMS_NOT_RESTRICTED_SHARED_UID);
+
+ eventually(
+ () -> assertThat(isGranted(PKG_USES_SMS_RESTRICTED_SHARED_UID, READ_SMS)).isTrue());
+ // The apps share a UID, hence the whitelisting is shared too
+ assertThat(isGranted(PKG_USES_SMS_NOT_RESTRICTED_SHARED_UID, READ_SMS)).isTrue();
+ }
+
+ private static void installRestrictedPermissionUserApp(@NonNull SessionParams params)
+ throws Exception {
+ final CountDownLatch installLatch = new CountDownLatch(1);
+
+ // Create an install result receiver.
+ final BroadcastReceiver installReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE_INVALID)
+ == PackageInstaller.STATUS_SUCCESS) {
+ installLatch.countDown();
+ }
+ }
+ };
+
+ // Register the result receiver.
+ final String action = "android.permissionpolicy.cts.ACTION_INSTALL_COMMIT";
+ final IntentFilter intentFilter = new IntentFilter(action);
+ getContext().registerReceiver(installReceiver, intentFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
+
+ try {
+ // Create a session.
+ final PackageInstaller packageInstaller = getContext()
+ .getPackageManager().getPackageInstaller();
+ final int sessionId = packageInstaller.createSession(params);
+ final Session session = packageInstaller.openSession(sessionId);
+
+ // Write the apk.
+ try (
+ InputStream in = new BufferedInputStream(new FileInputStream(
+ new File(APK_USES_SMS_CALL_LOG_29)));
+ OutputStream out = session.openWrite(
+ APK_NAME_USES_SMS_CALL_LOG_29, 0, -1);
+ ) {
+ final byte[] buf = new byte[8192];
+ int size;
+ while ((size = in.read(buf)) != -1) {
+ out.write(buf, 0, size);
+ }
+ }
+
+ final Intent intent = new Intent(action);
+ intent.setPackage("android.permissionpolicy.cts");
+ final IntentSender intentSender = PendingIntent.getBroadcast(getContext(),
+ 1, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE)
+ .getIntentSender();
+
+ // Commit as shell to avoid confirm UI
+ runWithShellPermissionIdentity(() -> {
+ session.commit(intentSender);
+ installLatch.await(UI_TIMEOUT, TimeUnit.MILLISECONDS);
+ });
+ } finally {
+ getContext().unregisterReceiver(installReceiver);
+ }
+ }
+
+ private void assertWeCannotReadOrWriteWhileShellCanReadAndWrite(int whitelist)
+ throws Exception {
+ final PackageManager packageManager = getContext().getPackageManager();
+ try {
+ packageManager.getWhitelistedRestrictedPermissions(PKG, whitelist);
+ fail();
+ } catch (SecurityException expected) {
+ /*ignore*/
+ }
+ try {
+ packageManager.addWhitelistedRestrictedPermission(PKG,
+ permission.SEND_SMS, whitelist);
+ fail();
+ } catch (SecurityException expected) {
+ /*ignore*/
+ }
+ runWithShellPermissionIdentity(() -> {
+ packageManager.addWhitelistedRestrictedPermission(PKG,
+ permission.SEND_SMS, whitelist);
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ whitelist)).contains(permission.SEND_SMS);
+ packageManager.removeWhitelistedRestrictedPermission(PKG,
+ permission.SEND_SMS, whitelist);
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ whitelist)).doesNotContain(permission.SEND_SMS);
+ });
+ }
+
+ private @NonNull Set<String> getPermissionsOfAppWithAnyOfFlags(int flags) throws Exception {
+ final PackageManager packageManager = getContext().getPackageManager();
+ final Set<String> restrictedPermissions = new ArraySet<>();
+ for (String permission : getRequestedPermissionsOfApp()) {
+ PermissionInfo permInfo = packageManager.getPermissionInfo(permission, 0);
+
+ if ((permInfo.flags & flags) != 0) {
+ restrictedPermissions.add(permission);
+ }
+ }
+ return restrictedPermissions;
+ }
+
+ private @NonNull Set<String> getRestrictedPermissionsOfApp() throws Exception {
+ return getPermissionsOfAppWithAnyOfFlags(
+ PermissionInfo.FLAG_HARD_RESTRICTED | PermissionInfo.FLAG_SOFT_RESTRICTED);
+ }
+
+ private @NonNull String[] getRequestedPermissionsOfApp() throws Exception {
+ final PackageManager packageManager = getContext().getPackageManager();
+ final PackageInfo packageInfo = packageManager.getPackageInfo(PKG,
+ PackageManager.GET_PERMISSIONS);
+ return packageInfo.requestedPermissions;
+ }
+
+ private void assertAllRestrictedPermissionWhitelisted() throws Exception {
+ assertRestrictedPermissionWhitelisted(getRestrictedPermissionsOfApp());
+ }
+
+ private void assertNoRestrictedPermissionWhitelisted() throws Exception {
+ assertRestrictedPermissionWhitelisted(
+ Collections.EMPTY_SET /*expectedWhitelistedPermissions*/);
+ }
+
+ /**
+ * Assert that the passed in restrictions are whitelisted and that their app-op is set
+ * correctly.
+ *
+ * @param expectedWhitelistedPermissions The expected white listed permissions
+ */
+ private void assertRestrictedPermissionWhitelisted(
+ @NonNull Set<String> expectedWhitelistedPermissions) throws Exception {
+ final PackageManager packageManager = getContext().getPackageManager();
+ eventually(() -> runWithShellPermissionIdentity(() -> {
+ final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+ final PackageInfo packageInfo = packageManager.getPackageInfo(PKG,
+ PackageManager.GET_PERMISSIONS);
+
+ final Set<String> whitelistedPermissions = packageManager
+ .getWhitelistedRestrictedPermissions(PKG,
+ PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+ | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
+ | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE);
+
+ assertThat(whitelistedPermissions).isNotNull();
+ assertWithMessage("Whitelisted permissions").that(whitelistedPermissions)
+ .containsExactlyElementsIn(expectedWhitelistedPermissions);
+
+ // Also assert that apps ops are properly set
+ for (String permission : getRestrictedPermissionsOfApp()) {
+ String op = AppOpsManager.permissionToOp(permission);
+ ArraySet<Integer> possibleModes = new ArraySet<>();
+
+ if (permission.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
+ op = AppOpsManager.OPSTR_FINE_LOCATION;
+
+ // If permission is denied app-op might be allowed/fg or ignored. It does
+ // not matter. If permission is granted, it has to be allowed/fg.
+ if (isPermissionGranted(PKG, Manifest.permission.ACCESS_FINE_LOCATION)) {
+ if (expectedWhitelistedPermissions.contains(permission)
+ && isPermissionGranted(PKG, permission)) {
+ possibleModes.add(AppOpsManager.MODE_ALLOWED);
+ } else {
+ possibleModes.add(AppOpsManager.MODE_FOREGROUND);
+ }
+ } else {
+ possibleModes.add(AppOpsManager.MODE_IGNORED);
+ possibleModes.add(AppOpsManager.MODE_ALLOWED);
+ possibleModes.add(AppOpsManager.MODE_FOREGROUND);
+ }
+ } else {
+ if (expectedWhitelistedPermissions.contains(permission)) {
+ // If permission is denied app-op might be allowed or ignored. It does not
+ // matter. If permission is granted, it has to be allowed.
+ possibleModes.add(AppOpsManager.MODE_ALLOWED);
+ if (!isPermissionGranted(PKG, permission)) {
+ possibleModes.add(AppOpsManager.MODE_IGNORED);
+ }
+ } else {
+ possibleModes.add(AppOpsManager.MODE_IGNORED);
+ }
+ }
+
+ assertWithMessage(op).that(appOpsManager.unsafeCheckOpRawNoThrow(op,
+ packageInfo.applicationInfo.uid, PKG)).isIn(possibleModes);
+ }
+ }));
+ }
+
+ private void assertAllRestrictedPermissionGranted() throws Exception {
+ final PackageManager packageManager = getContext().getPackageManager();
+ final PackageInfo packageInfo = packageManager.getPackageInfo(
+ PKG, PackageManager.GET_PERMISSIONS);
+ if (packageInfo.requestedPermissions != null) {
+ final int permissionCount = packageInfo.requestedPermissions.length;
+ for (int i = 0; i < permissionCount; i++) {
+ final String permission = packageInfo.requestedPermissions[i];
+ final PermissionInfo permissionInfo = packageManager.getPermissionInfo(
+ permission, 0);
+ if ((permissionInfo.flags & PermissionInfo.FLAG_HARD_RESTRICTED) != 0) {
+ assertThat((packageInfo.requestedPermissionsFlags[i]
+ & PackageInfo.REQUESTED_PERMISSION_GRANTED)).isNotEqualTo(0);
+ }
+ }
+ }
+ }
+
+ private void assertNoRestrictedPermissionGranted() throws Exception {
+ assertRestrictedPermissionGranted(Collections.EMPTY_SET);
+ }
+
+ private void assertRestrictedPermissionGranted(@NonNull Set<String> expectedGrantedPermissions)
+ throws Exception {
+ final PackageManager packageManager = getContext().getPackageManager();
+ final PackageInfo packageInfo = packageManager.getPackageInfo(
+ PKG, PackageManager.GET_PERMISSIONS);
+ if (packageInfo.requestedPermissions != null) {
+ final int permissionCount = packageInfo.requestedPermissions.length;
+ for (int i = 0; i < permissionCount; i++) {
+ final String permission = packageInfo.requestedPermissions[i];
+ final PermissionInfo permissionInfo = packageManager.getPermissionInfo(
+ permission, 0);
+ if ((permissionInfo.flags & PermissionInfo.FLAG_HARD_RESTRICTED) != 0
+ || (permissionInfo.flags & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0) {
+ if (expectedGrantedPermissions.contains(permission)) {
+ assertThat((packageInfo.requestedPermissionsFlags[i]
+ & PackageInfo.REQUESTED_PERMISSION_GRANTED)).isNotEqualTo(0);
+ } else {
+ assertThat((packageInfo.requestedPermissionsFlags[i]
+ & PackageInfo.REQUESTED_PERMISSION_GRANTED)).isEqualTo(0);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Install {@link #APK_USES_SMS_CALL_LOG_29}.
+ *
+ * @param whitelistedPermissions The permission to be whitelisted. {@code null} == all
+ * @param grantedPermissions The permission to be granted. {@code null} == all
+ */
+ private void installRestrictedPermissionUserApp(@Nullable Set<String> whitelistedPermissions,
+ @Nullable Set<String> grantedPermissions) throws Exception {
+ installApp(APK_USES_SMS_CALL_LOG_29, whitelistedPermissions, grantedPermissions);
+ }
+
+ /**
+ * Install app and grant all permission.
+ *
+ * @param app The app to be installed
+ * @param whitelistedPermissions The permission to be whitelisted. {@code null} == all
+ */
+ private void installApp(@NonNull String app, @Nullable Set<String> whitelistedPermissions)
+ throws Exception {
+ installApp(app, whitelistedPermissions, null /*grantedPermissions*/);
+ }
+
+ /**
+ * Install an app.
+ *
+ * @param app The app to be installed
+ * @param whitelistedPermissions The permission to be whitelisted. {@code null} == all
+ * @param grantedPermissions The permission to be granted. {@code null} == all
+ */
+ private void installApp(@NonNull String app, @Nullable Set<String> whitelistedPermissions,
+ @Nullable Set<String> grantedPermissions) throws Exception {
+ String bypassLowTargetSdkFlag = "";
+ if (SdkLevel.isAtLeastU()) {
+ bypassLowTargetSdkFlag = " --bypass-low-target-sdk-block";
+ }
+
+ // Install the app and whitelist/grant all permission if requested.
+ String installResult = runShellCommand("pm install -r --force-queryable"
+ + bypassLowTargetSdkFlag + " --restrict-permissions " + app);
+ assertThat(installResult.trim()).isEqualTo("Success");
+
+ final Set<String> adjustedWhitelistedPermissions;
+ if (whitelistedPermissions == null) {
+ adjustedWhitelistedPermissions = getRestrictedPermissionsOfApp();
+ } else {
+ adjustedWhitelistedPermissions = whitelistedPermissions;
+ }
+
+ final Set<String> adjustedGrantedPermissions;
+ if (grantedPermissions == null) {
+ adjustedGrantedPermissions = getRestrictedPermissionsOfApp();
+ } else {
+ adjustedGrantedPermissions = grantedPermissions;
+ }
+
+ // Whitelist subset of permissions if requested
+ runWithShellPermissionIdentity(() -> {
+ final PackageManager packageManager = getContext().getPackageManager();
+ for (String permission : adjustedWhitelistedPermissions) {
+ packageManager.addWhitelistedRestrictedPermission(PKG, permission,
+ PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+ }
+ });
+
+ // Grant subset of permissions if requested
+ runWithShellPermissionIdentity(() -> {
+ final PackageManager packageManager = getContext().getPackageManager();
+ for (String permission : adjustedGrantedPermissions) {
+ packageManager.grantRuntimePermission(PKG, permission,
+ getContext().getUser());
+ packageManager.updatePermissionFlags(permission, PKG,
+ PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0, getContext().getUser());
+ }
+ });
+
+ // Mark all permissions as reviewed as for pre-22 apps the restriction state might not be
+ // applied until reviewed
+ runWithShellPermissionIdentity(() -> {
+ final PackageManager packageManager = getContext().getPackageManager();
+ for (String permission : getRequestedPermissionsOfApp()) {
+ packageManager.updatePermissionFlags(permission, PKG,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0,
+ getContext().getUser());
+ }
+ });
+ }
+
+ @After
+ public void uninstallApp() {
+ runShellCommand("pm uninstall " + PKG);
+ runShellCommand("pm uninstall " + PKG_USES_SMS_NOT_RESTRICTED_SHARED_UID);
+ runShellCommand("pm uninstall " + PKG_USES_SMS_RESTRICTED_SHARED_UID);
+ }
+
+ private static @NonNull Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getContext();
+ }
+
+ private static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable command)
+ throws Exception {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity();
+ try {
+ command.run();
+ } finally {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedStoragePermissionSharedUidTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedStoragePermissionSharedUidTest.java
new file mode 100644
index 000000000..e36841168
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedStoragePermissionSharedUidTest.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2019 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.permissionpolicy.cts;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
+import static android.permission.cts.PermissionUtils.isGranted;
+import static android.permissionpolicy.cts.RestrictedStoragePermissionSharedUidTest.StorageState.DENIED;
+import static android.permissionpolicy.cts.RestrictedStoragePermissionSharedUidTest.StorageState.ISOLATED;
+import static android.permissionpolicy.cts.RestrictedStoragePermissionSharedUidTest.StorageState.NON_ISOLATED;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static java.lang.Integer.min;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+
+@AppModeFull(reason = "Instant apps cannot access other app's properties")
+@RunWith(Parameterized.class)
+public class RestrictedStoragePermissionSharedUidTest {
+ private static final String LOG_TAG =
+ RestrictedStoragePermissionSharedUidTest.class.getSimpleName();
+
+ public enum StorageState {
+ /** The app has non-isolated storage */
+ NON_ISOLATED,
+
+ /** The app has isolated storage */
+ ISOLATED,
+
+ /** The read-external-storage permission cannot be granted */
+ DENIED
+ }
+
+ /**
+ * An app that is tested
+ */
+ private static class TestApp {
+ private static @NonNull Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+ private static @NonNull AppOpsManager sAppOpsManager =
+ sContext.getSystemService(AppOpsManager.class);
+ private static @NonNull PackageManager sPackageManager = sContext.getPackageManager();
+
+ private final String mApk;
+ private final String mPkg;
+
+ public final boolean isRestricted;
+ public final boolean hasRequestedLegacyExternalStorage;
+
+ TestApp(@NonNull String apk, @NonNull String pkg, boolean isRestricted,
+ @NonNull boolean hasRequestedLegacyExternalStorage) {
+ mApk = apk;
+ mPkg = pkg;
+
+ this.isRestricted = isRestricted;
+ this.hasRequestedLegacyExternalStorage = hasRequestedLegacyExternalStorage;
+ }
+
+ /**
+ * Assert that the read-external-storage permission was granted or not granted.
+ *
+ * @param expectGranted {@code true} if the permission is expected to be granted
+ */
+ void assertStoragePermGranted(boolean expectGranted) {
+ eventually(() -> assertWithMessage(this + " read storage granted").that(
+ isGranted(mPkg, READ_EXTERNAL_STORAGE)).isEqualTo(expectGranted));
+ }
+
+ /**
+ * Assert that the app has non-isolated storage
+ *
+ * @param expectGranted {@code true} if the app is expected to have non-isolated storage
+ */
+ void assertHasNotIsolatedStorage(boolean expectHasNotIsolatedStorage) {
+ eventually(() -> runWithShellPermissionIdentity(() -> {
+ int uid = sContext.getPackageManager().getPackageUid(mPkg, 0);
+ if (expectHasNotIsolatedStorage) {
+ assertWithMessage(this + " legacy storage mode").that(
+ sAppOpsManager.unsafeCheckOpRawNoThrow(OPSTR_LEGACY_STORAGE, uid,
+ mPkg)).isEqualTo(MODE_ALLOWED);
+ } else {
+ assertWithMessage(this + " legacy storage mode").that(
+ sAppOpsManager.unsafeCheckOpRawNoThrow(OPSTR_LEGACY_STORAGE, uid,
+ mPkg)).isNotEqualTo(MODE_ALLOWED);
+ }
+ }));
+ }
+
+ int getTargetSDK() throws Exception {
+ return sPackageManager.getApplicationInfo(mPkg, 0).targetSdkVersion;
+ }
+
+ void install() {
+ if (isRestricted) {
+ runShellCommand("pm install -g --force-queryable --restrict-permissions " + mApk);
+ } else {
+ runShellCommand("pm install -g --force-queryable " + mApk);
+ }
+ }
+
+ void uninstall() {
+ runShellCommand("pm uninstall " + mPkg);
+ }
+
+ @Override
+ public String toString() {
+ return mPkg.substring(PKG_PREFIX.length());
+ }
+ }
+
+ /**
+ * Placeholder for "no app". The properties are chosen that when combined with another app, the
+ * other app always decides the resulting property,
+ */
+ private static class NoApp extends TestApp {
+ NoApp() {
+ super("", PKG_PREFIX + "(none)", true, false);
+ }
+
+ void assertStoragePermGranted(boolean ignored) {
+ // empty
+ }
+
+ void assertHasNotIsolatedStorage(boolean ignored) {
+ // empty
+ }
+
+ @Override
+ int getTargetSDK() {
+ return 10000;
+ }
+
+ @Override
+ public void install() {
+ // empty
+ }
+
+ @Override
+ public void uninstall() {
+ // empty
+ }
+ }
+
+ private static final String APK_PATH = "/data/local/tmp/cts/permissions2/";
+ private static final String PKG_PREFIX = "android.permissionpolicy.cts.legacystoragewithshareduid.";
+
+ private static final TestApp[] TEST_APPS = new TestApp[]{
+ new TestApp(APK_PATH + "CtsLegacyStorageNotIsolatedWithSharedUid.apk",
+ PKG_PREFIX + "notisolated", false, true),
+ new TestApp(APK_PATH + "CtsLegacyStorageIsolatedWithSharedUid.apk",
+ PKG_PREFIX + "isolated", false, false),
+ new TestApp(APK_PATH + "CtsLegacyStorageRestrictedWithSharedUid.apk",
+ PKG_PREFIX + "restricted", true, false),
+ new TestApp(APK_PATH + "CtsLegacyStorageRestrictedSdk28WithSharedUid.apk",
+ PKG_PREFIX + "restrictedsdk28", true, true),
+ new NoApp()};
+
+ /**
+ * First app to be tested. This is the first in an entry created by {@link
+ * #getTestAppCombinations}
+ */
+ @Parameter(0)
+ public @NonNull TestApp app1;
+
+ /**
+ * Second app to be tested. This is the second in an entry created by {@link
+ * #getTestAppCombinations}
+ */
+ @Parameter(1)
+ public @NonNull TestApp app2;
+
+ /**
+ * Run this test for all combination of two tests-apps out of {@link #TEST_APPS}. This includes
+ * the {@link NoApp}, i.e. we also test a single test-app by itself.
+ *
+ * @return All combinations of two test-apps
+ */
+ @Parameters(name = "{0} and {1}")
+ public static Iterable<Object[]> getTestAppCombinations() {
+ ArrayList<Object[]> parameters = new ArrayList<>();
+
+ for (int firstApp = 0; firstApp < TEST_APPS.length; firstApp++) {
+ for (int secondApp = firstApp + 1; secondApp < TEST_APPS.length; secondApp++) {
+ parameters.add(new Object[]{TEST_APPS[firstApp], TEST_APPS[secondApp]});
+ }
+ }
+
+ return parameters;
+ }
+
+ @Test
+ public void checkExceptedStorageStateForAppsSharingUid() throws Exception {
+ app1.install();
+ app2.install();
+
+ int targetSDK = min(app1.getTargetSDK(), app2.getTargetSDK());
+ boolean isRestricted = app1.isRestricted && app2.isRestricted;
+ boolean hasRequestedLegacyExternalStorage =
+ app1.hasRequestedLegacyExternalStorage || app2.hasRequestedLegacyExternalStorage;
+
+ StorageState expectedState;
+ if (isRestricted) {
+ if (targetSDK < Build.VERSION_CODES.Q) {
+ expectedState = DENIED;
+ } else {
+ expectedState = ISOLATED;
+ }
+ } else if (hasRequestedLegacyExternalStorage && targetSDK <= Build.VERSION_CODES.Q) {
+ expectedState = NON_ISOLATED;
+ } else {
+ expectedState = ISOLATED;
+ }
+
+ Log.i(LOG_TAG, "Expected state=" + expectedState);
+
+ app1.assertStoragePermGranted(expectedState != DENIED);
+ app2.assertStoragePermGranted(expectedState != DENIED);
+
+ if (expectedState != DENIED) {
+ app1.assertHasNotIsolatedStorage(expectedState == NON_ISOLATED);
+ app2.assertHasNotIsolatedStorage(expectedState == NON_ISOLATED);
+ }
+ }
+
+ @After
+ public void uninstallAllTestPackages() {
+ app1.uninstall();
+ app2.uninstall();
+ }
+}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedStoragePermissionTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedStoragePermissionTest.java
new file mode 100644
index 000000000..b96ea4ba9
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedStoragePermissionTest.java
@@ -0,0 +1,754 @@
+/*
+ * Copyright (C) 2020 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.permissionpolicy.cts;
+
+import static android.permission.cts.PermissionUtils.isGranted;
+import static android.permission.cts.PermissionUtils.isPermissionGranted;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.Manifest.permission;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.platform.test.annotations.AppModeFull;
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ThrowingRunnable;
+import com.android.modules.utils.build.SdkLevel;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/** Tests for restricted storage-related permissions. */
+public class RestrictedStoragePermissionTest {
+ private static final String APK_USES_STORAGE_DEFAULT_22 =
+ "/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserDefaultSdk22.apk";
+
+ private static final String APK_USES_STORAGE_DEFAULT_28 =
+ "/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserDefaultSdk28.apk";
+
+ private static final String APK_USES_STORAGE_DEFAULT_29 =
+ "/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserDefaultSdk29.apk";
+
+ private static final String APK_USES_STORAGE_OPT_IN_22 =
+ "/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserOptInSdk22.apk";
+
+ private static final String APK_USES_STORAGE_OPT_IN_28 =
+ "/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserOptInSdk28.apk";
+
+ private static final String APK_USES_STORAGE_OPT_OUT_29 =
+ "/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserOptOutSdk29.apk";
+
+ private static final String APK_USES_STORAGE_OPT_OUT_30 =
+ "/data/local/tmp/cts/permissions2/CtsStoragePermissionsUserOptOutSdk30.apk";
+
+ private static final String APK_USES_STORAGE_PRESERVED_OPT_OUT_30 =
+ "/data/local/tmp/cts/permissions2/CtsStoragePermissionsPreservedUserOptOutSdk30.apk";
+
+ private static final String PKG = "android.permissionpolicy.cts.restrictedpermissionuser";
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk22DefaultWhitelistedHasFullAccess() throws Exception {
+ // Install with whitelisted permissions.
+ installApp(APK_USES_STORAGE_DEFAULT_22, null /*whitelistedPermissions*/);
+
+ // Check expected storage mode
+ assertHasFullStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk22OptInWhitelistedHasIsolatedAccess() throws Exception {
+ // Install with whitelisted permissions.
+ installApp(APK_USES_STORAGE_OPT_IN_22, null /*whitelistedPermissions*/);
+
+ // Check expected storage mode
+ assertHasIsolatedStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk28DefaultWhitelistedHasFullAccess() throws Exception {
+ // Install with whitelisted permissions.
+ installApp(APK_USES_STORAGE_DEFAULT_28, null /*whitelistedPermissions*/);
+
+ // Check expected storage mode
+ assertHasFullStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk28OptInWhitelistedHasIsolatedAccess() throws Exception {
+ // Install with whitelisted permissions.
+ installApp(APK_USES_STORAGE_OPT_IN_28, null /*whitelistedPermissions*/);
+
+ // Check expected storage mode
+ assertHasIsolatedStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk29DefaultWhitelistedHasIsolatedAccess() throws Exception {
+ // Install with whitelisted permissions.
+ installApp(APK_USES_STORAGE_DEFAULT_29, Collections.emptySet());
+
+ // Check expected storage mode
+ assertHasIsolatedStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk29DefaultNotWhitelistedHasIsolatedAccess() throws Exception {
+ // Install with no whitelisted permissions.
+ installApp(APK_USES_STORAGE_DEFAULT_29, null /*whitelistedPermissions*/);
+
+ // Check expected storage mode
+ assertHasIsolatedStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk29OptOutWhitelistedHasFullAccess() throws Exception {
+ // Install with whitelisted permissions.
+ installApp(APK_USES_STORAGE_OPT_OUT_29, null /*whitelistedPermissions*/);
+
+ // Check expected storage mode
+ assertHasFullStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk29OptOutNotWhitelistedHasIsolatedAccess() throws Exception {
+ // Install with no whitelisted permissions.
+ installApp(APK_USES_STORAGE_OPT_OUT_29, Collections.emptySet());
+
+ // Check expected storage mode
+ assertHasIsolatedStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk29CanOptOutViaUpdate() throws Exception {
+ installApp(APK_USES_STORAGE_DEFAULT_29, null);
+ installApp(APK_USES_STORAGE_OPT_OUT_29, null);
+
+ assertHasFullStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk29CanOptOutViaDowngradeTo28() throws Exception {
+ installApp(APK_USES_STORAGE_DEFAULT_29, null);
+ installApp(APK_USES_STORAGE_DEFAULT_28, null);
+
+ assertHasFullStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk30_cannotOptOut() throws Exception {
+ // Apps that target R and above cannot opt out of isolated storage.
+ installApp(APK_USES_STORAGE_OPT_OUT_30, null);
+
+ // Check expected storage mode
+ assertHasIsolatedStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk28CanRemoveOptInViaUpdate() throws Exception {
+ installApp(APK_USES_STORAGE_OPT_IN_28, null);
+ installApp(APK_USES_STORAGE_DEFAULT_28, null);
+
+ assertHasFullStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk28CanRemoveOptInByOptingOut() throws Exception {
+ installApp(APK_USES_STORAGE_OPT_IN_28, null);
+ installApp(APK_USES_STORAGE_OPT_OUT_29, null);
+
+ assertHasFullStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk28DoesNotLoseAccessWhenOptingIn() throws Exception {
+ installApp(APK_USES_STORAGE_DEFAULT_28, null);
+ assertHasFullStorageAccess();
+ installApp(APK_USES_STORAGE_OPT_IN_28, null);
+
+ assertHasFullStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk28DoesNotLoseAccessViaUpdate() throws Exception {
+ installApp(APK_USES_STORAGE_DEFAULT_28, null);
+ assertHasFullStorageAccess();
+ installApp(APK_USES_STORAGE_DEFAULT_29, null);
+
+ assertHasFullStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk29DoesNotLoseAccessViaUpdate() throws Exception {
+ installApp(APK_USES_STORAGE_OPT_OUT_29, null);
+ assertHasFullStorageAccess();
+ installApp(APK_USES_STORAGE_DEFAULT_29, null);
+
+ assertHasFullStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk29DoesNotLoseAccessWhenOptingIn() throws Exception {
+ installApp(APK_USES_STORAGE_OPT_OUT_29, null);
+ assertHasFullStorageAccess();
+ installApp(APK_USES_STORAGE_OPT_IN_28, null);
+
+ assertHasFullStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk29LosesAccessViaUpdateToTargetSdk30() throws Exception {
+ installApp(APK_USES_STORAGE_OPT_OUT_29, null);
+ assertHasFullStorageAccess();
+
+ installApp(APK_USES_STORAGE_OPT_OUT_30, null); // opt-out is a no-op on 30
+ assertHasIsolatedStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testTargetingSdk28LosesAccessViaUpdateToTargetSdk30() throws Exception {
+ installApp(APK_USES_STORAGE_DEFAULT_28, null);
+ assertHasFullStorageAccess();
+
+ installApp(APK_USES_STORAGE_OPT_OUT_30, null); // opt-out is a no-op on 30
+ assertHasIsolatedStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testCannotControlStorageWhitelistPostInstall1() throws Exception {
+ // Install with whitelisted permissions.
+ installApp(APK_USES_STORAGE_DEFAULT_28, null /*whitelistedPermissions*/);
+
+ // Check expected state of restricted permissions.
+ assertCannotUnWhitelistStorage();
+ }
+
+ @Test
+ @AppModeFull
+ public void testCannotControlStorageWhitelistPostInstall2() throws Exception {
+ // Install with no whitelisted permissions.
+ installApp(APK_USES_STORAGE_DEFAULT_28, Collections.emptySet());
+
+ // Check expected state of restricted permissions.
+ assertCannotWhitelistStorage();
+ }
+
+ @Test
+ @AppModeFull
+ public void cannotGrantStorageTargetingSdk22NotWhitelisted() throws Exception {
+ // Install with no whitelisted permissions.
+ installApp(APK_USES_STORAGE_DEFAULT_22, Collections.emptySet());
+
+ eventually(() -> {
+ // Could not grant permission+app-op as targetSDK<29 and not whitelisted
+ assertThat(isGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE)).isFalse();
+
+ // Permissions are always granted for pre-23 apps
+ assertThat(isPermissionGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE))
+ .isTrue();
+ });
+ }
+
+ @Test
+ @AppModeFull
+ public void cannotGrantStorageTargetingSdk22OptInNotWhitelisted() throws Exception {
+ // Install with no whitelisted permissions.
+ installApp(APK_USES_STORAGE_OPT_IN_22, Collections.emptySet());
+
+ eventually(() -> {
+ // Could not grant permission as targetSDK<29 and not whitelisted
+ assertThat(isGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE)).isFalse();
+
+ // Permissions are always granted for pre-23 apps
+ assertThat(isPermissionGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE))
+ .isTrue();
+ });
+ }
+
+ @Test
+ @AppModeFull
+ public void canGrantStorageTargetingSdk22Whitelisted() throws Exception {
+ // Install with whitelisted permissions.
+ installApp(APK_USES_STORAGE_DEFAULT_22, null);
+
+ // Could grant permission
+ eventually(() ->
+ assertThat(isGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue());
+ }
+
+ @Test
+ @AppModeFull
+ public void canGrantStorageTargetingSdk22OptInWhitelisted() throws Exception {
+ // Install with whitelisted permissions.
+ installApp(APK_USES_STORAGE_OPT_IN_22, null);
+
+ // Could grant permission
+ eventually(() ->
+ assertThat(isGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue());
+ }
+
+ @Test
+ @AppModeFull
+ public void cannotGrantStorageTargetingSdk28NotWhitelisted() throws Exception {
+ // Install with no whitelisted permissions.
+ installApp(APK_USES_STORAGE_DEFAULT_28, Collections.emptySet());
+
+ // Could not grant permission as targetSDK<29 and not whitelisted
+ eventually(() ->
+ assertThat(isGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE)).isFalse());
+ }
+
+ @Test
+ @AppModeFull
+ public void cannotGrantStorageTargetingSdk28OptInNotWhitelisted() throws Exception {
+ // Install with no whitelisted permissions.
+ installApp(APK_USES_STORAGE_OPT_IN_28, Collections.emptySet());
+
+ // Could not grant permission as targetSDK<29 and not whitelisted
+ eventually(() ->
+ assertThat(isGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE)).isFalse());
+ }
+
+ @Test
+ @AppModeFull
+ public void canGrantStorageTargetingSdk28Whitelisted() throws Exception {
+ // Install with whitelisted permissions.
+ installApp(APK_USES_STORAGE_DEFAULT_28, null);
+
+ // Could grant permission
+ eventually(() ->
+ assertThat(isGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue());
+ }
+
+ @Test
+ @AppModeFull
+ public void canGrantStorageTargetingSdk28OptInWhitelisted() throws Exception {
+ // Install with whitelisted permissions.
+ installApp(APK_USES_STORAGE_OPT_IN_28, null);
+
+ // Could grant permission
+ eventually(() ->
+ assertThat(isGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue());
+ }
+
+ @Test
+ @AppModeFull
+ public void canGrantStorageTargetingSdk29NotWhitelisted() throws Exception {
+ // Install with no whitelisted permissions.
+ installApp(APK_USES_STORAGE_DEFAULT_29, Collections.emptySet());
+
+ // Could grant permission as targetSDK=29 apps can always grant
+ eventually(() ->
+ assertThat(isGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue());
+ }
+
+ @Test
+ @AppModeFull
+ public void canGrantStorageTargetingSdk29OptOutNotWhitelisted() throws Exception {
+ // Install with no whitelisted permissions.
+ installApp(APK_USES_STORAGE_OPT_OUT_29, Collections.emptySet());
+
+ // Could grant permission as targetSDK=29 apps can always grant
+ eventually(() ->
+ assertThat(isGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue());
+ }
+
+ @Test
+ @AppModeFull
+ public void canGrantStorageTargetingSdk29Whitelisted() throws Exception {
+ // Install with whitelisted permissions.
+ installApp(APK_USES_STORAGE_DEFAULT_29, null);
+
+ // Could grant permission as targetSDK=29 apps can always grant
+ eventually(() ->
+ assertThat(isGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue());
+ }
+
+ @Test
+ @AppModeFull
+ public void canGrantStorageTargetingSdk29OptOutWhitelisted() throws Exception {
+ // Install with whitelisted permissions.
+ installApp(APK_USES_STORAGE_OPT_OUT_29, null);
+
+ // Could grant permission as targetSDK=29 apps can always grant
+ eventually(() ->
+ assertThat(isGranted(PKG, Manifest.permission.READ_EXTERNAL_STORAGE)).isTrue());
+ }
+
+ @Test
+ @AppModeFull
+ public void restrictedWritePermDoesNotImplyIsolatedStorageAccess() throws Exception {
+ // Install with whitelisted read permissions.
+ installApp(
+ APK_USES_STORAGE_OPT_OUT_29,
+ Collections.singleton(Manifest.permission.READ_EXTERNAL_STORAGE));
+
+ // It does not matter that write is restricted as the storage access level is only
+ // controlled by the read perm
+ assertHasFullStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void whitelistedWritePermDoesNotImplyFullStorageAccess() throws Exception {
+ // Install with whitelisted read permissions.
+ installApp(
+ APK_USES_STORAGE_OPT_OUT_29,
+ Collections.singleton(Manifest.permission.WRITE_EXTERNAL_STORAGE));
+
+ // It does not matter that write is white listed as the storage access level is only
+ // controlled by the read perm
+ assertHasIsolatedStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testStorageTargetingSdk30CanPreserveLegacyOnUpdateFromLegacy() throws Exception {
+ installApp(APK_USES_STORAGE_OPT_OUT_29, null);
+ assertHasFullStorageAccess();
+
+ // Updating with the flag preserves legacy
+ installApp(APK_USES_STORAGE_PRESERVED_OPT_OUT_30, null);
+ assertHasFullStorageAccess();
+
+ // And with the flag still preserves legacy
+ installApp(APK_USES_STORAGE_PRESERVED_OPT_OUT_30, null);
+ assertHasFullStorageAccess();
+
+ // But without the flag loses legacy
+ installApp(APK_USES_STORAGE_OPT_OUT_30, null);
+ assertHasIsolatedStorageAccess();
+
+ // And again with the flag doesn't bring back legacy
+ installApp(APK_USES_STORAGE_PRESERVED_OPT_OUT_30, null);
+ assertHasIsolatedStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testStorageTargetingSdk30CannotPreserveLegacyAfterLegacyUninstall()
+ throws Exception {
+ installApp(APK_USES_STORAGE_OPT_OUT_29, null);
+ assertHasFullStorageAccess();
+
+ runShellCommand("pm uninstall " + PKG);
+
+ installApp(APK_USES_STORAGE_PRESERVED_OPT_OUT_30, null);
+ assertHasIsolatedStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testStorageTargetingSdk30CannotPreserveLegacyOnUpdateFromNonLegacy()
+ throws Exception {
+ installApp(APK_USES_STORAGE_DEFAULT_29, null);
+ installApp(APK_USES_STORAGE_PRESERVED_OPT_OUT_30, null);
+
+ assertHasIsolatedStorageAccess();
+ }
+
+ @Test
+ @AppModeFull
+ public void testStorageTargetingSdk30CannotPreserveLegacyOnInstall() throws Exception {
+ installApp(APK_USES_STORAGE_PRESERVED_OPT_OUT_30, null);
+
+ assertHasIsolatedStorageAccess();
+ }
+
+ private void assertHasFullStorageAccess() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+ final int uid = getContext().getPackageManager().getPackageUid(PKG, 0);
+ eventually(() -> assertThat(appOpsManager.unsafeCheckOpRawNoThrow(
+ AppOpsManager.OPSTR_LEGACY_STORAGE,
+ uid, PKG)).isEqualTo(AppOpsManager.MODE_ALLOWED));
+ });
+ }
+
+ private void assertHasIsolatedStorageAccess() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+ final int uid = getContext().getPackageManager().getPackageUid(PKG, 0);
+ eventually(() -> assertThat(appOpsManager.unsafeCheckOpRawNoThrow(
+ AppOpsManager.OPSTR_LEGACY_STORAGE,
+ uid, PKG)).isNotEqualTo(AppOpsManager.MODE_ALLOWED));
+ });
+ }
+
+ private void assertCannotWhitelistStorage() throws Exception {
+ final PackageManager packageManager = getContext().getPackageManager();
+
+ runWithShellPermissionIdentity(() -> {
+ // Assert added only to none whitelist.
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+ | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+ | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER))
+ .doesNotContain(permission.READ_EXTERNAL_STORAGE);
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+ | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+ | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER))
+ .doesNotContain(permission.WRITE_EXTERNAL_STORAGE);
+ });
+
+ // Assert we cannot add.
+ try {
+ packageManager.addWhitelistedRestrictedPermission(
+ PKG,
+ permission.READ_EXTERNAL_STORAGE,
+ PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ try {
+ packageManager.addWhitelistedRestrictedPermission(
+ PKG,
+ permission.WRITE_EXTERNAL_STORAGE,
+ PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+ fail();
+ } catch (SecurityException expected) {
+ }
+
+ runWithShellPermissionIdentity(() -> {
+ // Assert added only to none whitelist.
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+ | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+ | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER))
+ .doesNotContain(permission.READ_EXTERNAL_STORAGE);
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+ | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+ | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER))
+ .doesNotContain(permission.WRITE_EXTERNAL_STORAGE);
+ });
+ }
+
+ private void assertCannotUnWhitelistStorage() throws Exception {
+ final PackageManager packageManager = getContext().getPackageManager();
+
+ runWithShellPermissionIdentity(() -> {
+ // Assert added only to install whitelist.
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER))
+ .contains(permission.READ_EXTERNAL_STORAGE);
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER))
+ .contains(permission.WRITE_EXTERNAL_STORAGE);
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+ | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM))
+ .doesNotContain(permission.READ_EXTERNAL_STORAGE);
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+ | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM))
+ .doesNotContain(permission.WRITE_EXTERNAL_STORAGE);
+ });
+
+ try {
+ // Assert we cannot remove.
+ packageManager.removeWhitelistedRestrictedPermission(
+ PKG,
+ permission.READ_EXTERNAL_STORAGE,
+ PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ try {
+ packageManager.removeWhitelistedRestrictedPermission(
+ PKG,
+ permission.WRITE_EXTERNAL_STORAGE,
+ PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+ fail();
+ } catch (SecurityException expected) {
+ }
+
+ runWithShellPermissionIdentity(() -> {
+ // Assert added only to install whitelist.
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER))
+ .contains(permission.READ_EXTERNAL_STORAGE);
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER))
+ .contains(permission.WRITE_EXTERNAL_STORAGE);
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+ | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM))
+ .doesNotContain(permission.READ_EXTERNAL_STORAGE);
+ assertThat(packageManager.getWhitelistedRestrictedPermissions(PKG,
+ PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+ | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM))
+ .doesNotContain(permission.WRITE_EXTERNAL_STORAGE);
+ });
+ }
+
+ private @NonNull Set<String> getPermissionsOfAppWithAnyOfFlags(int flags) throws Exception {
+ final PackageManager packageManager = getContext().getPackageManager();
+ final Set<String> restrictedPermissions = new ArraySet<>();
+ for (String permission : getRequestedPermissionsOfApp()) {
+ PermissionInfo permInfo = packageManager.getPermissionInfo(permission, 0);
+
+ if ((permInfo.flags & flags) != 0) {
+ restrictedPermissions.add(permission);
+ }
+ }
+ return restrictedPermissions;
+ }
+
+ private @NonNull Set<String> getRestrictedPermissionsOfApp() throws Exception {
+ return getPermissionsOfAppWithAnyOfFlags(
+ PermissionInfo.FLAG_HARD_RESTRICTED | PermissionInfo.FLAG_SOFT_RESTRICTED);
+ }
+
+ private @NonNull String[] getRequestedPermissionsOfApp() throws Exception {
+ final PackageManager packageManager = getContext().getPackageManager();
+ final PackageInfo packageInfo =
+ packageManager.getPackageInfo(PKG, PackageManager.GET_PERMISSIONS);
+ return packageInfo.requestedPermissions;
+ }
+
+ private static @NonNull Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getContext();
+ }
+
+ private static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable command)
+ throws Exception {
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity();
+ try {
+ command.run();
+ } finally {
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+
+ /**
+ * Install an app.
+ *
+ * @param app The app to be installed
+ * @param whitelistedPermissions The permission to be whitelisted. {@code null} == all
+ * @param grantedPermissions The permission to be granted. {@code null} == all
+ */
+ private void installApp(
+ @NonNull String app,
+ @Nullable Set<String> whitelistedPermissions)
+ throws Exception {
+ String bypassLowTargetSdkFlag = "";
+ if (SdkLevel.isAtLeastU()) {
+ bypassLowTargetSdkFlag = " --bypass-low-target-sdk-block";
+ }
+
+ // Install the app and whitelist/grant all permission if requested.
+ String installResult = runShellCommand("pm install"
+ + bypassLowTargetSdkFlag + " -t -r --restrict-permissions " + app);
+ assertThat(installResult.trim()).isEqualTo("Success");
+
+ final Set<String> adjustedWhitelistedPermissions;
+ if (whitelistedPermissions == null) {
+ adjustedWhitelistedPermissions = getRestrictedPermissionsOfApp();
+ } else {
+ adjustedWhitelistedPermissions = whitelistedPermissions;
+ }
+
+ final Set<String> adjustedGrantedPermissions = getRestrictedPermissionsOfApp();
+
+ // Whitelist subset of permissions if requested
+ runWithShellPermissionIdentity(() -> {
+ final PackageManager packageManager = getContext().getPackageManager();
+ for (String permission : adjustedWhitelistedPermissions) {
+ packageManager.addWhitelistedRestrictedPermission(
+ PKG,
+ permission,
+ PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+ }
+ });
+
+ // Grant subset of permissions if requested
+ runWithShellPermissionIdentity(() -> {
+ final PackageManager packageManager = getContext().getPackageManager();
+ for (String permission : adjustedGrantedPermissions) {
+ packageManager.grantRuntimePermission(PKG, permission, getContext().getUser());
+ packageManager.updatePermissionFlags(
+ permission,
+ PKG,
+ PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+ 0,
+ getContext().getUser());
+ }
+ });
+
+ // Mark all permissions as reviewed as for pre-22 apps the restriction state might not be
+ // applied until reviewed
+ runWithShellPermissionIdentity(() -> {
+ final PackageManager packageManager = getContext().getPackageManager();
+ for (String permission : getRequestedPermissionsOfApp()) {
+ packageManager.updatePermissionFlags(
+ permission,
+ PKG,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED,
+ 0,
+ getContext().getUser());
+ }
+ });
+ }
+
+ @After
+ public void uninstallApp() {
+ runShellCommand("pm uninstall " + PKG);
+ }
+}
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt
new file mode 100644
index 000000000..0e35b9228
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2019 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.permissionpolicy.cts
+
+import android.Manifest.permission.ACCEPT_HANDOVER
+import android.Manifest.permission.ACCESS_COARSE_LOCATION
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.Manifest.permission.ACTIVITY_RECOGNITION
+import android.Manifest.permission.ADD_VOICEMAIL
+import android.Manifest.permission.ANSWER_PHONE_CALLS
+import android.Manifest.permission.BLUETOOTH_ADVERTISE
+import android.Manifest.permission.BLUETOOTH_CONNECT
+import android.Manifest.permission.BLUETOOTH_SCAN
+import android.Manifest.permission.BODY_SENSORS
+import android.Manifest.permission.CALL_PHONE
+import android.Manifest.permission.CAMERA
+import android.Manifest.permission.GET_ACCOUNTS
+import android.Manifest.permission.NEARBY_WIFI_DEVICES
+import android.Manifest.permission.PACKAGE_USAGE_STATS
+import android.Manifest.permission.POST_NOTIFICATIONS
+import android.Manifest.permission.PROCESS_OUTGOING_CALLS
+import android.Manifest.permission.READ_CALENDAR
+import android.Manifest.permission.READ_CALL_LOG
+import android.Manifest.permission.READ_CELL_BROADCASTS
+import android.Manifest.permission.READ_CONTACTS
+import android.Manifest.permission.READ_EXTERNAL_STORAGE
+import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+import android.Manifest.permission.READ_PHONE_NUMBERS
+import android.Manifest.permission.READ_PHONE_STATE
+import android.Manifest.permission.READ_SMS
+import android.Manifest.permission.RECEIVE_MMS
+import android.Manifest.permission.RECEIVE_SMS
+import android.Manifest.permission.RECEIVE_WAP_PUSH
+import android.Manifest.permission.RECORD_AUDIO
+import android.Manifest.permission.SEND_SMS
+import android.Manifest.permission.USE_SIP
+import android.Manifest.permission.UWB_RANGING
+import android.Manifest.permission.WRITE_CALENDAR
+import android.Manifest.permission.WRITE_CALL_LOG
+import android.Manifest.permission.WRITE_CONTACTS
+import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+import android.Manifest.permission_group.UNDEFINED
+import android.app.AppOpsManager.permissionToOp
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.GET_PERMISSIONS
+import android.content.pm.PermissionInfo.PROTECTION_DANGEROUS
+import android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP
+import android.os.Build
+import android.permission.PermissionManager
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RuntimePermissionProperties {
+ private val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
+ private val pm = context.packageManager
+
+ private val platformPkg = pm.getPackageInfo("android", GET_PERMISSIONS)
+ private val platformRuntimePerms = platformPkg.permissions
+ .filter { it.protection == PROTECTION_DANGEROUS }
+ private val platformBgPermNames = platformRuntimePerms.mapNotNull { it.backgroundPermission }
+
+ @Test
+ fun allRuntimeForegroundPermissionNeedAnAppOp() {
+ val platformFgPerms =
+ platformRuntimePerms.filter { !platformBgPermNames.contains(it.name) }
+
+ for (perm in platformFgPerms) {
+ assertWithMessage("AppOp for ${perm.name}").that(permissionToOp(perm.name)).isNotNull()
+ }
+ }
+
+ @Test
+ fun groupOfRuntimePermissionsShouldBeUnknown() {
+ for (perm in platformRuntimePerms) {
+ assertWithMessage("Group of ${perm.name}").that(perm.group).isEqualTo(UNDEFINED)
+ }
+ }
+
+ @Test
+ fun allAppOpPermissionNeedAnAppOp() {
+ val platformAppOpPerms = platformPkg.permissions
+ .filter { (it.protectionFlags and PROTECTION_FLAG_APPOP) != 0 }
+ .filter {
+ // Grandfather incomplete definition of PACKAGE_USAGE_STATS
+ it.name != PACKAGE_USAGE_STATS
+ }
+
+ for (perm in platformAppOpPerms) {
+ assertWithMessage("AppOp for ${perm.name}").that(permissionToOp(perm.name)).isNotNull()
+ }
+ }
+
+ /**
+ * The permission of a background permission is the one of its foreground permission
+ */
+ @Test
+ fun allRuntimeBackgroundPermissionCantHaveAnAppOp() {
+ val platformBgPerms =
+ platformRuntimePerms.filter { platformBgPermNames.contains(it.name) }
+
+ for (perm in platformBgPerms) {
+ assertWithMessage("AppOp for ${perm.name}").that(permissionToOp(perm.name)).isNull()
+ }
+ }
+
+ /**
+ * Commonly a new runtime permission is created by splitting an old one into twice
+ */
+ @Test
+ fun runtimePermissionsShouldHaveBeenSplitFromPreviousPermission() {
+ // Runtime permissions in Android P
+ val expectedPerms = mutableSetOf(READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS, READ_CALENDAR,
+ WRITE_CALENDAR, SEND_SMS, RECEIVE_SMS, READ_SMS, RECEIVE_MMS, RECEIVE_WAP_PUSH,
+ READ_CELL_BROADCASTS, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE,
+ ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, READ_CALL_LOG, WRITE_CALL_LOG,
+ PROCESS_OUTGOING_CALLS, READ_PHONE_STATE, READ_PHONE_NUMBERS, CALL_PHONE,
+ ADD_VOICEMAIL, USE_SIP, ANSWER_PHONE_CALLS, ACCEPT_HANDOVER, RECORD_AUDIO, CAMERA,
+ BODY_SENSORS)
+
+ // Add permission split since P
+ for (sdkVersion in Build.VERSION_CODES.P + 1..Build.VERSION_CODES.CUR_DEVELOPMENT + 1) {
+ for (splitPerm in
+ context.getSystemService(PermissionManager::class.java)!!.splitPermissions) {
+ if (splitPerm.targetSdk == sdkVersion &&
+ expectedPerms.contains(splitPerm.splitPermission)) {
+ expectedPerms.addAll(splitPerm.newPermissions)
+ }
+ }
+ }
+
+ // Add runtime permission added in Q which were _not_ split from a previously existing
+ // runtime permission
+ expectedPerms.add(ACTIVITY_RECOGNITION)
+
+ // Add runtime permissions added in S which were _not_ split from a previously existing
+ // runtime permission
+ expectedPerms.add(BLUETOOTH_ADVERTISE)
+ expectedPerms.add(BLUETOOTH_CONNECT)
+ expectedPerms.add(BLUETOOTH_SCAN)
+ expectedPerms.add(UWB_RANGING)
+
+ // Add runtime permissions added in T which were _not_ split from a previously existing
+ // runtime permission
+ expectedPerms.add(POST_NOTIFICATIONS)
+ expectedPerms.add(NEARBY_WIFI_DEVICES)
+
+ // Add runtime permissions added in U which were _not_ split from a previously existing
+ // runtime permission
+ expectedPerms.add(READ_MEDIA_VISUAL_USER_SELECTED)
+
+ assertThat(expectedPerms).containsExactlyElementsIn(platformRuntimePerms.map { it.name })
+ }
+}
diff --git a/tests/cts/permissionui/Android.bp b/tests/cts/permissionui/Android.bp
new file mode 100644
index 000000000..8b0541ff3
--- /dev/null
+++ b/tests/cts/permissionui/Android.bp
@@ -0,0 +1,79 @@
+//
+// Copyright (C) 2020 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: "CtsPermissionUiTestCases",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ min_sdk_version: "30",
+ srcs: [
+ "src/**/*.kt",
+ ":CtsProviderTestUtils",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "bluetooth-test-util-lib",
+ "modules-utils-build_system",
+ "androidx.test.core",
+ "permission-test-util-lib",
+ "sts-device-util",
+ "cts-wm-util",
+ "CtsAccessibilityCommon",
+ "platform-test-rules",
+ "platform-test-annotations",
+ ],
+ data: [
+ ":CtsPermissionPolicyApp25",
+ ":CtsUsePermissionApp22",
+ ":CtsUsePermissionApp22CalendarOnly",
+ ":CtsUsePermissionApp22None",
+ ":CtsUsePermissionApp23",
+ ":CtsUsePermissionApp25",
+ ":CtsUsePermissionApp26",
+ ":CtsUsePermissionApp28",
+ ":CtsUsePermissionApp29",
+ ":CtsUsePermissionApp30",
+ ":CtsUsePermissionApp30WithBackground",
+ ":CtsUsePermissionApp30WithBluetooth",
+ ":CtsUsePermissionApp31",
+ ":CtsUsePermissionApp32",
+ ":CtsUsePermissionAppLatest",
+ ":CtsUsePermissionAppLatestNone",
+ ":CtsUsePermissionAppWithOverlay",
+ ":CtsAccessMicrophoneAppLocationProvider",
+ ":CtsHelperAppOverlay",
+ ":CtsCreateNotificationChannelsApp31",
+ ":CtsMediaPermissionApp33WithStorage",
+ ":CtsDifferentPkgNameApp",
+ ":CtsUsePermissionAppImplicitUserSelectStorage",
+ ":CtsAppThatAccessesMicAndCameraPermission",
+ ],
+ test_suites: [
+ "cts",
+ "sts",
+ "general-tests",
+ "mts-permission",
+ "automotive-tests",
+ "automotive-general-tests",
+ ],
+}
diff --git a/tests/cts/permissionui/AndroidManifest.xml b/tests/cts/permissionui/AndroidManifest.xml
new file mode 100644
index 000000000..5629366a2
--- /dev/null
+++ b/tests/cts/permissionui/AndroidManifest.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2020 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.permissionui.cts">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.GET_TASKS" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+
+ <application>
+
+ <uses-library android:name="android.test.runner" />
+
+ <service android:name="android.permission.cts.CtsNotificationListenerService"
+ android:exported="true"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService"/>
+ </intent-filter>
+ </service>
+
+ <activity android:name="com.android.compatibility.common.util.FutureResultActivity" />
+ <activity android:name=".StartForFutureActivity" />
+
+ <activity android:name=".TestInstallerActivity"
+ android:exported="true"
+ android:enabled="false"
+ android:launchMode="singleInstance">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.SHOW_APP_INFO" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <service android:name=".AccessibilityTestService1"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+ android:label="@string/test_accessibility_service"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService"/>
+ </intent-filter>
+ <meta-data android:name="android.accessibilityservice"
+ android:resource="@xml/test_accessibilityservice"/>
+ </service>
+
+ <service android:name=".AccessibilityTestService2"
+ android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+ android:label="@string/test_accessibility_service_2"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService"/>
+ </intent-filter>
+ <meta-data android:name="android.accessibilityservice"
+ android:resource="@xml/test_accessibilityservice"/>
+ </service>
+
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.permissionui.cts"
+ android:label="CTS UI tests for permissions">
+ </instrumentation>
+</manifest>
diff --git a/tests/cts/permissionui/AndroidTest.xml b/tests/cts/permissionui/AndroidTest.xml
new file mode 100644
index 000000000..5c74df2b6
--- /dev/null
+++ b/tests/cts/permissionui/AndroidTest.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2020 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 Permission UI 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="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" />
+
+ <!-- Keep screen on for Bluetooth scanning -->
+ <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" />
+ <option name="screen-always-on" value="on" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsPermissionUiTestCases.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="CtsAccessMicrophoneAppLocationProvider.apk->/data/local/tmp/cts/permissionui/CtsAccessMicrophoneAppLocationProvider.apk" />
+ <option name="push" value="CtsPermissionPolicyApp25.apk->/data/local/tmp/cts/permissionui/CtsPermissionPolicyApp25.apk" />
+ <option name="push" value="CtsUsePermissionApp22.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionApp22.apk" />
+ <option name="push" value="CtsUsePermissionApp22CalendarOnly.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionApp22CalendarOnly.apk" />
+ <option name="push" value="CtsUsePermissionApp22None.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionApp22None.apk" />
+ <option name="push" value="CtsUsePermissionApp23.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionApp23.apk" />
+ <option name="push" value="CtsUsePermissionApp25.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionApp25.apk" />
+ <option name="push" value="CtsUsePermissionApp26.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionApp26.apk" />
+ <option name="push" value="CtsUsePermissionApp28.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionApp28.apk" />
+ <option name="push" value="CtsUsePermissionApp29.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionApp29.apk" />
+ <option name="push" value="CtsUsePermissionApp30.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionApp30.apk" />
+ <option name="push" value="CtsUsePermissionApp30WithBackground.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionApp30WithBackground.apk" />
+ <option name="push" value="CtsUsePermissionApp30WithBluetooth.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionApp30WithBluetooth.apk" />
+ <option name="push" value="CtsUsePermissionApp31.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionApp31.apk" />
+ <option name="push" value="CtsUsePermissionApp32.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionApp32.apk" />
+ <option name="push" value="CtsUsePermissionAppLatest.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionAppLatest.apk" />
+ <option name="push" value="CtsUsePermissionAppLatestNone.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionAppLatestNone.apk" />
+ <option name="push" value="CtsUsePermissionAppWithOverlay.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionAppWithOverlay.apk" />
+ <option name="push" value="CtsHelperAppOverlay.apk->/data/local/tmp/cts/permissionui/CtsHelperAppOverlay.apk" />
+ <option name="push" value="CtsCreateNotificationChannelsApp31.apk->/data/local/tmp/cts/permissionui/CtsCreateNotificationChannelsApp31.apk" />
+ <option name="push" value="CtsDifferentPkgNameApp.apk->/data/local/tmp/cts/permissionui/CtsDifferentPkgNameApp.apk" />
+ <option name="push" value="CtsMediaPermissionApp33WithStorage.apk->/data/local/tmp/cts/permissionui/CtsMediaPermissionApp33WithStorage.apk" />
+ <option name="push" value="CtsUsePermissionAppImplicitUserSelectStorage.apk->/data/local/tmp/cts/permissionui/CtsUsePermissionAppImplicitUserSelectStorage.apk" />
+ <option name="push" value="CtsAppThatAccessesMicAndCameraPermission.apk->/data/local/tmp/cts/permissionui/CtsAppThatAccessesMicAndCameraPermission.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="appops set android.permissionui.cts REQUEST_INSTALL_PACKAGES allow" />
+ <option name="run-command" value="am wait-for-broadcast-barrier" />
+ <!-- ensure user setup is completed -->
+ <option name="run-command" value="settings put secure user_setup_complete 1" />
+ <!-- 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" />
+ </target_preparer>
+
+ <!-- Create place to store apks -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/permissionui" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+ </target_preparer>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/android.permissionui.cts/files" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.permissionui.cts" />
+ <option name="runtime-hint" value="5m" />
+ </test>
+</configuration>
diff --git a/tests/cts/permissionui/AppThatAccessesCameraAndMic/Android.bp b/tests/cts/permissionui/AppThatAccessesCameraAndMic/Android.bp
new file mode 100644
index 000000000..e0d9fd791
--- /dev/null
+++ b/tests/cts/permissionui/AppThatAccessesCameraAndMic/Android.bp
@@ -0,0 +1,35 @@
+//
+// 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: "CtsAppThatAccessesMicAndCameraPermission",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "31",
+
+ static_libs: [
+ "androidx.test.rules",
+ "kotlin-stdlib",
+ "kotlinx-coroutines-android",
+ ],
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
diff --git a/tests/cts/permissionui/AppThatAccessesCameraAndMic/AndroidManifest.xml b/tests/cts/permissionui/AppThatAccessesCameraAndMic/AndroidManifest.xml
new file mode 100644
index 000000000..52a84378c
--- /dev/null
+++ b/tests/cts/permissionui/AppThatAccessesCameraAndMic/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.permissionui.cts.appthataccessescameraandmic"
+ android:versionCode="1">
+
+ <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+ <uses-permission android:name="android.permission.CAMERA"/>
+
+ <application android:label="CtsCameraMicAccess">
+ <activity android:name=".AccessCameraOrMicActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="test.action.USE_CAMERA_OR_MIC" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/AppThatAccessesCameraAndMic/src/android/permissionui/cts/appthataccessescameraandmic/AccessCameraOrMicActivity.kt b/tests/cts/permissionui/AppThatAccessesCameraAndMic/src/android/permissionui/cts/appthataccessescameraandmic/AccessCameraOrMicActivity.kt
new file mode 100644
index 000000000..1adc4f064
--- /dev/null
+++ b/tests/cts/permissionui/AppThatAccessesCameraAndMic/src/android/permissionui/cts/appthataccessescameraandmic/AccessCameraOrMicActivity.kt
@@ -0,0 +1,229 @@
+/*
+ * 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.permissionui.cts.appthataccessescameraandmic
+
+import android.app.Activity
+import android.app.AppOpsManager
+import android.hardware.camera2.CameraAccessException
+import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraManager
+import android.hardware.camera2.params.OutputConfiguration
+import android.hardware.camera2.params.SessionConfiguration
+import android.media.AudioFormat.CHANNEL_IN_MONO
+import android.media.AudioFormat.ENCODING_PCM_16BIT
+import android.media.AudioRecord
+import android.media.ImageReader
+import android.media.MediaRecorder.AudioSource.MIC
+import android.os.Handler
+import android.os.Process
+import android.util.Log
+import android.util.Size
+import androidx.annotation.NonNull
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+private const val USE_CAMERA = "use_camera"
+private const val USE_MICROPHONE = "use_microphone"
+private const val USE_HOTWORD = "use_hotword"
+private const val FINISH_EARLY = "finish_early"
+private const val USE_DURATION_MS = 10000L
+private const val SAMPLE_RATE_HZ = 44100
+
+/**
+ * Activity which will, depending on the extra passed in the intent, use the camera, the microphone,
+ * or both.
+ */
+class AccessCameraOrMicActivity : Activity() {
+ private lateinit var cameraManager: CameraManager
+ private lateinit var cameraId: String
+ private var cameraDevice: CameraDevice? = null
+ private var recorder: AudioRecord? = null
+ private var appOpsManager: AppOpsManager? = null
+ private var cameraFinished = false
+ private var runCamera = false
+ private var backupCameraOpRunning = true
+ private var micFinished = false
+ private var runMic = false
+ private var hotwordFinished = false
+ private var runHotword = false
+ private var finishEarly = false
+
+ override fun onStart() {
+ super.onStart()
+ runCamera = intent.getBooleanExtra(USE_CAMERA, false)
+ runMic = intent.getBooleanExtra(USE_MICROPHONE, false)
+ runHotword = intent.getBooleanExtra(USE_HOTWORD, false)
+ finishEarly = intent.getBooleanExtra(FINISH_EARLY, false)
+
+ if (runMic) {
+ useMic()
+ }
+
+ if (runCamera) {
+ useCamera()
+ }
+
+ if (runHotword) {
+ useHotword()
+ }
+ }
+
+ override fun finish() {
+ super.finish()
+ cameraDevice?.close()
+ cameraDevice = null
+ recorder?.stop()
+ recorder = null
+ if (runCamera) {
+ appOpsManager?.finishOp(AppOpsManager.OPSTR_CAMERA, Process.myUid(), packageName)
+ }
+ if (runHotword) {
+ appOpsManager?.finishOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, Process.myUid(),
+ packageName)
+ }
+ appOpsManager = null
+ }
+
+ override fun onStop() {
+ super.onStop()
+ finish()
+ }
+
+ private val stateCallback = object : CameraDevice.StateCallback() {
+ override fun onOpened(@NonNull camDevice: CameraDevice) {
+ cameraDevice = camDevice
+ val config = cameraManager!!.getCameraCharacteristics(cameraId)
+ .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
+ val outputFormat = config!!.outputFormats[0]
+ val outputSize: Size = config!!.getOutputSizes(outputFormat)[0]
+ val handler = Handler(mainLooper)
+
+ val imageReader = ImageReader.newInstance(
+ outputSize.width, outputSize.height, outputFormat, 2)
+
+ val builder = cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
+ builder.addTarget(imageReader.surface)
+ val captureRequest = builder.build()
+ val sessionConfiguration = SessionConfiguration(
+ SessionConfiguration.SESSION_REGULAR,
+ listOf(OutputConfiguration(imageReader.surface)),
+ mainExecutor,
+ object : CameraCaptureSession.StateCallback() {
+ override fun onConfigured(session: CameraCaptureSession) {
+ session.capture(captureRequest, null, handler)
+ }
+
+ override fun onConfigureFailed(session: CameraCaptureSession) {}
+
+ override fun onReady(session: CameraCaptureSession) {}
+ })
+
+ imageReader.setOnImageAvailableListener({
+ GlobalScope.launch {
+ delay(USE_DURATION_MS)
+ if (!backupCameraOpRunning) {
+ cameraFinished = true
+ if (!runMic || micFinished) {
+ finish()
+ }
+ }
+ }
+ }, handler)
+ cameraDevice!!.createCaptureSession(sessionConfiguration)
+ }
+
+ override fun onDisconnected(@NonNull camDevice: CameraDevice) {
+ Log.e("CameraMicIndicatorsPermissionTest", "camera disconnected")
+ startBackupCamera(camDevice)
+ }
+
+ override fun onError(@NonNull camDevice: CameraDevice, error: Int) {
+ Log.e("CameraMicIndicatorsPermissionTest", "camera error $error")
+ startBackupCamera(camDevice)
+ }
+ }
+
+ private fun startBackupCamera(camDevice: CameraDevice?) {
+ // Something went wrong with the camera. Fallback to direct app op usage
+ if (runCamera && !cameraFinished) {
+ backupCameraOpRunning = true
+ appOpsManager = getSystemService(AppOpsManager::class.java)
+ appOpsManager?.startOpNoThrow(AppOpsManager.OPSTR_CAMERA, Process.myUid(), packageName)
+
+ GlobalScope.launch {
+ delay(USE_DURATION_MS)
+ cameraFinished = true
+ backupCameraOpRunning = false
+ finishIfAllDone()
+ }
+ }
+ camDevice?.close()
+ if (camDevice == cameraDevice) {
+ cameraDevice = null
+ }
+ }
+
+ @Throws(CameraAccessException::class)
+ private fun useCamera() {
+ // TODO 192690992: determine why the camera manager code is flaky
+ startBackupCamera(null)
+ /*
+ cameraManager = getSystemService(CameraManager::class.java)!!
+ cameraId = cameraManager.cameraIdList[0]
+ cameraManager.openCamera(cameraId, mainExecutor, stateCallback)
+ */
+ }
+
+ private fun useMic() {
+ val minSize =
+ AudioRecord.getMinBufferSize(SAMPLE_RATE_HZ, CHANNEL_IN_MONO, ENCODING_PCM_16BIT)
+ recorder = AudioRecord(MIC, SAMPLE_RATE_HZ, CHANNEL_IN_MONO, ENCODING_PCM_16BIT, minSize)
+ recorder?.startRecording()
+ if (finishEarly) {
+ appOpsManager = getSystemService(AppOpsManager::class.java)
+ appOpsManager?.finishOp(AppOpsManager.OPSTR_RECORD_AUDIO, Process.myUid(), packageName)
+ return
+ }
+ GlobalScope.launch {
+ delay(USE_DURATION_MS)
+ micFinished = true
+ finishIfAllDone()
+ }
+ }
+
+ private fun useHotword() {
+ appOpsManager = getSystemService(AppOpsManager::class.java)
+ appOpsManager?.startOpNoThrow(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD, Process.myUid(),
+ packageName)
+
+ GlobalScope.launch {
+ delay(USE_DURATION_MS)
+ hotwordFinished = true
+ finishIfAllDone()
+ }
+ }
+
+ private fun finishIfAllDone() {
+ if ((!runMic || micFinished) && (!runCamera || cameraFinished) &&
+ (!runHotword || hotwordFinished)) {
+ finish()
+ }
+ }
+}
diff --git a/tests/cts/permissionui/CreateNotificationChannelsApp31/Android.bp b/tests/cts/permissionui/CreateNotificationChannelsApp31/Android.bp
new file mode 100644
index 000000000..265a01c69
--- /dev/null
+++ b/tests/cts/permissionui/CreateNotificationChannelsApp31/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsCreateNotificationChannelsApp31",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "31",
+
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
diff --git a/tests/cts/permissionui/CreateNotificationChannelsApp31/AndroidManifest.xml b/tests/cts/permissionui/CreateNotificationChannelsApp31/AndroidManifest.xml
new file mode 100644
index 000000000..524dfab69
--- /dev/null
+++ b/tests/cts/permissionui/CreateNotificationChannelsApp31/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permissionui.cts.usepermission"
+ android:versionCode="1">
+
+ <uses-sdk android:minSdkVersion="31" android:targetSdkVersion="31" />
+
+ <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+
+ <application android:label="CreateNotif">
+ <activity android:name=".CreateNotificationChannelsActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="usepermission.createchannels.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/CreateNotificationChannelsApp31/src/android/permissionui/cts/usepermission/CreateNotificationChannelsActivity.kt b/tests/cts/permissionui/CreateNotificationChannelsApp31/src/android/permissionui/cts/usepermission/CreateNotificationChannelsActivity.kt
new file mode 100644
index 000000000..25d584592
--- /dev/null
+++ b/tests/cts/permissionui/CreateNotificationChannelsApp31/src/android/permissionui/cts/usepermission/CreateNotificationChannelsActivity.kt
@@ -0,0 +1,160 @@
+/*
+ * 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 android.permissionui.cts.usepermission
+
+import android.Manifest
+import android.app.Activity
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+
+const val EXTRA_CREATE_CHANNELS = "extra_create"
+const val EXTRA_REQUEST_NOTIF_PERMISSION = "extra_request_notif_permission"
+const val EXTRA_REQUEST_OTHER_PERMISSIONS = "extra_request_permissions"
+const val EXTRA_START_SECOND_ACTIVITY = "extra_start_second_activity"
+const val EXTRA_START_SECOND_APP = "extra_start_second_app"
+const val SECONDARY_APP_INTENT = "emptyactivity.main"
+const val SECONDARY_APP_PKG = "android.permissionui.cts.usepermissionother"
+const val TEST_PKG = "android.permissionui.cts"
+const val CHANNEL_ID_31 = "test_channel_id"
+const val BROADCAST_ACTION = "usepermission.createchannels.BROADCAST"
+const val DELAY_MS = 1000L
+const val LONG_DELAY_MS = 2000L
+
+class CreateNotificationChannelsActivity : Activity() {
+ private lateinit var notificationManager: NotificationManager
+ private var launchActivityOnSecondResume = false
+ private var isFirstResume = true
+ private var windowHasFocus = false
+ private var pendingCreateChannel = false
+ private val handler = Handler(Looper.getMainLooper())
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ registerReceiver(receiver, IntentFilter(BROADCAST_ACTION), RECEIVER_EXPORTED)
+ handleIntent(intent)
+ }
+
+ private fun handleIntent(providedIntent: Intent?, broacastAfterComplete: Boolean = false) {
+ if (providedIntent == null) {
+ return
+ }
+ val launchSecondActivity =
+ providedIntent.getBooleanExtra(EXTRA_START_SECOND_ACTIVITY, false)
+ notificationManager = baseContext.getSystemService(NotificationManager::class.java)!!
+ if (providedIntent.getBooleanExtra(EXTRA_START_SECOND_APP, false)) {
+ handler.postDelayed({
+ val intent2 = Intent(SECONDARY_APP_INTENT)
+ intent2.`package` = SECONDARY_APP_PKG
+ intent2.addCategory(Intent.CATEGORY_DEFAULT)
+ handler.postDelayed({
+ createChannel()
+ }, DELAY_MS)
+ startActivity(intent2)
+ }, LONG_DELAY_MS)
+ } else if (providedIntent.getBooleanExtra(EXTRA_CREATE_CHANNELS, false)) {
+ createChannel()
+ if (launchSecondActivity) {
+ launchActivityOnSecondResume = true
+ }
+ } else if (launchSecondActivity) {
+ launchSecondActivity()
+ }
+
+ if (providedIntent.getBooleanExtra(EXTRA_REQUEST_OTHER_PERMISSIONS, false)) {
+ requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), 0)
+ }
+
+ if (providedIntent.getBooleanExtra(EXTRA_REQUEST_NOTIF_PERMISSION, false)) {
+ requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), 0)
+ }
+
+ if (broacastAfterComplete) {
+ sendBroadcast(Intent(BROADCAST_ACTION).setPackage(TEST_PKG))
+ }
+ }
+
+ private val receiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ handleIntent(intent, true)
+ }
+ }
+
+ private fun launchSecondActivity() {
+ handler.postDelayed({
+ val intent2 = Intent(Intent.ACTION_MAIN)
+ intent2.`package` = packageName
+ intent2.addCategory(Intent.CATEGORY_DEFAULT)
+ intent2.putExtra(EXTRA_CREATE_CHANNELS, true)
+ startActivity(intent2)
+ }, LONG_DELAY_MS)
+ }
+
+ override fun onWindowFocusChanged(hasFocus: Boolean) {
+ windowHasFocus = hasFocus
+ if (windowHasFocus && pendingCreateChannel) {
+ pendingCreateChannel = false
+ createChannel()
+ }
+ }
+
+ private fun createChannel() {
+ // Wait until window has focus so the permission prompt can be displayed
+ if (!windowHasFocus) {
+ pendingCreateChannel = true
+ return
+ }
+
+ if (notificationManager.getNotificationChannel(CHANNEL_ID_31) == null) {
+ notificationManager.createNotificationChannel(NotificationChannel(CHANNEL_ID_31,
+ "Foreground Services", NotificationManager.IMPORTANCE_HIGH))
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (!isFirstResume && launchActivityOnSecondResume) {
+ launchSecondActivity()
+ }
+ isFirstResume = false
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array<out String>,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ val grantedPerms = arrayListOf<String>()
+ for ((i, permName) in permissions.withIndex()) {
+ if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
+ grantedPerms.add(permName)
+ }
+ }
+ sendBroadcast(
+ Intent(BROADCAST_ACTION).putStringArrayListExtra(
+ PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, grantedPerms)
+ .setPackage(TEST_PKG))
+ }
+}
diff --git a/tests/cts/permissionui/DifferentPkgNameApp/Android.bp b/tests/cts/permissionui/DifferentPkgNameApp/Android.bp
new file mode 100644
index 000000000..3db3c30b2
--- /dev/null
+++ b/tests/cts/permissionui/DifferentPkgNameApp/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsDifferentPkgNameApp",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "31",
+
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
diff --git a/tests/cts/permissionui/DifferentPkgNameApp/AndroidManifest.xml b/tests/cts/permissionui/DifferentPkgNameApp/AndroidManifest.xml
new file mode 100644
index 000000000..d0b63b597
--- /dev/null
+++ b/tests/cts/permissionui/DifferentPkgNameApp/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permissionui.cts.usepermissionother"
+ android:versionCode="1">
+
+ <uses-sdk android:minSdkVersion="31" android:targetSdkVersion="31" />
+
+ <application android:label="EmptyActivity">
+ <activity android:name=".EmptyActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="emptyactivity.main" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/DifferentPkgNameApp/src/android/permissionui/cts/usepermissionother/EmptyActivity.kt b/tests/cts/permissionui/DifferentPkgNameApp/src/android/permissionui/cts/usepermissionother/EmptyActivity.kt
new file mode 100644
index 000000000..fc4518a90
--- /dev/null
+++ b/tests/cts/permissionui/DifferentPkgNameApp/src/android/permissionui/cts/usepermissionother/EmptyActivity.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts.usepermissionother
+
+import android.app.Activity
+
+class EmptyActivity : Activity()
diff --git a/tests/cts/permissionui/HelperAppOverlay/Android.bp b/tests/cts/permissionui/HelperAppOverlay/Android.bp
new file mode 100644
index 000000000..94d9653e9
--- /dev/null
+++ b/tests/cts/permissionui/HelperAppOverlay/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 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: "CtsHelperAppOverlay",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "30",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+ certificate: ":cts-testkey2",
+}
diff --git a/tests/cts/permissionui/HelperAppOverlay/AndroidManifest.xml b/tests/cts/permissionui/HelperAppOverlay/AndroidManifest.xml
new file mode 100644
index 000000000..b26459660
--- /dev/null
+++ b/tests/cts/permissionui/HelperAppOverlay/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2020 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.permissionui.cts.helper.overlay">
+
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <application>
+ <activity android:name=".OverlayActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/HelperAppOverlay/src/android/permissionui/cts/helper/overlay/OverlayActivity.kt b/tests/cts/permissionui/HelperAppOverlay/src/android/permissionui/cts/helper/overlay/OverlayActivity.kt
new file mode 100644
index 000000000..0ac0fd027
--- /dev/null
+++ b/tests/cts/permissionui/HelperAppOverlay/src/android/permissionui/cts/helper/overlay/OverlayActivity.kt
@@ -0,0 +1,26 @@
+package android.permissionui.cts.helper.overlay
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.WindowManager
+import android.widget.LinearLayout
+import android.widget.TextView
+
+class OverlayActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val mainLayout = LinearLayout(this)
+ mainLayout.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+ val textView = TextView(this)
+
+ textView.text = "Find me!"
+ mainLayout.addView(textView)
+
+ val windowParams = WindowManager.LayoutParams()
+ windowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+ windowManager.addView(mainLayout, windowParams)
+ }
+}
diff --git a/tests/cts/permissionui/ImplicitUserSelectStorageApp/Android.bp b/tests/cts/permissionui/ImplicitUserSelectStorageApp/Android.bp
new file mode 100644
index 000000000..10088bee7
--- /dev/null
+++ b/tests/cts/permissionui/ImplicitUserSelectStorageApp/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsUsePermissionAppImplicitUserSelectStorage",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+ target_sdk_version: "33",
+ min_sdk_version: "33",
+}
diff --git a/tests/cts/permissionui/ImplicitUserSelectStorageApp/AndroidManifest.xml b/tests/cts/permissionui/ImplicitUserSelectStorageApp/AndroidManifest.xml
new file mode 100644
index 000000000..212bf1508
--- /dev/null
+++ b/tests/cts/permissionui/ImplicitUserSelectStorageApp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permissionui.cts.usepermission">
+
+ <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
+ <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
+ <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
+
+ <application>
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/MediaPermissionApp33WithStorage/Android.bp b/tests/cts/permissionui/MediaPermissionApp33WithStorage/Android.bp
new file mode 100644
index 000000000..77664c40b
--- /dev/null
+++ b/tests/cts/permissionui/MediaPermissionApp33WithStorage/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsMediaPermissionApp33WithStorage",
+ min_sdk_version: "33",
+ target_sdk_version: "33",
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+}
diff --git a/tests/cts/permissionui/MediaPermissionApp33WithStorage/AndroidManifest.xml b/tests/cts/permissionui/MediaPermissionApp33WithStorage/AndroidManifest.xml
new file mode 100644
index 000000000..ae21d1612
--- /dev/null
+++ b/tests/cts/permissionui/MediaPermissionApp33WithStorage/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permissionui.cts.usepermission"
+ android:versionCode="1">
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application>
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/OWNERS b/tests/cts/permissionui/OWNERS
new file mode 100644
index 000000000..01fbb4851
--- /dev/null
+++ b/tests/cts/permissionui/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 137825
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
+
diff --git a/tests/cts/permissionui/PermissionPolicyApp25/Android.bp b/tests/cts/permissionui/PermissionPolicyApp25/Android.bp
new file mode 100644
index 000000000..d3f88954c
--- /dev/null
+++ b/tests/cts/permissionui/PermissionPolicyApp25/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2017 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: "CtsPermissionPolicyApp25",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+ certificate: ":cts-testkey2",
+ min_sdk_version: "25",
+}
diff --git a/tests/cts/permissionui/PermissionPolicyApp25/AndroidManifest.xml b/tests/cts/permissionui/PermissionPolicyApp25/AndroidManifest.xml
new file mode 100644
index 000000000..531ea47ac
--- /dev/null
+++ b/tests/cts/permissionui/PermissionPolicyApp25/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2017 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.permissionui.cts.permissionpolicy">
+
+ <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="25" />
+
+ <application>
+ <activity android:name=".TestProtectionFlagsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/PermissionPolicyApp25/src/android/permissionui/cts/permissionpolicy/TestProtectionFlagsActivity.kt b/tests/cts/permissionui/PermissionPolicyApp25/src/android/permissionui/cts/permissionpolicy/TestProtectionFlagsActivity.kt
new file mode 100644
index 000000000..b5fea9b8f
--- /dev/null
+++ b/tests/cts/permissionui/PermissionPolicyApp25/src/android/permissionui/cts/permissionpolicy/TestProtectionFlagsActivity.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 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.permissionui.cts.permissionpolicy
+
+import android.app.Activity
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.PermissionInfo
+import android.os.Bundle
+
+/**
+ * An activity that can test platform permission protection flags.
+ */
+class TestProtectionFlagsActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setResult(
+ RESULT_OK, Intent().apply {
+ putExtra("$packageName.ERROR_MESSAGE", getProtectionFlagsErrorMessage())
+ }
+ )
+ finish()
+ }
+
+ private fun getProtectionFlagsErrorMessage(): String {
+ val packageInfo = packageManager.getPackageInfo("android", PackageManager.GET_PERMISSIONS)
+ val errorMessageBuilder = StringBuilder()
+ for (declaredPermissionInfo in packageInfo.permissions) {
+ val permissionInfo = packageManager.getPermissionInfo(declaredPermissionInfo.name, 0)
+ val protection = permissionInfo.protection and (
+ PermissionInfo.PROTECTION_NORMAL
+ or PermissionInfo.PROTECTION_DANGEROUS
+ or PermissionInfo.PROTECTION_SIGNATURE
+ or PermissionInfo.PROTECTION_INTERNAL
+ )
+ val protectionFlags = permissionInfo.protectionLevel and protection.inv()
+ if ((protection == PermissionInfo.PROTECTION_NORMAL ||
+ protection == PermissionInfo.PROTECTION_DANGEROUS) && protectionFlags != 0) {
+ errorMessageBuilder.apply {
+ if (isNotEmpty()) {
+ append("\n")
+ }
+ append("Cannot add protection flags ${protectionFlagsToString(protectionFlags)
+ } to a ${protectionToString(protection)} protection permission: ${
+ permissionInfo.name}")
+ }
+ }
+ }
+ return errorMessageBuilder.toString()
+ }
+
+ private fun protectionToString(protection: Int): String =
+ when (protection) {
+ PermissionInfo.PROTECTION_NORMAL -> "normal"
+ PermissionInfo.PROTECTION_DANGEROUS -> "dangerous"
+ PermissionInfo.PROTECTION_SIGNATURE -> "signature"
+ PermissionInfo.PROTECTION_INTERNAL -> "internal"
+ else -> Integer.toHexString(protection)
+ }
+
+ private fun protectionFlagsToString(protectionFlags: Int): String {
+ var unknownProtectionFlags = protectionFlags
+ val stringBuilder = StringBuilder()
+ val appendProtectionFlag = { protectionFlag: Int, protectionFlagString: String ->
+ if (unknownProtectionFlags and protectionFlag == protectionFlag) {
+ stringBuilder.apply {
+ if (isNotEmpty()) {
+ append("|")
+ }
+ append(protectionFlagString)
+ }
+ unknownProtectionFlags = unknownProtectionFlags and protectionFlag.inv()
+ }
+ }
+ appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_PRIVILEGED, "privileged")
+ appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_DEVELOPMENT, "development")
+ appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_APPOP, "appop")
+ appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_PRE23, "pre23")
+ appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_INSTALLER, "installer")
+ appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_VERIFIER, "verifier")
+ appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_PREINSTALLED, "preinstalled")
+ appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_SETUP, "setup")
+ appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_INSTANT, "instant")
+ appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY, "runtimeOnly")
+ appendProtectionFlag(PermissionInfo.PROTECTION_FLAG_ROLE, "role")
+ if (unknownProtectionFlags != 0) {
+ appendProtectionFlag(
+ unknownProtectionFlags, Integer.toHexString(unknownProtectionFlags)
+ )
+ }
+ return stringBuilder.toString()
+ }
+}
diff --git a/tests/cts/permissionui/TEST_MAPPING b/tests/cts/permissionui/TEST_MAPPING
new file mode 100644
index 000000000..f992ea1a3
--- /dev/null
+++ b/tests/cts/permissionui/TEST_MAPPING
@@ -0,0 +1,32 @@
+{
+ "presubmit-large": [
+ {
+ "name": "CtsPermissionUiTestCases",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "CtsPermissionUiTestCases[com.google.android.permission.apex]",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsPermissionUiTestCases"
+ }
+ ],
+ "mainline-postsubmit": [
+ {
+ "name": "CtsPermissionUiTestCases[com.google.android.permission.apex]"
+ }
+ ]
+} \ No newline at end of file
diff --git a/tests/cts/permissionui/UsePermissionApp22/Android.bp b/tests/cts/permissionui/UsePermissionApp22/Android.bp
new file mode 100644
index 000000000..dcb04f6a4
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp22/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2015 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: "CtsUsePermissionApp22",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+ min_sdk_version: "22",
+}
diff --git a/tests/cts/permissionui/UsePermissionApp22/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp22/AndroidManifest.xml
new file mode 100644
index 000000000..6a8bb1fd8
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp22/AndroidManifest.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2015 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.permissionui.cts.usepermission">
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22" />
+
+ <!-- Make sure permission code can handle invalid permissions -->
+ <uses-permission android:name="android.permissionui.cts.usepermission.INVALID_PERMISSION_NAME" />
+
+ <!-- Request two different permissions within the same group -->
+ <uses-permission android:name="android.permission.SEND_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+
+ <!-- Contacts -->
+ <!-- Deliberately request WRITE_CONTACTS but *not* READ_CONTACTS -->
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+
+ <!-- Calendar -->
+ <uses-permission android:name="android.permission.READ_CALENDAR" />
+ <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+
+ <!-- SMS -->
+ <uses-permission android:name="android.permission.SEND_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+ <uses-permission android:name="android.permission.READ_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
+ <uses-permission android:name="android.permission.RECEIVE_MMS" />
+ <uses-permission android:name="android.permission.READ_CELL_BROADCASTS" />
+
+ <!-- Storage -->
+ <!-- Special case: WRITE_EXTERNAL_STORAGE implies READ_EXTERNAL_STORAGE -->
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <!-- Location -->
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Phone -->
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.CALL_PHONE" />
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+ <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
+ <uses-permission android:name="android.permission.USE_SIP" />
+ <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
+
+ <!-- Phone -->
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+ <!-- Camera -->
+ <uses-permission android:name="android.permission.CAMERA" />
+
+ <!-- Body Sensors -->
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
+
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionApp22CalendarOnly/Android.bp b/tests/cts/permissionui/UsePermissionApp22CalendarOnly/Android.bp
new file mode 100644
index 000000000..26ff13566
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp22CalendarOnly/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2015 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: "CtsUsePermissionApp22CalendarOnly",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+ min_sdk_version: "22",
+}
diff --git a/tests/cts/permissionui/UsePermissionApp22CalendarOnly/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp22CalendarOnly/AndroidManifest.xml
new file mode 100644
index 000000000..0610562b0
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp22CalendarOnly/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2018 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.permissionui.cts.usepermission">
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22" />
+
+ <!-- Calendar -->
+ <uses-permission android:name="android.permission.READ_CALENDAR" />
+ <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ <activity android:name=".StartCheckPermissionServiceActivity" android:exported="true" />
+ <service android:name=".CheckPermissionService" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionApp22CalendarOnly/src/android/permissionui/cts/usepermission/CheckPermissionService.kt b/tests/cts/permissionui/UsePermissionApp22CalendarOnly/src/android/permissionui/cts/usepermission/CheckPermissionService.kt
new file mode 100644
index 000000000..635462470
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp22CalendarOnly/src/android/permissionui/cts/usepermission/CheckPermissionService.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.permissionui.cts.usepermission
+
+import android.app.IntentService
+import android.content.Intent
+import android.os.ResultReceiver
+
+/**
+ * A service that can check if a permission is currently granted
+ */
+class CheckPermissionService : IntentService(CheckPermissionService::class.java.simpleName) {
+ companion object {
+ private const val TEST_PACKAGE_NAME = "android.permissionui.cts"
+ }
+
+ override fun onHandleIntent(intent: Intent?) {
+ val extras = intent!!.extras!!
+ // Load bundle with context of client package so ResultReceiver class can be resolved
+ val testContext = createPackageContext(
+ TEST_PACKAGE_NAME, CONTEXT_INCLUDE_CODE or CONTEXT_IGNORE_SECURITY
+ )
+ extras.classLoader = testContext.classLoader
+ val result = extras.getParcelable<ResultReceiver>("$packageName.RESULT")!!
+ val permission = extras.getString("$packageName.PERMISSION")!!
+ result.send(checkSelfPermission(permission), null)
+ }
+}
diff --git a/tests/cts/permissionui/UsePermissionApp22CalendarOnly/src/android/permissionui/cts/usepermission/StartCheckPermissionServiceActivity.kt b/tests/cts/permissionui/UsePermissionApp22CalendarOnly/src/android/permissionui/cts/usepermission/StartCheckPermissionServiceActivity.kt
new file mode 100644
index 000000000..ebb4c2f12
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp22CalendarOnly/src/android/permissionui/cts/usepermission/StartCheckPermissionServiceActivity.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 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.permissionui.cts.usepermission
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+
+class StartCheckPermissionServiceActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ startService(Intent(this, CheckPermissionService::class.java).putExtras(intent))
+ finish()
+ }
+}
diff --git a/tests/cts/permissionui/UsePermissionApp22None/Android.bp b/tests/cts/permissionui/UsePermissionApp22None/Android.bp
new file mode 100644
index 000000000..7d43f46ef
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp22None/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 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: "CtsUsePermissionApp22None",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+ min_sdk_version: "22",
+}
diff --git a/tests/cts/permissionui/UsePermissionApp22None/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp22None/AndroidManifest.xml
new file mode 100644
index 000000000..8ee3094ea
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp22None/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2020 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.permissionui.cts.usepermission">
+
+ <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22" />
+
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionApp23/Android.bp b/tests/cts/permissionui/UsePermissionApp23/Android.bp
new file mode 100644
index 000000000..b98b2d3a3
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp23/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2015 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: "CtsUsePermissionApp23",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+ min_sdk_version: "23",
+}
diff --git a/tests/cts/permissionui/UsePermissionApp23/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp23/AndroidManifest.xml
new file mode 100644
index 000000000..c1bb806a5
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp23/AndroidManifest.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2015 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.permissionui.cts.usepermission">
+
+ <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23" />
+
+ <!-- Request two different permissions within the same group -->
+ <uses-permission android:name="android.permission.SEND_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+
+ <!-- Contacts -->
+ <!-- Deliberately request WRITE_CONTACTS but *not* READ_CONTACTS -->
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+
+ <!-- Calendar -->
+ <uses-permission android:name="android.permission.READ_CALENDAR" />
+ <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+
+ <!-- SMS -->
+ <uses-permission android:name="android.permission.SEND_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+ <uses-permission android:name="android.permission.READ_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
+ <uses-permission android:name="android.permission.RECEIVE_MMS" />
+ <uses-permission android:name="android.permission.READ_CELL_BROADCASTS" />
+
+ <!-- Storage -->
+ <!-- Special case: WRITE_EXTERNAL_STORAGE implies READ_EXTERNAL_STORAGE -->
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <!-- Location -->
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+ <!-- Phone -->
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.CALL_PHONE" />
+ <uses-permission android:name="android.permission.READ_CALL_LOG" />
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+ <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
+ <uses-permission android:name="android.permission.USE_SIP" />
+ <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
+
+ <!-- Phone -->
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+ <!-- Camera -->
+ <uses-permission android:name="android.permission.CAMERA" />
+
+ <!-- Body Sensors -->
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
+
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionApp25/Android.bp b/tests/cts/permissionui/UsePermissionApp25/Android.bp
new file mode 100644
index 000000000..d16d2c486
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp25/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2017 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: "CtsUsePermissionApp25",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+ min_sdk_version: "25",
+}
diff --git a/tests/cts/permissionui/UsePermissionApp25/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp25/AndroidManifest.xml
new file mode 100644
index 000000000..f97a6b329
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp25/AndroidManifest.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2017 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.permissionui.cts.usepermission">
+
+ <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="25" />
+
+ <!-- Request two different permissions within the same group -->
+ <uses-permission android:name="android.permission.SEND_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+
+ <!-- Contacts -->
+ <!-- Deliberately request WRITE_CONTACTS but *not* READ_CONTACTS -->
+ <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+
+ <!-- Calendar -->
+ <uses-permission android:name="android.permission.READ_CALENDAR"/>
+ <uses-permission android:name="android.permission.WRITE_CALENDAR"/>
+
+ <!-- SMS -->
+ <uses-permission android:name="android.permission.SEND_SMS"/>
+ <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+ <uses-permission android:name="android.permission.READ_SMS"/>
+ <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>
+ <uses-permission android:name="android.permission.RECEIVE_MMS"/>
+ <uses-permission android:name="android.permission.READ_CELL_BROADCASTS"/>
+
+ <!-- Storage -->
+ <!-- Special case: WRITE_EXTERNAL_STORAGE implies READ_EXTERNAL_STORAGE -->
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+ <!-- Location -->
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+
+ <!-- Phone -->
+ <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+ <uses-permission android:name="android.permission.CALL_PHONE"/>
+ <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+ <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
+ <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"/>
+ <uses-permission android:name="android.permission.USE_SIP"/>
+ <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
+
+ <!-- Phone -->
+ <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+
+ <!-- Camera -->
+ <uses-permission android:name="android.permission.CAMERA"/>
+
+ <!-- Body Sensors -->
+ <uses-permission android:name="android.permission.BODY_SENSORS"/>
+
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionApp26/Android.bp b/tests/cts/permissionui/UsePermissionApp26/Android.bp
new file mode 100644
index 000000000..d76163b3a
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp26/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2017 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: "CtsUsePermissionApp26",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+ min_sdk_version: "26",
+}
diff --git a/tests/cts/permissionui/UsePermissionApp26/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp26/AndroidManifest.xml
new file mode 100644
index 000000000..4c29436cf
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp26/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2017 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.permissionui.cts.usepermission">
+
+ <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="26" />
+
+ <!-- Request two different permissions within the same group -->
+ <uses-permission android:name="android.permission.SEND_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionApp28/Android.bp b/tests/cts/permissionui/UsePermissionApp28/Android.bp
new file mode 100644
index 000000000..1f388fdc7
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp28/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2018 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: "CtsUsePermissionApp28",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+ min_sdk_version: "28",
+}
diff --git a/tests/cts/permissionui/UsePermissionApp28/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp28/AndroidManifest.xml
new file mode 100644
index 000000000..98ede951f
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp28/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2018 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.permissionui.cts.usepermission">
+
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionApp29/Android.bp b/tests/cts/permissionui/UsePermissionApp29/Android.bp
new file mode 100644
index 000000000..8375b1e77
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp29/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2018 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: "CtsUsePermissionApp29",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+ min_sdk_version: "29",
+}
diff --git a/tests/cts/permissionui/UsePermissionApp29/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp29/AndroidManifest.xml
new file mode 100644
index 000000000..f1ae6c9d8
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp29/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2018 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.permissionui.cts.usepermission">
+
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionApp30/Android.bp b/tests/cts/permissionui/UsePermissionApp30/Android.bp
new file mode 100644
index 000000000..c1b4b8a9f
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp30/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2020 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: "CtsUsePermissionApp30",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+
+ min_sdk_version: "30",
+}
diff --git a/tests/cts/permissionui/UsePermissionApp30/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp30/AndroidManifest.xml
new file mode 100644
index 000000000..7266fc1b6
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp30/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2020 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.permissionui.cts.usepermission">
+
+ <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+
+ <!-- Request two different permissions within the same group -->
+ <uses-permission android:name="android.permission.SEND_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+ <uses-permission android:name="android.permission.CAMERA" />
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionApp30WithBackground/Android.bp b/tests/cts/permissionui/UsePermissionApp30WithBackground/Android.bp
new file mode 100644
index 000000000..eeac4ff8b
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp30WithBackground/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 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: "CtsUsePermissionApp30WithBackground",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+ certificate: ":cts-testkey2",
+
+ min_sdk_version: "30",
+}
diff --git a/tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml
new file mode 100644
index 000000000..5949d08f2
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2020 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.permissionui.cts.usepermission">
+
+ <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+ <application>
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionApp30WithBackground/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt b/tests/cts/permissionui/UsePermissionApp30WithBackground/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt
new file mode 100644
index 000000000..c4f8b4927
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp30WithBackground/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.permissionui.cts.usepermission
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+
+class RequestPermissionsActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val permissions = intent.getStringArrayExtra("$packageName.PERMISSIONS")!!
+ requestPermissions(permissions, 1)
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array<out String>,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+
+ setResult(RESULT_OK, Intent().apply {
+ putExtra("$packageName.PERMISSIONS", permissions)
+ putExtra("$packageName.GRANT_RESULTS", grantResults)
+ })
+ finish()
+ }
+}
diff --git a/tests/cts/permissionui/UsePermissionApp30WithBluetooth/Android.bp b/tests/cts/permissionui/UsePermissionApp30WithBluetooth/Android.bp
new file mode 100644
index 000000000..d92ce5732
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp30WithBluetooth/Android.bp
@@ -0,0 +1,34 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsUsePermissionApp30WithBluetooth",
+ srcs: [
+ "src/**/*.kt"
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ ],
+ certificate: ":cts-testkey2",
+
+ min_sdk_version: "30",
+}
diff --git a/tests/cts/permissionui/UsePermissionApp30WithBluetooth/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp30WithBluetooth/AndroidManifest.xml
new file mode 100644
index 000000000..4d01e99c5
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp30WithBluetooth/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permissionui.cts.usepermission">
+
+ <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+
+ <application>
+ <provider
+ android:name=".AccessBluetoothOnCommand"
+ android:authorities="android.permissionui.cts.usepermission.AccessBluetoothOnCommand"
+ android:exported="true" />
+ </application>
+
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionApp30WithBluetooth/src/android/permissionui/cts/usepermission/AccessBluetoothOnCommand.kt b/tests/cts/permissionui/UsePermissionApp30WithBluetooth/src/android/permissionui/cts/usepermission/AccessBluetoothOnCommand.kt
new file mode 100644
index 000000000..107dd4469
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp30WithBluetooth/src/android/permissionui/cts/usepermission/AccessBluetoothOnCommand.kt
@@ -0,0 +1,145 @@
+/*
+ * 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 android.permissionui.cts.usepermission
+
+import android.bluetooth.BluetoothManager
+import android.bluetooth.le.BluetoothLeScanner
+import android.bluetooth.le.ScanCallback
+import android.bluetooth.le.ScanResult
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Intent
+import android.database.Cursor
+import android.net.Uri
+import android.os.Bundle
+import android.os.SystemClock
+import android.util.Base64
+import android.util.Log
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicInteger
+
+private const val LOG_TAG = "AccessBluetoothOnCommand"
+
+class AccessBluetoothOnCommand : ContentProvider() {
+
+ private enum class Result {
+ UNKNOWN, ERROR, EXCEPTION, EMPTY, FILTERED, FULL
+ }
+
+ override fun call(authority: String, method: String, arg: String?, extras: Bundle?): Bundle? {
+ Log.v(LOG_TAG, "call() - start")
+ val res = Bundle()
+
+ var scanner: BluetoothLeScanner? = null
+ var scanCallback: ScanCallback? = null
+
+ try {
+
+ scanner = context!!.getSystemService(BluetoothManager::class.java)
+ ?.adapter!!.bluetoothLeScanner
+
+ val observedScans: MutableSet<String> = ConcurrentHashMap.newKeySet()
+ val observedErrorCode = AtomicInteger(0)
+
+ scanCallback = object : ScanCallback() {
+ override fun onScanResult(callbackType: Int, result: ScanResult) {
+ Log.v(LOG_TAG, "onScanResult() - result = $result")
+ observedScans.add(Base64.encodeToString(result.scanRecord!!.bytes, 0))
+ }
+
+ override fun onBatchScanResults(results: List<ScanResult>) {
+ Log.v(LOG_TAG, "onBatchScanResults() - results.size = ${results.size}")
+ for (result in results) {
+ onScanResult(0, result)
+ }
+ }
+
+ override fun onScanFailed(errorCode: Int) {
+ Log.e(LOG_TAG, "onScanFailed() - errorCode = $errorCode")
+ observedErrorCode.set(errorCode)
+ }
+ }
+
+ Log.v(LOG_TAG, "call() - startScan...")
+ scanner.startScan(scanCallback)
+
+ // Wait a few seconds to figure out what we actually observed
+ Log.v(LOG_TAG, "call() - sleep...")
+ SystemClock.sleep(3000)
+
+ if (observedErrorCode.get() > 0) {
+ Log.v(LOG_TAG, "call() observed error: ${observedErrorCode.get()}")
+ res.putInt(Intent.EXTRA_INDEX, Result.ERROR.ordinal)
+ return res
+ }
+ Log.v(LOG_TAG, "call() - (scanCount=${observedScans.size})")
+
+ when (observedScans.size) {
+ 0 -> res.putInt(Intent.EXTRA_INDEX, Result.EMPTY.ordinal)
+ 1 -> res.putInt(Intent.EXTRA_INDEX, Result.FILTERED.ordinal)
+ 5 -> res.putInt(Intent.EXTRA_INDEX, Result.FULL.ordinal)
+ else -> res.putInt(Intent.EXTRA_INDEX, Result.UNKNOWN.ordinal)
+ }
+ } catch (t: Throwable) {
+ Log.e(LOG_TAG, "call() - EXCEPTION", t)
+ res.putInt(Intent.EXTRA_INDEX, Result.EXCEPTION.ordinal)
+ } finally {
+ try {
+ Log.v(LOG_TAG, "call() - finally - stopScan...")
+ scanner!!.stopScan(scanCallback)
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "call() - finally - EXCEPTION", e)
+ }
+ }
+ Log.v(LOG_TAG, "call() - end")
+ return res
+ }
+
+ override fun onCreate(): Boolean {
+ return true
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array<String>?,
+ selection: String?,
+ selectionArgs: Array<String>?,
+ sortOrder: String?
+ ): Cursor? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getType(uri: Uri): String? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
+ throw UnsupportedOperationException()
+ }
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<String>?
+ ): Int {
+ throw UnsupportedOperationException()
+ }
+}
diff --git a/tests/cts/permissionui/UsePermissionApp31/Android.bp b/tests/cts/permissionui/UsePermissionApp31/Android.bp
new file mode 100644
index 000000000..4424d95fe
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp31/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsUsePermissionApp31",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+ target_sdk_version: "31",
+ min_sdk_version: "31",
+}
diff --git a/tests/cts/permissionui/UsePermissionApp31/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp31/AndroidManifest.xml
new file mode 100644
index 000000000..31d025ea0
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp31/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permissionui.cts.usepermission">
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionApp32/Android.bp b/tests/cts/permissionui/UsePermissionApp32/Android.bp
new file mode 100644
index 000000000..874af0e06
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp32/Android.bp
@@ -0,0 +1,34 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsUsePermissionApp32",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+
+ min_sdk_version: "32",
+ target_sdk_version: "32",
+}
diff --git a/tests/cts/permissionui/UsePermissionApp32/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp32/AndroidManifest.xml
new file mode 100644
index 000000000..923139e66
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp32/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permissionui.cts.usepermission">
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <application>
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionAppLatest/Android.bp b/tests/cts/permissionui/UsePermissionAppLatest/Android.bp
new file mode 100644
index 000000000..c9e9f8fb0
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLatest/Android.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2017 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"],
+}
+
+filegroup {
+ name: "CtsUsePermissionAppSrc",
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsUsePermissionAppLatest",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "30",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+}
diff --git a/tests/cts/permissionui/UsePermissionAppLatest/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionAppLatest/AndroidManifest.xml
new file mode 100644
index 000000000..49e45fc01
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLatest/AndroidManifest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2017 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.permissionui.cts.usepermission">
+
+ <!-- Request two different permissions within the same group -->
+ <uses-permission android:name="android.permission.SEND_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
+
+ <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
+ <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
+ <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
+ <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
+ <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
+
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ <activity-alias
+ android:name=".ViewPermissionUsageActivity"
+ android:exported="true"
+ android:permission="android.permission.START_VIEW_PERMISSION_USAGE"
+ android:targetActivity=".FinishOnCreateActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
+ </intent-filter>
+ </activity-alias>
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/CheckCalendarAccessActivity.kt b/tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/CheckCalendarAccessActivity.kt
new file mode 100644
index 000000000..41edbd3f1
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/CheckCalendarAccessActivity.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 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.permissionui.cts.usepermission
+
+import android.app.Activity
+import android.content.ContentValues
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.provider.CalendarContract
+
+/**
+ * An activity that can check calendar access.
+ */
+class CheckCalendarAccessActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val supportsRuntimePermissions = applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M
+ val hasAccess: Boolean
+ val uri = try {
+ contentResolver.insert(
+ CalendarContract.Calendars.CONTENT_URI, ContentValues().apply {
+ put(CalendarContract.Calendars.NAME, "cts" + System.nanoTime())
+ put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, "cts")
+ put(CalendarContract.Calendars.CALENDAR_COLOR, 0xffff0000)
+ }
+ )!!
+ } catch (e: SecurityException) {
+ null
+ }
+ hasAccess = if (uri != null) {
+ val count = contentResolver.query(uri, null, null, null).use { it!!.count }
+ if (supportsRuntimePermissions) {
+ assert(count == 1)
+ true
+ } else {
+ // Without access we're handed back a "fake" Uri that doesn't contain
+ // any of the data we tried persisting
+ assert(count == 0 || count == 1)
+ count == 1
+ }
+ } else {
+ assert(supportsRuntimePermissions)
+ try {
+ contentResolver.query(CalendarContract.Calendars.CONTENT_URI, null, null, null)
+ .use {}
+ error("Expected SecurityException")
+ } catch (e: SecurityException) {}
+ false
+ }
+ setResult(RESULT_OK, Intent().apply { putExtra("$packageName.HAS_ACCESS", hasAccess) })
+ finish()
+ }
+}
diff --git a/tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/FinishOnCreateActivity.kt b/tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/FinishOnCreateActivity.kt
new file mode 100644
index 000000000..693a8c8f9
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/FinishOnCreateActivity.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 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.permissionui.cts.usepermission
+
+import android.app.Activity
+import android.os.Bundle
+
+class FinishOnCreateActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setResult(RESULT_OK)
+ finish()
+ }
+}
diff --git a/tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt b/tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt
new file mode 100644
index 000000000..4df0cf10d
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 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.permissionui.cts.usepermission
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import com.android.modules.utils.build.SdkLevel
+
+class RequestPermissionsActivity : Activity() {
+
+ private var shouldAskTwice = false
+ private var timesAsked = 0
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (savedInstanceState == null) {
+ val permissions = intent.getStringArrayExtra("$packageName.PERMISSIONS")!!
+ shouldAskTwice = intent.getBooleanExtra("$packageName.ASK_TWICE", false)
+ if (SdkLevel.isAtLeastV()) {
+ // TODO: make deviceId dynamic
+ requestPermissions(permissions, 1, Context.DEVICE_ID_DEFAULT)
+ } else {
+ requestPermissions(permissions, 1)
+ }
+ timesAsked = 1
+ }
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array<out String>,
+ grantResults: IntArray
+ ) {
+ handleResult(permissions, grantResults)
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array<out String>,
+ grantResults: IntArray,
+ deviceId: Int
+ ) {
+ handleResult(permissions, grantResults, deviceId)
+ }
+
+ private fun handleResult(
+ permissions: Array<out String>,
+ grantResults: IntArray,
+ deviceId: Int? = null
+ ) {
+ if (shouldAskTwice && timesAsked < 2) {
+ requestPermissions(permissions, 1)
+ timesAsked += 1
+ return
+ }
+
+ setResult(
+ RESULT_OK,
+ Intent().apply {
+ putExtra("$packageName.PERMISSIONS", permissions)
+ putExtra("$packageName.GRANT_RESULTS", grantResults)
+ if (deviceId != null) {
+ putExtra("$packageName.DEVICE_ID", deviceId)
+ }
+ }
+ )
+ finish()
+ }
+}
diff --git a/tests/cts/permissionui/UsePermissionAppLatestNone/Android.bp b/tests/cts/permissionui/UsePermissionAppLatestNone/Android.bp
new file mode 100644
index 000000000..cbc934eef
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLatestNone/Android.bp
@@ -0,0 +1,33 @@
+//
+// Copyright (C) 2020 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: "CtsUsePermissionAppLatestNone",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "30",
+ srcs: [
+ ":CtsUsePermissionAppSrc",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ certificate: ":cts-testkey2",
+}
diff --git a/tests/cts/permissionui/UsePermissionAppLatestNone/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionAppLatestNone/AndroidManifest.xml
new file mode 100644
index 000000000..49103769e
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLatestNone/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2020 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.permissionui.cts.usepermission">
+
+ <application>
+ <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+ <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionAppLocationProvider/Android.bp b/tests/cts/permissionui/UsePermissionAppLocationProvider/Android.bp
new file mode 100644
index 000000000..a2cd88d4e
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLocationProvider/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAccessMicrophoneAppLocationProvider",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "31",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+} \ No newline at end of file
diff --git a/tests/cts/permissionui/UsePermissionAppLocationProvider/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionAppLocationProvider/AndroidManifest.xml
new file mode 100644
index 000000000..03edc78a2
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLocationProvider/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permissionui.cts.accessmicrophoneapplocationprovider">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+ <attribution
+ android:label="@string/attribution_label"
+ android:tag="test.tag" />
+
+ <application
+ android:attributionsAreUserVisible="true"
+ android:label="LocationProviderWithMicApp">
+ <activity android:name=".AddLocationProviderActivity" android:exported="true" />
+ <activity android:name=".UseMicrophoneActivity" android:exported="true"/>
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionAppLocationProvider/res/values/strings.xml b/tests/cts/permissionui/UsePermissionAppLocationProvider/res/values/strings.xml
new file mode 100644
index 000000000..9682d7f8a
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLocationProvider/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <string name="attribution_label">Attribution Label</string>
+</resources>
diff --git a/tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt b/tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
new file mode 100644
index 000000000..759700a12
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
@@ -0,0 +1,40 @@
+/*
+ * 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 android.permissionui.cts.accessmicrophoneapplocationprovider
+
+import android.app.Activity
+import android.location.Criteria
+import android.location.LocationManager
+import android.os.Bundle
+
+/**
+ * An activity that adds this package as a test location provider and uses microphone.
+ */
+class AddLocationProviderActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val attrContext = createAttributionContext("test.tag")
+ val locationManager = attrContext.getSystemService(LocationManager::class.java)
+ locationManager.addTestProvider(
+ packageName, false, false, false, false, false, false, false, Criteria.POWER_LOW,
+ Criteria.ACCURACY_COARSE
+ )
+
+ setResult(RESULT_OK)
+ finish()
+ }
+}
diff --git a/tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt b/tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt
new file mode 100644
index 000000000..e605f6eff
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt
@@ -0,0 +1,67 @@
+/*
+ * 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 android.permissionui.cts.accessmicrophoneapplocationprovider
+
+import android.app.Activity
+import android.content.Context
+import android.media.AudioFormat
+import android.media.AudioRecord
+import android.media.MediaRecorder
+import android.os.Bundle
+import android.os.Handler
+
+private const val USE_DURATION_MS = 10000L
+private const val SAMPLE_RATE_HZ = 44100
+
+/**
+ * An activity that uses microphone.
+ */
+class UseMicrophoneActivity : Activity() {
+ private var recorder: AudioRecord? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val attrContext = createAttributionContext("test.tag")
+ useMic(attrContext)
+ setResult(RESULT_OK)
+ finish()
+ }
+
+ override fun finish() {
+ recorder?.stop()
+ recorder = null
+ super.finish()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ finish()
+ }
+
+ private fun useMic(context: Context) {
+ recorder = AudioRecord.Builder()
+ .setAudioSource(MediaRecorder.AudioSource.MIC)
+ .setAudioFormat(AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setSampleRate(SAMPLE_RATE_HZ)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .build())
+ .setContext(context)
+ .build()
+ recorder?.startRecording()
+ Handler().postDelayed({ finish() }, USE_DURATION_MS)
+ }
+}
diff --git a/tests/cts/permissionui/UsePermissionAppWithOverlay/Android.bp b/tests/cts/permissionui/UsePermissionAppWithOverlay/Android.bp
new file mode 100644
index 000000000..6ae324577
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppWithOverlay/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2020 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: "CtsUsePermissionAppWithOverlay",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "30",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ resource_dirs: [
+ "res",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ ],
+ certificate: ":cts-testkey2",
+}
diff --git a/tests/cts/permissionui/UsePermissionAppWithOverlay/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionAppWithOverlay/AndroidManifest.xml
new file mode 100644
index 000000000..037ff548f
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppWithOverlay/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2020 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.permissionui.cts.usepermission">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <application>
+ <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+ <activity android:name=".OverlayActivity"
+ android:theme="@style/OverlayTheme" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionui/UsePermissionAppWithOverlay/res/drawable/border.xml b/tests/cts/permissionui/UsePermissionAppWithOverlay/res/drawable/border.xml
new file mode 100644
index 000000000..1e637be61
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppWithOverlay/res/drawable/border.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2020 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#FFFFFF" />
+ <corners
+ android:radius="4dp"/>
+ <stroke
+ android:width="4dp"
+ android:color="@android:color/black" />
+</shape>
diff --git a/tests/cts/permissionui/UsePermissionAppWithOverlay/res/layout/overlay_activity.xml b/tests/cts/permissionui/UsePermissionAppWithOverlay/res/layout/overlay_activity.xml
new file mode 100644
index 000000000..ea355f761
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppWithOverlay/res/layout/overlay_activity.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2020 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/overlay"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/border"
+ android:padding="8dp" >
+
+ <View android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <TextView android:id="@+id/overlay_description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@android:color/black"
+ android:text="@string/app_description" />
+</LinearLayout> \ No newline at end of file
diff --git a/tests/cts/permissionui/UsePermissionAppWithOverlay/res/values/strings.xml b/tests/cts/permissionui/UsePermissionAppWithOverlay/res/values/strings.xml
new file mode 100644
index 000000000..ca0b4ffc1
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppWithOverlay/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2020 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.
+-->
+<resources>
+ <string name="app_description">This activity attempts to tapjack the activity below.\nAny security sensitive controls below should not respond to taps as long as this activity is visible.</string>
+</resources> \ No newline at end of file
diff --git a/tests/cts/permissionui/UsePermissionAppWithOverlay/res/values/styles.xml b/tests/cts/permissionui/UsePermissionAppWithOverlay/res/values/styles.xml
new file mode 100644
index 000000000..880d94037
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppWithOverlay/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2020 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.
+-->
+<resources>
+ <style name="OverlayTheme" parent="android:Theme.Dialog">
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ </style>
+</resources>
diff --git a/tests/cts/permissionui/UsePermissionAppWithOverlay/src/android/permissionui/cts/usepermission/OverlayActivity.kt b/tests/cts/permissionui/UsePermissionAppWithOverlay/src/android/permissionui/cts/usepermission/OverlayActivity.kt
new file mode 100644
index 000000000..518b51e66
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppWithOverlay/src/android/permissionui/cts/usepermission/OverlayActivity.kt
@@ -0,0 +1,55 @@
+package android.permissionui.cts.usepermission
+
+import android.app.Activity
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+
+class OverlayActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.overlay_activity)
+ val params = window.attributes
+ params.flags = (WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
+
+ if (!intent.getBooleanExtra(EXTRA_FULL_OVERLAY, true)) {
+ params.gravity = Gravity.LEFT or Gravity.TOP
+ val left = intent.getIntExtra(OVERLAY_LEFT, params.x)
+ val top = intent.getIntExtra(OVERLAY_TOP, params.y)
+ val right = intent.getIntExtra(OVERLAY_RIGHT, params.x + params.width)
+ val bottom = intent.getIntExtra(OVERLAY_BOTTOM, top + 1)
+ params.x = left
+ params.y = top
+ params.width = right - left
+ params.height = bottom - top
+ }
+
+ registerReceiver(object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (intent?.action != RequestPermissionsActivity.ACTION_HIDE_OVERLAY) {
+ return
+ }
+
+ finish()
+ }
+ }, IntentFilter(RequestPermissionsActivity.ACTION_HIDE_OVERLAY), RECEIVER_EXPORTED)
+ }
+
+ companion object {
+ const val EXTRA_FULL_OVERLAY = "android.permissionui.cts.usepermission.extra.FULL_OVERLAY"
+
+ const val OVERLAY_LEFT = "android.permissionui.cts.usepermission.extra.OVERLAY_LEFT"
+ const val OVERLAY_TOP = "android.permissionui.cts.usepermission.extra.OVERLAY_TOP"
+ const val OVERLAY_RIGHT = "android.permissionui.cts.usepermission.extra.OVERLAY_RIGHT"
+ const val OVERLAY_BOTTOM = "android.permissionui.cts.usepermission.extra.OVERLAY_BOTTOM"
+ }
+}
diff --git a/tests/cts/permissionui/UsePermissionAppWithOverlay/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt b/tests/cts/permissionui/UsePermissionAppWithOverlay/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt
new file mode 100644
index 000000000..cb0182b3a
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppWithOverlay/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 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.permissionui.cts.usepermission
+
+import android.app.Activity
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Bundle
+import android.os.Handler
+
+class RequestPermissionsActivity : Activity() {
+
+ var paused = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ registerReceiver(object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (intent?.action != ACTION_SHOW_OVERLAY) {
+ return
+ }
+
+ startActivity(intent
+ .setAction(null)
+ .setComponent(ComponentName(context!!, OverlayActivity::class.java))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+ }
+ }, IntentFilter(ACTION_SHOW_OVERLAY), RECEIVER_EXPORTED)
+ Handler(mainLooper).post(this::eventuallyRequestPermission)
+ }
+
+ /**
+ * Keep trying to requestPermissions until the dialog shows. It may fail the first few times
+ * due to rapid install/uninstall tests do
+ */
+ private fun eventuallyRequestPermission() {
+ if (!paused) {
+ val permissions = intent.getStringArrayExtra("$packageName.PERMISSIONS")!!
+ requestPermissions(permissions, 1)
+ Handler(mainLooper).postDelayed(this::eventuallyRequestPermission, 200)
+ }
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array<out String>,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+
+ setResult(RESULT_OK, Intent().apply {
+ putExtra("$packageName.PERMISSIONS", permissions)
+ putExtra("$packageName.GRANT_RESULTS", grantResults)
+ })
+ finish()
+ }
+
+ override fun onPause() {
+ paused = true
+ super.onPause()
+ }
+
+ override fun onResume() {
+ paused = false
+ super.onResume()
+ }
+
+ companion object {
+ const val ACTION_SHOW_OVERLAY = "android.permissionui.cts.usepermission.ACTION_SHOW_OVERLAY"
+ const val ACTION_HIDE_OVERLAY = "android.permissionui.cts.usepermission.ACTION_HIDE_OVERLAY"
+ }
+}
diff --git a/tests/cts/permissionui/res/raw/lg_g4_iso_800_jpg.jpg b/tests/cts/permissionui/res/raw/lg_g4_iso_800_jpg.jpg
new file mode 100644
index 000000000..d26419604
--- /dev/null
+++ b/tests/cts/permissionui/res/raw/lg_g4_iso_800_jpg.jpg
Binary files differ
diff --git a/tests/cts/permissionui/res/raw/test_video.mp4 b/tests/cts/permissionui/res/raw/test_video.mp4
new file mode 100644
index 000000000..ab95ac07d
--- /dev/null
+++ b/tests/cts/permissionui/res/raw/test_video.mp4
Binary files differ
diff --git a/tests/cts/permissionui/res/values-en-rGB/strings.xml b/tests/cts/permissionui/res/values-en-rGB/strings.xml
new file mode 100755
index 000000000..7c98df768
--- /dev/null
+++ b/tests/cts/permissionui/res/values-en-rGB/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+
+<resources>
+ <string name="permissions">Permission</string>
+</resources>
diff --git a/tests/cts/permissionui/res/values/strings.xml b/tests/cts/permissionui/res/values/strings.xml
new file mode 100755
index 000000000..b2f8e3dac
--- /dev/null
+++ b/tests/cts/permissionui/res/values/strings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+
+<resources>
+ <string name="permissions">Permissions</string>
+ <string name="allow_foreground">Allow only while using the app</string>
+ <string name="allow_media_storage">Allow access to media only</string>
+ <string name="allow_external_storage">Allow management of all files</string>
+ <string name="car_mic_privacy_chip_id">com.android.systemui:id/mic_privacy_chip</string>
+ <string name="car_camera_privacy_chip_id">com.android.systemui:id/camera_privacy_chip</string>
+ <string name="test_accessibility_service">TestService1</string>
+ <string name="test_accessibility_service_2">TestService2</string>
+</resources>
diff --git a/tests/cts/permissionui/res/xml/test_accessibilityservice.xml b/tests/cts/permissionui/res/xml/test_accessibilityservice.xml
new file mode 100644
index 000000000..fa87e2e0f
--- /dev/null
+++ b/tests/cts/permissionui/res/xml/test_accessibilityservice.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accessibilityEventTypes="typeAllMask"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:canRetrieveWindowContent="true"
+ android:accessibilityFlags="flagDefault"
+ android:notificationTimeout="0" />
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/AppDataSharingUpdatesTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/AppDataSharingUpdatesTest.kt
new file mode 100644
index 000000000..6b7fe51f8
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/AppDataSharingUpdatesTest.kt
@@ -0,0 +1,545 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_OTHER
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_STORE
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED
+import android.os.Build
+import android.os.PersistableBundle
+import android.permission.cts.PermissionUtils
+import android.permissionui.cts.AppMetadata.createAppMetadataWithLocationSharingAds
+import android.permissionui.cts.AppMetadata.createAppMetadataWithLocationSharingNoAds
+import android.permissionui.cts.AppMetadata.createAppMetadataWithNoSharing
+import android.platform.test.annotations.FlakyTest
+import android.provider.DeviceConfig
+import android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED
+import android.util.Log
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.waitForBroadcasts
+import com.android.modules.utils.build.SdkLevel
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/** Tests the UI that displays information about apps' updates to their data sharing policies. */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+@FlakyTest
+class AppDataSharingUpdatesTest : BaseUsePermissionTest() {
+ // TODO(b/263838456): Add tests for personal and work profile.
+
+ private var activityManager: ActivityManager? = null
+
+ @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 setup() {
+ Assume.assumeTrue(
+ "Data sharing updates page is only available on U+", SdkLevel.isAtLeastU())
+ Assume.assumeFalse(isAutomotive)
+ Assume.assumeFalse(isTv)
+ Assume.assumeFalse(isWatch)
+
+ PermissionUtils.clearAppState(context.packageManager.permissionControllerPackageName)
+ waitForBroadcasts()
+ activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppGrantedCoarseLocation_noSharingToNoAdsSharing_showsUpdate() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31, createAppMetadataWithNoSharing(), waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds())
+ grantCoarseLocationPermission(APP_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertUpdatesPresent()
+ findView(By.textContains(APP_PACKAGE_NAME_SUBSTRING), true)
+ findView(By.textContains(NOW_SHARED_WITH_THIRD_PARTIES), true)
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppGrantedFineLocation_showsUpdate() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31, createAppMetadataWithNoSharing(), waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds())
+ grantFineLocationPermission(APP_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertUpdatesPresent()
+ findView(By.textContains(APP_PACKAGE_NAME_SUBSTRING), true)
+ findView(By.textContains(NOW_SHARED_WITH_THIRD_PARTIES), true)
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppGrantedBackgroundLocation_showsUpdate() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31, createAppMetadataWithNoSharing(), waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds())
+ grantBackgroundLocationPermission(APP_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertUpdatesPresent()
+ findView(By.textContains(APP_PACKAGE_NAME_SUBSTRING), true)
+ findView(By.textContains(NOW_SHARED_WITH_THIRD_PARTIES), true)
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppGrantedLocation_noSharingToAdsSharing_showsUpdate() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31, createAppMetadataWithNoSharing(), waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(APP_APK_NAME_31, createAppMetadataWithLocationSharingAds())
+ grantCoarseLocationPermission(APP_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertUpdatesPresent()
+ findView(By.textContains(APP_PACKAGE_NAME_SUBSTRING), true)
+ findView(By.textContains(NOW_SHARED_WITH_THIRD_PARTIES_FOR_ADS), true)
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppGrantedLocation_noAdsSharingToAdsSharing_showsUpdate() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31,
+ createAppMetadataWithLocationSharingNoAds(),
+ waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(APP_APK_NAME_31, createAppMetadataWithLocationSharingAds())
+ grantCoarseLocationPermission(APP_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertUpdatesPresent()
+ findView(By.textContains(APP_PACKAGE_NAME_SUBSTRING), true)
+ findView(By.textContains(NOW_SHARED_WITH_THIRD_PARTIES_FOR_ADS), true)
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppGrantedLocation_adsSharingToNoAdsSharing_showsNoUpdate() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31,
+ createAppMetadataWithLocationSharingAds(),
+ waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds())
+ grantCoarseLocationPermission(APP_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertNoUpdatesPresent()
+ findView(By.textContains(APP_PACKAGE_NAME_SUBSTRING), false)
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppGrantedLocation_noAdsSharingToNoSharing_showsNoUpdate() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31,
+ createAppMetadataWithLocationSharingNoAds(),
+ waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(APP_APK_NAME_31, createAppMetadataWithNoSharing())
+ grantCoarseLocationPermission(APP_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertNoUpdatesPresent()
+ findView(By.textContains(APP_PACKAGE_NAME_SUBSTRING), false)
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppGrantedLocation_adsSharingToNoSharing_showsNoUpdate() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31,
+ createAppMetadataWithLocationSharingAds(),
+ waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(APP_APK_NAME_31, createAppMetadataWithNoSharing())
+ grantCoarseLocationPermission(APP_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertNoUpdatesPresent()
+ findView(By.textContains(APP_PACKAGE_NAME_SUBSTRING), false)
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Ignore("b/282063206")
+ @Test
+ fun clickLearnMore_opensHelpCenter() {
+ Assume.assumeFalse(getPermissionControllerResString(HELP_CENTER_URL_ID).isNullOrEmpty())
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ findView(By.descContains(DATA_SHARING_UPDATES), true)
+ findView(By.textContains(LEARN_ABOUT_DATA_SHARING), true)
+ waitForIdle()
+
+ click(By.textContains(LEARN_ABOUT_DATA_SHARING))
+ waitForIdle()
+
+ eventually({assertHelpCenterLinkClickSuccessful()}, HELP_CENTER_TIMEOUT_MILLIS)
+ } finally {
+ pressBack()
+ pressBack()
+ }
+ }
+
+ @Test
+ fun noHelpCenterLinkAvailable_noHelpCenterClickAction() {
+ Assume.assumeTrue(getPermissionControllerResString(HELP_CENTER_URL_ID).isNullOrEmpty())
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ findView(By.descContains(DATA_SHARING_UPDATES), true)
+ findView(By.textContains(LEARN_ABOUT_DATA_SHARING), false)
+ } finally {
+ pressBack()
+ pressBack()
+ }
+ }
+
+ @Test
+ fun clickUpdate_opensAppLocationPermissionPage() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31, createAppMetadataWithNoSharing(), waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds())
+ grantFineLocationPermission(APP_PACKAGE_NAME)
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ findView(By.descContains(DATA_SHARING_UPDATES), true)
+ findView(By.textContains(UPDATES_IN_LAST_30_DAYS), true)
+ findView(By.textContains(APP_PACKAGE_NAME_SUBSTRING), true)
+ waitForIdle()
+
+ click(By.textContains(APP_PACKAGE_NAME_SUBSTRING))
+
+ findView(By.descContains(LOCATION_PERMISSION), true)
+ findView(By.textContains(APP_PACKAGE_NAME), true)
+ } finally {
+ pressBack()
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppNotGrantedLocation_showsNoUpdates() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31, createAppMetadataWithNoSharing(), waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds())
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertNoUpdatesPresent()
+ findView(By.textContains(APP_PACKAGE_NAME_SUBSTRING), false)
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_noMetadata_showsNoUpdates() {
+ installPackageWithoutInstallSource(APP_APK_PATH_31)
+ waitForBroadcasts()
+ installPackageWithoutInstallSource(APP_APK_PATH_31)
+ waitForBroadcasts()
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertNoUpdatesPresent()
+ findView(By.textContains(APP_PACKAGE_NAME_SUBSTRING), false)
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_featureDisabled_doesNotOpenDataSharingUpdatesPage() {
+ setDeviceConfigPrivacyProperty(SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, false.toString())
+
+ startAppDataSharingUpdatesActivity()
+
+ findView(By.descContains(DATA_SHARING_UPDATES), false)
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppGrantedFineLocation_packageSourceUnspecified_showsUpdate() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31,
+ createAppMetadataWithNoSharing(),
+ PACKAGE_SOURCE_UNSPECIFIED,
+ waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31,
+ createAppMetadataWithLocationSharingNoAds(),
+ PACKAGE_SOURCE_UNSPECIFIED)
+ grantFineLocationPermission(APP_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertUpdatesPresent()
+ findView(By.textContains(APP_PACKAGE_NAME_SUBSTRING), true)
+ findView(By.textContains(NOW_SHARED_WITH_THIRD_PARTIES), true)
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppGrantedLocation_packageSourceOther_doesntShowUpdate() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31,
+ createAppMetadataWithNoSharing(),
+ PACKAGE_SOURCE_OTHER,
+ waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds(), PACKAGE_SOURCE_OTHER)
+ grantFineLocationPermission(APP_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertNoUpdatesPresent()
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppGrantedLocation_packageSourceStore_showsUpdate() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31,
+ createAppMetadataWithNoSharing(),
+ PACKAGE_SOURCE_STORE,
+ waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds(), PACKAGE_SOURCE_STORE)
+ grantFineLocationPermission(APP_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertUpdatesPresent()
+ findView(By.textContains(APP_PACKAGE_NAME_SUBSTRING), true)
+ findView(By.textContains(NOW_SHARED_WITH_THIRD_PARTIES), true)
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppGrantedLocation_packageSourceLocalFile_doesntShowUpdate() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31,
+ createAppMetadataWithNoSharing(),
+ PACKAGE_SOURCE_LOCAL_FILE,
+ waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds(), PACKAGE_SOURCE_LOCAL_FILE)
+ grantFineLocationPermission(APP_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertNoUpdatesPresent()
+ } finally {
+ pressBack()
+ }
+ }
+
+ @Test
+ fun startActivityWithIntent_whenAppGrantedLocation_packageSourceDownloaded_doesntShowUpdate() {
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31,
+ createAppMetadataWithNoSharing(),
+ PACKAGE_SOURCE_DOWNLOADED_FILE,
+ waitTillBroadcastProcessed = true)
+ installAndWaitTillPackageAdded(
+ APP_APK_NAME_31,
+ createAppMetadataWithLocationSharingNoAds(),
+ PACKAGE_SOURCE_DOWNLOADED_FILE)
+ grantFineLocationPermission(APP_PACKAGE_NAME)
+
+ startAppDataSharingUpdatesActivity()
+
+ try {
+ assertNoUpdatesPresent()
+ } finally {
+ pressBack()
+ }
+ }
+
+ /** Installs an app and waits for the package added broadcast to be dispatched. */
+ private fun installAndWaitTillPackageAdded(
+ apkPath: String,
+ appMetadata: PersistableBundle,
+ packageSource: Int? = null,
+ waitTillBroadcastProcessed: Boolean = false
+ ) {
+ installPackageViaSession(apkPath, appMetadata, packageSource)
+ waitForBroadcasts()
+ // TODO(b/279455955): Investigate why this is necessary and remove if possible.
+ if (waitTillBroadcastProcessed) Thread.sleep(500)
+ }
+
+ 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)
+ findView(By.textContains(LEARN_ABOUT_DATA_SHARING), shouldShowLearnMoreLink())
+ }
+
+ 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(APP_PACKAGE_NAME_SUBSTRING), false)
+ findView(By.textContains(UPDATES_IN_LAST_30_DAYS), false)
+ findView(By.textContains(DATA_SHARING_UPDATES_FOOTER_MESSAGE), true)
+ findView(By.textContains(LEARN_ABOUT_DATA_SHARING), shouldShowLearnMoreLink())
+ }
+
+ private fun grantFineLocationPermission(packageName: String) {
+ uiAutomation.grantRuntimePermission(
+ packageName, android.Manifest.permission.ACCESS_FINE_LOCATION)
+ }
+ private fun grantCoarseLocationPermission(packageName: String) {
+ uiAutomation.grantRuntimePermission(
+ packageName, android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ }
+ private fun grantBackgroundLocationPermission(packageName: String) {
+ uiAutomation.grantRuntimePermission(
+ packageName, android.Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+ }
+
+ private fun assertHelpCenterLinkClickSuccessful() {
+ runWithShellPermissionIdentity {
+ val runningTasks = activityManager!!.getRunningTasks(5)
+
+ Log.v(TAG, "# running tasks: ${runningTasks.size}")
+ assertFalse("Expected runningTasks to not be empty", runningTasks.isEmpty())
+
+ runningTasks.forEachIndexed { index, runningTaskInfo ->
+ Log.v(TAG, "task $index ${runningTaskInfo.baseIntent}")
+ }
+
+ val taskInfo = runningTasks[0]
+ val observedIntentAction = taskInfo.baseIntent.action
+ val observedIntentDataString = taskInfo.baseIntent.dataString
+ val observedIntentScheme: String? = taskInfo.baseIntent.scheme
+
+ Log.v(TAG, "task base intent: ${taskInfo.baseIntent}")
+ assertEquals("Unexpected intent action", Intent.ACTION_VIEW, observedIntentAction)
+
+ val expectedUrl = getPermissionControllerResString(HELP_CENTER_URL_ID)!!
+ assertFalse(observedIntentDataString.isNullOrEmpty())
+ assertTrue(observedIntentDataString?.startsWith(expectedUrl) ?: false)
+
+ assertFalse(observedIntentScheme.isNullOrEmpty())
+ assertEquals("https", observedIntentScheme)
+ }
+ }
+
+ private fun shouldShowLearnMoreLink(): Boolean {
+ return !getPermissionControllerResString(HELP_CENTER_URL_ID).isNullOrEmpty()
+ }
+
+ /** Companion object for [AppDataSharingUpdatesTest]. */
+ companion object {
+ private val TAG = AppDataSharingUpdatesTest::class.java.simpleName
+
+ private const val HELP_CENTER_URL_ID = "data_sharing_help_center_link"
+ private const val HELP_CENTER_TIMEOUT_MILLIS: Long = 20000
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/AppMetadata.kt b/tests/cts/permissionui/src/android/permissionui/cts/AppMetadata.kt
new file mode 100644
index 000000000..9117e6205
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/AppMetadata.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.permissionui.cts
+
+import android.os.PersistableBundle
+
+/** Helper methods for creating test app metadata [PersistableBundle] */
+object AppMetadata {
+ /** Returns empty App Metadata [PersistableBundle] representation */
+ fun createEmptyAppMetadata(): PersistableBundle {
+ return PersistableBundle()
+ }
+
+ /** Returns valid App Metadata [PersistableBundle] representation */
+ fun createDefaultAppMetadata(): PersistableBundle {
+ val approximateLocationBundle =
+ PersistableBundle().apply { putIntArray(KEY_PURPOSES, (1..7).toList().toIntArray()) }
+
+ val locationBundle =
+ PersistableBundle().apply {
+ putPersistableBundle(APPROX_LOCATION, approximateLocationBundle)
+ }
+
+ val dataSharedBundle =
+ PersistableBundle().apply { putPersistableBundle(LOCATION_CATEGORY, locationBundle) }
+
+ 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)
+ }
+ }
+
+ /**
+ * Returns invalid App Metadata [PersistableBundle] representation. Invalidity due to invalid
+ * label name usage
+ */
+ fun createInvalidAppMetadata(): PersistableBundle {
+ val validAppMetaData = createDefaultAppMetadata()
+ val validSafetyLabel = validAppMetaData.getPersistableBundle(KEY_SAFETY_LABELS)
+
+ return PersistableBundle().apply {
+ putLong(KEY_VERSION, INITIAL_TOP_LEVEL_VERSION)
+ putPersistableBundle(KEY_INVALID, validSafetyLabel)
+ }
+ }
+
+ /**
+ * Returns invalid App Metadata [PersistableBundle] representation. Invalidity due to no top
+ * level meta data version number.
+ */
+ fun createInvalidAppMetadataWithoutTopLevelVersion(): PersistableBundle {
+ val validAppMetaData = createDefaultAppMetadata()
+ val validSafetyLabel = validAppMetaData.getPersistableBundle(KEY_SAFETY_LABELS)
+
+ return PersistableBundle().apply {
+ putPersistableBundle(KEY_SAFETY_LABELS, validSafetyLabel)
+ }
+ }
+
+ /**
+ * Returns invalid App Metadata [PersistableBundle] representation. Invalidity due to invalid
+ * top level meta data version number.
+ */
+ fun createInvalidAppMetadataWithInvalidTopLevelVersion(): PersistableBundle {
+ val validAppMetaData = createDefaultAppMetadata()
+ val validSafetyLabel = validAppMetaData.getPersistableBundle(KEY_SAFETY_LABELS)
+
+ return PersistableBundle().apply {
+ putLong(KEY_VERSION, INVALID_TOP_LEVEL_VERSION)
+ putPersistableBundle(KEY_SAFETY_LABELS, validSafetyLabel)
+ }
+ }
+
+ /**
+ * Returns invalid App Metadata [PersistableBundle] representation. Invalidity due to no safety
+ * label version number.
+ */
+ fun createInvalidAppMetadataWithoutSafetyLabelVersion(): PersistableBundle {
+ val validAppMetaData = createDefaultAppMetadata()
+ val invalidSafetyLabel =
+ validAppMetaData.getPersistableBundle(KEY_SAFETY_LABELS).apply {
+ this?.remove(KEY_VERSION)
+ }
+
+ return PersistableBundle().apply {
+ putLong(KEY_VERSION, INITIAL_TOP_LEVEL_VERSION)
+ putPersistableBundle(KEY_SAFETY_LABELS, invalidSafetyLabel)
+ }
+ }
+
+ /**
+ * Returns invalid App Metadata [PersistableBundle] representation. Invalidity due to invalid
+ * safety label version number.
+ */
+ fun createInvalidAppMetadataWithInvalidSafetyLabelVersion(): PersistableBundle {
+ val validAppMetaData = createDefaultAppMetadata()
+ val invalidSafetyLabel =
+ validAppMetaData.getPersistableBundle(KEY_SAFETY_LABELS)?.apply {
+ putLong(KEY_VERSION, INVALID_SAFETY_LABELS_VERSION)
+ }
+
+ return PersistableBundle().apply {
+ putLong(KEY_VERSION, INITIAL_TOP_LEVEL_VERSION)
+ putPersistableBundle(KEY_SAFETY_LABELS, invalidSafetyLabel)
+ }
+ }
+ /** Returns an App Metadata [PersistableBundle] representation where no data is shared. */
+ fun createAppMetadataWithNoSharing(): PersistableBundle {
+ return createMetadataWithDataShared(PersistableBundle())
+ }
+
+ /**
+ * Returns an App Metadata [PersistableBundle] representation where location data is shared, but
+ * not for advertising purpose.
+ */
+ 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 location data is shared,
+ * including for advertising purpose.
+ */
+ fun createAppMetadataWithLocationSharingAds(): PersistableBundle {
+ val locationBundle =
+ PersistableBundle().apply {
+ putPersistableBundle(
+ APPROX_LOCATION,
+ PersistableBundle().apply {
+ putIntArray(KEY_PURPOSES, listOf(PURPOSE_ADVERTISING).toIntArray())
+ })
+ }
+
+ val dataSharedBundle =
+ PersistableBundle().apply { putPersistableBundle(LOCATION_CATEGORY, locationBundle) }
+
+ return createMetadataWithDataShared(dataSharedBundle)
+ }
+
+ 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)
+ }
+ }
+
+ private const val INITIAL_SAFETY_LABELS_VERSION = 1L
+ private const val INITIAL_TOP_LEVEL_VERSION = 1L
+ private const val INVALID_SAFETY_LABELS_VERSION = -1L
+ private const val INVALID_TOP_LEVEL_VERSION = 0L
+
+ private const val LOCATION_CATEGORY = "location"
+ private const val APPROX_LOCATION = "approx_location"
+ private const val PURPOSE_FRAUD_PREVENTION_SECURITY = 4
+ private const val PURPOSE_ADVERTISING = 5
+
+ private const val KEY_VERSION = "version"
+ private const val KEY_SAFETY_LABELS = "safety_labels"
+ private const val KEY_INVALID = "invalid_safety_labels"
+ private const val KEY_DATA_SHARED = "data_shared"
+ private const val KEY_DATA_LABELS = "data_labels"
+ private const val KEY_PURPOSES = "purposes"
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt
new file mode 100644
index 000000000..548bf033a
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.permissionui.cts
+
+import android.Manifest.permission.ACCESS_COARSE_LOCATION
+import android.os.Build
+import android.platform.test.annotations.FlakyTest
+import android.provider.DeviceConfig
+import android.provider.Settings
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule
+import com.android.modules.utils.build.SdkLevel
+import com.google.common.truth.Truth
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+/** App Permission page UI tests. */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+@FlakyTest
+class AppPermissionTest : BaseUsePermissionTest() {
+
+ @get:Rule
+ val deviceConfigPermissionRationaleEnabled =
+ DeviceConfigStateChangerRule(
+ context, DeviceConfig.NAMESPACE_PRIVACY, PERMISSION_RATIONALE_ENABLED, true.toString())
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue("Permission rationale is only available on U+", SdkLevel.isAtLeastU())
+ Assume.assumeFalse(isAutomotive)
+ Assume.assumeFalse(isTv)
+ Assume.assumeFalse(isWatch)
+
+ val userSetupComplete =
+ Settings.Secure.getInt(context.contentResolver, USER_SETUP_COMPLETE, 0) == 1
+
+ Truth.assertWithMessage("User setup must be complete before running this test")
+ .that(userSetupComplete)
+ .isTrue()
+ }
+
+ @Test
+ fun showPermissionRationaleContainer_withInstallSourceAndMetadata_packageSourceUnspecified() {
+ // Unspecified is the default, so no need to explicitly set it
+ installPackageWithInstallSourceAndMetadata(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(true)
+
+ clickPermissionRationaleContentInAppPermission()
+ assertPermissionRationaleDialogIsVisible(expected = true, showSettingsSection = false)
+ }
+
+ @Test
+ fun showPermissionRationaleContainer_withInstallSourceAndMetadata_packageSourceStore() {
+ installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(true)
+
+ clickPermissionRationaleContentInAppPermission()
+ assertPermissionRationaleDialogIsVisible(expected = true, showSettingsSection = false)
+ }
+
+ @Test
+ fun showPermissionRationaleContainer_withInstallSourceAndMetadata_packageSourceLocalFile() {
+ installPackageWithInstallSourceAndMetadataFromLocalFile(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(false)
+ }
+
+ @Test
+ fun showPermissionRationaleContainer_withInstallSourceAndMetadata_packageSourceDownloadedFile() {
+ installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(false)
+ }
+
+ @Test
+ fun showPermissionRationaleContainer_withInstallSourceAndMetadata_packageSourceOther() {
+ installPackageWithInstallSourceAndMetadataFromOther(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(false)
+ }
+
+ @Test
+ fun noShowPermissionRationaleContainer_withInstallSourceAndNoMetadata() {
+ installPackageWithInstallSourceAndNoMetadata(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(false)
+ }
+
+ @Test
+ fun noShowPermissionRationaleContainer_withInstallSourceAndNullMetadata() {
+ installPackageWithInstallSourceAndNoMetadata(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(false)
+ }
+
+ @Test
+ fun noShowPermissionRationaleContainer_withInstallSourceAndEmptyMetadata() {
+ installPackageWithInstallSourceAndEmptyMetadata(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(false)
+ }
+
+ @Test
+ fun noShowPermissionRationaleContainer_withInstallSourceAndInvalidMetadata() {
+ installPackageWithInstallSourceAndInvalidMetadata(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(false)
+ }
+
+ @Test
+ fun noShowPermissionRationaleContainer_withInstallSourceAndMetadataWithoutTopLevelVersion() {
+ installPackageWithInstallSourceAndMetadataWithoutTopLevelVersion(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(false)
+ }
+
+ @Test
+ fun noShowPermissionRationaleContainer_withInstallSourceAndMetadataWithInvalidTopLevelVersion() {
+ installPackageWithInstallSourceAndMetadataWithInvalidTopLevelVersion(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(false)
+ }
+
+ @Test
+ fun noShowPermissionRationaleContainer_withInstallSourceAndMetadataWithoutSafetyLabelVersion() {
+ installPackageWithInstallSourceAndMetadataWithoutSafetyLabelVersion(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(false)
+ }
+
+ @Test
+ fun noShowPermissionRationaleContainer_withInstallSourceAndMetadataWithInvalidSafetyLabelVersion()
+ {
+ installPackageWithInstallSourceAndMetadataWithInvalidSafetyLabelVersion(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(false)
+ }
+
+ @Test
+ fun noShowPermissionRationaleContainer_withOutInstallSource() {
+ installPackageWithoutInstallSource(APP_APK_PATH_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(false)
+ }
+
+ @Test
+ fun noShowPermissionRationaleContainer_withoutMetadata() {
+ installPackageWithInstallSourceAndNoMetadata(APP_APK_NAME_31)
+
+ navigateToIndividualPermissionSetting(ACCESS_COARSE_LOCATION)
+
+ assertAppPermissionRationaleContainerIsVisible(false)
+ }
+
+ private fun assertAppPermissionRationaleContainerIsVisible(expected: Boolean) {
+ findView(By.res(APP_PERMISSION_RATIONALE_CONTAINER_VIEW), expected)
+ }
+
+ companion object {
+ private const val PERMISSION_RATIONALE_ENABLED = "permission_rationale_enabled"
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt
new file mode 100644
index 000000000..5028384dc
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2016 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.permissionui.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.ComponentName
+import android.content.Context
+import android.content.Context.RECEIVER_EXPORTED
+import android.content.Intent
+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.res.Resources
+import android.os.PersistableBundle
+import android.os.SystemClock
+import android.platform.test.rule.ScreenRecordRule
+import android.provider.DeviceConfig
+import android.provider.Settings
+import android.text.Html
+import android.util.Log
+import androidx.test.core.app.ActivityScenario
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.StaleObjectException
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import com.android.compatibility.common.util.DisableAnimationRule
+import com.android.compatibility.common.util.FreezeRotationRule
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.UiAutomatorUtils2
+import com.android.modules.utils.build.SdkLevel
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import java.util.regex.Pattern
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Before
+import org.junit.Rule
+
+@ScreenRecordRule.ScreenRecord
+abstract class BasePermissionTest {
+ companion object {
+ private const val TAG = "BasePermissionTest"
+
+ private const val INSTALL_ACTION_CALLBACK = "BasePermissionTest.install_callback"
+
+ private const val MAX_SWIPES = 5
+
+ const val APK_DIRECTORY = "/data/local/tmp/cts/permissionui"
+
+ const val QUICK_CHECK_TIMEOUT_MILLIS = 100L
+ const val IDLE_TIMEOUT_MILLIS: Long = 1000
+ const val UNEXPECTED_TIMEOUT_MILLIS = 1000
+ const val TIMEOUT_MILLIS: Long = 20000
+ const val PACKAGE_INSTALLER_TIMEOUT = 60000L
+
+ @JvmStatic
+ protected val instrumentation: Instrumentation =
+ InstrumentationRegistry.getInstrumentation()
+ @JvmStatic
+ protected val context: Context = instrumentation.context
+ @JvmStatic
+ protected val uiAutomation: UiAutomation = instrumentation.uiAutomation
+ @JvmStatic
+ protected val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
+ @JvmStatic
+ protected val packageManager: PackageManager = context.packageManager
+ private val packageInstaller = packageManager.packageInstaller
+ @JvmStatic
+ private val mPermissionControllerResources: Resources = context.createPackageContext(
+ context.packageManager.permissionControllerPackageName, 0).resources
+
+ @JvmStatic
+ protected val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ @JvmStatic
+ protected val isWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
+ @JvmStatic
+ protected val isAutomotive =
+ packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ }
+
+ @get:Rule
+ val screenRecordRule = ScreenRecordRule(false, false)
+
+ @get:Rule
+ val disableAnimationRule = DisableAnimationRule()
+
+ @get:Rule
+ val freezeRotationRule = FreezeRotationRule()
+
+ var activityScenario: ActivityScenario<StartForFutureActivity>? = null
+
+ data class SessionResult(val status: Int?)
+
+ /** If a status was received the value of the status, otherwise null */
+ 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))
+ }
+ }
+
+ private var screenTimeoutBeforeTest: Long = 0L
+
+ @Before
+ fun setUp() {
+ runWithShellPermissionIdentity {
+ screenTimeoutBeforeTest = Settings.System.getLong(
+ context.contentResolver, Settings.System.SCREEN_OFF_TIMEOUT
+ )
+ Settings.System.putLong(
+ context.contentResolver, Settings.System.SCREEN_OFF_TIMEOUT, 1800000L
+ )
+ }
+
+ uiDevice.wakeUp()
+ runShellCommand(instrumentation, "wm dismiss-keyguard")
+
+ uiDevice.findObject(By.text("Close"))?.click()
+ }
+
+ @Before
+ fun registerInstallSessionResultReceiver() {
+ context.registerReceiver(
+ installSessionResultReceiver, IntentFilter(INSTALL_ACTION_CALLBACK), RECEIVER_EXPORTED)
+ }
+
+ @After
+ fun unregisterInstallSessionResultReceiver() {
+ try {
+ context.unregisterReceiver(installSessionResultReceiver)
+ } catch (ignored: IllegalArgumentException) {}
+ }
+
+ @After
+ fun tearDown() {
+ runWithShellPermissionIdentity {
+ Settings.System.putLong(
+ context.contentResolver, Settings.System.SCREEN_OFF_TIMEOUT,
+ screenTimeoutBeforeTest
+ )
+ }
+
+ try {
+ activityScenario?.close()
+ } catch (e: NullPointerException) {
+ // ignore
+ }
+
+ pressHome()
+ }
+
+ protected fun setDeviceConfigPrivacyProperty(
+ propertyName: String,
+ value: String,
+ ) {
+ runWithShellPermissionIdentity(instrumentation.uiAutomation) {
+ val valueWasSet =
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ /* name = */ propertyName,
+ /* value = */ value,
+ /* makeDefault = */ false)
+ check(valueWasSet) { "Could not set $propertyName to $value" }
+ }
+ }
+
+ protected fun getPermissionControllerString(res: String, vararg formatArgs: Any): Pattern {
+ val textWithHtml = mPermissionControllerResources.getString(
+ mPermissionControllerResources.getIdentifier(
+ res, "string", "com.android.permissioncontroller"), *formatArgs)
+ val textWithoutHtml = Html.fromHtml(textWithHtml, 0).toString()
+ return Pattern.compile(Pattern.quote(textWithoutHtml),
+ Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE)
+ }
+
+ protected fun getPermissionControllerResString(res: String): String? {
+ try {
+ return mPermissionControllerResources.getString(
+ mPermissionControllerResources.getIdentifier(
+ res, "string", "com.android.permissioncontroller"))
+ } catch (e: Resources.NotFoundException) {
+ return null
+ }
+ }
+
+ protected fun byAnyText(vararg texts: String?): BySelector {
+ var regex = ""
+ for (text in texts) {
+ if (text != null) {
+ regex = regex + Pattern.quote(text) + "|"
+ }
+ }
+ if (regex.endsWith("|")) {
+ regex = regex.dropLast(1)
+ }
+ return By.text(Pattern.compile(regex, Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE))
+ }
+
+ protected open fun installPackage(
+ apkPath: String,
+ reinstall: Boolean = false,
+ grantRuntimePermissions: Boolean = false,
+ expectSuccess: Boolean = true,
+ installSource: String? = null
+ ) {
+ val output = runShellCommand(
+ "pm install${if (SdkLevel.isAtLeastU()) " --bypass-low-target-sdk-block" else ""} " +
+ "${if (reinstall) " -r" else ""}${if (grantRuntimePermissions) " -g"
+ else ""}${if (installSource != null) " -i $installSource" else ""} $apkPath"
+ ).trim()
+ if (expectSuccess) {
+ assertEquals("Success", output)
+ } else {
+ assertNotEquals("Success", output)
+ }
+ }
+
+ protected fun installPackageViaSession(
+ apkName: String,
+ appMetadata: PersistableBundle? = null,
+ packageSource: Int? = null
+ ) {
+ val (sessionId, 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 interaciton to complete install. Install should have succeeded.
+ val result = getInstallSessionResult()
+ assertThat(result.status).isEqualTo(STATUS_SUCCESS)
+ }
+ }
+
+ protected fun uninstallPackage(packageName: String, requireSuccess: Boolean = true) {
+ val output = runShellCommand("pm uninstall $packageName").trim()
+ if (requireSuccess) {
+ assertEquals("Success", output)
+ }
+ }
+
+ protected fun waitFindObject(selector: BySelector): UiObject2 {
+ waitForIdle()
+ return findObjectWithRetry({ t -> UiAutomatorUtils2.waitFindObject(selector, t) })!!
+ }
+
+ protected fun waitFindObject(selector: BySelector, timeoutMillis: Long): UiObject2 {
+ waitForIdle()
+ return findObjectWithRetry({ t -> UiAutomatorUtils2.waitFindObject(selector, t) },
+ timeoutMillis)!!
+ }
+
+ protected fun waitFindObjectOrNull(selector: BySelector): UiObject2? {
+ waitForIdle()
+ return findObjectWithRetry({ t -> UiAutomatorUtils2.waitFindObjectOrNull(selector, t) })
+ }
+
+ protected fun waitFindObjectOrNull(selector: BySelector, timeoutMillis: Long): UiObject2? {
+ waitForIdle()
+ return findObjectWithRetry({ t -> UiAutomatorUtils2.waitFindObjectOrNull(selector, t) },
+ timeoutMillis)
+ }
+
+ private fun findObjectWithRetry(
+ automatorMethod: (timeoutMillis: Long) -> UiObject2?,
+ timeoutMillis: Long = 20_000L
+ ): UiObject2? {
+ waitForIdle()
+ val startTime = SystemClock.elapsedRealtime()
+ return try {
+ automatorMethod(timeoutMillis)
+ } catch (e: StaleObjectException) {
+ val remainingTime = timeoutMillis - (SystemClock.elapsedRealtime() - startTime)
+ if (remainingTime <= 0) {
+ throw e
+ }
+ automatorMethod(remainingTime)
+ }
+ }
+
+ protected fun click(selector: BySelector, timeoutMillis: Long = 20_000) {
+ waitFindObject(selector, timeoutMillis).click()
+ waitForIdle()
+ }
+
+ protected fun findView(selector: BySelector, expected: Boolean) {
+ val timeoutMs = if (expected) {
+ 10000L
+ } else {
+ 1000L
+ }
+
+ val exception = try {
+ waitFindObject(selector, timeoutMs)
+ null
+ } catch (e: Exception) {
+ e
+ }
+ Assert.assertTrue("Expected to find view: $expected", (exception == null) == expected)
+ }
+
+ protected fun clickPermissionControllerUi(selector: BySelector, timeoutMillis: Long = 20_000) {
+ click(selector.pkg(context.packageManager.permissionControllerPackageName), timeoutMillis)
+ }
+
+ /**
+ * Clicks Permission Controller UI with a swipe based timeout instead of a time based one
+ *
+ * Use this if finding some Permission Controller UI isn't time bound.
+ *
+ * @param text The text to search for
+ * @param maxSearchSwipes See {@link UiScrollable#setMaxSearchSwipes}
+ */
+ protected fun clickPermissionControllerUi(text: String, maxSearchSwipes: Int = 5) {
+ scrollToText(text, maxSearchSwipes).click()
+ }
+
+ private fun scrollToText(text: String, maxSearchSwipes: Int = MAX_SWIPES): UiObject2 {
+ val scrollable = UiScrollable(UiSelector().scrollable(true)).apply {
+ this.maxSearchSwipes = maxSearchSwipes
+ }
+
+ scrollable.scrollTextIntoView(text)
+
+ val foundObject = uiDevice.findObject(
+ By.text(text).pkg(context.packageManager.permissionControllerPackageName))
+ Assert.assertNotNull("View not found after scrolling", foundObject)
+
+ return foundObject
+ }
+
+ protected fun pressBack() {
+ uiDevice.pressBack()
+ waitForIdle()
+ }
+
+ protected fun pressHome() {
+ uiDevice.pressHome()
+ waitForIdle()
+ }
+
+ protected fun pressDPadDown() {
+ uiDevice.pressDPadDown()
+ waitForIdle()
+ }
+
+ protected fun waitForIdle() = uiAutomation.waitForIdle(IDLE_TIMEOUT_MILLIS, TIMEOUT_MILLIS)
+
+ protected fun startActivityForFuture(
+ intent: Intent
+ ): CompletableFuture<Instrumentation.ActivityResult> =
+ CompletableFuture<Instrumentation.ActivityResult>().also {
+ activityScenario = ActivityScenario.launch(
+ StartForFutureActivity::class.java).onActivity { activity ->
+ activity.startActivityForFuture(intent, it)
+ }
+ }
+
+ open fun enableComponent(component: ComponentName) {
+ packageManager.setComponentEnabledSetting(
+ component,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP)
+ }
+
+ open fun disableComponent(component: ComponentName) {
+ packageManager.setComponentEnabledSetting(
+ component,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP)
+ }
+
+ private fun createPackageInstallerSession(
+ packageSource: Int? = null
+ ): Pair<Int, PackageInstaller.Session> {
+ // Create session
+ val sessionParam = SessionParams(SessionParams.MODE_FULL_INSTALL)
+ if (packageSource != null) {
+ sessionParam.setPackageSource(packageSource)
+ }
+
+ val sessionId = packageInstaller.createSession(sessionParam)
+ val session = packageInstaller.openSession(sessionId)!!
+
+ return Pair(sessionId, session)
+ }
+
+ private fun writePackageInstallerSession(session: PackageInstaller.Session, apkName: String) {
+ val apkFile = File(APK_DIRECTORY, apkName)
+ // Write data to session
+ 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
+ }
+ }
+
+ /** Wait for session's install result and return it */
+ private fun getInstallSessionResult(timeout: Long = PACKAGE_INSTALLER_TIMEOUT): SessionResult {
+ return installSessionResult.poll(timeout, TimeUnit.MILLISECONDS)
+ ?: SessionResult(null /* status */)
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt
new file mode 100644
index 000000000..ef5208c09
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt
@@ -0,0 +1,1205 @@
+/*
+ * Copyright (C) 2016 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.permissionui.cts
+
+import android.Manifest
+import android.app.Activity
+import android.app.ActivityManager
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.content.Intent
+import android.content.Intent.ACTION_REVIEW_APP_DATA_SHARING_UPDATES
+import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_OTHER
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_STORE
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Build
+import android.os.Process
+import android.provider.DeviceConfig
+import android.provider.Settings
+import android.text.Spanned
+import android.text.style.ClickableSpan
+import android.view.View
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.StaleObjectException
+import androidx.test.uiautomator.UiObjectNotFoundException
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import com.android.compatibility.common.util.SystemUtil
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.modules.utils.build.SdkLevel
+import java.util.concurrent.TimeUnit
+import java.util.regex.Pattern
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+
+abstract class BaseUsePermissionTest : BasePermissionTest() {
+ companion object {
+ const val APP_APK_NAME_31 = "CtsUsePermissionApp31.apk"
+
+ const val APP_APK_PATH_22 = "$APK_DIRECTORY/CtsUsePermissionApp22.apk"
+ const val APP_APK_PATH_22_CALENDAR_ONLY =
+ "$APK_DIRECTORY/CtsUsePermissionApp22CalendarOnly.apk"
+ const val APP_APK_PATH_22_NONE = "$APK_DIRECTORY/CtsUsePermissionApp22None.apk"
+ const val APP_APK_PATH_23 = "$APK_DIRECTORY/CtsUsePermissionApp23.apk"
+ const val APP_APK_PATH_25 = "$APK_DIRECTORY/CtsUsePermissionApp25.apk"
+ const val APP_APK_PATH_26 = "$APK_DIRECTORY/CtsUsePermissionApp26.apk"
+ const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsUsePermissionApp28.apk"
+ const val APP_APK_PATH_29 = "$APK_DIRECTORY/CtsUsePermissionApp29.apk"
+ const val APP_APK_PATH_30 = "$APK_DIRECTORY/CtsUsePermissionApp30.apk"
+ const val APP_APK_PATH_31 = "$APK_DIRECTORY/$APP_APK_NAME_31"
+ const val APP_APK_PATH_32 = "$APK_DIRECTORY/CtsUsePermissionApp32.apk"
+
+ const val APP_APK_PATH_30_WITH_BACKGROUND =
+ "$APK_DIRECTORY/CtsUsePermissionApp30WithBackground.apk"
+ const val APP_APK_PATH_30_WITH_BLUETOOTH =
+ "$APK_DIRECTORY/CtsUsePermissionApp30WithBluetooth.apk"
+ const val APP_APK_PATH_LATEST = "$APK_DIRECTORY/CtsUsePermissionAppLatest.apk"
+ const val APP_APK_PATH_LATEST_NONE = "$APK_DIRECTORY/CtsUsePermissionAppLatestNone.apk"
+ const val APP_APK_PATH_WITH_OVERLAY = "$APK_DIRECTORY/CtsUsePermissionAppWithOverlay.apk"
+ const val APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31 =
+ "$APK_DIRECTORY/CtsCreateNotificationChannelsApp31.apk"
+ const val APP_APK_PATH_MEDIA_PERMISSION_33_WITH_STORAGE =
+ "$APK_DIRECTORY/CtsMediaPermissionApp33WithStorage.apk"
+ const val APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE =
+ "$APK_DIRECTORY/CtsUsePermissionAppImplicitUserSelectStorage.apk"
+ const val APP_APK_PATH_OTHER_APP =
+ "$APK_DIRECTORY/CtsDifferentPkgNameApp.apk"
+ const val APP_PACKAGE_NAME = "android.permissionui.cts.usepermission"
+ const val OTHER_APP_PACKAGE_NAME = "android.permissionui.cts.usepermissionother"
+ const val TEST_INSTALLER_PACKAGE_NAME = "android.permissionui.cts"
+
+ const val ALLOW_ALL_BUTTON =
+ "com.android.permissioncontroller:id/permission_allow_all_button"
+ const val SELECT_BUTTON =
+ "com.android.permissioncontroller:id/permission_allow_selected_button"
+ const val DONT_SELECT_MORE_BUTTON =
+ "com.android.permissioncontroller:id/permission_dont_allow_more_selected_button"
+ const val ALLOW_BUTTON =
+ "com.android.permissioncontroller:id/permission_allow_button"
+ const val ALLOW_FOREGROUND_BUTTON =
+ "com.android.permissioncontroller:id/permission_allow_foreground_only_button"
+ const val DENY_BUTTON = "com.android.permissioncontroller:id/permission_deny_button"
+ const val DENY_AND_DONT_ASK_AGAIN_BUTTON =
+ "com.android.permissioncontroller:id/permission_deny_and_dont_ask_again_button"
+ const val NO_UPGRADE_BUTTON =
+ "com.android.permissioncontroller:id/permission_no_upgrade_button"
+ const val NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON =
+ "com.android.permissioncontroller:" +
+ "id/permission_no_upgrade_and_dont_ask_again_button"
+
+ const val ALLOW_ALWAYS_RADIO_BUTTON =
+ "com.android.permissioncontroller:id/allow_always_radio_button"
+ const val ALLOW_RADIO_BUTTON = "com.android.permissioncontroller:id/allow_radio_button"
+ const val ALLOW_FOREGROUND_RADIO_BUTTON =
+ "com.android.permissioncontroller:id/allow_foreground_only_radio_button"
+ const val ASK_RADIO_BUTTON = "com.android.permissioncontroller:id/ask_radio_button"
+ const val DENY_RADIO_BUTTON = "com.android.permissioncontroller:id/deny_radio_button"
+ const val SELECT_RADIO_BUTTON =
+ "com.android.permissioncontroller:id/select_radio_button"
+
+ const val NOTIF_TEXT = "permgrouprequest_notifications"
+ const val ALLOW_BUTTON_TEXT = "grant_dialog_button_allow"
+ const val ALLOW_ALL_FILES_BUTTON_TEXT = "app_permission_button_allow_all_files"
+ const val ALLOW_FOREGROUND_BUTTON_TEXT = "grant_dialog_button_allow_foreground"
+ const val ALLOW_FOREGROUND_PREFERENCE_TEXT = "permission_access_only_foreground"
+ const val ASK_BUTTON_TEXT = "app_permission_button_ask"
+ const val ALLOW_ONE_TIME_BUTTON_TEXT = "grant_dialog_button_allow_one_time"
+ const val DENY_BUTTON_TEXT = "grant_dialog_button_deny"
+ const val DENY_ANYWAY_BUTTON_TEXT = "grant_dialog_button_deny_anyway"
+ const val DENY_AND_DONT_ASK_AGAIN_BUTTON_TEXT =
+ "grant_dialog_button_deny_and_dont_ask_again"
+ const val NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON_TEXT = "grant_dialog_button_no_upgrade"
+ const val ALERT_DIALOG_MESSAGE = "android:id/message"
+ const val ALERT_DIALOG_OK_BUTTON = "android:id/button1"
+ const val APP_PERMISSION_RATIONALE_CONTAINER_VIEW =
+ "com.android.permissioncontroller:id/app_permission_rationale_container"
+ const val APP_PERMISSION_RATIONALE_CONTENT_VIEW =
+ "com.android.permissioncontroller:id/app_permission_rationale_content"
+ const val GRANT_DIALOG_PERMISSION_RATIONALE_CONTAINER_VIEW =
+ "com.android.permissioncontroller:id/permission_rationale_container"
+ const val PERMISSION_RATIONALE_ACTIVITY_TITLE_VIEW =
+ "com.android.permissioncontroller:id/permission_rationale_title"
+ const val DATA_SHARING_SOURCE_TITLE_ID =
+ "com.android.permissioncontroller:id/data_sharing_source_title"
+ const val DATA_SHARING_SOURCE_MESSAGE_ID =
+ "com.android.permissioncontroller:id/data_sharing_source_message"
+ const val PURPOSE_TITLE_ID = "com.android.permissioncontroller:id/purpose_title"
+ const val PURPOSE_MESSAGE_ID = "com.android.permissioncontroller:id/purpose_message"
+ const val LEARN_MORE_TITLE_ID = "com.android.permissioncontroller:id/learn_more_title"
+ const val LEARN_MORE_MESSAGE_ID = "com.android.permissioncontroller:id/learn_more_message"
+ const val PERMISSION_RATIONALE_SETTINGS_SECTION =
+ "com.android.permissioncontroller:id/settings_section"
+ const val SETTINGS_TITLE_ID =
+ "com.android.permissioncontroller:id/settings_title"
+ const val SETTINGS_MESSAGE_ID =
+ "com.android.permissioncontroller:id/settings_message"
+
+ const val REQUEST_LOCATION_MESSAGE = "permgrouprequest_location"
+
+ const val DATA_SHARING_UPDATES = "Data sharing updates for location"
+ 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."
+ const val DATA_SHARING_NO_UPDATES_MESSAGE = "No updates at this time"
+ const val UPDATES_IN_LAST_30_DAYS = "Updated within 30 days"
+ 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."
+ const val LEARN_ABOUT_DATA_SHARING = "Learn about data sharing"
+ const val LOCATION_PERMISSION = "Location permission"
+ const val APP_PACKAGE_NAME_SUBSTRING = "android.permissionui"
+ const val NOW_SHARED_WITH_THIRD_PARTIES = "Your location data is now shared with third " +
+ "parties"
+ const val NOW_SHARED_WITH_THIRD_PARTIES_FOR_ADS = "Your location data is now shared with " +
+ "third parties for advertising or marketing"
+ const val PROPERTY_DATA_SHARING_UPDATE_PERIOD_MILLIS =
+ "data_sharing_update_period_millis"
+ const val PROPERTY_MAX_SAFETY_LABELS_PERSISTED_PER_APP =
+ "max_safety_labels_persisted_per_app"
+
+ // The highest SDK for which the system will show a "low SDK" warning when launching the app
+ const val MAX_SDK_FOR_SDK_WARNING = 27
+ const val MIN_SDK_FOR_RUNTIME_PERMS = 23
+
+ val TEST_INSTALLER_ACTIVITY_COMPONENT_NAME =
+ ComponentName(context, TestInstallerActivity::class.java)
+
+ val MEDIA_PERMISSIONS: Set<String> = mutableSetOf(
+ Manifest.permission.ACCESS_MEDIA_LOCATION,
+ Manifest.permission.READ_MEDIA_AUDIO,
+ Manifest.permission.READ_MEDIA_IMAGES,
+ Manifest.permission.READ_MEDIA_VIDEO,
+ ).apply {
+ if (SdkLevel.isAtLeastU()) {
+ add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
+ }
+ }.toSet()
+
+ val STORAGE_AND_MEDIA_PERMISSIONS = MEDIA_PERMISSIONS
+ .plus(Manifest.permission.READ_EXTERNAL_STORAGE)
+ .plus(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+
+ @JvmStatic
+ protected val PICKER_ENABLED_SETTING = "photo_picker_prompt_enabled"
+
+ @JvmStatic
+ protected fun isPhotoPickerPermissionPromptEnabled(): Boolean {
+ return SdkLevel.isAtLeastU() &&
+ !isTv && !isAutomotive && !isWatch &&
+ callWithShellPermissionIdentity {
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY, PICKER_ENABLED_SETTING, true)
+ }
+ }
+ }
+
+ enum class PermissionState {
+ ALLOWED,
+ DENIED,
+ DENIED_WITH_PREJUDICE
+ }
+
+ private val platformResources = context.createPackageContext("android", 0).resources
+ private val permissionToLabelResNameMap = mapOf(
+ // Contacts
+ android.Manifest.permission.READ_CONTACTS
+ to "@android:string/permgrouplab_contacts",
+ android.Manifest.permission.WRITE_CONTACTS
+ to "@android:string/permgrouplab_contacts",
+ // Calendar
+ android.Manifest.permission.READ_CALENDAR
+ to "@android:string/permgrouplab_calendar",
+ android.Manifest.permission.WRITE_CALENDAR
+ to "@android:string/permgrouplab_calendar",
+ // SMS
+ android.Manifest.permission.SEND_SMS to "@android:string/permgrouplab_sms",
+ android.Manifest.permission.RECEIVE_SMS to "@android:string/permgrouplab_sms",
+ android.Manifest.permission.READ_SMS to "@android:string/permgrouplab_sms",
+ android.Manifest.permission.RECEIVE_WAP_PUSH to "@android:string/permgrouplab_sms",
+ android.Manifest.permission.RECEIVE_MMS to "@android:string/permgrouplab_sms",
+ "android.permission.READ_CELL_BROADCASTS" to "@android:string/permgrouplab_sms",
+ // Storage
+ android.Manifest.permission.READ_EXTERNAL_STORAGE
+ to "@android:string/permgrouplab_storage",
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+ to "@android:string/permgrouplab_storage",
+ // Location
+ android.Manifest.permission.ACCESS_FINE_LOCATION
+ to "@android:string/permgrouplab_location",
+ android.Manifest.permission.ACCESS_COARSE_LOCATION
+ to "@android:string/permgrouplab_location",
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
+ to "@android:string/permgrouplab_location",
+ // Phone
+ android.Manifest.permission.READ_PHONE_STATE
+ to "@android:string/permgrouplab_phone",
+ android.Manifest.permission.CALL_PHONE to "@android:string/permgrouplab_phone",
+ "android.permission.ACCESS_IMS_CALL_SERVICE"
+ to "@android:string/permgrouplab_phone",
+ android.Manifest.permission.READ_CALL_LOG to "@android:string/permgrouplab_phone",
+ android.Manifest.permission.WRITE_CALL_LOG to "@android:string/permgrouplab_phone",
+ android.Manifest.permission.ADD_VOICEMAIL to "@android:string/permgrouplab_phone",
+ android.Manifest.permission.USE_SIP to "@android:string/permgrouplab_phone",
+ android.Manifest.permission.PROCESS_OUTGOING_CALLS
+ to "@android:string/permgrouplab_phone",
+ // Microphone
+ android.Manifest.permission.RECORD_AUDIO
+ to "@android:string/permgrouplab_microphone",
+ // Camera
+ android.Manifest.permission.CAMERA to "@android:string/permgrouplab_camera",
+ // Body sensors
+ android.Manifest.permission.BODY_SENSORS to "@android:string/permgrouplab_sensors",
+ android.Manifest.permission.BODY_SENSORS_BACKGROUND
+ to "@android:string/permgrouplab_sensors",
+ // Bluetooth
+ android.Manifest.permission.BLUETOOTH_CONNECT to
+ "@android:string/permgrouplab_nearby_devices",
+ android.Manifest.permission.BLUETOOTH_SCAN to
+ "@android:string/permgrouplab_nearby_devices",
+ // Aural
+ android.Manifest.permission.READ_MEDIA_AUDIO to
+ "@android:string/permgrouplab_readMediaAural",
+ // Visual
+ android.Manifest.permission.READ_MEDIA_IMAGES to
+ "@android:string/permgrouplab_readMediaVisual",
+ android.Manifest.permission.READ_MEDIA_VIDEO to
+ "@android:string/permgrouplab_readMediaVisual"
+ )
+
+ @Before
+ @After
+ fun uninstallApp() {
+ uninstallPackage(APP_PACKAGE_NAME, requireSuccess = false)
+ }
+
+ override fun installPackage(
+ apkPath: String,
+ reinstall: Boolean,
+ grantRuntimePermissions: Boolean,
+ expectSuccess: Boolean,
+ installSource: String?
+ ) {
+ installPackage(apkPath, reinstall, grantRuntimePermissions, expectSuccess, installSource,
+ false)
+ }
+
+ fun installPackage(
+ apkPath: String,
+ reinstall: Boolean = false,
+ grantRuntimePermissions: Boolean = false,
+ expectSuccess: Boolean = true,
+ installSource: String? = null,
+ skipClearLowSdkDialog: Boolean = false
+ ) {
+ super.installPackage(apkPath, reinstall, grantRuntimePermissions, expectSuccess,
+ installSource
+ )
+
+ val targetSdk = getTargetSdk()
+ // If the targetSDK is high enough, the low sdk warning won't show. If the SDK is
+ // below runtime permissions, the dialog will be delayed by the permission review screen.
+ // If success is not expected, don't bother trying
+ if (targetSdk > MAX_SDK_FOR_SDK_WARNING || targetSdk < MIN_SDK_FOR_RUNTIME_PERMS ||
+ !expectSuccess || skipClearLowSdkDialog) {
+ return
+ }
+
+ val finishOnCreateIntent = Intent().apply {
+ component = ComponentName(
+ APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.FinishOnCreateActivity"
+ )
+ flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK
+ }
+
+ // Check if an activity resolves for the test app. If it doesn't, then our test app doesn't
+ // have the usual set of activities, and likely won't be opened, and thus, won't show the
+ // dialog
+ callWithShellPermissionIdentity {
+ context.packageManager.resolveActivity(finishOnCreateIntent, PackageManager.MATCH_ALL)
+ } ?: return
+
+ // Start the test app, and expect the targetSDK warning dialog
+ context.startActivity(finishOnCreateIntent)
+ clearTargetSdkWarning()
+ // Kill the test app, so that the next time we launch, we don't see the app warning dialog
+ killTestApp()
+ }
+
+ protected fun clearTargetSdkWarning(timeoutMillis: Long = TIMEOUT_MILLIS) {
+ if (SdkLevel.isAtLeastV()) {
+ // In V and above, the target SDK dialog can be disabled via system property
+ return
+ }
+
+ waitForIdle()
+ waitFindObjectOrNull(By.res("android:id/button1"), timeoutMillis)?.let {
+ try {
+ it.click()
+ } catch (e: StaleObjectException) {
+ // Click sometimes fails with StaleObjectException (b/280430717).
+ e.printStackTrace()
+ }
+ }
+ }
+
+ protected fun killTestApp() {
+ pressBack()
+ pressBack()
+ runWithShellPermissionIdentity {
+ val am = context.getSystemService(ActivityManager::class.java)!!
+ am.forceStopPackage(APP_PACKAGE_NAME)
+ }
+ waitForIdle()
+ }
+
+ protected fun clickPermissionReviewContinue() {
+ if (isAutomotive || isWatch) {
+ click(By.text(getPermissionControllerString("review_button_continue")))
+ } else {
+ click(By.res("com.android.permissioncontroller:id/continue_button"))
+ }
+ }
+
+ protected fun clickPermissionReviewContinueAndClearSdkWarning() {
+ clickPermissionReviewContinue()
+ waitForIdle()
+ clearTargetSdkWarning()
+ }
+
+ protected fun installPackageWithInstallSourceAndEmptyMetadata(
+ apkName: String
+ ) {
+ installPackageViaSession(apkName, AppMetadata.createEmptyAppMetadata())
+ }
+
+ protected fun installPackageWithInstallSourceAndMetadata(
+ apkName: String
+ ) {
+ installPackageViaSession(apkName, AppMetadata.createDefaultAppMetadata())
+ }
+
+ protected fun installPackageWithInstallSourceAndMetadataFromStore(
+ apkName: String
+ ) {
+ installPackageViaSession(apkName, AppMetadata.createDefaultAppMetadata(),
+ PACKAGE_SOURCE_STORE)
+ }
+
+ protected fun installPackageWithInstallSourceAndMetadataFromLocalFile(
+ apkName: String
+ ) {
+ installPackageViaSession(apkName, AppMetadata.createDefaultAppMetadata(),
+ PACKAGE_SOURCE_LOCAL_FILE)
+ }
+
+ protected fun installPackageWithInstallSourceAndMetadataFromDownloadedFile(
+ apkName: String
+ ) {
+ installPackageViaSession(apkName, AppMetadata.createDefaultAppMetadata(),
+ PACKAGE_SOURCE_DOWNLOADED_FILE)
+ }
+
+ protected fun installPackageWithInstallSourceAndMetadataFromOther(
+ apkName: String
+ ) {
+ installPackageViaSession(apkName, AppMetadata.createDefaultAppMetadata(),
+ PACKAGE_SOURCE_OTHER)
+ }
+
+ protected fun installPackageWithInstallSourceAndNoMetadata(
+ apkName: String
+ ) {
+ installPackageViaSession(apkName)
+ }
+
+ protected fun installPackageWithInstallSourceAndInvalidMetadata(
+ apkName: String
+ ) {
+ installPackageViaSession(apkName, AppMetadata.createInvalidAppMetadata())
+ }
+
+ protected fun installPackageWithInstallSourceAndMetadataWithoutTopLevelVersion(
+ apkName: String
+ ) {
+ installPackageViaSession(
+ apkName, AppMetadata.createInvalidAppMetadataWithoutTopLevelVersion()
+ )
+ }
+
+ protected fun installPackageWithInstallSourceAndMetadataWithInvalidTopLevelVersion(
+ apkName: String
+ ) {
+ installPackageViaSession(
+ apkName, AppMetadata.createInvalidAppMetadataWithInvalidTopLevelVersion()
+ )
+ }
+
+ protected fun installPackageWithInstallSourceAndMetadataWithoutSafetyLabelVersion(
+ apkName: String
+ ) {
+ installPackageViaSession(
+ apkName, AppMetadata.createInvalidAppMetadataWithoutSafetyLabelVersion()
+ )
+ }
+
+ protected fun installPackageWithInstallSourceAndMetadataWithInvalidSafetyLabelVersion(
+ apkName: String
+ ) {
+ installPackageViaSession(
+ apkName, AppMetadata.createInvalidAppMetadataWithInvalidSafetyLabelVersion()
+ )
+ }
+
+ protected fun installPackageWithoutInstallSource(
+ apkName: String
+ ) {
+ // TODO(b/257293222): Update/remove when hooking up PackageManager APIs
+ installPackage(apkName)
+ }
+
+ protected fun assertPermissionRationaleActivityTitleIsVisible(expected: Boolean) {
+ findView(By.res(PERMISSION_RATIONALE_ACTIVITY_TITLE_VIEW), expected = expected)
+ }
+
+ protected fun assertPermissionRationaleActivityDataSharingSourceSectionVisible(
+ expected: Boolean
+ ) {
+ findView(By.res(DATA_SHARING_SOURCE_TITLE_ID), expected = expected)
+ findView(By.res(DATA_SHARING_SOURCE_MESSAGE_ID), expected = expected)
+ }
+
+ protected fun assertPermissionRationaleActivityPurposeSectionVisible(expected: Boolean) {
+ findView(By.res(PURPOSE_TITLE_ID), expected = expected)
+ findView(By.res(PURPOSE_MESSAGE_ID), expected = expected)
+ }
+
+ protected fun assertPermissionRationaleActivityLearnMoreSectionVisible(expected: Boolean) {
+ findView(By.res(LEARN_MORE_TITLE_ID), expected = expected)
+ findView(By.res(LEARN_MORE_MESSAGE_ID), expected = expected)
+ }
+
+ protected fun assertPermissionRationaleActivitySettingsSectionVisible(expected: Boolean) {
+ findView(By.res(PERMISSION_RATIONALE_SETTINGS_SECTION), expected = expected)
+ findView(By.res(SETTINGS_TITLE_ID), expected = expected)
+ findView(By.res(SETTINGS_MESSAGE_ID), expected = expected)
+ }
+
+ protected fun assertPermissionRationaleDialogIsVisible(
+ expected: Boolean,
+ showSettingsSection: Boolean = true
+ ) {
+ assertPermissionRationaleActivityTitleIsVisible(expected)
+ assertPermissionRationaleActivityDataSharingSourceSectionVisible(expected)
+ assertPermissionRationaleActivityPurposeSectionVisible(expected)
+ assertPermissionRationaleActivityLearnMoreSectionVisible(expected)
+ if (expected) {
+ assertPermissionRationaleActivitySettingsSectionVisible(showSettingsSection)
+ }
+ }
+
+ protected fun assertPermissionRationaleContainerOnGrantDialogIsVisible(expected: Boolean) {
+ findView(By.res(GRANT_DIALOG_PERMISSION_RATIONALE_CONTAINER_VIEW), expected = expected)
+ }
+
+ protected fun clickPermissionReviewCancel() {
+ if (isAutomotive || isWatch) {
+ click(By.text(getPermissionControllerString("review_button_cancel")))
+ } else {
+ click(By.res("com.android.permissioncontroller:id/cancel_button"))
+ }
+ }
+
+ protected fun approvePermissionReview() {
+ startAppActivityAndAssertResultCode(Activity.RESULT_OK) {
+ clickPermissionReviewContinueAndClearSdkWarning()
+ waitForIdle()
+ }
+ }
+
+ protected fun cancelPermissionReview() {
+ startAppActivityAndAssertResultCode(Activity.RESULT_CANCELED) {
+ clickPermissionReviewCancel()
+ }
+ }
+
+ protected fun assertAppDoesNotNeedPermissionReview() {
+ startAppActivityAndAssertResultCode(Activity.RESULT_OK) {}
+ }
+
+ protected inline fun startAppActivityAndAssertResultCode(
+ expectedResultCode: Int,
+ block: () -> Unit
+ ) {
+ val future = startActivityForFuture(
+ Intent().apply {
+ component = ComponentName(
+ APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.FinishOnCreateActivity"
+ )
+ }
+ )
+ block()
+ assertEquals(
+ expectedResultCode, future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS).resultCode
+ )
+ }
+
+ protected inline fun requestAppPermissionsForNoResult(
+ vararg permissions: String?,
+ block: () -> Unit
+ ) {
+ // Request the permissions
+ context.startActivity(
+ Intent().apply {
+ component = ComponentName(
+ APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.RequestPermissionsActivity"
+ )
+ putExtra("$APP_PACKAGE_NAME.PERMISSIONS", permissions)
+ addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
+ }
+ )
+ waitForIdle()
+ // Perform the post-request action
+ block()
+ }
+
+ protected inline fun requestAppPermissions(
+ vararg permissions: String?,
+ askTwice: Boolean = false,
+ block: () -> Unit
+ ): Instrumentation.ActivityResult {
+ // Request the permissions
+ val future = startActivityForFuture(
+ Intent().apply {
+ component = ComponentName(
+ APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.RequestPermissionsActivity"
+ )
+ putExtra("$APP_PACKAGE_NAME.PERMISSIONS", permissions)
+ putExtra("$APP_PACKAGE_NAME.ASK_TWICE", askTwice)
+ }
+ )
+ waitForIdle()
+
+ // Notification permission prompt is shown first, so get it out of the way
+ clickNotificationPermissionRequestAllowButtonIfAvailable()
+ // Perform the post-request action
+ block()
+ return future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ }
+
+ protected inline fun requestAppPermissionsAndAssertResult(
+ permissions: Array<out String?>,
+ permissionAndExpectedGrantResults: Array<out Pair<String?, Boolean>>,
+ askTwice: Boolean = false,
+ block: () -> Unit
+ ) {
+ val result = requestAppPermissions(*permissions, askTwice = askTwice, block = block)
+ assertEquals(Activity.RESULT_OK, result.resultCode)
+
+ val responseSize: Int =
+ result.resultData!!.getStringArrayExtra("$APP_PACKAGE_NAME.PERMISSIONS")!!.size
+ assertEquals(
+ responseSize,
+ result.resultData!!.getIntArrayExtra("$APP_PACKAGE_NAME.GRANT_RESULTS")!!.size
+ )
+
+ // Note that the behavior around requesting `null` permissions changed in the platform
+ // in Android U. Currently, null permissions are ignored and left out of the result set.
+ assertTrue(permissions.size >= responseSize)
+ assertEquals(
+ permissionAndExpectedGrantResults
+ .filter { it.first != null }
+ .toList(),
+ result.resultData!!.getStringArrayExtra("$APP_PACKAGE_NAME.PERMISSIONS")!!
+ .filterNotNull()
+ .zip(
+ result.resultData!!.getIntArrayExtra("$APP_PACKAGE_NAME.GRANT_RESULTS")!!
+ .map { it == PackageManager.PERMISSION_GRANTED }
+ )
+ )
+ permissionAndExpectedGrantResults.forEach {
+ it.first?.let { permission ->
+ assertAppHasPermission(permission, it.second)
+ }
+ }
+ }
+
+ protected inline fun requestAppPermissionsAndAssertResult(
+ vararg permissionAndExpectedGrantResults: Pair<String?, Boolean>,
+ askTwice: Boolean = false,
+ block: () -> Unit
+ ) = requestAppPermissionsAndAssertResult(
+ permissionAndExpectedGrantResults.map { it.first }.toTypedArray(),
+ permissionAndExpectedGrantResults,
+ askTwice,
+ block
+ )
+
+ protected fun findPermissionRequestAllowButton(timeoutMillis: Long = 20000) {
+ if (isAutomotive) {
+ waitFindObject(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)), timeoutMillis)
+ } else {
+ waitFindObject(By.res(ALLOW_BUTTON), timeoutMillis)
+ }
+ }
+
+ protected fun clickPermissionRequestAllowButton(timeoutMillis: Long = 20000) {
+ if (isAutomotive) {
+ click(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)), timeoutMillis)
+ } else {
+ click(By.res(ALLOW_BUTTON), timeoutMillis)
+ }
+ }
+
+ protected fun clickPermissionRequestAllowAllButton(timeoutMillis: Long = 20000) {
+ click(By.res(ALLOW_ALL_BUTTON), timeoutMillis)
+ }
+
+ /**
+ * Only for use in tests that are not testing the notification permission popup, on T devices
+ */
+ protected fun clickNotificationPermissionRequestAllowButtonIfAvailable() {
+ if (!SdkLevel.isAtLeastT()) {
+ return
+ }
+
+ if (waitFindObjectOrNull(By.text(getPermissionControllerString(
+ NOTIF_TEXT, APP_PACKAGE_NAME
+ )), 1000) != null) {
+ if (isAutomotive) {
+ click(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)))
+ } else {
+ click(By.res(ALLOW_BUTTON))
+ }
+ }
+ }
+
+ protected fun clickPermissionRequestSettingsLinkAndAllowAlways() {
+ clickPermissionRequestSettingsLink()
+ eventually({
+ clickAllowAlwaysInSettings()
+ }, TIMEOUT_MILLIS * 2)
+ pressBack()
+ }
+
+ protected fun clickAllowAlwaysInSettings() {
+ if (isAutomotive || isTv || isWatch) {
+ click(By.text(getPermissionControllerString("app_permission_button_allow_always")))
+ } else {
+ click(By.res("com.android.permissioncontroller:id/allow_always_radio_button"))
+ }
+ }
+
+ protected fun clickAllowForegroundInSettings() {
+ click(By.res(ALLOW_FOREGROUND_RADIO_BUTTON))
+ }
+
+ protected fun clicksDenyInSettings() {
+ if (isAutomotive || isWatch) {
+ click(By.text(getPermissionControllerString("app_permission_button_deny")))
+ } else {
+ click(By.res("com.android.permissioncontroller:id/deny_radio_button"))
+ }
+ }
+
+ protected fun findPermissionRequestAllowForegroundButton(timeoutMillis: Long = 20000) {
+ if (isAutomotive) {
+ waitFindObject(By.text(
+ getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT)), timeoutMillis)
+ } else {
+ waitFindObject(By.res(ALLOW_FOREGROUND_BUTTON), timeoutMillis)
+ }
+ }
+
+ protected fun clickPermissionRequestAllowForegroundButton(timeoutMillis: Long = 10_000) {
+ if (isAutomotive) {
+ click(By.text(
+ getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT)), timeoutMillis)
+ } else {
+ click(By.res(ALLOW_FOREGROUND_BUTTON), timeoutMillis)
+ }
+ }
+
+ protected fun clickPermissionRequestDenyButton() {
+ if (isAutomotive || isWatch || isTv) {
+ click(By.text(getPermissionControllerString(DENY_BUTTON_TEXT)))
+ } else {
+ click(By.res(DENY_BUTTON))
+ }
+ }
+
+ protected fun clickPermissionRequestSettingsLinkAndDeny() {
+ clickPermissionRequestSettingsLink()
+ eventually({
+ clicksDenyInSettings()
+ }, TIMEOUT_MILLIS * 2)
+ waitForIdle()
+ pressBack()
+ }
+
+ protected fun clickPermissionRequestSettingsLink() {
+ waitForIdle()
+ eventually {
+ // UiObject2 doesn't expose CharSequence.
+ val node = if (isAutomotive) {
+ // Should match "Allow in settings." (location) and "go to settings." (body sensors)
+ uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByText(
+ " settings."
+ )[0]
+ } else {
+ uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByViewId(
+ "com.android.permissioncontroller:id/detail_message"
+ )[0]
+ }
+ if (!node.isVisibleToUser) {
+ scrollToBottom()
+ }
+ assertTrue(node.isVisibleToUser)
+ val text = node.text as Spanned
+ val clickableSpan = text.getSpans(0, text.length, ClickableSpan::class.java)[0]
+ // We could pass in null here in Java, but we need an instance in Kotlin.
+ clickableSpan.onClick(View(context))
+ }
+ waitForIdle()
+ }
+
+ protected fun clickPermissionRequestDenyAndDontAskAgainButton() {
+ if (isAutomotive) {
+ click(By.text(getPermissionControllerString(DENY_AND_DONT_ASK_AGAIN_BUTTON_TEXT)))
+ } else if (isWatch) {
+ click(By.text(getPermissionControllerString(DENY_BUTTON_TEXT)))
+ } else {
+ click(By.res(DENY_AND_DONT_ASK_AGAIN_BUTTON))
+ }
+ }
+
+ // Only used in TV and Watch form factors
+ protected fun clickPermissionRequestDontAskAgainButton() {
+ if (isWatch) {
+ click(By.text(getPermissionControllerString(DENY_BUTTON_TEXT)))
+ } else {
+ click(
+ By.res("com.android.permissioncontroller:id/permission_deny_dont_ask_again_button")
+ )
+ }
+ }
+
+ protected fun clickPermissionRequestNoUpgradeAndDontAskAgainButton() {
+ if (isAutomotive) {
+ click(By.text(getPermissionControllerString(NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON_TEXT)))
+ } else {
+ click(By.res(NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON))
+ }
+ }
+
+ protected fun clickPermissionRationaleContentInAppPermission() {
+ waitForIdle()
+ click(By.res(APP_PERMISSION_RATIONALE_CONTENT_VIEW))
+ }
+
+ protected fun clickPermissionRationaleViewInGrantDialog() {
+ waitForIdle()
+ click(By.res(GRANT_DIALOG_PERMISSION_RATIONALE_CONTAINER_VIEW))
+ }
+
+ protected fun grantAppPermissionsByUi(vararg permissions: String) {
+ setAppPermissionState(*permissions, state = PermissionState.ALLOWED, isLegacyApp = false)
+ }
+
+ protected fun grantRuntimePermissions(vararg permissions: String) {
+ for (permission in permissions) {
+ uiAutomation.grantRuntimePermission(APP_PACKAGE_NAME, permission)
+ }
+ }
+
+ protected fun revokeAppPermissionsByUi(
+ vararg permissions: String,
+ isLegacyApp: Boolean = false
+ ) {
+ setAppPermissionState(*permissions, state = PermissionState.DENIED,
+ isLegacyApp = isLegacyApp)
+ }
+
+ private fun navigateToAppPermissionSettings() {
+ if (isTv) {
+ // Dismiss DeprecatedTargetSdkVersionDialog, if present
+ if (waitFindObjectOrNull(By.text(APP_PACKAGE_NAME), 1000L) != null) {
+ pressBack()
+ }
+ pressHome()
+ } else {
+ pressBack()
+ pressBack()
+ pressBack()
+ }
+
+ // Try multiple times as the AppInfo page might have read stale data
+ eventually({
+ try {
+ // Open the app details settings
+ context.startActivity(
+ Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
+ data = Uri.fromParts("package", APP_PACKAGE_NAME, null)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ }
+ )
+ if (isTv) {
+ waitForIdle()
+ pressDPadDown()
+ pressDPadDown()
+ pressDPadDown()
+ pressDPadDown()
+ }
+ // Open the permissions UI
+ click(byTextRes(R.string.permissions).enabled(true))
+ } catch (e: Exception) {
+ pressBack()
+ throw e
+ }
+ }, TIMEOUT_MILLIS)
+ }
+
+ private fun getTargetSdk(packageName: String = APP_PACKAGE_NAME): Int {
+ return callWithShellPermissionIdentity {
+ try {
+ context.packageManager.getApplicationInfo(packageName, 0).targetSdkVersion
+ } catch (e: PackageManager.NameNotFoundException) {
+ -1
+ }
+ }
+ }
+
+ protected fun navigateToIndividualPermissionSetting(
+ permission: String,
+ manuallyNavigate: Boolean = false
+ ) {
+
+ val useLegacyNavigation = isWatch || isAutomotive || manuallyNavigate
+ if (useLegacyNavigation) {
+ navigateToAppPermissionSettings()
+ val permissionLabel = getPermissionLabel(permission)
+ if (isWatch) {
+ click(By.text(permissionLabel), 40_000)
+ } else {
+ clickPermissionControllerUi(By.text(permissionLabel))
+ }
+ return
+ }
+
+ runWithShellPermissionIdentity {
+ context.startActivity(
+ Intent(Intent.ACTION_MANAGE_APP_PERMISSION).apply {
+ putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
+ putExtra(Intent.EXTRA_PERMISSION_NAME, permission)
+ putExtra(Intent.EXTRA_USER, Process.myUserHandle())
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ })
+ }
+ }
+
+ /** Starts activity with intent [ACTION_REVIEW_APP_DATA_SHARING_UPDATES]. */
+ fun startAppDataSharingUpdatesActivity() {
+ runWithShellPermissionIdentity {
+ context.startActivity(
+ Intent(ACTION_REVIEW_APP_DATA_SHARING_UPDATES).apply {
+ addFlags(FLAG_ACTIVITY_NEW_TASK)
+ })
+ }
+ }
+
+ private fun setAppPermissionState(
+ vararg permissions: String,
+ state: PermissionState,
+ isLegacyApp: Boolean,
+ manuallyNavigate: Boolean = false,
+ ) {
+ val useLegacyNavigation = isWatch || isAutomotive || manuallyNavigate
+ if (useLegacyNavigation) {
+ navigateToAppPermissionSettings()
+ }
+
+ val navigatedGroupLabels = mutableSetOf<String>()
+ for (permission in permissions) {
+ // Find the permission screen
+ val permissionLabel = getPermissionLabel(permission)
+ if (navigatedGroupLabels.contains(getPermissionLabel(permission))) {
+ continue
+ }
+ navigatedGroupLabels.add(permissionLabel)
+ if (useLegacyNavigation) {
+ if (isWatch) {
+ click(By.text(permissionLabel), 40_000)
+ } else if (isAutomotive) {
+ clickPermissionControllerUi(permissionLabel)
+ } else {
+ clickPermissionControllerUi(By.text(permissionLabel))
+ }
+ } else {
+ runWithShellPermissionIdentity {
+ context.startActivity(
+ Intent(Intent.ACTION_MANAGE_APP_PERMISSION).apply {
+ putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
+ putExtra(Intent.EXTRA_PERMISSION_NAME, permission)
+ putExtra(Intent.EXTRA_USER, Process.myUserHandle())
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ })
+ }
+ }
+
+ val wasGranted = if (isAutomotive) {
+ // Automotive doesn't support one time permissions, and thus
+ // won't show an "Ask every time" message
+ !waitFindObject(By.text(
+ getPermissionControllerString("app_permission_button_deny"))).isChecked
+ } else if (isTv || isWatch) {
+ !(waitFindObject(
+ By.text(getPermissionControllerString(DENY_BUTTON_TEXT))).isChecked ||
+ (!isLegacyApp && hasAskButton(permission) && waitFindObject(
+ By.text(getPermissionControllerString(ASK_BUTTON_TEXT))).isChecked))
+ } else {
+ !(waitFindObject(By.res(DENY_RADIO_BUTTON)).isChecked ||
+ (!isLegacyApp && hasAskButton(permission) &&
+ waitFindObject(By.res(ASK_RADIO_BUTTON)).isChecked))
+ }
+ var alreadyChecked = false
+ val button = waitFindObject(
+ if (isAutomotive) {
+ // Automotive doesn't support one time permissions, and thus
+ // won't show an "Ask every time" message
+ when (state) {
+ PermissionState.ALLOWED ->
+ if (showsForegroundOnlyButton(permission)) {
+ By.text(getPermissionControllerString(
+ "app_permission_button_allow_foreground"))
+ } else {
+ By.text(getPermissionControllerString(
+ "app_permission_button_allow"))
+ }
+ PermissionState.DENIED -> By.text(
+ getPermissionControllerString("app_permission_button_deny"))
+ PermissionState.DENIED_WITH_PREJUDICE -> By.text(
+ getPermissionControllerString("app_permission_button_deny"))
+ }
+ } else if (isTv || isWatch) {
+ when (state) {
+ PermissionState.ALLOWED ->
+ if (showsForegroundOnlyButton(permission)) {
+ By.text(getPermissionControllerString(
+ ALLOW_FOREGROUND_PREFERENCE_TEXT
+ ))
+ } else {
+ byAnyText(getPermissionControllerResString(ALLOW_BUTTON_TEXT),
+ getPermissionControllerResString(ALLOW_ALL_FILES_BUTTON_TEXT))
+ }
+ PermissionState.DENIED ->
+ if (!isLegacyApp && hasAskButton(permission)) {
+ By.text(getPermissionControllerString(ASK_BUTTON_TEXT))
+ } else {
+ By.text(getPermissionControllerString(DENY_BUTTON_TEXT))
+ }
+ PermissionState.DENIED_WITH_PREJUDICE -> By.text(
+ getPermissionControllerString(DENY_BUTTON_TEXT))
+ }
+ } else {
+ when (state) {
+ PermissionState.ALLOWED ->
+ if (showsForegroundOnlyButton(permission)) {
+ By.res(ALLOW_FOREGROUND_RADIO_BUTTON)
+ } else if (showsAlwaysButton(permission)) {
+ By.res(ALLOW_ALWAYS_RADIO_BUTTON)
+ } else {
+ By.res(ALLOW_RADIO_BUTTON)
+ }
+ PermissionState.DENIED ->
+ if (!isLegacyApp && hasAskButton(permission)) {
+ By.res(ASK_RADIO_BUTTON)
+ } else {
+ By.res(DENY_RADIO_BUTTON)
+ }
+ PermissionState.DENIED_WITH_PREJUDICE -> By.res(DENY_RADIO_BUTTON)
+ }
+ }
+ )
+ alreadyChecked = button.isChecked
+ if (!alreadyChecked) {
+ button.click()
+ }
+
+ val shouldShowStorageWarning = SdkLevel.isAtLeastT() &&
+ getTargetSdk() <= Build.VERSION_CODES.S_V2 &&
+ permission in MEDIA_PERMISSIONS
+ if (shouldShowStorageWarning) {
+ click(By.res(ALERT_DIALOG_OK_BUTTON))
+ } else if (!alreadyChecked && isLegacyApp && wasGranted) {
+ if (!isTv) {
+ // Wait for alert dialog to popup, then scroll to the bottom of it
+ if (isWatch) {
+ waitFindObject(By.text(
+ getPermissionControllerString("old_sdk_deny_warning")))
+ } else {
+ waitFindObject(By.res(ALERT_DIALOG_MESSAGE))
+ }
+ scrollToBottom()
+ }
+
+ // Due to the limited real estate, Wear uses buttons with icons instead of text
+ // for dialogs
+ if (isWatch) {
+ click(By.res(
+ "com.android.permissioncontroller:id/wear_alertdialog_positive_button"))
+ } else {
+ val resources = context.createPackageContext(
+ packageManager.permissionControllerPackageName, 0
+ ).resources
+ val confirmTextRes = resources.getIdentifier(
+ "com.android.permissioncontroller:string/grant_dialog_button_deny_anyway",
+ null, null
+ )
+
+ val confirmText = resources.getString(confirmTextRes)
+ click(byTextStartsWithCaseInsensitive(confirmText))
+ }
+ }
+ pressBack()
+ }
+ pressBack()
+ pressBack()
+ }
+
+ private fun getPermissionLabel(permission: String): String {
+ val labelResName = permissionToLabelResNameMap[permission]
+ assertNotNull("Unknown permission $permission", labelResName)
+ val labelRes = platformResources.getIdentifier(labelResName, null, null)
+ return platformResources.getString(labelRes)
+ }
+
+ private fun hasAskButton(permission: String): Boolean =
+ when (permission) {
+ android.Manifest.permission.CAMERA,
+ android.Manifest.permission.RECORD_AUDIO,
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION -> true
+ else -> false
+ }
+ private fun showsAllowPhotosButton(permission: String): Boolean {
+ if (!isPhotoPickerPermissionPromptEnabled()) {
+ return false
+ }
+ return when (permission) {
+ Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED,
+ Manifest.permission.READ_MEDIA_IMAGES,
+ Manifest.permission.READ_MEDIA_VIDEO -> true
+ else -> false
+ }
+ }
+
+ private fun showsForegroundOnlyButton(permission: String): Boolean =
+ when (permission) {
+ android.Manifest.permission.CAMERA,
+ android.Manifest.permission.RECORD_AUDIO -> true
+ else -> false
+ }
+
+ private fun showsAlwaysButton(permission: String): Boolean =
+ when (permission) {
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION -> true
+ else -> false
+ }
+
+ private fun scrollToBottom() {
+ val scrollable = UiScrollable(UiSelector().scrollable(true)).apply {
+ if (isWatch) {
+ swipeDeadZonePercentage = 0.1
+ } else {
+ swipeDeadZonePercentage = 0.25
+ }
+ }
+ waitForIdle()
+ if (scrollable.exists()) {
+ try {
+ scrollable.flingToEnd(10)
+ } catch (e: UiObjectNotFoundException) {
+ // flingToEnd() sometimes still fails despite waitForIdle() and the exists() check
+ // (b/246984354).
+ e.printStackTrace()
+ }
+ }
+ }
+
+ private fun byTextRes(textRes: Int): BySelector = By.text(context.getString(textRes))
+
+ private fun byTextStartsWithCaseInsensitive(prefix: String): BySelector =
+ By.text(Pattern.compile("(?i)^${Pattern.quote(prefix)}.*$"))
+
+ protected fun assertAppHasPermission(permissionName: String, expectPermission: Boolean) {
+ assertEquals( "Permission $permissionName",
+ if (expectPermission) {
+ PackageManager.PERMISSION_GRANTED
+ } else {
+ PackageManager.PERMISSION_DENIED
+ },
+ packageManager.checkPermission(permissionName, APP_PACKAGE_NAME)
+ )
+ }
+
+ protected fun assertAppHasCalendarAccess(expectAccess: Boolean) {
+ val future = startActivityForFuture(
+ Intent().apply {
+ component = ComponentName(
+ APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.CheckCalendarAccessActivity"
+ )
+ }
+ )
+ waitForIdle()
+ clickNotificationPermissionRequestAllowButtonIfAvailable()
+ val result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ assertEquals(Activity.RESULT_OK, result.resultCode)
+ assertTrue(result.resultData!!.hasExtra("$APP_PACKAGE_NAME.HAS_ACCESS"))
+ assertEquals(
+ expectAccess,
+ result.resultData!!.getBooleanExtra("$APP_PACKAGE_NAME.HAS_ACCESS", false)
+ )
+ }
+
+ protected fun assertPermissionFlags(permName: String, vararg flags: Pair<Int, Boolean>) {
+ val user = Process.myUserHandle()
+ SystemUtil.runWithShellPermissionIdentity {
+ val currFlags =
+ packageManager.getPermissionFlags(permName, APP_PACKAGE_NAME, user)
+ for ((flag, set) in flags) {
+ assertEquals("flag $flag: ", set, currFlags and flag != 0)
+ }
+ }
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt
new file mode 100644
index 000000000..11d3845ba
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt
@@ -0,0 +1,649 @@
+/*
+ * 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.permissionui.cts
+
+import android.Manifest
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.app.compat.CompatChanges
+import android.content.AttributionSource
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.hardware.camera2.CameraManager
+import android.os.Build
+import android.os.Process
+import android.os.SystemClock
+import android.os.SystemProperties
+import android.permission.PermissionManager
+import android.platform.test.annotations.AsbSecurityTest
+import android.platform.test.annotations.FlakyTest
+import android.provider.DeviceConfig
+import android.provider.Settings
+import android.safetycenter.SafetyCenterManager
+import android.server.wm.WindowManagerStateHelper
+import androidx.annotation.RequiresApi
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.StaleObjectException
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.UiSelector
+import com.android.compatibility.common.util.CddTest
+import com.android.compatibility.common.util.DisableAnimationRule
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.UiAutomatorUtils2
+import com.android.modules.utils.build.SdkLevel
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase
+import java.util.regex.Pattern
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+private const val APK_PATH =
+ "/data/local/tmp/cts/permissionui/CtsAppThatAccessesMicAndCameraPermission.apk"
+private const val APP_LABEL = "CtsCameraMicAccess"
+private const val APP_PKG = "android.permissionui.cts.appthataccessescameraandmic"
+private const val SHELL_PKG = "com.android.shell"
+private const val USE_CAMERA = "use_camera"
+private const val USE_MICROPHONE = "use_microphone"
+private const val USE_HOTWORD = "use_hotword"
+private const val FINISH_EARLY = "finish_early"
+private const val USE_INTENT_ACTION = "test.action.USE_CAMERA_OR_MIC"
+private const val PRIVACY_CHIP_ID = "com.android.systemui:id/privacy_chip"
+private const val PRIVACY_ITEM_ID = "com.android.systemui:id/privacy_item"
+private const val INDICATORS_FLAG = "camera_mic_icons_enabled"
+private const val PERMISSION_INDICATORS_NOT_PRESENT = 162547999L
+private const val IDLE_TIMEOUT_MILLIS: Long = 1000
+private const val UNEXPECTED_TIMEOUT_MILLIS = 1000L
+private const val TIMEOUT_MILLIS: Long = 20000
+private const val TV_MIC_INDICATOR_WINDOW_TITLE = "MicrophoneCaptureIndicator"
+private const val MIC_LABEL_NAME = "microphone_toggle_label_qs"
+private const val CAMERA_LABEL_NAME = "camera_toggle_label_qs"
+private val HOTWORD_DETECTION_SERVICE_REQUIRED =
+ SystemProperties.getBoolean("ro.hotword.detection_service_required", false)
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+@FlakyTest
+class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context: Context = instrumentation.context
+ private val uiAutomation: UiAutomation = instrumentation.uiAutomation
+ private val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
+ private val packageManager: PackageManager = context.packageManager
+ private val permissionManager: PermissionManager =
+ context.getSystemService(PermissionManager::class.java)!!
+
+ private val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ private val isCar = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ private val safetyCenterMicLabel = getPermissionControllerString(MIC_LABEL_NAME)
+ private val safetyCenterCameraLabel = getPermissionControllerString(CAMERA_LABEL_NAME)
+ private val originalCameraLabel = packageManager.getPermissionGroupInfo(
+ Manifest.permission_group.CAMERA, 0).loadLabel(packageManager).toString()
+ private val originalMicLabel = packageManager.getPermissionGroupInfo(
+ Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
+ private val cameraLabel = originalCameraLabel.lowercase()
+ private val micLabel = originalMicLabel.lowercase()
+ private var wasEnabled = false
+ private var isScreenOn = false
+ private var screenTimeoutBeforeTest: Long = 0L
+ private lateinit var carMicPrivacyChipId: String
+ private lateinit var carCameraPrivacyChipId: String
+
+ @get:Rule
+ val disableAnimationRule = DisableAnimationRule()
+
+ constructor() : super()
+
+ companion object {
+ private const val AUTO_MIC_INDICATOR_DISMISSAL_TIMEOUT_MS = 30_000L
+ const val SAFETY_CENTER_ENABLED = "safety_center_is_enabled"
+ const val DELAY_MILLIS = 3000L
+ }
+
+ private val safetyCenterEnabled = callWithShellPermissionIdentity {
+ DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY,
+ SAFETY_CENTER_ENABLED, false.toString())!!
+ }
+
+ private fun uninstall() {
+ val output = runShellCommand("pm uninstall $APP_PKG").trim()
+ assertEquals("Success", output)
+ }
+
+ private fun install() {
+ val output = runShellCommand("pm install -g $APK_PATH").trim()
+ assertEquals("Success", output)
+ }
+
+ @Before
+ fun setUp() {
+ runWithShellPermissionIdentity {
+ screenTimeoutBeforeTest = Settings.System.getLong(
+ context.contentResolver, Settings.System.SCREEN_OFF_TIMEOUT
+ )
+ Settings.System.putLong(
+ context.contentResolver, Settings.System.SCREEN_OFF_TIMEOUT, 1800000L
+ )
+ }
+
+ if (!isScreenOn) {
+ uiDevice.wakeUp()
+ runShellCommand(instrumentation, "wm dismiss-keyguard")
+ Thread.sleep(DELAY_MILLIS)
+ isScreenOn = true
+ }
+ uiDevice.findObject(By.text("Close"))?.click()
+ wasEnabled = setIndicatorsEnabledStateIfNeeded(true)
+ // If the change Id is not present, then isChangeEnabled will return true. To bypass this,
+ // the change is set to "false" if present.
+ assumeFalse("feature not present on this device", callWithShellPermissionIdentity {
+ CompatChanges.isChangeEnabled(PERMISSION_INDICATORS_NOT_PRESENT, Process.SYSTEM_UID)
+ })
+ install()
+ }
+
+ private fun setIndicatorsEnabledStateIfNeeded(shouldBeEnabled: Boolean): Boolean {
+ var currentlyEnabled = false
+ runWithShellPermissionIdentity {
+ currentlyEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ INDICATORS_FLAG, true)
+ if (currentlyEnabled != shouldBeEnabled) {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, INDICATORS_FLAG,
+ shouldBeEnabled.toString(), false)
+ }
+ }
+ return currentlyEnabled
+ }
+
+ @After
+ fun tearDown() {
+ uninstall()
+ if (isCar) {
+ // Deselect the indicator since it persists otherwise
+ pressBack()
+ }
+ eventually({
+ assertIndicatorsShown(false, false, false)
+ }, AUTO_MIC_INDICATOR_DISMISSAL_TIMEOUT_MS)
+ if (!wasEnabled) {
+ setIndicatorsEnabledStateIfNeeded(false)
+ }
+ runWithShellPermissionIdentity {
+ Settings.System.putLong(
+ context.contentResolver, Settings.System.SCREEN_OFF_TIMEOUT,
+ screenTimeoutBeforeTest
+ )
+ }
+ changeSafetyCenterFlag(safetyCenterEnabled)
+ if (!isTv) {
+ pressBack()
+ pressBack()
+ }
+ pressHome()
+ pressHome()
+ }
+
+ private fun openApp(
+ useMic: Boolean,
+ useCamera: Boolean,
+ useHotword: Boolean,
+ finishEarly: Boolean = false
+ ) {
+ context.startActivity(Intent(USE_INTENT_ACTION).apply {
+ putExtra(USE_CAMERA, useCamera)
+ putExtra(USE_MICROPHONE, useMic)
+ putExtra(USE_HOTWORD, useHotword)
+ putExtra(FINISH_EARLY, finishEarly)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ })
+ }
+
+ @Test
+ @CddTest(requirement = "9.8.2/H-5-1,T-5-1,A-2-1")
+ fun testCameraIndicator() {
+ // If camera is not available skip the test
+ assumeTrue(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA))
+ val manager = context.getSystemService(CameraManager::class.java)!!
+ assumeTrue(manager.cameraIdList.isNotEmpty())
+ changeSafetyCenterFlag(false.toString())
+ testCameraAndMicIndicator(useMic = false, useCamera = true)
+ }
+
+ @Test
+ @CddTest(requirement = "9.8.2/H-4-1,T-4-1,A-1-1")
+ fun testMicIndicator() {
+ changeSafetyCenterFlag(false.toString())
+ testCameraAndMicIndicator(useMic = true, useCamera = false)
+ }
+
+ // TODO b/269687722: remove once mainline presubmit uses a more recent S build
+ @Test
+ @AsbSecurityTest(cveBugId = [258672042])
+ fun testMicIndicatorWithManualFinishOpStillShows() {
+ changeSafetyCenterFlag(false.toString())
+ testCameraAndMicIndicator(useMic = true, useCamera = false, finishEarly = true)
+ }
+
+ @Test
+ @CddTest(requirement = "9.8.2/H-4-1,T-4-1,A-1-1")
+ fun testHotwordIndicatorBehavior() {
+ changeSafetyCenterFlag(false.toString())
+ testCameraAndMicIndicator(useMic = false, useCamera = false, useHotword = true)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ fun testChainUsageWithOtherUsage() {
+ // TV has only the mic icon
+ assumeFalse(isTv)
+ // Car has separate panels for mic and camera for now.
+ // TODO(b/218788634): enable this test for car once the new camera indicator is implemented.
+ assumeFalse(isCar)
+ // If camera is not available skip the test
+ assumeTrue(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA))
+ changeSafetyCenterFlag(false.toString())
+ testCameraAndMicIndicator(useMic = false, useCamera = true, chainUsage = true)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ fun testSafetyCenterCameraIndicator() {
+ assumeFalse(isTv)
+ assumeFalse(isCar)
+ val manager = context.getSystemService(CameraManager::class.java)!!
+ assumeTrue(manager.cameraIdList.isNotEmpty())
+ changeSafetyCenterFlag(true.toString())
+ assumeSafetyCenterEnabled()
+ testCameraAndMicIndicator(useMic = false, useCamera = true, safetyCenterEnabled = true)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ fun testSafetyCenterMicIndicator() {
+ assumeFalse(isTv)
+ assumeFalse(isCar)
+ changeSafetyCenterFlag(true.toString())
+ assumeSafetyCenterEnabled()
+ testCameraAndMicIndicator(useMic = true, useCamera = false, safetyCenterEnabled = true)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ fun testSafetyCenterHotwordIndicatorBehavior() {
+ assumeFalse(isTv)
+ assumeFalse(isCar)
+ assumeTrue(HOTWORD_DETECTION_SERVICE_REQUIRED)
+ changeSafetyCenterFlag(true.toString())
+ assumeSafetyCenterEnabled()
+ testCameraAndMicIndicator(
+ useMic = false,
+ useCamera = false,
+ useHotword = true,
+ safetyCenterEnabled = true
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ fun testSafetyCenterChainUsageWithOtherUsage() {
+ assumeFalse(isTv)
+ assumeFalse(isCar)
+ changeSafetyCenterFlag(true.toString())
+ assumeSafetyCenterEnabled()
+ testCameraAndMicIndicator(
+ useMic = false,
+ useCamera = true,
+ chainUsage = true,
+ safetyCenterEnabled = true
+ )
+ }
+
+ private fun testCameraAndMicIndicator(
+ useMic: Boolean,
+ useCamera: Boolean,
+ useHotword: Boolean = false,
+ chainUsage: Boolean = false,
+ safetyCenterEnabled: Boolean = false,
+ finishEarly: Boolean = false
+ ) {
+ // If camera is not available skip the test
+ if (useCamera) {
+ assumeTrue(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA))
+ }
+ var chainAttribution: AttributionSource? = null
+ openApp(useMic, useCamera, useHotword, finishEarly)
+ try {
+ eventually {
+ val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
+ assertTrue("View with text $APP_LABEL not found", appView.exists())
+ }
+ if (chainUsage) {
+ chainAttribution = createChainAttribution()
+ runWithShellPermissionIdentity {
+ val ret = permissionManager.checkPermissionForStartDataDelivery(
+ Manifest.permission.RECORD_AUDIO, chainAttribution!!, "")
+ assertEquals(PermissionManager.PERMISSION_GRANTED, ret)
+ }
+ }
+
+ assertIndicatorsShown(useMic, useCamera, useHotword, chainUsage,
+ safetyCenterEnabled)
+
+ if (finishEarly) {
+ // Assert that the indicator doesn't go away
+ val indicatorGoneException: Exception? = try {
+ eventually {
+ assertIndicatorsShown(false, false, false)
+ }
+ null
+ } catch (e: Exception) {
+ e
+ }
+ assertNotNull("Expected the indicator to be present", indicatorGoneException)
+ }
+ } finally {
+ if (chainAttribution != null) {
+ runWithShellPermissionIdentity {
+ permissionManager.finishDataDelivery(Manifest.permission.RECORD_AUDIO,
+ chainAttribution)
+ }
+ }
+ }
+ }
+
+ private fun assertIndicatorsShown(
+ useMic: Boolean,
+ useCamera: Boolean,
+ useHotword: Boolean = false,
+ chainUsage: Boolean = false,
+ safetyCenterEnabled: Boolean = false,
+ ) {
+ if (isTv) {
+ assertTvIndicatorsShown(useMic, useCamera, useHotword)
+ } else if (isCar) {
+ assertCarIndicatorsShown(useMic, useCamera, useHotword, chainUsage)
+ } else {
+ uiDevice.openQuickSettings()
+ val micInUse = if (SdkLevel.isAtLeastU() && HOTWORD_DETECTION_SERVICE_REQUIRED) {
+ useMic || useHotword
+ } else {
+ useMic
+ }
+ assertPrivacyChipAndIndicatorsPresent(micInUse, useCamera, chainUsage,
+ safetyCenterEnabled)
+ uiDevice.pressBack()
+ }
+ }
+
+ private fun assertTvIndicatorsShown(useMic: Boolean, useCamera: Boolean, useHotword: Boolean) {
+ if (useMic || useHotword || (!useMic && !useCamera && !useHotword)) {
+ eventually {
+ val found = WindowManagerStateHelper()
+ .waitFor("Waiting for the mic indicator window to come up") {
+ it.containsWindow(TV_MIC_INDICATOR_WINDOW_TITLE) &&
+ it.isWindowVisible(TV_MIC_INDICATOR_WINDOW_TITLE)
+ }
+ if (useMic) {
+ assertTrue("Did not find chip", found)
+ } else {
+ assertFalse("Found chip, but did not expect to", found)
+ }
+ }
+ }
+ if (useCamera) {
+ // There is no camera indicator on TVs.
+ }
+ }
+
+ private fun assertCarIndicatorsShown(
+ useMic: Boolean,
+ useCamera: Boolean,
+ useHotword: Boolean,
+ chainUsage: Boolean
+ ) {
+ eventually {
+ // Ensure the privacy chip is present (or not)
+ carMicPrivacyChipId = context.getString(R.string.car_mic_privacy_chip_id)
+ carCameraPrivacyChipId = context.getString(R.string.car_camera_privacy_chip_id)
+ var micPrivacyChip = uiDevice.findObject(By.res(carMicPrivacyChipId))
+ var cameraPrivacyChip = uiDevice.findObject(By.res(carCameraPrivacyChipId))
+ if (useMic) {
+ assertNotNull("Did not find mic chip", micPrivacyChip)
+ // Click to chip to show the panel.
+ micPrivacyChip.click()
+ } else if (useCamera) {
+ assertNotNull("Did not find camera chip", cameraPrivacyChip)
+ // Click to chip to show the panel.
+ cameraPrivacyChip.click()
+ } else {
+ assertNull("Found mic chip, but did not expect to", micPrivacyChip)
+ assertNull("Found camera chip, but did not expect to", cameraPrivacyChip)
+ }
+ }
+
+ eventually {
+ if (chainUsage) {
+ // Not applicable for car
+ assertChainMicAndOtherCameraUsed(false)
+ return@eventually
+ }
+ if (useMic) {
+ // There should be a mic privacy panel after mic privacy chip is clicked
+ val micLabelView = uiDevice.findObject(UiSelector().textContains(micLabel))
+ assertTrue("View with text $micLabel not found", micLabelView.exists())
+ val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
+ assertTrue("View with text $APP_LABEL not found", appView.exists())
+ } else if (useCamera) {
+ // There should be a camera privacy panel after camera privacy chip is clicked
+ val cameraLabelView = uiDevice.findObject(UiSelector().textContains(cameraLabel))
+ assertTrue("View with text $cameraLabel not found", cameraLabelView.exists())
+ val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
+ assertTrue("View with text $APP_LABEL not found", appView.exists())
+ } else {
+ // There should be no privacy panel when using hot word
+ val micLabelView = uiDevice.findObject(UiSelector().textContains(micLabel))
+ assertFalse("View with text $micLabel found, but did not expect to",
+ micLabelView.exists())
+ val cameraLabelView = uiDevice.findObject(UiSelector().textContains(cameraLabel))
+ assertFalse("View with text $cameraLabel found, but did not expect to",
+ cameraLabelView.exists())
+ val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
+ assertFalse("View with text $APP_LABEL found, but did not expect to",
+ appView.exists())
+ }
+ }
+ }
+
+ private fun assertPrivacyChipAndIndicatorsPresent(
+ useMic: Boolean,
+ useCamera: Boolean,
+ chainUsage: Boolean,
+ safetyCenterEnabled: Boolean = false
+ ) {
+ // Ensure the privacy chip is present
+ if (useCamera || useMic) {
+ eventually {
+ val privacyChip = UiAutomatorUtils2.waitFindObjectOrNull(By.res(PRIVACY_CHIP_ID))
+ assertNotNull("view with id $PRIVACY_CHIP_ID not found", privacyChip)
+ privacyChip.click()
+ }
+ } else {
+ UiAutomatorUtils2.waitUntilObjectGone(By.res(PRIVACY_CHIP_ID))
+ return
+ }
+
+ eventually {
+ if (chainUsage) {
+ assertChainMicAndOtherCameraUsed(safetyCenterEnabled)
+ return@eventually
+ }
+ if (useMic) {
+ if (safetyCenterEnabled) {
+ assertSafetyCenterMicViewNotNull()
+ } else {
+ val iconView = waitFindObject(By.descContains(micLabel))
+ assertNotNull("View with description '$micLabel' not found", iconView)
+ }
+ }
+ if (useCamera) {
+ if (safetyCenterEnabled) {
+ assertSafetyCenterCameraViewNotNull()
+ } else {
+ val iconView = waitFindObject(By.descContains(cameraLabel))
+ assertNotNull("View with description '$cameraLabel' not found", iconView)
+ }
+ }
+ var appView = waitFindObject(By.textContains(APP_LABEL))
+ assertNotNull("View with text $APP_LABEL not found", appView)
+ }
+ uiDevice.pressBack()
+ }
+
+ private fun createChainAttribution(): AttributionSource? {
+ var attrSource: AttributionSource? = null
+ runWithShellPermissionIdentity {
+ try {
+ val appUid = packageManager.getPackageUid(APP_PKG, 0)
+ val childAttribution = AttributionSource(appUid, APP_PKG, null)
+ val attribution = AttributionSource(Process.myUid(), context.packageName, null,
+ null, permissionManager.registerAttributionSource(childAttribution))
+ attrSource = permissionManager.registerAttributionSource(attribution)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Assert.fail("Expected to find a UID for $APP_LABEL")
+ }
+ }
+ return attrSource
+ }
+
+ private fun assertChainMicAndOtherCameraUsed(safetyCenterEnabled: Boolean) {
+ val shellLabel = try {
+ context.packageManager.getApplicationInfo(SHELL_PKG, 0)
+ .loadLabel(context.packageManager).toString()
+ } catch (e: PackageManager.NameNotFoundException) {
+ "Did not find shell package"
+ }
+
+ if (safetyCenterEnabled) {
+ assertSafetyCenterMicViewNotNull()
+ assertSafetyCenterCameraViewNotNull()
+ var shellView = waitFindObject(By.textContains(shellLabel))
+ assertNotNull("View with text $shellLabel not found", shellView)
+ } else {
+ val usageViews = uiDevice.findObjects(By.res(PRIVACY_ITEM_ID))
+ assertEquals("Expected two usage views", 2, usageViews.size)
+ val appViews = uiDevice.findObjects(By.textContains(APP_LABEL))
+ assertEquals("Expected two $APP_LABEL view", 2, appViews.size)
+ val shellView = uiDevice.findObjects(By.textContains(shellLabel))
+ assertEquals("Expected only one shell view", 1, shellView.size)
+ }
+ }
+
+ private fun pressBack() {
+ uiDevice.pressBack()
+ waitForIdle()
+ }
+
+ private fun pressHome() {
+ uiDevice.pressHome()
+ waitForIdle()
+ }
+
+ private fun waitForIdle() =
+ uiAutomation.waitForIdle(IDLE_TIMEOUT_MILLIS, TIMEOUT_MILLIS)
+
+ private fun changeSafetyCenterFlag(safetyCenterEnabled: String) {
+ runWithShellPermissionIdentity {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+ SAFETY_CENTER_ENABLED, safetyCenterEnabled, false)
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ private fun assumeSafetyCenterEnabled() {
+ val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!!
+ val isSafetyCenterEnabled: Boolean = runWithShellPermissionIdentity<Boolean> {
+ safetyCenterManager.isSafetyCenterEnabled
+ }
+ assumeTrue(isSafetyCenterEnabled)
+ }
+
+ protected fun waitFindObject(selector: BySelector): UiObject2? {
+ waitForIdle()
+ return findObjectWithRetry({ t -> UiAutomatorUtils2.waitFindObject(selector, t) })
+ }
+
+ private fun findObjectWithRetry(
+ automatorMethod: (timeoutMillis: Long) -> UiObject2?,
+ timeoutMillis: Long = TIMEOUT_MILLIS
+ ): UiObject2? {
+ waitForIdle()
+ val startTime = SystemClock.elapsedRealtime()
+ return try {
+ automatorMethod(timeoutMillis)
+ } catch (e: StaleObjectException) {
+ val remainingTime = timeoutMillis - (SystemClock.elapsedRealtime() - startTime)
+ if (remainingTime <= 0) {
+ throw e
+ }
+ automatorMethod(remainingTime)
+ }
+ }
+
+ private fun getPermissionControllerString(resourceName: String): String {
+ val permissionControllerPkg = context.packageManager.permissionControllerPackageName
+ try {
+ val permissionControllerContext =
+ context.createPackageContext(permissionControllerPkg, 0)
+ val resourceId =
+ permissionControllerContext.resources.getIdentifier(
+ resourceName, "string", "com.android.permissioncontroller")
+ return permissionControllerContext.getString(resourceId)
+ } catch (e: PackageManager.NameNotFoundException) {
+ throw RuntimeException(e)
+ }
+ }
+
+ private fun assertSafetyCenterMicViewNotNull() {
+ val micView = waitFindObject(byOneOfText(originalMicLabel, safetyCenterMicLabel))
+ assertNotNull(
+ "View with text '$originalMicLabel' or '$safetyCenterMicLabel' not found", micView)
+ }
+
+ private fun assertSafetyCenterCameraViewNotNull() {
+ val cameraView = waitFindObject(byOneOfText(originalCameraLabel, safetyCenterCameraLabel))
+ assertNotNull(
+ "View with text '$originalCameraLabel' or '$safetyCenterCameraLabel' not found",
+ cameraView)
+ }
+
+ private fun byOneOfText(vararg textValues: String) =
+ By.text(Pattern.compile(textValues.joinToString(separator = "|") { Pattern.quote(it) }))
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt
new file mode 100644
index 000000000..a904549b4
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
+import android.Manifest.permission.ACCESS_COARSE_LOCATION
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.platform.test.annotations.FlakyTest
+import androidx.test.uiautomator.By
+import com.android.modules.utils.build.SdkLevel
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+
+@FlakyTest
+class LocationAccuracyTest : BaseUsePermissionTest() {
+
+ companion object {
+ private const val LOCATION_ACCURACY_PRECISE_RADIO_BUTTON =
+ "com.android.permissioncontroller:id/permission_location_accuracy_radio_fine"
+ private const val LOCATION_ACCURACY_COARSE_RADIO_BUTTON =
+ "com.android.permissioncontroller:id/permission_location_accuracy_radio_coarse"
+ private const val LOCATION_ACCURACY_PRECISE_ONLY_VIEW =
+ "com.android.permissioncontroller:id/permission_location_accuracy_fine_only"
+ private const val LOCATION_ACCURACY_COARSE_ONLY_VIEW =
+ "com.android.permissioncontroller:id/permission_location_accuracy_coarse_only"
+ }
+
+ @Before
+ fun setup() {
+ assumeTrue("Location Accuracy is only available on S+", SdkLevel.isAtLeastS())
+ assumeFalse(isAutomotive)
+ assumeFalse(isTv)
+ assumeFalse(isWatch)
+ }
+
+ @Test
+ fun testCoarsePermissionIsGranted() {
+ installPackage(APP_APK_PATH_31)
+
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_BACKGROUND_LOCATION, false)
+
+ requestAppPermissionsAndAssertResult(
+ ACCESS_FINE_LOCATION to false,
+ ACCESS_COARSE_LOCATION to true
+ ) {
+ clickCoarseLocationRadioButton()
+ clickPreciseLocationRadioButton()
+ clickCoarseLocationRadioButton()
+ clickPermissionRequestAllowForegroundButton()
+ }
+ }
+
+ @Test
+ fun testPrecisePermissionIsGranted() {
+ installPackage(APP_APK_PATH_31)
+
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_BACKGROUND_LOCATION, false)
+
+ requestAppPermissionsAndAssertResult(
+ ACCESS_FINE_LOCATION to true,
+ ACCESS_COARSE_LOCATION to true
+ ) {
+ clickPreciseLocationRadioButton()
+ clickCoarseLocationRadioButton()
+ clickPreciseLocationRadioButton()
+ clickPermissionRequestAllowForegroundButton()
+ }
+ }
+
+ @Test
+ fun testPermissionUpgradeFlow() {
+ installPackage(APP_APK_PATH_31)
+
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_BACKGROUND_LOCATION, false)
+
+ requestAppPermissionsAndAssertResult(
+ ACCESS_FINE_LOCATION to false,
+ ACCESS_COARSE_LOCATION to true
+ ) {
+ clickCoarseLocationRadioButton()
+ clickPreciseLocationRadioButton()
+ clickCoarseLocationRadioButton()
+ clickPermissionRequestAllowForegroundButton()
+ }
+
+ // now request again to change to precise location
+ requestAppPermissionsAndAssertResult(
+ ACCESS_FINE_LOCATION to true,
+ ACCESS_COARSE_LOCATION to true
+ ) {
+ clickPreciseLocationOnlyView()
+ clickPermissionRequestAllowForegroundButton()
+ }
+ }
+
+ @Test
+ fun testCoarseRequestAndGrant() {
+ installPackage(APP_APK_PATH_31)
+
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_BACKGROUND_LOCATION, false)
+
+ requestAppPermissionsAndAssertResult(
+ ACCESS_COARSE_LOCATION to true
+ ) {
+ clickCoarseLocationOnlyView()
+ clickPermissionRequestAllowForegroundButton()
+ }
+
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ assertAppHasPermission(ACCESS_BACKGROUND_LOCATION, false)
+ }
+
+ @Test
+ fun testPreSAppsAutograntFineIfCoarseGranted() {
+ installPackage(APP_APK_PATH_30)
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ requestAppPermissionsAndAssertResult(
+ ACCESS_COARSE_LOCATION to true
+ ) {
+ clickPermissionRequestAllowForegroundButton()
+ }
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ requestAppPermissionsAndAssertResult(
+ ACCESS_FINE_LOCATION to true
+ ) { }
+ }
+
+ private fun clickPreciseLocationRadioButton() {
+ click(By.res(LOCATION_ACCURACY_PRECISE_RADIO_BUTTON))
+ }
+
+ private fun clickCoarseLocationRadioButton() {
+ click(By.res(LOCATION_ACCURACY_COARSE_RADIO_BUTTON))
+ }
+
+ private fun clickPreciseLocationOnlyView() {
+ click(By.res(LOCATION_ACCURACY_PRECISE_ONLY_VIEW))
+ }
+
+ private fun clickCoarseLocationOnlyView() {
+ click(By.res(LOCATION_ACCURACY_COARSE_ONLY_VIEW))
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/LocationProviderInterceptDialogTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/LocationProviderInterceptDialogTest.kt
new file mode 100644
index 000000000..15af9298a
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/LocationProviderInterceptDialogTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.app.Activity
+import android.app.AppOpsManager
+import android.content.ComponentName
+import android.content.Intent
+import android.location.LocationManager
+import android.os.Build
+import android.permission.cts.PermissionUtils
+import android.platform.test.annotations.FlakyTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.AppOpsUtils
+import com.android.compatibility.common.util.CddTest
+import com.android.compatibility.common.util.SystemUtil
+import java.util.concurrent.TimeUnit
+import org.junit.Assert
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+
+private const val EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME"
+private const val ACTION_MANAGE_APP_PERMISSIONS = "android.intent.action.MANAGE_APP_PERMISSIONS"
+
+/**
+ * Tests that LocationProviderInterceptDialog (a warning dialog) shows when attempting to view the
+ * location permission for location a service provider app (e.g., usually GMS, but we use a custom
+ * app in this test).
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+@FlakyTest
+@CddTest(requirement = "9.1/C-0-1")
+class LocationProviderInterceptDialogTest : BaseUsePermissionTest() {
+ @Before
+ fun setup() {
+ assumeFalse(isAutomotive)
+ assumeFalse(isTv)
+ assumeFalse(isWatch)
+ installPackage(MIC_LOCATION_PROVIDER_APP_APK_PATH, grantRuntimePermissions = true)
+ AppOpsUtils.setOpMode(
+ MIC_LOCATION_PROVIDER_APP_PACKAGE_NAME,
+ AppOpsManager.OPSTR_MOCK_LOCATION,
+ AppOpsManager.MODE_ALLOWED)
+ enableMicrophoneAppAsLocationProvider()
+ }
+
+ @Test
+ fun clickLocationPermission_showDialog_clickOk() {
+ openPermissionScreenForApp()
+ click(By.text("Location"))
+ findView(
+ By.textContains("Location access can be modified from location settings"),
+ true)
+ click(By.res(OK_BUTTON_RES))
+ }
+
+ @Test
+ fun clickLocationPermission_showDialog_clickLocationAccess() {
+ openPermissionScreenForApp()
+ click(By.text("Location"))
+ findView(
+ By.textContains("Location access can be modified from location settings"),
+ true)
+ click(By.res(LOCATION_ACCESS_BUTTON_RES))
+ findView(By.res(USE_LOCATION_LABEL_ID), true)
+ }
+
+ @Test
+ fun checkRestrictedPermissions() {
+ context.sendBroadcast(Intent(PermissionTapjackingTest.ACTION_SHOW_OVERLAY)
+ .putExtra("package", MIC_LOCATION_PROVIDER_APP_PACKAGE_NAME)
+ .putExtra("permission", "android.permission.BACKGROUND_CAMERA"))
+ }
+
+ private fun openPermissionScreenForApp() {
+ restartPermissionController()
+ SystemUtil.runWithShellPermissionIdentity {
+ context.startActivity(
+ Intent(ACTION_MANAGE_APP_PERMISSIONS).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ putExtra(EXTRA_PACKAGE_NAME, MIC_LOCATION_PROVIDER_APP_PACKAGE_NAME)
+ }
+ )
+ }
+ waitForIdle()
+ }
+
+ private fun restartPermissionController() {
+ PermissionUtils.clearAppState(permissionControllerPackageName)
+ }
+
+ private fun enableMicrophoneAppAsLocationProvider() {
+ val locationManager = context.getSystemService(LocationManager::class.java)!!
+ val future =
+ startActivityForFuture(
+ Intent().apply {
+ component =
+ ComponentName(
+ MIC_LOCATION_PROVIDER_APP_PACKAGE_NAME,
+ "$MIC_LOCATION_PROVIDER_APP_PACKAGE_NAME.AddLocationProviderActivity")
+ })
+ val result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ Assert.assertEquals(Activity.RESULT_OK, result.resultCode)
+ Assert.assertTrue(
+ SystemUtil.callWithShellPermissionIdentity {
+ locationManager.isProviderPackage(MIC_LOCATION_PROVIDER_APP_PACKAGE_NAME)
+ })
+ }
+
+ companion object {
+ private const val USE_LOCATION_LABEL_ID =
+ "com.android.settings:id/switch_text"
+ private const val MIC_LOCATION_PROVIDER_APP_APK_PATH =
+ "$APK_DIRECTORY/CtsAccessMicrophoneAppLocationProvider.apk"
+ private const val MIC_LOCATION_PROVIDER_APP_PACKAGE_NAME =
+ "android.permissionui.cts.accessmicrophoneapplocationprovider"
+ private const val OK_BUTTON_RES = "android:id/button2"
+ private const val LOCATION_ACCESS_BUTTON_RES = "android:id/button1"
+ private val permissionControllerPackageName =
+ context.packageManager.permissionControllerPackageName
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionTest.kt
new file mode 100644
index 000000000..1dc21fcea
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.Manifest
+import android.os.Build
+import android.platform.test.annotations.FlakyTest
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.CddTest
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.Assume
+import org.junit.Test
+
+/**
+ * Tests media storage supergroup behavior. I.e., on a T+ platform, for legacy (targetSdk<T) apps,
+ * the storage permission groups (STORAGE, AURAL, and VISUAL) form a supergroup, which effectively
+ * treats them as one group and therefore their permission state must always be equal.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+@CddTest(requirement = "9.1/C-0-1")
+@FlakyTest
+class MediaPermissionTest : BaseUsePermissionTest() {
+ private fun assertStorageAndMediaPermissionState(state: Boolean) {
+ for (permission in STORAGE_AND_MEDIA_PERMISSIONS) {
+ assertAppHasPermission(permission, state)
+ }
+ }
+
+ @Test
+ fun testWhenRESIsGrantedFromGrantDialogThenShouldGrantAllPermissions() {
+ installPackage(APP_APK_PATH_23)
+ requestAppPermissionsAndAssertResult(Manifest.permission.READ_EXTERNAL_STORAGE to true) {
+ clickPermissionRequestAllowButton()
+ }
+ assertStorageAndMediaPermissionState(true)
+ }
+
+ @Test
+ fun testWhenRESIsGrantedManuallyThenShouldGrantAllPermissions() {
+ installPackage(APP_APK_PATH_23)
+ grantAppPermissionsByUi(Manifest.permission.READ_EXTERNAL_STORAGE)
+ assertStorageAndMediaPermissionState(true)
+ }
+
+ @Test
+ fun testWhenAuralIsGrantedManuallyThenShouldGrantAllPermissions() {
+ installPackage(APP_APK_PATH_23)
+ grantAppPermissionsByUi(Manifest.permission.READ_MEDIA_AUDIO)
+ assertStorageAndMediaPermissionState(true)
+ }
+
+ @Test
+ fun testWhenVisualIsGrantedManuallyThenShouldGrantAllPermissions() {
+ installPackage(APP_APK_PATH_23)
+ grantAppPermissionsByUi(Manifest.permission.READ_MEDIA_VIDEO)
+ assertStorageAndMediaPermissionState(true)
+ }
+
+ @Test
+ fun testWhenRESIsDeniedFromGrantDialogThenShouldDenyAllPermissions() {
+ installPackage(APP_APK_PATH_23)
+ requestAppPermissionsAndAssertResult(Manifest.permission.READ_EXTERNAL_STORAGE to false) {
+ clickPermissionRequestDenyButton()
+ }
+ assertStorageAndMediaPermissionState(false)
+ }
+
+ @Test
+ fun testWhenRESIsDeniedManuallyThenShouldDenyAllPermissions() {
+ installPackage(APP_APK_PATH_23)
+ grantAppPermissionsByUi(Manifest.permission.READ_EXTERNAL_STORAGE)
+ assertStorageAndMediaPermissionState(true)
+ revokeAppPermissionsByUi(Manifest.permission.READ_EXTERNAL_STORAGE)
+ assertStorageAndMediaPermissionState(false)
+ }
+
+ @Test
+ fun testWhenAuralIsDeniedManuallyThenShouldDenyAllPermissions() {
+ installPackage(APP_APK_PATH_23)
+ grantAppPermissionsByUi(Manifest.permission.READ_MEDIA_AUDIO)
+ revokeAppPermissionsByUi(Manifest.permission.READ_MEDIA_AUDIO)
+ assertStorageAndMediaPermissionState(false)
+ }
+
+ @Test
+ fun testWhenVisualIsDeniedManuallyThenShouldDenyAllPermissions() {
+ // TODO: Re-enable after b/239249703 is fixed
+ Assume.assumeFalse("skip on TV due to flaky", isTv)
+ installPackage(APP_APK_PATH_23)
+ grantAppPermissionsByUi(Manifest.permission.READ_MEDIA_VIDEO)
+ revokeAppPermissionsByUi(Manifest.permission.READ_MEDIA_VIDEO)
+ assertStorageAndMediaPermissionState(false)
+ }
+
+ @Test
+ fun testWhenA33AppRequestsStorageThenNoDialogAndNoGrant() {
+ installPackage(APP_APK_PATH_MEDIA_PERMISSION_33_WITH_STORAGE)
+ requestAppPermissions(
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE
+ ) {
+ }
+ assertStorageAndMediaPermissionState(false)
+ }
+
+ @Test
+ fun testWhenA33AppRequestsAuralThenDialogAndGrant() {
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissions(Manifest.permission.READ_MEDIA_AUDIO) {
+ clickPermissionRequestAllowButton()
+ }
+ assertAppHasPermission(Manifest.permission.READ_EXTERNAL_STORAGE, false)
+ assertAppHasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, false)
+ assertAppHasPermission(Manifest.permission.READ_MEDIA_AUDIO, true)
+ assertAppHasPermission(Manifest.permission.READ_MEDIA_VIDEO, false)
+ assertAppHasPermission(Manifest.permission.READ_MEDIA_IMAGES, false)
+ }
+
+ @Test
+ fun testWhenA33AppRequestsVisualThenDialogAndGrant() {
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissions(
+ Manifest.permission.READ_MEDIA_VIDEO,
+ Manifest.permission.READ_MEDIA_IMAGES) {
+ if (isPhotoPickerPermissionPromptEnabled()) {
+ clickPermissionRequestAllowAllButton()
+ } else {
+ clickPermissionRequestAllowButton()
+ }
+ }
+ assertAppHasPermission(Manifest.permission.READ_EXTERNAL_STORAGE, false)
+ assertAppHasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, false)
+ assertAppHasPermission(Manifest.permission.READ_MEDIA_AUDIO, false)
+ assertAppHasPermission(Manifest.permission.READ_MEDIA_VIDEO, true)
+ assertAppHasPermission(Manifest.permission.READ_MEDIA_IMAGES, true)
+ }
+
+ @Test
+ fun testWhenA30AppRequestsStorageWhenMediaPermsHaveRWRFlag() {
+ installPackage(APP_APK_PATH_30)
+
+ requestAppPermissionsAndAssertResult(
+ Manifest.permission.READ_EXTERNAL_STORAGE to true
+ ) {
+ clickPermissionRequestAllowButton()
+ }
+
+ fun setRevokeWhenRequested(permission: String) = SystemUtil.runShellCommandOrThrow(
+ "pm set-permission-flags android.permissionui.cts.usepermission " +
+ permission + " revoke-when-requested")
+ setRevokeWhenRequested("android.permission.READ_MEDIA_AUDIO")
+ setRevokeWhenRequested("android.permission.READ_MEDIA_VIDEO")
+ setRevokeWhenRequested("android.permission.READ_MEDIA_IMAGES")
+
+ requestAppPermissionsAndAssertResult(
+ Manifest.permission.READ_EXTERNAL_STORAGE to true
+ ) {
+ // No dialog should appear
+ }
+
+ assertAppHasPermission(Manifest.permission.READ_EXTERNAL_STORAGE, true)
+ assertAppHasPermission(Manifest.permission.READ_MEDIA_AUDIO, true)
+ assertAppHasPermission(Manifest.permission.READ_MEDIA_VIDEO, true)
+ assertAppHasPermission(Manifest.permission.READ_MEDIA_IMAGES, true)
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionUpgradeTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionUpgradeTest.kt
new file mode 100644
index 000000000..bed5c225f
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionUpgradeTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.Manifest.permission.READ_EXTERNAL_STORAGE
+import android.Manifest.permission.READ_MEDIA_AUDIO
+import android.Manifest.permission.READ_MEDIA_IMAGES
+import android.Manifest.permission.READ_MEDIA_VIDEO
+import android.os.Build
+import android.platform.test.annotations.FlakyTest
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.CddTest
+import org.junit.Test
+
+/**
+ * Tests media storage permission behavior upon app upgrade.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+@CddTest(requirement = "9.1/C-0-1")
+@FlakyTest
+class MediaPermissionUpgradeTest : BaseUsePermissionTest() {
+ @Test
+ fun testAfterUpgradeToTiramisuThenNoGrantDialogShownForMediaPerms() {
+ // Install 32
+ installPackage(APP_APK_PATH_32)
+
+ // Request STORAGE, and click allow
+ requestAppPermissionsAndAssertResult(
+ READ_EXTERNAL_STORAGE to true
+ ) {
+ clickPermissionRequestAllowButton()
+ }
+
+ // Upgrade 32 -> 33
+ installPackage(APP_APK_PATH_LATEST, reinstall = true)
+
+ // Request READ_MEDIA_*
+ requestAppPermissionsAndAssertResult(
+ READ_MEDIA_AUDIO to true,
+ READ_MEDIA_VIDEO to true,
+ READ_MEDIA_IMAGES to true
+ ) {
+ // Don't click any grant dialog buttons because no grant dialog should appear
+ }
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/MultiDevicePermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/MultiDevicePermissionTest.kt
new file mode 100644
index 000000000..2c98e5209
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/MultiDevicePermissionTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.permissionui.cts
+
+import android.Manifest
+import android.os.Build
+import android.platform.test.annotations.FlakyTest
+import androidx.test.filters.SdkSuppress
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+/**
+ * This test validates multi device permission APIs.
+ *
+ * TODO(mrulhania): will update the test once all iris permission API changes are merged.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName = "VanillaIceCream")
+@FlakyTest
+class MultiDevicePermissionTest : BaseUsePermissionTest() {
+ @Before
+ fun setup() {
+ Assume.assumeFalse(isAutomotive)
+ Assume.assumeFalse(isTv)
+ Assume.assumeFalse(isWatch)
+ }
+
+ @Test
+ fun testPermissionGrantForDefaultDevice() {
+ installPackage(APP_APK_PATH_LATEST)
+ assertAppHasPermission(Manifest.permission.CAMERA, false)
+ assertAppHasPermission(Manifest.permission.RECORD_AUDIO, false)
+
+ requestAppPermissionsAndAssertResult(Manifest.permission.CAMERA to true) {
+ clickPermissionRequestAllowForegroundButton()
+ }
+ requestAppPermissionsAndAssertResult(Manifest.permission.RECORD_AUDIO to true) {
+ clickPermissionRequestAllowForegroundButton()
+ }
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/NoPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/NoPermissionTest.kt
new file mode 100644
index 000000000..a62197eee
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/NoPermissionTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 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.permissionui.cts
+
+import android.app.Activity
+import android.platform.test.annotations.FlakyTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.modules.utils.build.SdkLevel
+import org.junit.Assume
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@FlakyTest
+class NoPermissionTest : BaseUsePermissionTest() {
+ @Test
+ fun testStartActivity22() {
+ Assume.assumeFalse(SdkLevel.isAtLeastT())
+ installPackage(APP_APK_PATH_22_NONE)
+
+ startAppActivityAndAssertResultCode(Activity.RESULT_OK) {}
+
+ clearTargetSdkWarning()
+ }
+
+ @Test
+ fun testStartActivityLatest() {
+ installPackage(APP_APK_PATH_LATEST_NONE)
+
+ startAppActivityAndAssertResultCode(Activity.RESULT_OK) {}
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/NotificationPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/NotificationPermissionTest.kt
new file mode 100644
index 000000000..f7dfbfdf5
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/NotificationPermissionTest.kt
@@ -0,0 +1,408 @@
+/*
+ * 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 android.permissionui.cts
+
+import android.Manifest.permission.POST_NOTIFICATIONS
+import android.Manifest.permission.RECORD_AUDIO
+import android.app.ActivityOptions
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Context.RECEIVER_EXPORTED
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.os.Build
+import android.os.UserHandle
+import android.platform.test.annotations.FlakyTest
+import android.provider.Settings
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import java.util.concurrent.CountDownLatch
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+
+const val EXTRA_CREATE_CHANNELS = "extra_create"
+const val EXTRA_REQUEST_OTHER_PERMISSIONS = "extra_request_permissions"
+const val EXTRA_REQUEST_NOTIF_PERMISSION = "extra_request_notif_permission"
+const val EXTRA_START_SECOND_ACTIVITY = "extra_start_second_activity"
+const val EXTRA_START_SECOND_APP = "extra_start_second_app"
+const val ACTIVITY_LABEL = "CreateNotif"
+const val SECOND_ACTIVITY_LABEL = "EmptyActivity"
+const val ALLOW = "to send you"
+const val INTENT_ACTION = "usepermission.createchannels.MAIN"
+const val BROADCAST_ACTION = "usepermission.createchannels.BROADCAST"
+const val NOTIFICATION_PERMISSION_ENABLED = "notification_permission_enabled"
+const val EXPECTED_TIMEOUT_MS = 2000L
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+@FlakyTest
+class NotificationPermissionTest : BaseUsePermissionTest() {
+
+ private val cr = callWithShellPermissionIdentity {
+ context.createContextAsUser(UserHandle.SYSTEM, 0).contentResolver
+ }
+ private var previousEnableState = -1
+ private var countDown: CountDownLatch = CountDownLatch(1)
+ private var allowedGroups = listOf<String>()
+ private val receiver: BroadcastReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ allowedGroups =
+ intent?.getStringArrayListExtra(
+ PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS
+ )
+ ?: emptyList()
+ countDown.countDown()
+ }
+ }
+
+ @Before
+ fun setLatchAndEnablePermission() {
+ // b/220968160: Notification permission is not enabled on TV devices.
+ assumeFalse(isTv)
+ runWithShellPermissionIdentity {
+ previousEnableState = Settings.Secure.getInt(cr, NOTIFICATION_PERMISSION_ENABLED, 0)
+ Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, 1)
+ }
+ countDown = CountDownLatch(1)
+ allowedGroups = listOf()
+ context.registerReceiver(receiver, IntentFilter(BROADCAST_ACTION), RECEIVER_EXPORTED)
+ }
+
+ @After
+ fun resetPermissionAndRemoveReceiver() {
+ if (previousEnableState >= 0) {
+ runWithShellPermissionIdentity {
+ Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, previousEnableState)
+ }
+ context.unregisterReceiver(receiver)
+ }
+ }
+
+ @Test
+ fun notificationPermissionAddedForLegacyApp() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ runWithShellPermissionIdentity {
+ Assert.assertTrue("SDK < 32 apps should have POST_NOTIFICATIONS added implicitly",
+ context.packageManager.getPackageInfo(APP_PACKAGE_NAME,
+ PackageManager.GET_PERMISSIONS).requestedPermissions!!
+ .contains(POST_NOTIFICATIONS))
+ }
+ }
+
+ @Test
+ fun notificationPermissionIsNotImplicitlyAddedTo33Apps() {
+ installPackage(APP_APK_PATH_LATEST_NONE, expectSuccess = true)
+ runWithShellPermissionIdentity {
+ val requestedPerms = context.packageManager.getPackageInfo(APP_PACKAGE_NAME,
+ PackageManager.GET_PERMISSIONS).requestedPermissions
+ Assert.assertTrue("SDK >= 33 apps should NOT have POST_NOTIFICATIONS added implicitly",
+ requestedPerms == null || !requestedPerms.contains(POST_NOTIFICATIONS))
+ }
+ }
+
+ @Test
+ fun notificationPromptShowsForLegacyAppAfterCreatingNotificationChannels() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp()
+ clickPermissionRequestAllowButton()
+ }
+
+ @Test
+ fun notificationPromptShowsForLegacyAppWithNotificationChannelsOnStart() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ // create channels, then leave the app
+ launchApp()
+ killTestApp()
+ launchApp()
+ waitFindObject(By.textContains(ALLOW))
+ clickPermissionRequestAllowButton()
+ }
+
+ @Test
+ fun notificationPromptDoesNotShowForLegacyAppWithNoNotificationChannels_onLaunch() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp(createChannels = false)
+ assertDialogNotShowing()
+ }
+ @Test
+ fun notificationPromptDoesNotShowForNonLauncherIntentCategoryLaunches_onChannelCreate() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp(launcherCategory = false)
+ assertDialogNotShowing()
+ }
+
+ @Test
+ fun notificationPromptDoesNotShowForNonLauncherIntentCategoryLaunches_onLaunch() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ // create channels, then leave the app
+ launchApp()
+ killTestApp()
+ launchApp(launcherCategory = false)
+ assertDialogNotShowing()
+ }
+
+ @Test
+ fun notificationPromptDoesNotShowForNonMainIntentActionLaunches_onLaunch() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ // create channels, then leave the app
+ launchApp()
+ killTestApp()
+ launchApp(intentAction = INTENT_ACTION)
+ assertDialogNotShowing()
+ }
+
+ @Test
+ fun notificationPromptDoesNotShowForNonMainIntentActionLaunches_onChannelCreate() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp(intentAction = INTENT_ACTION)
+ assertDialogNotShowing()
+ }
+
+ @Test
+ fun notificationPromptShowsIfActivityOptionSet() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ // create channels, then leave the app
+ launchApp()
+ killTestApp()
+ launchApp(intentAction = INTENT_ACTION, isEligibleForPromptOption = true)
+ clickPermissionRequestAllowButton()
+ }
+
+ @Test
+ fun notificationPromptShownForSubsequentStartsIfTaskStartWasLauncher() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp(startSecondActivity = true)
+ if (isAutomotive) {
+ waitFindObject(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)))
+ } else {
+ waitFindObject(By.res(ALLOW_BUTTON))
+ }
+ pressBack()
+ clickPermissionRequestAllowButton()
+ }
+
+ @Test
+ fun notificationPromptNotShownForSubsequentStartsIfTaskStartWasNotLauncher() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp(intentAction = INTENT_ACTION, startSecondActivity = true)
+ assertDialogNotShowing()
+ }
+
+ @Test
+ fun notificationPromptShownForChannelCreateInSecondActivityIfTaskStartWasLauncher() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp(startSecondActivity = true, createChannels = false)
+ clickPermissionRequestAllowButton()
+ }
+
+ @Test
+ fun notificationPromptNotShownForChannelCreateInSecondActivityIfTaskStartWasntLauncher() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp(intentAction = INTENT_ACTION, startSecondActivity = true, createChannels = false)
+ assertDialogNotShowing()
+ }
+
+ @Test
+ fun notificationPromptNotShownForSubsequentStartsIfSubsequentIsDifferentPkg() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ installPackage(APP_APK_PATH_OTHER_APP, expectSuccess = true)
+ // perform a launcher start, then start a secondary app
+ launchApp(startSecondaryAppAndCreateChannelsAfterSecondStart = true)
+ try {
+ // Watch does not have app bar
+ if (!isWatch) {
+ waitFindObject(By.textContains(SECOND_ACTIVITY_LABEL))
+ }
+ assertDialogNotShowing()
+ } finally {
+ uninstallPackage(OTHER_APP_PACKAGE_NAME)
+ }
+ }
+
+ @Test
+ fun notificationGrantedOnLegacyGrant() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp()
+ clickPermissionRequestAllowButton()
+ assertAppPermissionGrantedState(POST_NOTIFICATIONS, granted = true)
+ }
+
+ @Test
+ fun nonSystemServerPackageCannotShowPromptForOtherPackage() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ runWithShellPermissionIdentity {
+ val grantPermission = Intent(PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER)
+ grantPermission.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
+ grantPermission.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES,
+ arrayOf(POST_NOTIFICATIONS))
+ grantPermission.setPackage(context.packageManager.permissionControllerPackageName)
+ grantPermission.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(grantPermission)
+ }
+ try {
+ clickPermissionRequestAllowButton(timeoutMillis = EXPECTED_TIMEOUT_MS)
+ Assert.fail("Expected not to find permission request dialog")
+ } catch (expected: RuntimeException) {
+ // Do nothing
+ }
+ }
+
+ @Test
+ fun mergeAppPermissionRequestIntoNotificationAndVerifyResult() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp()
+ findPermissionRequestAllowButton()
+ // Notification dialog is showing, trigger RECORD_AUDIO check, and wait until it has been
+ // requested
+ val intent = createIntent(requestPermissions = true, intentAction = BROADCAST_ACTION)
+ context.sendBroadcast(intent)
+ countDown.await()
+ Thread.sleep(1000)
+ // reset countDownLatch
+ countDown = CountDownLatch(1)
+
+ clickPermissionRequestAllowButton()
+ assertAppPermissionGrantedState(POST_NOTIFICATIONS, granted = true)
+ clickPermissionRequestAllowForegroundButton()
+ assertAppPermissionGrantedState(RECORD_AUDIO, granted = true)
+ countDown.await()
+ // Result should contain only the microphone request
+ Assert.assertEquals(listOf(RECORD_AUDIO), allowedGroups)
+ }
+
+ @Test
+ fun mergeNotificationRequestIntoAppPermissionRequestAndVerifyResult() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp(createChannels = false, requestPermissions = true)
+ findPermissionRequestAllowForegroundButton()
+ // Microphone dialog is showing, trigger Notification check, and wait until it has been
+ // requested
+ val intent = createIntent(createChannels = true, intentAction = BROADCAST_ACTION)
+ context.sendBroadcast(intent)
+ countDown.await()
+ Thread.sleep(1000)
+ // reset countDownLatch
+ countDown = CountDownLatch(1)
+
+ clickPermissionRequestAllowForegroundButton()
+ assertAppPermissionGrantedState(RECORD_AUDIO, granted = true)
+ clickPermissionRequestAllowButton()
+ assertAppPermissionGrantedState(POST_NOTIFICATIONS, granted = true)
+ countDown.await()
+ // Result should contain only the microphone request
+ Assert.assertEquals(listOf(RECORD_AUDIO), allowedGroups)
+ }
+
+ @Test
+ fun legacyAppCannotExplicitlyRequestNotifications() {
+ installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+ launchApp(createChannels = false, requestNotificationPermission = true)
+ try {
+ clickPermissionRequestAllowButton(timeoutMillis = EXPECTED_TIMEOUT_MS)
+ Assert.fail("Expected not to find permission request dialog")
+ } catch (expected: RuntimeException) {
+ // Do nothing
+ }
+ }
+
+ private fun assertAppPermissionGrantedState(permission: String, granted: Boolean) {
+ SystemUtil.eventually {
+ runWithShellPermissionIdentity {
+ Assert.assertEquals(
+ "Expected $permission to be granted",
+ context.packageManager.checkPermission(permission, APP_PACKAGE_NAME),
+ PERMISSION_GRANTED
+ )
+ }
+ }
+ }
+
+ private fun createIntent(
+ createChannels: Boolean = true,
+ requestNotificationPermission: Boolean = false,
+ requestPermissions: Boolean = false,
+ launcherCategory: Boolean = true,
+ intentAction: String = Intent.ACTION_MAIN,
+ startSecondActivity: Boolean = false,
+ startSecondaryAppAndCreateChannelsAfterSecondStart: Boolean = false
+ ): Intent {
+ val intent =
+ if (intentAction == Intent.ACTION_MAIN && launcherCategory) {
+ packageManager.getLaunchIntentForPackage(APP_PACKAGE_NAME)!!
+ } else {
+ Intent(intentAction)
+ }
+
+ intent.`package` = APP_PACKAGE_NAME
+ intent.putExtra(EXTRA_CREATE_CHANNELS, createChannels)
+ intent.putExtra(EXTRA_REQUEST_OTHER_PERMISSIONS, requestPermissions)
+ intent.putExtra(EXTRA_REQUEST_NOTIF_PERMISSION, requestNotificationPermission)
+ intent.putExtra(EXTRA_START_SECOND_ACTIVITY, startSecondActivity)
+ intent.putExtra(EXTRA_START_SECOND_APP, startSecondaryAppAndCreateChannelsAfterSecondStart)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ return intent
+ }
+
+ private fun launchApp(
+ createChannels: Boolean = true,
+ requestNotificationPermission: Boolean = false,
+ requestPermissions: Boolean = false,
+ launcherCategory: Boolean = true,
+ intentAction: String = Intent.ACTION_MAIN,
+ isEligibleForPromptOption: Boolean = false,
+ startSecondActivity: Boolean = false,
+ startSecondaryAppAndCreateChannelsAfterSecondStart: Boolean = false
+ ) {
+ val intent =
+ createIntent(
+ createChannels,
+ requestNotificationPermission,
+ requestPermissions,
+ launcherCategory,
+ intentAction,
+ startSecondActivity,
+ startSecondaryAppAndCreateChannelsAfterSecondStart
+ )
+
+ val options = ActivityOptions.makeBasic()
+ options.isEligibleForLegacyPermissionPrompt = isEligibleForPromptOption
+ context.startActivity(intent, options.toBundle())
+
+ // Watch does not have app bar
+ if (!isWatch) {
+ waitFindObject(By.textContains(ACTIVITY_LABEL))
+ }
+ waitForIdle()
+ }
+
+ private fun assertDialogNotShowing(timeoutMillis: Long = EXPECTED_TIMEOUT_MS) {
+ try {
+ clickPermissionRequestAllowButton(timeoutMillis)
+ Assert.fail("Expected not to find permission request dialog")
+ } catch (expected: RuntimeException) {
+ // Do nothing
+ }
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionDecisionsTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionDecisionsTest.kt
new file mode 100644
index 000000000..ad7da60ae
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionDecisionsTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.Manifest
+import android.content.Intent
+import android.os.Build
+import android.permission.PermissionManager
+import android.platform.test.annotations.FlakyTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.Assert.assertNull
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+@FlakyTest
+class PermissionDecisionsTest : BaseUsePermissionTest() {
+
+ companion object {
+ const val ASSERT_ABSENT_SELECTOR_TIMEOUT_MS = 500L
+ }
+
+ // Permission decisions has only been implemented on Auto
+ @Before
+ fun assumeAuto() {
+ assumeTrue(isAutomotive)
+ }
+
+ @Test
+ fun testAcceptPermissionDialogShowsDecisionWithGrantedAccess() {
+ installPackage(APP_APK_PATH_30_WITH_BACKGROUND)
+ requestAppPermissionsAndAssertResult(Manifest.permission.ACCESS_FINE_LOCATION to true) {
+ clickPermissionRequestAllowForegroundButton()
+ }
+
+ openPermissionDecisions()
+ waitFindObject(
+ By.hasChild(By.text("You gave $APP_PACKAGE_NAME access to location"))
+ .hasChild(By.text("Today"))
+ )
+ }
+
+ @Test
+ fun testDenyPermissionDialogShowsDecisionWithDeniedAccess() {
+ installPackage(APP_APK_PATH_30_WITH_BACKGROUND)
+ requestAppPermissionsAndAssertResult(Manifest.permission.ACCESS_FINE_LOCATION to false) {
+ clickPermissionRequestDenyButton()
+ }
+
+ openPermissionDecisions()
+ waitFindObject(
+ By.hasChild(By.text("You denied $APP_PACKAGE_NAME access to location"))
+ .hasChild(By.text("Today"))
+ )
+ }
+
+ @Test
+ fun testAppUninstallRemovesDecision() {
+ installPackage(APP_APK_PATH_30_WITH_BACKGROUND)
+ requestAppPermissionsAndAssertResult(Manifest.permission.ACCESS_FINE_LOCATION to false) {
+ clickPermissionRequestDenyButton()
+ }
+ uninstallApp()
+
+ openPermissionDecisions()
+ assertNull(
+ waitFindObjectOrNull(
+ By.hasChild(By.text("You denied $APP_PACKAGE_NAME access to location"))
+ .hasChild(By.text("Today")),
+ ASSERT_ABSENT_SELECTOR_TIMEOUT_MS
+ )
+ )
+ }
+
+ @Test
+ fun testClickOnDecisionAndChangeAccessUpdatesDecision() {
+ installPackage(APP_APK_PATH_30_WITH_BACKGROUND)
+ requestAppPermissionsAndAssertResult(Manifest.permission.ACCESS_FINE_LOCATION to true) {
+ clickPermissionRequestAllowForegroundButton()
+ }
+
+ openPermissionDecisions()
+
+ waitFindObject(
+ By.hasChild(By.text("You gave $APP_PACKAGE_NAME access to location"))
+ .hasChild(By.text("Today"))
+ )
+ .click()
+
+ waitFindObject(By.text(APP_PACKAGE_NAME))
+ waitFindObject(By.text("Location access for this app"))
+
+ // change the permission on the app permission screen and verify that updates the decision
+ // page
+ waitFindObject(By.text("Don’t allow")).click()
+ pressBack()
+ waitFindObject(
+ By.hasChild(By.text("You denied $APP_PACKAGE_NAME access to location"))
+ .hasChild(By.text("Today"))
+ )
+ }
+
+ private fun openPermissionDecisions() {
+ SystemUtil.runWithShellPermissionIdentity {
+ context.startActivity(
+ Intent(PermissionManager.ACTION_REVIEW_PERMISSION_DECISIONS).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ )
+ }
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionGroupTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionGroupTest.kt
new file mode 100644
index 000000000..6c2fe25e5
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionGroupTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 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.permissionui.cts
+
+import android.platform.test.annotations.FlakyTest
+import org.junit.Test
+
+/**
+ * Runtime permission behavior tests for permission groups.
+ */
+@FlakyTest
+class PermissionGroupTest : BaseUsePermissionTest() {
+ @Test
+ fun testRuntimeGroupGrantExpansion23() {
+ installPackage(APP_APK_PATH_23)
+ testRuntimeGroupGrantExpansion(true)
+ }
+
+ @Test
+ fun testRuntimeGroupGrantExpansion25() {
+ installPackage(APP_APK_PATH_25)
+ testRuntimeGroupGrantExpansion(true)
+ }
+
+ @Test
+ fun testRuntimeGroupGrantExpansion26() {
+ installPackage(APP_APK_PATH_26)
+ testRuntimeGroupGrantExpansion(false)
+ }
+
+ @Test
+ fun testRuntimeGroupGrantExpansion30() {
+ installPackage(APP_APK_PATH_30)
+ testRuntimeGroupGrantExpansion(false)
+ }
+
+ @Test
+ fun testPartiallyGrantedGroupExpansion() {
+ installPackage(APP_APK_PATH_30)
+
+ // Start out without permission
+ assertAppHasPermission(android.Manifest.permission.RECEIVE_SMS, false)
+ assertAppHasPermission(android.Manifest.permission.SEND_SMS, false)
+
+ // Grant only RECEIVE_SMS
+ uiAutomation.grantRuntimePermission(APP_PACKAGE_NAME,
+ android.Manifest.permission.RECEIVE_SMS)
+ assertAppHasPermission(android.Manifest.permission.RECEIVE_SMS, true)
+
+ // Request both permissions, and expect that SEND_SMS is granted
+ requestAppPermissionsAndAssertResult(android.Manifest.permission.RECEIVE_SMS to true,
+ android.Manifest.permission.SEND_SMS to true) { }
+
+ assertAppHasPermission(android.Manifest.permission.SEND_SMS, true)
+ }
+
+ private fun testRuntimeGroupGrantExpansion(expectExpansion: Boolean) {
+ // Start out without permission
+ assertAppHasPermission(android.Manifest.permission.RECEIVE_SMS, false)
+ assertAppHasPermission(android.Manifest.permission.SEND_SMS, false)
+
+ // Request only one permission from the 'SMS' permission group at runtime,
+ // but two from this group are <uses-permission> in the manifest
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.RECEIVE_SMS to true,
+ ) {
+ clickPermissionRequestAllowButton()
+ }
+
+ assertAppHasPermission(android.Manifest.permission.SEND_SMS, expectExpansion)
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionNoOpGtsTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionNoOpGtsTest.kt
new file mode 100644
index 000000000..d36b272ce
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionNoOpGtsTest.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import com.android.compatibility.common.util.CtsDownstreamingTest
+import org.junit.Test
+
+// NoOp test class so that at least one GTS test passes on all platforms.
+// b/235606392 for reference. Will be removed once we move all downstreaming
+// CtsPermissionUiTestCases to GTS.
+@CtsDownstreamingTest
+class PermissionNoOpGtsTest {
+
+ @Test
+ fun shouldAlwaysPass() {}
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionPolicyTest25.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionPolicyTest25.kt
new file mode 100644
index 000000000..acfeba08c
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionPolicyTest25.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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.permissionui.cts
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Intent
+import android.platform.test.annotations.FlakyTest
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests for the platform permission policy around apps targeting API 25.
+ */
+@FlakyTest
+class PermissionPolicyTest25 : BasePermissionTest() {
+ companion object {
+ const val APP_APK_PATH_25 = "$APK_DIRECTORY/CtsPermissionPolicyApp25.apk"
+ const val APP_PACKAGE_NAME = "android.permissionui.cts.permissionpolicy"
+ }
+
+ @Before
+ fun installApp25() {
+ uninstallPackage(APP_PACKAGE_NAME, requireSuccess = false)
+ installPackage(APP_APK_PATH_25)
+ }
+
+ @After
+ fun uninstallApp() {
+ uninstallPackage(APP_PACKAGE_NAME, requireSuccess = false)
+ }
+
+ @Test
+ fun testNoProtectionFlagsAddedToNonSignatureProtectionPermissions() {
+ val future = startActivityForFuture(
+ Intent().apply {
+ component = ComponentName(
+ APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.TestProtectionFlagsActivity"
+ )
+ }
+ )
+ val result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ assertEquals(Activity.RESULT_OK, result.resultCode)
+ assertEquals("", result.resultData!!.getStringExtra("$APP_PACKAGE_NAME.ERROR_MESSAGE"))
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt
new file mode 100644
index 000000000..06b4552f4
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.Manifest.permission.ACCESS_COARSE_LOCATION
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.Manifest.permission.CAMERA
+import android.os.Build
+import android.platform.test.annotations.FlakyTest
+import android.provider.DeviceConfig
+import android.safetylabel.SafetyLabelConstants.PERMISSION_RATIONALE_ENABLED
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule
+import com.android.modules.utils.build.SdkLevel
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Permission rationale in Grant Permission Dialog tests. Permission rationale is only available on
+ * U+
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+@FlakyTest
+class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
+
+ @get:Rule
+ val deviceConfigPermissionRationaleEnabled =
+ DeviceConfigStateChangerRule(
+ context,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PERMISSION_RATIONALE_ENABLED,
+ true.toString())
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue("Permission rationale is only available on U+", SdkLevel.isAtLeastU())
+ Assume.assumeFalse(isAutomotive)
+ Assume.assumeFalse(isTv)
+ Assume.assumeFalse(isWatch)
+ }
+
+ @Test
+ fun requestLocationPerm_flagDisabled_noPermissionRationale() {
+ setDeviceConfigPrivacyProperty(PERMISSION_RATIONALE_ENABLED, false.toString())
+ installPackageWithInstallSourceAndMetadata(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestLocationPerm_apkHasNoInstallSource_noPermissionRationale() {
+ installPackageWithoutInstallSource(APP_APK_PATH_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestLocationPerm_noAppMetadata_noPermissionRationale() {
+ installPackageWithInstallSourceAndNoMetadata(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestLocationPerm_nullAppMetadata_noPermissionRationale() {
+ installPackageWithInstallSourceAndNoMetadata(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestLocationPerm_emptyAppMetadata_noPermissionRationale() {
+ installPackageWithInstallSourceAndEmptyMetadata(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestLocationPerm_invalidAppMetadata_noPermissionRationale() {
+ installPackageWithInstallSourceAndInvalidMetadata(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestLocationPerm_invalidAppMetadataWithoutTopLevelVersion_noPermissionRationale() {
+ installPackageWithInstallSourceAndMetadataWithoutTopLevelVersion(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestLocationPerm_invalidAppMetadataWithInvalidTopLevelVersion_noPermissionRationale() {
+ installPackageWithInstallSourceAndMetadataWithInvalidTopLevelVersion(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestLocationPerm_invalidAppMetadataWithoutSafetyLabelVersion_noPermissionRationale() {
+ installPackageWithInstallSourceAndMetadataWithoutSafetyLabelVersion(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestLocationPerm_invalidAppMetadataWithInvalidSafetyLabelVersion_noPermissionRationale()
+ {
+ installPackageWithInstallSourceAndMetadataWithInvalidSafetyLabelVersion(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestCameraPerm_noPermissionRationale() {
+ installPackageWithInstallSourceAndMetadata(APP_APK_NAME_31)
+
+ assertAppHasPermission(CAMERA, false)
+
+ requestAppPermissionsForNoResult(CAMERA) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestCoarseLocationPerm_hasPermissionRationale_packageSourceUnspecified() {
+ installPackageWithInstallSourceAndMetadata(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(true)
+ }
+ }
+
+ @Test
+ fun requestCoarseLocationPerm_hasPermissionRationale_packageSourceStore() {
+ installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(true)
+ }
+ }
+
+ @Test
+ fun requestCoarseLocationPerm_hasPermissionRationale_packageSourceLocalFile() {
+ installPackageWithInstallSourceAndMetadataFromLocalFile(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestCoarseLocationPerm_hasPermissionRationale_packageSourceDownloadedFile() {
+ installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestCoarseLocationPerm_hasPermissionRationale_packageSourceOther() {
+ installPackageWithInstallSourceAndMetadataFromOther(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestFineLocationPerm_hasPermissionRationale() {
+ installPackageWithInstallSourceAndMetadata(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_FINE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(true)
+ }
+ }
+
+ @Test
+ fun requestLocationPerm_clicksPermissionRationale_startsPermissionRationaleActivity() {
+ installPackageWithInstallSourceAndMetadata(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_FINE_LOCATION) {
+ clickPermissionRationaleViewInGrantDialog()
+ assertPermissionRationaleDialogIsVisible(true)
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ @Test
+ fun requestLocationPerm_clicksPermissionRationale_startsPermissionRationaleActivity_comesBack(
+ ) {
+ installPackageWithInstallSourceAndMetadata(APP_APK_NAME_31)
+
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ requestAppPermissionsForNoResult(ACCESS_FINE_LOCATION) {
+ clickPermissionRationaleViewInGrantDialog()
+ waitForIdle()
+ assertPermissionRationaleDialogIsVisible(true)
+ pressBack()
+ waitForIdle()
+ assertPermissionRationaleDialogIsVisible(false)
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(true)
+ }
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationaleTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationaleTest.kt
new file mode 100644
index 000000000..cf080f745
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationaleTest.kt
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.Manifest
+import android.app.ActivityManager
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.platform.test.annotations.FlakyTest
+import android.provider.DeviceConfig
+import android.safetylabel.SafetyLabelConstants.PERMISSION_RATIONALE_ENABLED
+import android.text.Spanned
+import android.text.style.ClickableSpan
+import android.util.Log
+import android.view.View
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule
+import com.android.compatibility.common.util.SystemUtil
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.modules.utils.build.SdkLevel
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/** Permission rationale activity tests. Permission rationale is only available on U+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+@FlakyTest
+class PermissionRationaleTest : BaseUsePermissionTest() {
+
+ private var activityManager: ActivityManager? = null
+
+ @get:Rule
+ val deviceConfigPermissionRationaleEnabled =
+ DeviceConfigStateChangerRule(
+ context,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PERMISSION_RATIONALE_ENABLED,
+ true.toString())
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue("Permission rationale is only available on U+", SdkLevel.isAtLeastU())
+ Assume.assumeFalse(isAutomotive)
+ Assume.assumeFalse(isTv)
+ Assume.assumeFalse(isWatch)
+
+ activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+
+ enableComponent(TEST_INSTALLER_ACTIVITY_COMPONENT_NAME)
+
+ installPackageWithInstallSourceAndMetadata(APP_APK_NAME_31)
+
+ assertAppHasPermission(Manifest.permission.ACCESS_FINE_LOCATION, false)
+ }
+
+ @After
+ fun disableTestInstallerActivity() {
+ disableComponent(TEST_INSTALLER_ACTIVITY_COMPONENT_NAME)
+ }
+
+ @Test
+ fun startsPermissionRationaleActivity_failedByNullMetadata() {
+ installPackageWithInstallSourceAndNoMetadata(APP_APK_NAME_31)
+ navigateToPermissionRationaleActivity_failedShowPermissionRationaleContainer()
+ }
+
+ @Test
+ fun startsPermissionRationaleActivity_failedByEmptyMetadata() {
+ installPackageWithInstallSourceAndEmptyMetadata(APP_APK_NAME_31)
+ navigateToPermissionRationaleActivity_failedShowPermissionRationaleContainer()
+ }
+
+ @Test
+ fun startsPermissionRationaleActivity_failedByNoTopLevelVersion() {
+ installPackageWithInstallSourceAndMetadataWithoutTopLevelVersion(APP_APK_NAME_31)
+ navigateToPermissionRationaleActivity_failedShowPermissionRationaleContainer()
+ }
+
+ @Test
+ fun startsPermissionRationaleActivity_failedByInvalidTopLevelVersion() {
+ installPackageWithInstallSourceAndMetadataWithInvalidTopLevelVersion(APP_APK_NAME_31)
+ navigateToPermissionRationaleActivity_failedShowPermissionRationaleContainer()
+ }
+
+ @Test
+ fun startsPermissionRationaleActivity_failedByNoSafetyLabelVersion() {
+ installPackageWithInstallSourceAndMetadataWithoutSafetyLabelVersion(APP_APK_NAME_31)
+ navigateToPermissionRationaleActivity_failedShowPermissionRationaleContainer()
+ }
+
+ @Test
+ fun startsPermissionRationaleActivity_failedByInvalidSafetyLabelVersion() {
+ installPackageWithInstallSourceAndMetadataWithInvalidSafetyLabelVersion(APP_APK_NAME_31)
+ navigateToPermissionRationaleActivity_failedShowPermissionRationaleContainer()
+ }
+
+ @Test
+ fun startsPermissionRationaleActivity() {
+ navigateToPermissionRationaleActivity()
+
+ assertPermissionRationaleDialogIsVisible(true)
+ }
+
+ @Test
+ fun linksToInstallSource() {
+ navigateToPermissionRationaleActivity()
+
+ assertPermissionRationaleDialogIsVisible(true)
+
+ clickInstallSourceLink()
+
+ eventually {
+ assertStoreLinkClickSuccessful(installerPackageName = TEST_INSTALLER_PACKAGE_NAME)
+ }
+ }
+
+ @Ignore("b/282063206")
+ @Test
+ fun clickLinkToHelpCenter_opensHelpCenter() {
+ Assume.assumeFalse(getPermissionControllerResString(HELP_CENTER_URL_ID).isNullOrEmpty())
+
+ navigateToPermissionRationaleActivity()
+
+ assertPermissionRationaleActivityTitleIsVisible(true)
+ assertHelpCenterLinkAvailable(true)
+
+ clickHelpCenterLink()
+
+ eventually({assertHelpCenterLinkClickSuccessful()}, HELP_CENTER_TIMEOUT_MILLIS)
+ }
+
+ @Test
+ fun noHelpCenterLinkAvailable_noHelpCenterClickAction() {
+ Assume.assumeTrue(getPermissionControllerResString(HELP_CENTER_URL_ID).isNullOrEmpty())
+
+ navigateToPermissionRationaleActivity()
+
+ assertPermissionRationaleActivityTitleIsVisible(true)
+ assertHelpCenterLinkAvailable(false)
+ }
+
+ @Test
+ fun linksToSettings_noOp_dialogsNotClosed() {
+ navigateToPermissionRationaleActivity()
+
+ assertPermissionRationaleDialogIsVisible(true)
+
+ clicksSettings_doesNothing_leaves()
+
+ eventually {
+ assertPermissionRationaleDialogIsVisible(true)
+ }
+ }
+
+ @Test
+ fun linksToSettings_grants_dialogsClose() {
+ navigateToPermissionRationaleActivity()
+
+ assertPermissionRationaleDialogIsVisible(true)
+
+ clicksSettings_allowsForeground_leaves()
+
+ // Setting, Permission rationale and Grant dialog should be dismissed
+ eventually {
+ assertPermissionSettingsVisible(false)
+ assertPermissionRationaleDialogIsVisible(false)
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+
+ assertAppHasPermission(Manifest.permission.ACCESS_FINE_LOCATION, true)
+ }
+
+ @Test
+ fun linksToSettings_denies_dialogsClose() {
+ navigateToPermissionRationaleActivity()
+
+ assertPermissionRationaleDialogIsVisible(true)
+
+ clicksSettings_denies_leaves()
+
+ // Setting, Permission rationale and Grant dialog should be dismissed
+ eventually {
+ assertPermissionSettingsVisible(false)
+ assertPermissionRationaleDialogIsVisible(false)
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+
+ assertAppHasPermission(Manifest.permission.ACCESS_FINE_LOCATION, false)
+ }
+
+ private fun navigateToPermissionRationaleActivity_failedShowPermissionRationaleContainer() {
+ requestAppPermissionsForNoResult(Manifest.permission.ACCESS_FINE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
+ }
+ }
+
+ private fun navigateToPermissionRationaleActivity() {
+ requestAppPermissionsForNoResult(Manifest.permission.ACCESS_FINE_LOCATION) {
+ assertPermissionRationaleContainerOnGrantDialogIsVisible(true)
+ clickPermissionRationaleViewInGrantDialog()
+ }
+ }
+
+ private fun clickInstallSourceLink() {
+ findView(By.res(DATA_SHARING_SOURCE_MESSAGE_ID), true)
+
+ eventually {
+ // UiObject2 doesn't expose CharSequence.
+ val node = uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByViewId(
+ DATA_SHARING_SOURCE_MESSAGE_ID
+ )[0]
+ assertTrue(node.isVisibleToUser)
+ val text = node.text as Spanned
+ val clickableSpan = text.getSpans(0, text.length, ClickableSpan::class.java)[0]
+ // We could pass in null here in Java, but we need an instance in Kotlin.
+ clickableSpan.onClick(View(context))
+ }
+ waitForIdle()
+ }
+
+ private fun clickHelpCenterLink() {
+ findView(By.res(LEARN_MORE_MESSAGE_ID), true)
+
+ eventually {
+ // UiObject2 doesn't expose CharSequence.
+ val node = uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByViewId(
+ LEARN_MORE_MESSAGE_ID
+ )[0]
+ assertTrue(node.isVisibleToUser)
+ val text = node.text as Spanned
+ val clickableSpan = text.getSpans(0, text.length, ClickableSpan::class.java)[0]
+ // We could pass in null here in Java, but we need an instance in Kotlin.
+ clickableSpan.onClick(View(context))
+ }
+ waitForIdle()
+ }
+
+ private fun clickSettingsLink() {
+ findView(By.res(SETTINGS_MESSAGE_ID), true)
+
+ eventually {
+ // UiObject2 doesn't expose CharSequence.
+ val node = uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByViewId(
+ SETTINGS_MESSAGE_ID
+ )[0]
+ assertTrue(node.isVisibleToUser)
+ val text = node.text as Spanned
+ val clickableSpan = text.getSpans(0, text.length, ClickableSpan::class.java)[0]
+ // We could pass in null here in Java, but we need an instance in Kotlin.
+ clickableSpan.onClick(View(context))
+ }
+ waitForIdle()
+ }
+
+ private fun clicksSettings_doesNothing_leaves() {
+ clickSettingsLink()
+ eventually {
+ assertPermissionSettingsVisible(true)
+ }
+ pressBack()
+ }
+
+ private fun clicksSettings_allowsForeground_leaves() {
+ clickSettingsLink()
+ eventually {
+ clickAllowForegroundInSettings()
+ }
+ pressBack()
+ }
+
+ private fun clicksSettings_denies_leaves() {
+ clickSettingsLink()
+ eventually {
+ clicksDenyInSettings()
+ }
+ pressBack()
+ }
+
+ private fun assertHelpCenterLinkAvailable(expected: Boolean) {
+ // Message should always be visible
+ findView(By.res(LEARN_MORE_MESSAGE_ID), true)
+
+ // Verify the link is (or isn't) in message
+ eventually {
+ // UiObject2 doesn't expose CharSequence.
+ val node = uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByViewId(
+ LEARN_MORE_MESSAGE_ID
+ )[0]
+ assertTrue(node.isVisibleToUser)
+ val text = node.text as Spanned
+ val clickableSpans = text.getSpans(0, text.length, ClickableSpan::class.java)
+
+ if (expected) {
+ assertFalse("Expected help center link, but none found",
+ clickableSpans.isEmpty())
+ } else {
+ assertTrue("Expected no links, but found one", clickableSpans.isEmpty())
+ }
+ }
+ }
+
+ private fun assertPermissionSettingsVisible(expected: Boolean) {
+ findView(By.res(DENY_RADIO_BUTTON), expected = expected)
+ }
+
+ private fun assertStoreLinkClickSuccessful(
+ installerPackageName: String,
+ packageName: String? = null
+ ) {
+ SystemUtil.runWithShellPermissionIdentity {
+ val runningTasks = activityManager!!.getRunningTasks(1)
+
+ assertFalse("Expected runningTasks to not be empty",
+ runningTasks.isEmpty())
+
+ val taskInfo = runningTasks[0]
+ val observedIntentAction = taskInfo.baseIntent.action
+ val observedPackageName = taskInfo.baseIntent.getStringExtra(Intent.EXTRA_PACKAGE_NAME)
+ val observedInstallerPackageName = taskInfo.topActivity?.packageName
+
+ assertEquals("Unexpected intent action",
+ Intent.ACTION_SHOW_APP_INFO,
+ observedIntentAction)
+ assertEquals("Unexpected installer package name",
+ installerPackageName,
+ observedInstallerPackageName)
+ assertEquals("Unexpected package name",
+ packageName,
+ observedPackageName)
+ }
+ }
+
+ private fun assertHelpCenterLinkClickSuccessful() {
+ SystemUtil.runWithShellPermissionIdentity {
+ val runningTasks = activityManager!!.getRunningTasks(5)
+
+ Log.v(TAG, "# running tasks: ${runningTasks.size}")
+ assertFalse("Expected runningTasks to not be empty", runningTasks.isEmpty())
+
+ runningTasks.forEachIndexed { index, runningTaskInfo ->
+ Log.v(TAG, "task $index ${runningTaskInfo.baseIntent}")
+ }
+
+ val taskInfo = runningTasks[0]
+ val observedIntentAction = taskInfo.baseIntent.action
+ val observedIntentDataString = taskInfo.baseIntent.dataString
+ val observedIntentScheme: String? = taskInfo.baseIntent.scheme
+
+ Log.v(TAG, "task base intent: ${taskInfo.baseIntent}")
+ assertEquals("Unexpected intent action", Intent.ACTION_VIEW, observedIntentAction)
+
+ val expectedUrl = getPermissionControllerResString(HELP_CENTER_URL_ID)!!
+ assertFalse(observedIntentDataString.isNullOrEmpty())
+ assertTrue(observedIntentDataString?.startsWith(expectedUrl) ?: false)
+
+ assertFalse(observedIntentScheme.isNullOrEmpty())
+ assertEquals("https", observedIntentScheme)
+ }
+ }
+
+ companion object {
+ private val TAG = PermissionRationaleTest::class.java.simpleName
+
+ private const val DATA_SHARING_SOURCE_MESSAGE_ID =
+ "com.android.permissioncontroller:id/data_sharing_source_message"
+ private const val LEARN_MORE_MESSAGE_ID =
+ "com.android.permissioncontroller:id/learn_more_message"
+ private const val SETTINGS_MESSAGE_ID =
+ "com.android.permissioncontroller:id/settings_message"
+
+ private const val HELP_CENTER_URL_ID = "data_sharing_help_center_link"
+ private const val HELP_CENTER_TIMEOUT_MILLIS: Long = 20000
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionReviewTapjackingTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionReviewTapjackingTest.kt
new file mode 100644
index 000000000..270522ed2
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionReviewTapjackingTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 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.permissionui.cts
+
+import android.content.ComponentName
+import android.content.Intent
+import android.platform.test.annotations.FlakyTest
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil
+import java.lang.Exception
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Tests permission review screen can't be tapjacked
+ */
+@FlakyTest
+class PermissionReviewTapjackingTest : BaseUsePermissionTest() {
+
+ companion object {
+ const val HELPER_APP_OVERLAY = "$APK_DIRECTORY/CtsHelperAppOverlay.apk"
+ private const val HELPER_PACKAGE_NAME = "android.permissionui.cts.helper.overlay"
+ }
+
+ @Before
+ fun installApp22AndApprovePermissionReview() {
+ assumeFalse(packageManager.arePermissionsIndividuallyControlled())
+
+ installPackage(APP_APK_PATH_22)
+ installPackage(HELPER_APP_OVERLAY)
+
+ SystemUtil.runShellCommandOrThrow(
+ "appops set $HELPER_PACKAGE_NAME android:system_alert_window allow")
+ }
+
+ @After
+ fun uninstallPackages() {
+ SystemUtil.runShellCommandOrThrow(
+ "pm uninstall $APP_PACKAGE_NAME")
+ SystemUtil.runShellCommandOrThrow(
+ "pm uninstall $HELPER_PACKAGE_NAME")
+ }
+
+ @Test
+ fun testOverlaysAreHidden() {
+ context.startActivity(Intent()
+ .setComponent(ComponentName(HELPER_PACKAGE_NAME,
+ "$HELPER_PACKAGE_NAME.OverlayActivity"))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+ findOverlay()
+
+ context.startActivity(Intent()
+ .setComponent(ComponentName(APP_PACKAGE_NAME,
+ "$APP_PACKAGE_NAME.FinishOnCreateActivity"))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ )
+
+ if (isWatch) {
+ waitFindObject(By.text(getPermissionControllerString("review_button_cancel")))
+ } else {
+ waitFindObject(By.res("com.android.permissioncontroller:id/permissions_message"))
+ }
+
+ try {
+ findOverlay()
+ Assert.fail("Overlay was displayed")
+ } catch (e: Exception) {
+ // expected
+ }
+
+ pressHome()
+ findOverlay()
+ }
+
+ private fun findOverlay() = waitFindObject(By.text("Find me!"))
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionReviewTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionReviewTest.kt
new file mode 100644
index 000000000..8e890b230
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionReviewTest.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2018 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.permissionui.cts
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.os.ResultReceiver
+import android.platform.test.annotations.FlakyTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.AndroidJUnit4
+import androidx.test.uiautomator.By
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@FlakyTest
+class PermissionReviewTest : BaseUsePermissionTest() {
+
+ @Before
+ fun assumeNotIndividuallyControlled() {
+ Assume.assumeFalse(packageManager.arePermissionsIndividuallyControlled())
+ }
+
+ @Before
+ fun installApp22CalendarOnly() {
+ installPackage(APP_APK_PATH_22_CALENDAR_ONLY)
+ }
+
+ @get:Rule
+ val activityRule = ActivityTestRule(StartForFutureActivity::class.java, false, false)
+
+ @Test
+ fun testDenyCalendarDuringReview() {
+ startAppActivityAndAssertResultCode(Activity.RESULT_OK) {
+ // Deny
+ clickPermissionControllerUi(By.text("Calendar"))
+ // Confirm deny
+ click(By.res("android:id/button1"))
+
+ clickPermissionReviewContinue()
+ }
+
+ clearTargetSdkWarning()
+ assertAppHasCalendarAccess(false)
+ }
+
+ @Test
+ fun testDenyGrantCalendarDuringReview() {
+ startAppActivityAndAssertResultCode(Activity.RESULT_OK) {
+ // Deny
+ clickPermissionControllerUi(By.text("Calendar"))
+ // Confirm deny
+ click(By.res("android:id/button1"))
+
+ // Grant
+ clickPermissionControllerUi(By.text("Calendar"))
+
+ clickPermissionReviewContinue()
+ }
+
+ clearTargetSdkWarning()
+ assertAppHasCalendarAccess(true)
+ }
+
+ @Test
+ fun testDenyGrantDenyCalendarDuringReview() {
+ startAppActivityAndAssertResultCode(Activity.RESULT_OK) {
+ // Deny
+ clickPermissionControllerUi(By.text("Calendar"))
+ // Confirm deny
+ click(By.res("android:id/button1"))
+
+ // Grant
+ clickPermissionControllerUi(By.text("Calendar"))
+
+ // Deny
+ clickPermissionControllerUi(By.text("Calendar"))
+
+ clickPermissionReviewContinue()
+ }
+
+ clearTargetSdkWarning()
+ assertAppHasCalendarAccess(false)
+ }
+
+ @Test
+ fun testCancelReview() {
+ // Start APK_22_ONLY_CALENDAR, but cancel review
+ cancelPermissionReview()
+
+ // Start APK_22_ONLY_CALENDAR again, now approve review
+ approvePermissionReview()
+
+ assertAppDoesNotNeedPermissionReview()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "TIRAMISU")
+ fun testNotificationPermissionAddedToReview() {
+ startAppActivityAndAssertResultCode(Activity.RESULT_CANCELED) {
+ waitFindObject(By.text("Notifications"), 5000L)
+ clickPermissionReviewCancel()
+ }
+ }
+
+ @Test
+ fun testReviewPermissionWhenServiceIsBound() {
+ val results = LinkedBlockingQueue<Int>()
+ // We are starting a activity instead of the service directly, because
+ // the service comes from a different app than the CTS tests.
+ // This app will be considered idle on devices that have idling enabled (automotive),
+ // and the service wouldn't be allowed to be started without the activity.
+ activityRule.launchActivity(null).startActivity(
+ Intent().apply {
+ component = ComponentName(
+ APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.StartCheckPermissionServiceActivity"
+ )
+ putExtra(
+ "$APP_PACKAGE_NAME.RESULT",
+ object : ResultReceiver(Handler(Looper.getMainLooper())) {
+ override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
+ results.offer(resultCode)
+ }
+ }
+ )
+ putExtra(
+ "$APP_PACKAGE_NAME.PERMISSION", android.Manifest.permission.READ_CALENDAR
+ )
+ }
+ )
+
+ // Service is not started before permission are reviewed
+ assertNull(results.poll(UNEXPECTED_TIMEOUT_MILLIS.toLong(), TimeUnit.MILLISECONDS))
+
+ clickPermissionReviewContinueAndClearSdkWarning()
+
+ // Service should be started after permission review
+ assertEquals(
+ PackageManager.PERMISSION_GRANTED, results.poll(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ )
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
new file mode 100644
index 000000000..bfd0e4bac
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018 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.permissionui.cts
+
+import android.os.Build
+import android.platform.test.annotations.FlakyTest
+import androidx.test.filters.SdkSuppress
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Runtime permission behavior tests for permission splits.
+ */
+@FlakyTest
+class PermissionSplitTest : BaseUsePermissionTest() {
+ @Before
+ fun assumeNotTv() {
+ assumeFalse(isTv)
+ }
+
+ @Test
+ fun testPermissionSplit28() {
+ installPackage(APP_APK_PATH_28)
+ testLocationPermissionSplit(true)
+ }
+
+ @Test
+ fun testPermissionNotSplit29() {
+ installPackage(APP_APK_PATH_29)
+ testLocationPermissionSplit(false)
+ }
+
+ @Test
+ fun testPermissionNotSplit30() {
+ installPackage(APP_APK_PATH_30)
+ testLocationPermissionSplit(false)
+ }
+
+ @Test
+ fun testPermissionNotSplitLatest() {
+ installPackage(APP_APK_PATH_LATEST)
+ testLocationPermissionSplit(false)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ @Test
+ fun testBodySensorSplit() {
+ installPackage(APP_APK_PATH_31)
+ testBodySensorPermissionSplit(true)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ @Test
+ fun testBodySensorSplit32() {
+ installPackage(APP_APK_PATH_32)
+ testBodySensorPermissionSplit(true)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ @Test
+ fun testBodySensorNonSplit() {
+ installPackage(APP_APK_PATH_LATEST)
+ testBodySensorPermissionSplit(false)
+ }
+
+ private fun testLocationPermissionSplit(expectSplit: Boolean) {
+ assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, false)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
+
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.ACCESS_FINE_LOCATION to true
+ ) {
+ if (expectSplit) {
+ clickPermissionRequestSettingsLinkAndAllowAlways()
+ } else {
+ clickPermissionRequestAllowForegroundButton()
+ }
+ }
+
+ assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, expectSplit)
+ }
+
+ private fun testBodySensorPermissionSplit(expectSplit: Boolean) {
+ assertAppHasPermission(android.Manifest.permission.BODY_SENSORS, false)
+ assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, false)
+
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.BODY_SENSORS to true
+ ) {
+ if (expectSplit) {
+ clickPermissionRequestSettingsLinkAndAllowAlways()
+ } else {
+ clickPermissionRequestAllowForegroundButton()
+ }
+ }
+
+ assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, expectSplit)
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt
new file mode 100644
index 000000000..8dc01955d
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 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.permissionui.cts
+
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Point
+import android.os.Build
+import android.platform.test.annotations.FlakyTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Tests permissions can't be tapjacked
+ */
+@FlakyTest
+class PermissionTapjackingTest : BaseUsePermissionTest() {
+
+ @Before
+ fun installAppLatest() {
+ installPackage(APP_APK_PATH_WITH_OVERLAY)
+ }
+
+ @Test
+ fun testTapjackGrantDialog_fullOverlay() {
+ // PermissionController for television uses a floating window.
+ assumeFalse(isTv)
+
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ requestAppPermissionsForNoResult(ACCESS_FINE_LOCATION) {}
+
+ val buttonCenter = waitFindObject(By.text(
+ getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT))).visibleCenter
+
+ // Wait for overlay to hide the dialog
+ context.sendBroadcast(Intent(ACTION_SHOW_OVERLAY)
+ .putExtra(EXTRA_FULL_OVERLAY, true))
+ waitFindObject(By.res("android.permissionui.cts.usepermission:id/overlay"))
+
+ tryClicking(buttonCenter)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+ @Test
+ fun testTapjackGrantDialog_partialOverlay() {
+ // PermissionController for television uses a floating window.
+ assumeFalse(isTv)
+
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ requestAppPermissionsForNoResult(ACCESS_FINE_LOCATION) {}
+
+ val foregroundButtonCenter = waitFindObject(By.text(
+ getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT))).visibleCenter
+ val oneTimeButton = waitFindObjectOrNull(By.text(
+ getPermissionControllerString(ALLOW_ONE_TIME_BUTTON_TEXT)))
+ // If one-time button is not available, fallback to deny button
+ val overlayButtonBounds = oneTimeButton?.visibleBounds
+ ?: waitFindObject(By.text(getPermissionControllerString(
+ DENY_BUTTON_TEXT))).visibleBounds
+
+ // Wait for overlay to hide the dialog
+ context.sendBroadcast(Intent(ACTION_SHOW_OVERLAY)
+ .putExtra(EXTRA_FULL_OVERLAY, false)
+ .putExtra(OVERLAY_LEFT, overlayButtonBounds.left)
+ .putExtra(OVERLAY_TOP, overlayButtonBounds.top)
+ .putExtra(OVERLAY_RIGHT, overlayButtonBounds.right)
+ .putExtra(OVERLAY_BOTTOM, overlayButtonBounds.bottom))
+ waitFindObject(By.res("android.permissionui.cts.usepermission:id/overlay"))
+
+ tryClicking(foregroundButtonCenter)
+ }
+
+ private fun tryClicking(buttonCenter: Point) {
+ try {
+ // Try to grant the permission, this should fail
+ SystemUtil.eventually({
+ if (packageManager.checkPermission(ACCESS_FINE_LOCATION, APP_PACKAGE_NAME) ==
+ PackageManager.PERMISSION_DENIED) {
+ uiDevice.click(buttonCenter.x, buttonCenter.y)
+ Thread.sleep(100)
+ }
+ assertAppHasPermission(ACCESS_FINE_LOCATION, true)
+ }, 10000)
+ } catch (e: RuntimeException) {
+ // expected
+ }
+ // Permission should not be granted
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+
+ // Verify that clicking the dialog without the overlay still works
+ context.sendBroadcast(Intent(ACTION_HIDE_OVERLAY))
+ SystemUtil.eventually({
+ if (packageManager.checkPermission(ACCESS_FINE_LOCATION, APP_PACKAGE_NAME) ==
+ PackageManager.PERMISSION_DENIED) {
+ uiDevice.click(buttonCenter.x, buttonCenter.y)
+ Thread.sleep(100)
+ }
+ assertAppHasPermission(ACCESS_FINE_LOCATION, true)
+ }, 10000)
+ }
+
+ companion object {
+ const val ACTION_SHOW_OVERLAY = "android.permissionui.cts.usepermission.ACTION_SHOW_OVERLAY"
+ const val ACTION_HIDE_OVERLAY = "android.permissionui.cts.usepermission.ACTION_HIDE_OVERLAY"
+
+ const val EXTRA_FULL_OVERLAY = "android.permissionui.cts.usepermission.extra.FULL_OVERLAY"
+
+ const val OVERLAY_LEFT = "android.permissionui.cts.usepermission.extra.OVERLAY_LEFT"
+ const val OVERLAY_TOP = "android.permissionui.cts.usepermission.extra.OVERLAY_TOP"
+ const val OVERLAY_RIGHT = "android.permissionui.cts.usepermission.extra.OVERLAY_RIGHT"
+ const val OVERLAY_BOTTOM = "android.permissionui.cts.usepermission.extra.OVERLAY_BOTTOM"
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest22.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest22.kt
new file mode 100755
index 000000000..6206a0300
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest22.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 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.permissionui.cts
+
+import android.platform.test.annotations.FlakyTest
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Runtime permission behavior tests for apps targeting API 22.
+ */
+@FlakyTest
+class PermissionTest22 : BaseUsePermissionTest() {
+
+ @Before
+ fun installApp22AndApprovePermissionReview() {
+ Assume.assumeFalse(packageManager.arePermissionsIndividuallyControlled())
+
+ installPackage(APP_APK_PATH_22)
+ approvePermissionReview()
+ }
+
+ @Test
+ fun testCompatDefault() {
+ // Legacy permission model appears granted
+ assertAppHasPermission(android.Manifest.permission.READ_CALENDAR, true)
+ assertAppHasPermission(android.Manifest.permission.WRITE_CALENDAR, true)
+ assertAppHasCalendarAccess(true)
+ }
+
+ @Test
+ fun testCompatRevoked() {
+ // Revoke the permission
+ revokeAppPermissionsByUi(android.Manifest.permission.WRITE_CALENDAR, isLegacyApp = true)
+
+ // Legacy permission model appears granted
+ assertAppHasPermission(android.Manifest.permission.READ_CALENDAR, true)
+ assertAppHasPermission(android.Manifest.permission.WRITE_CALENDAR, true)
+ // Read/write access should be ignored
+ assertAppHasCalendarAccess(false)
+ }
+
+ @Test
+ fun testNoRuntimePrompt() {
+ // Request the permission and do nothing
+ // Expect the permission is not granted
+ requestAppPermissionsAndAssertResult(
+ arrayOf(android.Manifest.permission.SEND_SMS), emptyArray()
+ ) {}
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest23.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest23.kt
new file mode 100644
index 000000000..cc29fd328
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest23.kt
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2015 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.permissionui.cts
+
+import android.content.pm.PackageManager
+import android.permission.cts.MtsIgnore
+import android.platform.test.annotations.FlakyTest
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Runtime permission behavior tests for apps targeting API 23.
+ */
+@FlakyTest
+class PermissionTest23 : BaseUsePermissionTest() {
+ companion object {
+ private const val NON_EXISTENT_PERMISSION = "permission.does.not.exist"
+ private const val INVALID_PERMISSION = "$APP_PACKAGE_NAME.abadname"
+ }
+
+ @Before
+ fun installApp23() {
+ installPackage(APP_APK_PATH_23)
+ }
+
+ @Test
+ fun testDefault() {
+ // New permission model is denied by default
+ assertAppHasAllOrNoPermissions(false)
+ }
+
+ @Test
+ fun testGranted() {
+ grantAppPermissionsByUi(android.Manifest.permission.READ_CALENDAR)
+
+ // Read/write access should be allowed
+ assertAppHasPermission(android.Manifest.permission.READ_CALENDAR, true)
+ assertAppHasPermission(android.Manifest.permission.WRITE_CALENDAR, true)
+ assertAppHasCalendarAccess(true)
+ }
+
+ @Test
+ fun testInteractiveGrant() {
+ // Start out without permission
+ assertAppHasPermission(android.Manifest.permission.READ_CALENDAR, false)
+ assertAppHasPermission(android.Manifest.permission.WRITE_CALENDAR, false)
+ assertAppHasCalendarAccess(false)
+
+ // Go through normal grant flow
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.READ_CALENDAR to true,
+ android.Manifest.permission.WRITE_CALENDAR to true
+ ) {
+ clickPermissionRequestAllowButton()
+ }
+
+ // We should have permission now!
+ assertAppHasCalendarAccess(true)
+ }
+
+ @Test
+ fun testRuntimeGroupGrantSpecificity() {
+ // Start out without permission
+ assertAppHasPermission(android.Manifest.permission.READ_CONTACTS, false)
+ assertAppHasPermission(android.Manifest.permission.WRITE_CONTACTS, false)
+
+ // Request only one permission from the 'contacts' permission group
+ // Expect the permission is granted
+ requestAppPermissionsAndAssertResult(android.Manifest.permission.WRITE_CONTACTS to true) {
+ clickPermissionRequestAllowButton()
+ }
+
+ // Make sure no undeclared as used permissions are granted
+ assertAppHasPermission(android.Manifest.permission.READ_CONTACTS, false)
+ }
+
+ @Test
+ fun testCancelledPermissionRequest() {
+ // Make sure we don't have the permission
+ assertAppHasPermission(android.Manifest.permission.WRITE_CONTACTS, false)
+
+ // Request the permission and cancel the request
+ // Expect the permission is not granted
+ requestAppPermissionsAndAssertResult(android.Manifest.permission.WRITE_CONTACTS to false) {
+ clickPermissionRequestDenyButton()
+ }
+ }
+
+ @Test
+ fun testRequestGrantedPermission() {
+ // Make sure we don't have the permission
+ assertAppHasPermission(android.Manifest.permission.WRITE_CONTACTS, false)
+
+ // Request the permission and allow it
+ // Expect the permission is granted
+ requestAppPermissionsAndAssertResult(android.Manifest.permission.WRITE_CONTACTS to true) {
+ clickPermissionRequestAllowButton()
+ }
+
+ // Request the permission and do nothing
+ // Expect the permission is granted
+ requestAppPermissionsAndAssertResult(android.Manifest.permission.WRITE_CONTACTS to true) {}
+ }
+
+ @Test
+ fun testDenialWithPrejudice() {
+ // Make sure we don't have the permission
+ assertAppHasPermission(android.Manifest.permission.WRITE_CONTACTS, false)
+
+ // Request the permission and deny it twice
+ // Expect the permission is not granted
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.WRITE_CONTACTS to false,
+ askTwice = true
+ ) {
+ clickPermissionRequestDenyButton()
+ denyPermissionRequestWithPrejudice()
+ }
+
+ // Request the permission and do nothing
+ // Expect the permission is not granted
+ requestAppPermissionsAndAssertResult(android.Manifest.permission.WRITE_CONTACTS to false) {}
+ }
+
+ @FlakyTest
+ @MtsIgnore
+ @Test
+ fun testRevokeAffectsWholeGroup() {
+ // Grant the group
+ grantAppPermissionsByUi(android.Manifest.permission.READ_CALENDAR)
+
+ // Make sure we have the permissions
+ assertAppHasPermission(android.Manifest.permission.READ_CALENDAR, true)
+ assertAppHasPermission(android.Manifest.permission.WRITE_CALENDAR, true)
+
+ // Revoke the group
+ revokeAppPermissionsByUi(android.Manifest.permission.READ_CALENDAR)
+
+ // Make sure we don't have the permissions
+ assertAppHasPermission(android.Manifest.permission.READ_CALENDAR, false)
+ assertAppHasPermission(android.Manifest.permission.WRITE_CALENDAR, false)
+ }
+
+ @Test
+ fun testGrantPreviouslyRevokedWithPrejudiceShowsPrompt() {
+ // Make sure we don't have the permission
+ assertAppHasPermission(android.Manifest.permission.READ_CALENDAR, false)
+
+ // Request the permission and deny it twice
+ // Expect the permission is not granted
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.READ_CALENDAR to false,
+ askTwice = true
+ ) {
+ clickPermissionRequestDenyButton()
+ waitForIdle()
+ denyPermissionRequestWithPrejudice()
+ }
+
+ // Clear the denial with prejudice
+ uiAutomation.grantRuntimePermission(APP_PACKAGE_NAME,
+ android.Manifest.permission.READ_CALENDAR)
+ revokeAppPermissionsByUi(android.Manifest.permission.READ_CALENDAR)
+
+ // Make sure we don't have the permission
+ assertAppHasPermission(android.Manifest.permission.READ_CALENDAR, false)
+
+ // Request the permission and allow it
+ // Make sure the permission is granted
+ requestAppPermissionsAndAssertResult(android.Manifest.permission.READ_CALENDAR to true) {
+ clickPermissionRequestAllowButton()
+ }
+ }
+
+ @Test
+ fun testRequestNonRuntimePermission() {
+ // Make sure we don't have the permission
+ assertAppHasPermission(android.Manifest.permission.BIND_PRINT_SERVICE, false)
+
+ // Request the permission and do nothing
+ // Expect the permission is not granted
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.BIND_PRINT_SERVICE to false
+ ) {}
+ }
+
+ @Test
+ fun testRequestNonExistentPermission() {
+ // Make sure we don't have the permission
+ assertAppHasPermission(NON_EXISTENT_PERMISSION, false)
+
+ // Request the permission and do nothing
+ // Expect the permission is not granted
+ requestAppPermissionsAndAssertResult(NON_EXISTENT_PERMISSION to false) {}
+ }
+
+ @Test
+ fun testRequestPermissionFromTwoGroups() {
+ // Make sure we don't have the permissions
+ assertAppHasPermission(android.Manifest.permission.WRITE_CONTACTS, false)
+ assertAppHasPermission(android.Manifest.permission.WRITE_CALENDAR, false)
+ assertAppHasPermission(android.Manifest.permission.READ_CALENDAR, false)
+
+ // Request the permission and allow it
+ // Expect the permission are granted
+ val result = requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.WRITE_CONTACTS to true,
+ android.Manifest.permission.WRITE_CALENDAR to true
+ ) {
+ clickPermissionRequestAllowButton()
+ clickPermissionRequestAllowButton()
+ }
+
+ // In API < N_MR1 all permissions of a group are granted. I.e. the grant was "expanded"
+ assertAppHasPermission(android.Manifest.permission.READ_CALENDAR, true)
+ // Even the contacts group was expanded, the read-calendar permission is not in the
+ // manifest, hence not granted.
+ assertAppHasPermission(android.Manifest.permission.READ_CONTACTS, false)
+ }
+
+ @Test(timeout = 180000)
+ @FlakyTest
+ @MtsIgnore
+ fun testNoResidualPermissionsOnUninstall() {
+ Assume.assumeFalse(packageManager.arePermissionsIndividuallyControlled())
+
+ // Grant one permission via UI, and the rest via automation
+ grantAppPermissionsByUi(android.Manifest.permission.WRITE_CALENDAR)
+ grantRuntimePermissions(android.Manifest.permission.WRITE_CONTACTS,
+ android.Manifest.permission.READ_SMS,
+ android.Manifest.permission.CALL_PHONE,
+ android.Manifest.permission.RECORD_AUDIO,
+ android.Manifest.permission.CAMERA,
+ android.Manifest.permission.READ_EXTERNAL_STORAGE,
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ android.Manifest.permission.BODY_SENSORS,
+ )
+ uninstallPackage(APP_PACKAGE_NAME)
+ installPackage(APP_APK_PATH_23)
+
+ // Make no permissions are granted after uninstalling and installing the app
+ assertAppHasAllOrNoPermissions(false)
+ }
+
+ @Test
+ fun testNullPermissionRequest() {
+ val permissions: Array<String?> = arrayOf(null)
+ val results: Array<Pair<String?, Boolean>> = arrayOf()
+ // Go through normal grant flow
+ requestAppPermissionsAndAssertResult(permissions, results) {}
+ }
+
+ @Test
+ fun testNullAndRealPermission() {
+ // Make sure we don't have the permissions
+ assertAppHasPermission(android.Manifest.permission.WRITE_CONTACTS, false)
+ assertAppHasPermission(android.Manifest.permission.RECORD_AUDIO, false)
+
+ // Request the permission and allow it
+ // Expect the permission are granted
+ requestAppPermissionsAndAssertResult(
+ arrayOf(
+ android.Manifest.permission.WRITE_CONTACTS,
+ null,
+ android.Manifest.permission.RECORD_AUDIO,
+ null),
+ arrayOf(
+ android.Manifest.permission.WRITE_CONTACTS to true,
+ android.Manifest.permission.RECORD_AUDIO to true)
+ ) {
+ clickPermissionRequestAllowForegroundButton()
+ clickPermissionRequestAllowButton()
+ }
+ }
+
+ @Test
+ fun testInvalidPermission() {
+ // Request the permission and allow it
+ // Expect the permission is not granted
+ requestAppPermissionsAndAssertResult(INVALID_PERMISSION to false) {}
+ }
+
+ @Test
+ fun testAskButtonSetsFlags() {
+ Assume.assumeFalse("other form factors might not support the ask button",
+ isTv || isAutomotive || isWatch)
+
+ grantAppPermissionsByUi(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, true)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, true)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION, true)
+
+ revokeAppPermissionsByUi(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+ SystemUtil.runWithShellPermissionIdentity {
+ val perms = listOf(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ for (perm in perms) {
+ var flags = packageManager.getPermissionFlags(perm, APP_PACKAGE_NAME, context.user)
+ Assert.assertEquals("USER_SET should not be set for $perm", 0,
+ flags and PackageManager.FLAG_PERMISSION_USER_SET)
+ Assert.assertEquals("USER_FIXED should not be set for $perm", 0,
+ flags and PackageManager.FLAG_PERMISSION_USER_FIXED)
+ Assert.assertEquals("ONE_TIME should be set for $perm",
+ PackageManager.FLAG_PERMISSION_ONE_TIME,
+ flags and PackageManager.FLAG_PERMISSION_ONE_TIME)
+ }
+ }
+ }
+
+ private fun denyPermissionRequestWithPrejudice() {
+ if (isTv || isWatch) {
+ clickPermissionRequestDontAskAgainButton()
+ } else {
+ clickPermissionRequestDenyAndDontAskAgainButton()
+ }
+ }
+
+ private fun assertAppHasAllOrNoPermissions(expectPermissions: Boolean) {
+ arrayOf(
+ android.Manifest.permission.SEND_SMS,
+ android.Manifest.permission.RECEIVE_SMS,
+ android.Manifest.permission.RECEIVE_WAP_PUSH,
+ android.Manifest.permission.RECEIVE_MMS,
+ android.Manifest.permission.READ_CALENDAR,
+ android.Manifest.permission.WRITE_CALENDAR,
+ android.Manifest.permission.WRITE_CONTACTS,
+ android.Manifest.permission.READ_SMS,
+ android.Manifest.permission.READ_PHONE_STATE,
+ android.Manifest.permission.READ_CALL_LOG,
+ android.Manifest.permission.WRITE_CALL_LOG,
+ android.Manifest.permission.ADD_VOICEMAIL,
+ android.Manifest.permission.CALL_PHONE,
+ android.Manifest.permission.USE_SIP,
+ android.Manifest.permission.PROCESS_OUTGOING_CALLS,
+ android.Manifest.permission.RECORD_AUDIO,
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ android.Manifest.permission.CAMERA,
+ android.Manifest.permission.BODY_SENSORS,
+ android.Manifest.permission.READ_CELL_BROADCASTS,
+ // Split permissions
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+ // Storage permissions
+ android.Manifest.permission.READ_EXTERNAL_STORAGE,
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+ ).forEach {
+ assertAppHasPermission(it, expectPermissions)
+ }
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest29.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest29.kt
new file mode 100644
index 000000000..5e1b0d28f
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest29.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2018 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.permissionui.cts
+
+import android.permission.cts.MtsIgnore
+import android.platform.test.annotations.FlakyTest
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil.eventually
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Runtime permission behavior tests for apps targeting API 29.
+ */
+@FlakyTest
+class PermissionTest29 : BaseUsePermissionTest() {
+ @Before
+ fun assumeNotTv() {
+ assumeFalse(isTv)
+ }
+
+ @Before
+ fun installApp29() {
+ installPackage(APP_APK_PATH_29)
+ }
+
+ @Before
+ fun assertAppHasNoPermissions() {
+ assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, false)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
+ }
+
+ @Test
+ fun testRequestOnlyBackgroundNotPossible() {
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION to false
+ ) {}
+
+ assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
+ }
+
+ @Test
+ fun testRequestBoth() {
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.ACCESS_FINE_LOCATION to true,
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION to true
+ ) {
+ clickPermissionRequestSettingsLinkAndAllowAlways()
+ }
+ }
+
+ @Test
+ fun testRequestBothInSequence() {
+ // Step 1: request foreground only
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.ACCESS_FINE_LOCATION to true
+ ) {
+ clickPermissionRequestAllowForegroundButton()
+ }
+
+ assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
+
+ // Step 2: request background only
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION to true
+ ) {
+ clickPermissionRequestSettingsLinkAndAllowAlways()
+ }
+
+ assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, true)
+ }
+
+ @Test
+ fun testRequestBothButGrantInSequence() {
+ // Step 1: grant foreground only
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.ACCESS_FINE_LOCATION to true,
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION to false
+ ) {
+ clickPermissionRequestAllowForegroundButton()
+ }
+
+ // Step 2: grant background
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.ACCESS_FINE_LOCATION to true,
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION to true
+ ) {
+ clickPermissionRequestSettingsLinkAndAllowAlways()
+ }
+ }
+
+ @FlakyTest
+ @MtsIgnore
+ @Test
+ fun testDenyBackgroundWithPrejudice() {
+ // Step 1: deny the first time
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.ACCESS_FINE_LOCATION to false,
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION to false
+ ) {
+ clickPermissionRequestDenyButton()
+ }
+
+ // Step 2: deny with prejudice
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.ACCESS_FINE_LOCATION to false,
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION to false
+ ) {
+ clickPermissionRequestDenyAndDontAskAgainButton()
+ }
+
+ // Step 3: All further requests should be denied automatically
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.ACCESS_FINE_LOCATION to false,
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION to false
+ ) {}
+ }
+
+ @FlakyTest
+ @MtsIgnore
+ @Test
+ fun testGrantDialogToSettingsNoOp() {
+ // Step 1: Request both, go to settings, do nothing
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.ACCESS_FINE_LOCATION to true,
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION to false
+ ) {
+ openSettingsThenDoNothingThenLeave()
+
+ assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, false)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
+
+ clickPermissionRequestAllowForegroundButton()
+ }
+
+ // Step 2: Upgrade foreground to background, go to settings, do nothing
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.ACCESS_FINE_LOCATION to true,
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION to false
+ ) {
+ openSettingsThenDoNothingThenLeave()
+
+ assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, true)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
+
+ clickPermissionRequestNoUpgradeAndDontAskAgainButton()
+ }
+ }
+
+ private fun openSettingsThenDoNothingThenLeave() {
+ clickPermissionRequestSettingsLink()
+ eventually {
+ pressBack()
+ if (isAutomotive) {
+ waitFindObject(By.textContains("Allow in settings."), 100)
+ } else {
+ waitFindObject(By.res("com.android.permissioncontroller:id/grant_dialog"), 100)
+ }
+ }
+ }
+
+ @FlakyTest
+ @Test
+ fun testGrantDialogToSettingsDowngrade() {
+ // Request upgrade, downgrade permission to denied in settings
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.ACCESS_FINE_LOCATION to true,
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION to false
+ ) {
+ clickPermissionRequestAllowForegroundButton()
+ }
+
+ requestAppPermissions(
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
+ ) {
+ clickPermissionRequestSettingsLinkAndDeny()
+ waitForIdle()
+ pressBack()
+ }
+
+ assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, false)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest30.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest30.kt
new file mode 100644
index 000000000..bcf01265c
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest30.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 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.permissionui.cts
+
+import android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
+import android.Manifest.permission.ACCESS_COARSE_LOCATION
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.platform.test.annotations.FlakyTest
+import androidx.test.uiautomator.By
+import org.junit.Assert.assertNull
+import org.junit.Test
+
+/**
+ * Runtime permission behavior apps targeting API 30
+ */
+@FlakyTest
+class PermissionTest30 : BaseUsePermissionTest() {
+
+ @Test
+ fun testCantRequestFgAndBgAtOnce() {
+ // TODO(b/280542662): This delay is a temporary mitigation for an intermittent failure
+ Thread.sleep(500)
+ installPackage(APP_APK_PATH_30_WITH_BACKGROUND)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ assertAppHasPermission(ACCESS_BACKGROUND_LOCATION, false)
+
+ requestAppPermissionsAndAssertResult(ACCESS_FINE_LOCATION to false,
+ ACCESS_BACKGROUND_LOCATION to false) {
+ // Do nothing, should be automatically denied
+ }
+ }
+
+ @Test
+ fun testRequestBothInSequence() {
+ installPackage(APP_APK_PATH_30_WITH_BACKGROUND)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ assertAppHasPermission(ACCESS_BACKGROUND_LOCATION, false)
+
+ requestAppPermissionsAndAssertResult(ACCESS_FINE_LOCATION to true) {
+ clickPermissionRequestAllowForegroundButton()
+ }
+
+ requestAppPermissionsAndAssertResult(ACCESS_BACKGROUND_LOCATION to true) {
+ clickAllowAlwaysInSettings()
+ waitForIdle()
+ pressBack()
+ }
+ }
+
+ @Test
+ fun testRequestFgLocationAndNoAccuracyOptions() {
+ installPackage(APP_APK_PATH_30)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+
+ requestAppPermissionsAndAssertResult(ACCESS_FINE_LOCATION to false,
+ ACCESS_COARSE_LOCATION to false) {
+ // Verify there's no location accuracy options
+ val locationAccuracyOptions = waitFindObjectOrNull(By.res(
+ "com.android.permissioncontroller:id/permission_location_accuracy"), 1000L)
+ assertNull("For apps targetSDK < 31, location permission dialog shouldn't show " +
+ "accuracy options. Please update the system with " +
+ "the latest (at least Oct, 2021) mainline modules.",
+ locationAccuracyOptions)
+ return
+ }
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest30WithBluetooth.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest30WithBluetooth.kt
new file mode 100644
index 000000000..898ac6883
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest30WithBluetooth.kt
@@ -0,0 +1,200 @@
+/*
+ * 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 android.permissionui.cts
+
+import android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.Manifest.permission.BLUETOOTH_SCAN
+import android.app.AppOpsManager
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothManager
+import android.bluetooth.cts.BTAdapterUtils
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
+import android.location.LocationManager
+import android.os.Build
+import android.os.Process
+import android.os.UserHandle
+import android.platform.test.annotations.FlakyTest
+import android.util.Log
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.AssertionFailedError
+import org.junit.After
+import org.junit.Assert.assertNotEquals
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+
+private const val LOG_TAG = "PermissionTest30WithBluetooth"
+
+/**
+ * Runtime Bluetooth-permission behavior of apps targeting API 30
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+@FlakyTest
+class PermissionTest30WithBluetooth : BaseUsePermissionTest() {
+
+ private val TEST_APP_AUTHORITY =
+ "android.permissionui.cts.usepermission.AccessBluetoothOnCommand"
+ private val TEST_APP_PKG =
+ "android.permissionui.cts.usepermission"
+ private lateinit var bluetoothAdapter: BluetoothAdapter
+ private var bluetoothAdapterWasEnabled: Boolean = false
+ private val locationManager = context.getSystemService(LocationManager::class.java)!!
+ private var locationWasEnabled: Boolean = false
+
+ private enum class BluetoothScanResult {
+ UNKNOWN, ERROR, EXCEPTION, EMPTY, FILTERED, FULL
+ }
+
+ @Before
+ fun installApp() {
+ installPackage(APP_APK_PATH_30_WITH_BLUETOOTH)
+ }
+
+ private fun reinstallApp() {
+ installPackage(APP_APK_PATH_30_WITH_BLUETOOTH, reinstall = true)
+ }
+
+ @Before
+ fun enableBluetooth() {
+ assumeTrue(supportsBluetooth())
+ bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter
+ bluetoothAdapterWasEnabled = bluetoothAdapter.isEnabled()
+ runWithShellPermissionIdentity {
+ assertTrue(BTAdapterUtils.enableAdapter(bluetoothAdapter, context))
+ }
+ enableTestMode()
+ }
+
+ @Before
+ fun enableLocation() {
+ val userHandle: UserHandle = Process.myUserHandle()
+ locationWasEnabled = locationManager.isLocationEnabledForUser(userHandle)
+ if (!locationWasEnabled) {
+ runWithShellPermissionIdentity {
+ locationManager.setLocationEnabledForUser(true, userHandle)
+ }
+ }
+ }
+
+ @After
+ fun disableLocation() {
+ val userHandle: UserHandle = Process.myUserHandle()
+
+ if (!locationWasEnabled) {
+ runWithShellPermissionIdentity {
+ locationManager.setLocationEnabledForUser(false, userHandle)
+ }
+ }
+ }
+
+ @After
+ fun disableBluetooth() {
+ assumeTrue(supportsBluetooth())
+ disableTestMode()
+ if (!bluetoothAdapterWasEnabled) {
+ runWithShellPermissionIdentity {
+ assertTrue(BTAdapterUtils.disableAdapter(bluetoothAdapter, context))
+ }
+ }
+ }
+
+ // TODO:(b/220030722) Remove verbose logging (after test is stabilized)
+ @Test
+ fun testGivenBluetoothIsDeniedWhenScanIsAttemptedThenThenGetEmptyScanResult() {
+ assumeTrue(supportsBluetoothLe())
+
+ assertTrue("Please enable location to run this test. Bluetooth scanning " +
+ "requires location to be enabled.", locationManager.isLocationEnabled())
+ assertBluetoothRevokedCompatState(revoked = false)
+
+ Log.v(LOG_TAG, "Testing for: Given {BLUETOOTH_SCAN, !BLUETOOTH_SCAN.COMPAT_REVOKE, " +
+ "!ACCESS_*_LOCATION}, expect EMPTY")
+ assertEquals(BluetoothScanResult.EMPTY, scanForBluetoothDevices())
+
+ Log.v(LOG_TAG, "Testing for: Given {BLUETOOTH_SCAN, !BLUETOOTH_SCAN.COMPAT_REVOKE, " +
+ "ACCESS_*_LOCATION}, expect FULL")
+ uiAutomation.grantRuntimePermission(TEST_APP_PKG, ACCESS_FINE_LOCATION)
+ uiAutomation.grantRuntimePermission(TEST_APP_PKG, ACCESS_BACKGROUND_LOCATION)
+ setAppOp(context.packageName, AppOpsManager.OPSTR_FINE_LOCATION, AppOpsManager.MODE_ALLOWED)
+ assertEquals(BluetoothScanResult.FULL, scanForBluetoothDevices())
+
+ Log.v(LOG_TAG, "Testing for: Given {BLUETOOTH_SCAN, BLUETOOTH_SCAN.COMPAT_REVOKE, " +
+ "ACCESS_*_LOCATION}, expect ERROR")
+ revokeAppPermissionsByUi(BLUETOOTH_SCAN, isLegacyApp = true)
+ assertBluetoothRevokedCompatState(revoked = true)
+ val res = scanForBluetoothDevices()
+ if (res != BluetoothScanResult.ERROR && res != BluetoothScanResult.EMPTY) {
+ throw AssertionFailedError("Expected to be EMPTY or ERROR, but was $res")
+ }
+ }
+
+ private fun setAppOp(packageName: String, appOp: String, appOpMode: Int) {
+ runWithShellPermissionIdentity {
+ context.getSystemService(AppOpsManager::class.java)!!.setUidMode(
+ appOp, packageManager.getPackageUid(packageName, 0), appOpMode)
+ }
+ }
+
+ @Test
+ fun testRevokedCompatPersistsOnReinstall() {
+ assertBluetoothRevokedCompatState(revoked = false)
+ revokeAppPermissionsByUi(BLUETOOTH_SCAN, isLegacyApp = true)
+ assertBluetoothRevokedCompatState(revoked = true)
+ reinstallApp()
+ assertBluetoothRevokedCompatState(revoked = true)
+ installApp()
+ assertBluetoothRevokedCompatState(revoked = true)
+ }
+
+ private fun assertBluetoothRevokedCompatState(revoked: Boolean = true) {
+ runWithShellPermissionIdentity {
+ val flag = context.packageManager.getPermissionFlags(BLUETOOTH_SCAN,
+ TEST_APP_PKG, Process.myUserHandle()) and FLAG_PERMISSION_REVOKED_COMPAT
+ if (revoked) {
+ assertNotEquals(0, flag)
+ } else {
+ assertEquals(0, flag)
+ }
+ }
+ }
+
+ private fun scanForBluetoothDevices(): BluetoothScanResult {
+ val resolver = InstrumentationRegistry.getTargetContext().getContentResolver()
+ val result = resolver.call(TEST_APP_AUTHORITY, "", null, null)
+ return BluetoothScanResult.values()[result!!.getInt(Intent.EXTRA_INDEX)]
+ }
+
+ private fun supportsBluetooth(): Boolean =
+ context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)
+
+ private fun supportsBluetoothLe(): Boolean =
+ context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
+
+ private fun enableTestMode() = runShellCommandOrThrow("dumpsys activity service" +
+ " com.android.bluetooth.btservice.AdapterService set-test-mode enabled")
+
+ private fun disableTestMode() = runShellCommandOrThrow("dumpsys activity service" +
+ " com.android.bluetooth.btservice.AdapterService set-test-mode disabled")
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionUpgradeTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionUpgradeTest.kt
new file mode 100644
index 000000000..6f1ae8a7c
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionUpgradeTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2015 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.permissionui.cts
+
+import android.platform.test.annotations.FlakyTest
+import org.junit.Assume
+import org.junit.Test
+
+/**
+ * Runtime permission behavior tests for upgrading apps.
+ */
+@FlakyTest
+class PermissionUpgradeTest : BaseUsePermissionTest() {
+
+ @Test
+ fun testUpgradeKeepsPermissions() {
+ Assume.assumeFalse(packageManager.arePermissionsIndividuallyControlled())
+
+ installPackage(APP_APK_PATH_22)
+
+ approvePermissionReview()
+
+ assertAllPermissionsGrantedByDefault()
+
+ installPackage(APP_APK_PATH_23, reinstall = true, skipClearLowSdkDialog = true)
+
+ assertAllPermissionsGrantedOnUpgrade()
+ }
+
+ private fun assertAllPermissionsGrantedByDefault() {
+ arrayOf(
+ android.Manifest.permission.SEND_SMS,
+ android.Manifest.permission.RECEIVE_SMS,
+ // The APK does not request READ_CONTACTS because of other tests
+ android.Manifest.permission.WRITE_CONTACTS,
+ android.Manifest.permission.READ_CALENDAR,
+ android.Manifest.permission.WRITE_CALENDAR,
+ android.Manifest.permission.READ_SMS,
+ android.Manifest.permission.RECEIVE_WAP_PUSH,
+ android.Manifest.permission.RECEIVE_MMS,
+ "android.permission.READ_CELL_BROADCASTS",
+ android.Manifest.permission.READ_EXTERNAL_STORAGE,
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ android.Manifest.permission.READ_PHONE_STATE,
+ android.Manifest.permission.CALL_PHONE,
+ android.Manifest.permission.READ_CALL_LOG,
+ android.Manifest.permission.WRITE_CALL_LOG,
+ android.Manifest.permission.ADD_VOICEMAIL,
+ android.Manifest.permission.USE_SIP,
+ android.Manifest.permission.PROCESS_OUTGOING_CALLS,
+ android.Manifest.permission.CAMERA,
+ android.Manifest.permission.BODY_SENSORS,
+ // Split permissions
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
+ ).forEach {
+ assertAppHasPermission(it, true)
+ }
+ }
+
+ private fun assertAllPermissionsGrantedOnUpgrade() {
+ assertAppHasAllOrNoPermissions(true)
+ }
+
+ @Test
+ fun testNoDowngradePermissionModel() {
+ installPackage(APP_APK_PATH_23, skipClearLowSdkDialog = true)
+ installPackage(APP_APK_PATH_22, reinstall = true, expectSuccess = false)
+ }
+
+ @Test
+ fun testRevokePropagatedOnUpgradeOldToNewModel() {
+ Assume.assumeFalse(packageManager.arePermissionsIndividuallyControlled())
+
+ installPackage(APP_APK_PATH_22)
+
+ approvePermissionReview()
+
+ // Revoke a permission
+ revokeAppPermissionsByUi(android.Manifest.permission.WRITE_CALENDAR, isLegacyApp = true)
+
+ installPackage(APP_APK_PATH_23, reinstall = true, skipClearLowSdkDialog = true)
+
+ assertAppHasPermission(android.Manifest.permission.WRITE_CALENDAR, false)
+ }
+
+ @Test
+ fun testRevokePropagatedOnUpgradeNewToNewModel() {
+ installPackage(APP_APK_PATH_23)
+
+ // Make sure we don't have the permission
+ assertAppHasPermission(android.Manifest.permission.READ_CALENDAR, false)
+ assertAppHasPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE, false)
+
+ // Request the permission and allow it
+ // Make sure the permission is granted
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.READ_CALENDAR to true,
+ ) {
+ clickPermissionRequestAllowButton()
+ }
+
+ installPackage(APP_APK_PATH_23, reinstall = true, skipClearLowSdkDialog = true)
+
+ // Make sure the permission is still granted after the upgrade
+ assertAppHasPermission(android.Manifest.permission.READ_CALENDAR, true)
+ // Also make sure one of the not granted permissions is still not granted
+ assertAppHasPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE, false)
+ }
+
+ private fun assertAppHasAllOrNoPermissions(expectPermissions: Boolean) {
+ arrayOf(
+ android.Manifest.permission.SEND_SMS,
+ android.Manifest.permission.RECEIVE_SMS,
+ android.Manifest.permission.RECEIVE_WAP_PUSH,
+ android.Manifest.permission.RECEIVE_MMS,
+ android.Manifest.permission.READ_CALENDAR,
+ android.Manifest.permission.WRITE_CALENDAR,
+ android.Manifest.permission.WRITE_CONTACTS,
+ android.Manifest.permission.READ_SMS,
+ android.Manifest.permission.READ_PHONE_STATE,
+ android.Manifest.permission.READ_CALL_LOG,
+ android.Manifest.permission.WRITE_CALL_LOG,
+ android.Manifest.permission.ADD_VOICEMAIL,
+ android.Manifest.permission.CALL_PHONE,
+ android.Manifest.permission.USE_SIP,
+ android.Manifest.permission.PROCESS_OUTGOING_CALLS,
+ android.Manifest.permission.RECORD_AUDIO,
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ android.Manifest.permission.CAMERA,
+ android.Manifest.permission.BODY_SENSORS,
+ android.Manifest.permission.READ_CELL_BROADCASTS,
+ // Split permissions
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+ // Storage permissions
+ android.Manifest.permission.READ_EXTERNAL_STORAGE,
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+ ).forEach {
+ assertAppHasPermission(it, expectPermissions)
+ }
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionUsageInfoTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionUsageInfoTest.kt
new file mode 100644
index 000000000..7f61014cb
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionUsageInfoTest.kt
@@ -0,0 +1,56 @@
+/*
+ * 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 android.permissionui.cts
+
+import android.content.Intent
+import android.platform.test.annotations.FlakyTest
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Tests permission usage info action.
+ */
+@FlakyTest
+class PermissionUsageInfoTest : BaseUsePermissionTest() {
+ @Before
+ fun assumeHandheld() {
+ assumeFalse(isAutomotive)
+ assumeFalse(isTv)
+ assumeFalse(isWatch)
+ }
+
+ @Before
+ fun installApp() {
+ installPackage(APP_APK_PATH_LATEST)
+ }
+
+ @Test
+ fun testPermissionUsageInfo() {
+ runWithShellPermissionIdentity {
+ context.startActivity(
+ Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS).apply {
+ putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ )
+ }
+ click(By.res("com.android.permissioncontroller:id/icon"))
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerPermissionTest.kt
new file mode 100644
index 000000000..df422acfb
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerPermissionTest.kt
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.Manifest.permission.ACCESS_MEDIA_LOCATION
+import android.Manifest.permission.READ_MEDIA_IMAGES
+import android.Manifest.permission.READ_MEDIA_VIDEO
+import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME
+import android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
+import android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED
+import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET
+import android.net.Uri
+import android.os.Build
+import android.platform.test.annotations.FlakyTest
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_PRIVACY
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import org.junit.AfterClass
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+@FlakyTest
+class PhotoPickerPermissionTest : BaseUsePermissionTest() {
+
+ companion object {
+ private var photoUri: Uri? = null
+ private var videoUri: Uri? = null
+ private var oldEnableState: Boolean = true
+
+ @BeforeClass
+ @JvmStatic
+ fun enablePickerAndAddMedia() {
+ // Initialize media provider package name
+ PhotoPickerUtils.getMediaProviderPkgName(context)
+ oldEnableState = isPhotoPickerPermissionPromptEnabled()
+ runWithShellPermissionIdentity {
+ if (!oldEnableState) {
+ DeviceConfig.setProperty(
+ NAMESPACE_PRIVACY, PICKER_ENABLED_SETTING, true.toString(), false)
+ }
+ photoUri = PhotoPickerUtils.createImage(context)
+ videoUri = PhotoPickerUtils.createVideo(context)
+ }
+ }
+
+ @AfterClass
+ @JvmStatic
+ fun resetPickerAndRemoveMedia() {
+ if (!oldEnableState) {
+ runWithShellPermissionIdentity {
+ DeviceConfig.setProperty(NAMESPACE_PRIVACY, PICKER_ENABLED_SETTING,
+ false.toString(), false)
+ }
+ }
+
+ PhotoPickerUtils.deleteMedia(context, photoUri)
+ PhotoPickerUtils.deleteMedia(context, videoUri)
+ }
+ }
+
+ @Before
+ fun assumeEnabled() {
+ assumeTrue(isPhotoPickerPermissionPromptEnabled())
+ }
+
+ @Test
+ fun testAppWithoutStoragePermsDoesntHaveUserSelectedAdded() {
+ installPackage(APP_APK_PATH_LATEST_NONE)
+ runWithShellPermissionIdentity {
+ val packageInfo =
+ packageManager.getPackageInfo(APP_PACKAGE_NAME, PackageManager.GET_PERMISSIONS)
+ assertNotNull(packageInfo)
+ val permissions = packageInfo.requestedPermissions?.toList() ?: emptyList<String>()
+ assertFalse(
+ "Expected app to not request READ_MEDIA_VISUAL_USER_SELECTED",
+ permissions.contains(READ_MEDIA_VISUAL_USER_SELECTED))
+ }
+ }
+
+ @Test
+ fun testAppWithStoragePermsHasUserSelectedAdded() {
+ installPackage(APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE)
+ runWithShellPermissionIdentity {
+ val packageInfo =
+ packageManager.getPackageInfo(APP_PACKAGE_NAME, PackageManager.GET_PERMISSIONS)
+ assertNotNull(packageInfo)
+ val permissions = packageInfo.requestedPermissions?.toList() ?: emptyList<String>()
+ assertTrue(
+ "Expected app to request READ_MEDIA_VISUAL_USER_SELECTED",
+ permissions.contains(READ_MEDIA_VISUAL_USER_SELECTED))
+ }
+ }
+
+ @Test
+ fun testAppWithUserSelectedPermShowsSelectOption() {
+ installPackage(APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE)
+ requestAppPermissions(READ_MEDIA_IMAGES) {
+ assertNotNull(waitFindObjectOrNull(By.res(SELECT_BUTTON)))
+ click(By.res(DENY_BUTTON))
+ }
+ }
+
+ @Test
+ fun testNoPhotoSelectionTreatedAsCancel() {
+ installPackage(APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE)
+ requestAppPermissionsAndAssertResult(
+ arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VISUAL_USER_SELECTED),
+ arrayOf(READ_MEDIA_IMAGES to false, READ_MEDIA_VISUAL_USER_SELECTED to false)) {
+ click(By.res(SELECT_BUTTON))
+ findImageOrVideo(expected = true)
+ uiDevice.pressBack()
+ }
+ assertPermissionFlags(READ_MEDIA_IMAGES, FLAG_PERMISSION_USER_SET to false)
+ assertPermissionFlags(READ_MEDIA_VISUAL_USER_SELECTED, FLAG_PERMISSION_USER_SET to false)
+ }
+
+ @Test
+ fun testImplicitUserSelectHasOneTimeGrantsWithoutAppOp() {
+ installPackage(APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE)
+ requestAppPermissionsAndAssertResult(arrayOf(READ_MEDIA_IMAGES),
+ arrayOf(READ_MEDIA_IMAGES to true)) {
+ click(By.res(SELECT_BUTTON))
+ clickImageOrVideo()
+ clickAllow()
+ }
+ eventually {
+ // USER_SELECTED should be granted, but not returned in the result
+ assertAppHasPermission(READ_MEDIA_VISUAL_USER_SELECTED, expectPermission = true)
+ assertAppHasPermission(READ_MEDIA_VIDEO, expectPermission = true)
+ assertPermissionFlags(
+ READ_MEDIA_IMAGES,
+ FLAG_PERMISSION_ONE_TIME to true,
+ FLAG_PERMISSION_REVOKED_COMPAT to true)
+ assertPermissionFlags(
+ READ_MEDIA_VIDEO,
+ FLAG_PERMISSION_ONE_TIME to true,
+ FLAG_PERMISSION_REVOKED_COMPAT to true)
+ assertPermissionFlags(
+ READ_MEDIA_VISUAL_USER_SELECTED,
+ FLAG_PERMISSION_ONE_TIME to false,
+ FLAG_PERMISSION_REVOKED_COMPAT to false)
+ }
+ }
+
+ @Test
+ fun testImplicitShowsMorePhotosOnceSet() {
+ installPackage(APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE)
+ eventually {
+ uiAutomation.grantRuntimePermission(APP_PACKAGE_NAME, READ_MEDIA_VISUAL_USER_SELECTED)
+ assertAppHasPermission(READ_MEDIA_VISUAL_USER_SELECTED, true)
+ }
+
+ requestAppPermissions(READ_MEDIA_IMAGES) {
+ waitFindObject(By.res(DONT_SELECT_MORE_BUTTON))
+ uiDevice.pressBack()
+ }
+ }
+
+ @Test
+ fun testNonImplicitDoesntGrantOtherPermsWhenUserSelected() {
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissionsAndAssertResult(
+ arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VISUAL_USER_SELECTED),
+ arrayOf(READ_MEDIA_IMAGES to false, READ_MEDIA_VISUAL_USER_SELECTED to true)) {
+ click(By.res(SELECT_BUTTON))
+ clickImageOrVideo()
+ clickAllow()
+ }
+
+ assertPermissionFlags(READ_MEDIA_IMAGES, FLAG_PERMISSION_USER_SET to true)
+ assertPermissionFlags(READ_MEDIA_VISUAL_USER_SELECTED, FLAG_PERMISSION_USER_SET to true)
+ }
+
+ @Test
+ fun testNonImplicitAutomaticallyShowsPickerWhenUserFixed() {
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissions(READ_MEDIA_IMAGES) {
+ click(By.res(SELECT_BUTTON))
+ clickImageOrVideo()
+ clickAllow()
+ }
+
+ requestAppPermissions(READ_MEDIA_IMAGES) {
+ click(By.res(SELECT_BUTTON))
+ clickImageOrVideo()
+ clickAllow()
+ }
+
+ assertPermissionFlags(READ_MEDIA_VISUAL_USER_SELECTED, FLAG_PERMISSION_USER_FIXED to true)
+
+ requestAppPermissions(READ_MEDIA_IMAGES) {
+ findImageOrVideo(expected = true)
+ uiDevice.pressBack()
+ }
+ }
+
+ @Test
+ fun testRequestedPermsFilterMediaType() {
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissions(READ_MEDIA_IMAGES) {
+ click(By.res(SELECT_BUTTON))
+ findImageOrVideo(expected = true)
+ findVideo(expected = false)
+ uiDevice.pressBack()
+ }
+
+ requestAppPermissions(READ_MEDIA_VIDEO) {
+ click(By.res(SELECT_BUTTON))
+ findVideo(expected = true)
+ uiDevice.pressBack()
+ }
+ }
+
+ @Test
+ fun testGrantAllPhotosStateSameForImplicitAndNot() {
+ installPackage(APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE)
+ requestAppPermissionsAndAssertResult(arrayOf(READ_MEDIA_IMAGES),
+ arrayOf(READ_MEDIA_IMAGES to true)) {
+ click(By.res(ALLOW_ALL_BUTTON))
+ }
+
+ eventually {
+ assertAppHasPermission(READ_MEDIA_VISUAL_USER_SELECTED, expectPermission = true)
+ }
+
+ uninstallPackage(APP_PACKAGE_NAME)
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissionsAndAssertResult(
+ arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VISUAL_USER_SELECTED),
+ arrayOf(READ_MEDIA_IMAGES to true, READ_MEDIA_VISUAL_USER_SELECTED to true)) {
+ click(By.res(ALLOW_ALL_BUTTON))
+ }
+ }
+
+ @Test
+ fun testGrantAllPhotosInSettings() {
+ installPackage(APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE)
+ navigateToIndividualPermissionSetting(READ_MEDIA_IMAGES)
+ click(By.res(ALLOW_RADIO_BUTTON))
+
+ eventually {
+ assertAppHasPermission(READ_MEDIA_IMAGES, expectPermission = true)
+ assertAppHasPermission(READ_MEDIA_VIDEO, expectPermission = true)
+ assertAppHasPermission(ACCESS_MEDIA_LOCATION, expectPermission = true)
+ assertAppHasPermission(READ_MEDIA_VISUAL_USER_SELECTED, expectPermission = true)
+ }
+ }
+
+ @Test
+ fun testSelectPhotosInSettingsImplicit() {
+ installPackage(APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE)
+ navigateToIndividualPermissionSetting(READ_MEDIA_IMAGES)
+ click(By.res(SELECT_RADIO_BUTTON))
+
+ eventually {
+ assertAppHasPermission(READ_MEDIA_IMAGES, expectPermission = false)
+ assertAppHasPermission(READ_MEDIA_VIDEO, expectPermission = false)
+ assertAppHasPermission(ACCESS_MEDIA_LOCATION, expectPermission = false)
+ assertAppHasPermission(READ_MEDIA_VISUAL_USER_SELECTED, expectPermission = true)
+ }
+ }
+
+ @Test
+ fun testSelectPhotosInSettingsExplicit() {
+ installPackage(APP_APK_PATH_LATEST)
+ navigateToIndividualPermissionSetting(READ_MEDIA_IMAGES)
+ click(By.res(SELECT_RADIO_BUTTON))
+
+ eventually {
+ assertAppHasPermission(READ_MEDIA_IMAGES, expectPermission = false)
+ assertAppHasPermission(READ_MEDIA_VIDEO, expectPermission = false)
+ assertAppHasPermission(ACCESS_MEDIA_LOCATION, expectPermission = true)
+ assertAppHasPermission(READ_MEDIA_VISUAL_USER_SELECTED, expectPermission = true)
+ }
+ }
+
+ @Test
+ @Throws(PackageManager.NameNotFoundException::class)
+ fun testPre33AppDoesntShowSelect() {
+ installPackage(APP_APK_PATH_30)
+ runWithShellPermissionIdentity {
+ val requestedPerms = packageManager.getPackageInfo(APP_PACKAGE_NAME,
+ PackageManager.GET_PERMISSIONS).requestedPermissions!!.toList()
+ assertTrue("Expected package to have USER_SELECTED",
+ requestedPerms.contains(READ_MEDIA_VISUAL_USER_SELECTED))
+ }
+
+ requestAppPermissions(READ_MEDIA_IMAGES) {
+ findView(By.res(SELECT_BUTTON), expected = false)
+ pressBack()
+ }
+
+ navigateToIndividualPermissionSetting(READ_MEDIA_IMAGES)
+ findView(By.res(SELECT_RADIO_BUTTON), expected = false)
+ }
+
+ @Test
+ fun testAppCantRequestOnlyPartialStoragePerms() {
+ installPackage(APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE)
+ requestAppPermissionsAndAssertResult(READ_MEDIA_VISUAL_USER_SELECTED to false) {}
+ uninstallPackage(APP_PACKAGE_NAME)
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissionsAndAssertResult(READ_MEDIA_VISUAL_USER_SELECTED to false,
+ ACCESS_MEDIA_LOCATION to false) {}
+ }
+
+ @Test
+ fun testImplicitAppCanExpandAccessMediaLocation() {
+ installPackage(APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE)
+ requestAppPermissions(ACCESS_MEDIA_LOCATION) {
+ click(By.res(ALLOW_ALL_BUTTON))
+ }
+ requestAppPermissionsAndAssertResult(READ_MEDIA_IMAGES to true,
+ READ_MEDIA_VIDEO to true) {}
+ }
+
+ @Test
+ fun testExplicitAppCannotExpandAccessMediaLocation() {
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissionsAndAssertResult(READ_MEDIA_IMAGES to false,
+ ACCESS_MEDIA_LOCATION to true, READ_MEDIA_VISUAL_USER_SELECTED to true) {
+ click(By.res(SELECT_BUTTON))
+ clickImageOrVideo()
+ clickAllow()
+ }
+ requestAppPermissions(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO) {
+ click(By.res(ALLOW_ALL_BUTTON))
+ }
+ }
+
+ @Test
+ fun testExplicitAppCannotRequestOnlyPartialAccess() {
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissionsAndAssertResult(ACCESS_MEDIA_LOCATION to false,
+ READ_MEDIA_VISUAL_USER_SELECTED to false) {
+ findView(By.res(SELECT_BUTTON), expected = false)
+ }
+ }
+
+ @Test
+ fun testMorePhotosDialogShowsAfterClickingSelect() {
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissionsAndAssertResult(READ_MEDIA_IMAGES to false,
+ ACCESS_MEDIA_LOCATION to true, READ_MEDIA_VISUAL_USER_SELECTED to true) {
+ click(By.res(SELECT_BUTTON))
+ clickImageOrVideo()
+ clickAllow()
+ }
+
+ requestAppPermissions(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO) {
+ findView(By.res(DONT_SELECT_MORE_BUTTON), expected = true)
+ click(By.res(ALLOW_ALL_BUTTON))
+ }
+ }
+
+ @Test
+ fun testAMLNotGrantedIfNotRequested() {
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissionsAndAssertResult(READ_MEDIA_IMAGES to false,
+ READ_MEDIA_VISUAL_USER_SELECTED to true) {
+ click(By.res(SELECT_BUTTON))
+ clickImageOrVideo()
+ clickAllow()
+ }
+ assertAppHasPermission(ACCESS_MEDIA_LOCATION, false)
+ }
+
+ private fun clickImageOrVideo() {
+ click(By.res(PhotoPickerUtils.getImageOrVideoResId(context)))
+ }
+
+ private fun clickAllow() {
+ click(By.res(PhotoPickerUtils.getAllowId(context)))
+ waitForIdle()
+ }
+
+ private fun findImageOrVideo(expected: Boolean) {
+ findView(By.res(PhotoPickerUtils.getImageOrVideoResId(context)), expected)
+ }
+
+ private fun findVideo(expected: Boolean) {
+ findView(By.res(PhotoPickerUtils.getVideoResId(context)), expected)
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerUtils.kt b/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerUtils.kt
new file mode 100644
index 000000000..0b9e54f86
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerUtils.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Bundle
+import android.os.FileUtils
+import android.provider.MediaStore
+import android.provider.cts.ProviderTestUtils
+import android.provider.cts.media.MediaStoreUtils
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import java.io.IOException
+
+object PhotoPickerUtils {
+ private const val DISPLAY_NAME_PREFIX = "ctsPermissionPhotoPicker"
+ private const val VIDEO_ICON_ID = ":id/icon_video"
+ private const val IMAGE_CHECK_BOX_ID = ":id/icon_check"
+ private const val ALLOW_ID = ":id/button_add"
+ private var mediaProviderPkgName: String? = null
+
+ fun getImageOrVideoResId(context: Context): String {
+ return "${getMediaProviderPkgName(context)!!}$IMAGE_CHECK_BOX_ID"
+ }
+
+ fun getVideoResId(context: Context): String {
+ return "${getMediaProviderPkgName(context)!!}$VIDEO_ICON_ID"
+ }
+
+ fun getAllowId(context: Context): String {
+ return "${getMediaProviderPkgName(context)!!}$ALLOW_ID"
+ }
+
+ fun getMediaProviderPkgName(context: Context): String? {
+ return mediaProviderPkgName ?: callWithShellPermissionIdentity {
+ val pkgs = context.packageManager.getInstalledPackages(PackageManager.GET_PROVIDERS)
+ for (pkg in pkgs) {
+ pkg.providers?.let { providerInfos ->
+ for (providerInfo in providerInfos) {
+ if (providerInfo.authority == "media") {
+ mediaProviderPkgName = pkg.packageName
+ return@callWithShellPermissionIdentity mediaProviderPkgName
+ }
+ }
+ }
+ }
+ null
+ }
+ }
+
+ @Throws(java.lang.Exception::class)
+ fun createImage(context: Context): Uri {
+ return getPermissionAndStageMedia( context, R.raw.lg_g4_iso_800_jpg,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/jpeg").first
+ }
+
+ @Throws(java.lang.Exception::class)
+ fun createVideo(context: Context): Uri {
+ return getPermissionAndStageMedia(context, R.raw.test_video,
+ MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4").first
+ }
+
+ @Throws(Exception::class)
+ fun deleteMedia(context: Context, uri: Uri?) {
+ if (uri == null) {
+ return
+ }
+ try {
+ ProviderTestUtils.setOwner(uri, context.packageName)
+ context.contentResolver.delete(uri, Bundle.EMPTY)
+ } catch (ignored: Exception) {}
+ }
+
+ @Throws(java.lang.Exception::class)
+ private fun getPermissionAndStageMedia(
+ context: Context,
+ resId: Int,
+ collectionUri: Uri,
+ mimeType: String,
+ ): Pair<Uri, String> {
+ return callWithShellPermissionIdentity {
+ stageMedia(context, resId, collectionUri, mimeType)
+ }
+ }
+ @Throws(IOException::class)
+ private fun stageMedia(
+ context: Context,
+ resId: Int,
+ collectionUri: Uri,
+ mimeType: String,
+ ): Pair<Uri, String> {
+ val displayName = DISPLAY_NAME_PREFIX + System.nanoTime()
+ val params = MediaStoreUtils.PendingParams(collectionUri, displayName, mimeType)
+ val pendingUri = MediaStoreUtils.createPending(context, params)
+ MediaStoreUtils.openPending(context, pendingUri).use { session ->
+ context.resources.openRawResource(resId).use { source ->
+ session.openOutputStream().use { target -> FileUtils.copy(source, target) }
+ }
+ return session.publish() to displayName
+ }
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/ReviewAccessibilityServicesTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/ReviewAccessibilityServicesTest.kt
new file mode 100644
index 000000000..86f294875
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/ReviewAccessibilityServicesTest.kt
@@ -0,0 +1,197 @@
+/*
+ * 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.permissionui.cts
+
+import android.accessibility.cts.common.InstrumentedAccessibilityService
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule
+import android.app.UiAutomation
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.FlakyTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Configurator
+import androidx.test.uiautomator.StaleObjectException
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import com.android.compatibility.common.util.SystemUtil
+import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObjectOrNull
+import java.util.regex.Pattern
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@AppModeFull(reason = "Instant apps cannot be a11y services")
+@FlakyTest
+class ReviewAccessibilityServicesTest {
+
+ private val context: Context = InstrumentationRegistry.getInstrumentation().context
+ private val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ private val testService1String = context.getString(R.string.test_accessibility_service)
+ private val testService2String = context.getString(R.string.test_accessibility_service_2)
+
+ companion object {
+ private const val EXPECTED_TIMEOUT_MS = 500L
+ }
+
+ @get:Rule
+ val accessibilityServiceRule =
+ InstrumentedAccessibilityServiceTestRule(AccessibilityTestService1::class.java, false)
+
+ @get:Rule
+ val accessibilityServiceRule2 =
+ InstrumentedAccessibilityServiceTestRule(AccessibilityTestService2::class.java, false)
+
+ init {
+ Configurator.getInstance().uiAutomationFlags =
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES
+ }
+
+ @Before
+ fun assumeNotAutoTvOrWear() {
+ Assume.assumeFalse(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
+ Assume.assumeFalse(
+ context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
+ Assume.assumeFalse(context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
+ }
+
+ @After
+ fun cleanUp() {
+ uiDevice.pressHome()
+ }
+
+ @Test
+ fun testActivityShowsSingleEnabledAccessibilityService() {
+ accessibilityServiceRule.enableService()
+ startAccessibilityActivity()
+ findTestService(true)
+ findTestService2(false)
+ }
+
+ @Test
+ fun testActivityShowsMultipleEnabledAccessibilityServices() {
+ accessibilityServiceRule.enableService()
+ accessibilityServiceRule2.enableService()
+ startAccessibilityActivity()
+ findTestService(true)
+ findTestService2(true)
+ }
+
+ @Test
+ fun testClickingSettingsGoesToIndividualSettingsWhenOneServiceEnabled() {
+ accessibilityServiceRule.enableService()
+ startAccessibilityActivity()
+ clickSettings()
+ waitForSettingsButtonToDisappear()
+ findTestService(true)
+ findTestService2(false)
+ }
+
+ @Test
+ @Ignore("b/293507233")
+ fun testClickingSettingsGoesToGeneralSettingsWhenMultipleServicesEnabled() {
+ accessibilityServiceRule.enableService()
+ accessibilityServiceRule2.enableService()
+ startAccessibilityActivity()
+ clickSettings()
+ waitForSettingsButtonToDisappear()
+ findTestService(true)
+ findTestService2(true)
+ }
+
+ @Test
+ fun testClickingIndividualGoesToIndividualSettingsWhenMultipleServicesEnabled() {
+ accessibilityServiceRule.enableService()
+ accessibilityServiceRule2.enableService()
+ startAccessibilityActivity()
+ uiDevice.waitForIdle()
+ findTestService2(true)!!.click()
+ waitForSettingsButtonToDisappear()
+ findTestService2(true)
+ findTestService(false)
+ }
+
+ private fun startAccessibilityActivity() {
+ val automan =
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES)
+ automan.adoptShellPermissionIdentity()
+ try {
+ context.startActivity(
+ Intent(Intent.ACTION_REVIEW_ACCESSIBILITY_SERVICES)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK))
+ } catch (e: Exception) {
+ throw RuntimeException("Caught exception", e)
+ } finally {
+ automan.dropShellPermissionIdentity()
+ }
+ }
+
+ private fun findTestService(shouldBePresent: Boolean): UiObject2? {
+ return findObjectByText(shouldBePresent, testService1String)
+ }
+
+ private fun findTestService2(shouldBePresent: Boolean): UiObject2? {
+ return findObjectByText(shouldBePresent, testService2String)
+ }
+
+ private fun clickSettings() {
+ findObjectByText(true, "Settings")?.click()
+ }
+
+ private fun waitForSettingsButtonToDisappear() {
+ SystemUtil.eventually {
+ findObjectByText(false, "Settings")
+ }
+ }
+
+ private fun findObjectByTextWithoutRetry(shouldBePresent: Boolean, text: String, ): UiObject2? {
+ val containsWithoutCaseSelector =
+ By.text(Pattern.compile(".*$text.*", Pattern.CASE_INSENSITIVE))
+ val view = if (shouldBePresent) {
+ waitFindObjectOrNull(containsWithoutCaseSelector)
+ } else {
+ waitFindObjectOrNull(containsWithoutCaseSelector, EXPECTED_TIMEOUT_MS)
+ }
+
+ assertEquals("Expected to find view with text $text: $shouldBePresent",
+ shouldBePresent, view != null)
+ return view
+ }
+
+ private fun findObjectByText(expected: Boolean, text: String): UiObject2? {
+ try {
+ return findObjectByTextWithoutRetry(expected, text)
+ } catch (stale: StaleObjectException) {
+ return findObjectByTextWithoutRetry(expected, text)
+ }
+ }
+}
+
+/** Test Accessibility Services */
+class AccessibilityTestService1 : InstrumentedAccessibilityService()
+
+class AccessibilityTestService2 : InstrumentedAccessibilityService()
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/SafetyLabelChangesJobServiceTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/SafetyLabelChangesJobServiceTest.kt
new file mode 100644
index 000000000..a157d473d
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/SafetyLabelChangesJobServiceTest.kt
@@ -0,0 +1,459 @@
+/*
+ * 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.permissionui.cts
+
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.Context
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_OTHER
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_STORE
+import android.content.pm.PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.PersistableBundle
+import android.os.Process
+import android.permission.cts.CtsNotificationListenerHelperRule
+import android.permission.cts.CtsNotificationListenerServiceUtils
+import android.permission.cts.CtsNotificationListenerServiceUtils.getNotification
+import android.permission.cts.CtsNotificationListenerServiceUtils.getNotificationForPackageAndId
+import android.permission.cts.PermissionUtils
+import android.permission.cts.TestUtils
+import android.permissionui.cts.AppMetadata.createAppMetadataWithLocationSharingNoAds
+import android.permissionui.cts.AppMetadata.createAppMetadataWithNoSharing
+import android.platform.test.annotations.FlakyTest
+import android.provider.DeviceConfig
+import android.safetylabel.SafetyLabelConstants
+import android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule
+import com.android.compatibility.common.util.SystemUtil
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.waitForBroadcasts
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+
+/** End-to-end test for SafetyLabelChangesJobService. */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+@FlakyTest
+class SafetyLabelChangesJobServiceTest : BaseUsePermissionTest() {
+
+ @get:Rule
+ val safetyLabelChangeNotificationsEnabledConfig =
+ DeviceConfigStateChangerRule(
+ context,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED,
+ true.toString())
+
+ /**
+ * 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")
+
+ @get:Rule
+ val deviceConfigDataSharingUpdatesPeriod =
+ DeviceConfigStateChangerRule(
+ BasePermissionTest.context,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_DATA_SHARING_UPDATE_PERIOD_MILLIS,
+ "600000")
+
+ @Before
+ fun setup() {
+ val packageManager = context.packageManager
+ Assume.assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
+ Assume.assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
+ Assume.assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
+
+ SystemUtil.runShellCommand("input keyevent KEYCODE_WAKEUP")
+ SystemUtil.runShellCommand("wm dismiss-keyguard")
+
+ // Bypass battery saving restrictions
+ SystemUtil.runShellCommand(
+ "cmd tare set-vip " +
+ "${Process.myUserHandle().identifier} $permissionControllerPackageName true")
+ CtsNotificationListenerServiceUtils.cancelNotifications(permissionControllerPackageName)
+ resetPermissionControllerAndSimulateReboot()
+ }
+
+ @After
+ fun cancelJobsAndNotifications() {
+ cancelJob(SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID)
+ cancelJob(SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID)
+ CtsNotificationListenerServiceUtils.cancelNotifications(permissionControllerPackageName)
+ // Reset battery saving restrictions
+ SystemUtil.runShellCommand(
+ "cmd tare set-vip " +
+ "${Process.myUserHandle().identifier} $permissionControllerPackageName default")
+ }
+
+ @Test
+ fun runDetectUpdatesJob_initializesSafetyLabelsHistoryForApps() {
+ installPackageNoBroadcast(APP_APK_NAME_31, createAppMetadataWithNoSharing())
+ grantLocationPermission(APP_PACKAGE_NAME)
+
+ // Run the job to check whether the missing safety label for the above app install is
+ // identified and recorded.
+ runDetectUpdatesJob()
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds())
+ waitForBroadcasts()
+
+ assertNotificationNotShown()
+ assertDataSharingScreenHasUpdates()
+ }
+
+ @Test
+ fun runNotificationJob_initializesSafetyLabelsHistoryForApps() {
+ installPackageNoBroadcast(APP_APK_NAME_31, createAppMetadataWithNoSharing())
+ grantLocationPermission(APP_PACKAGE_NAME)
+
+ // Run the job to check whether the missing safety label for the above app install is
+ // identified and recorded.
+ runNotificationJob()
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds())
+ waitForBroadcasts()
+
+ assertDataSharingScreenHasUpdates()
+ }
+
+ @Test
+ fun runDetectUpdatesJob_updatesSafetyLabelHistoryForApps() {
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithNoSharing())
+ waitForBroadcastReceiverFinished()
+ installPackageNoBroadcast(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds())
+ grantLocationPermission(APP_PACKAGE_NAME)
+
+ // Run the job to check whether the missing safety label for the above app update is
+ // identified and recorded.
+ runDetectUpdatesJob()
+
+ assertNotificationNotShown()
+ assertDataSharingScreenHasUpdates()
+ }
+
+ @Test
+ fun runNotificationJob_updatesSafetyLabelHistoryForApps() {
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithNoSharing())
+ waitForBroadcastReceiverFinished()
+ installPackageNoBroadcast(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds())
+ grantLocationPermission(APP_PACKAGE_NAME)
+
+ // Run the job to check whether the missing safety label for the above app update is
+ // identified and recorded.
+ runNotificationJob()
+
+ assertDataSharingScreenHasUpdates()
+ }
+
+ @Test
+ fun runNotificationJob_whenLocationSharingUpdatesForLocationGrantedApps_showsNotification() {
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithNoSharing())
+ waitForBroadcasts()
+ // TODO(b/279455955): Investigate why this is necessary and remove if possible.
+ Thread.sleep(500)
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds())
+ waitForBroadcasts()
+ grantLocationPermission(APP_PACKAGE_NAME)
+
+ runNotificationJob()
+
+ waitForNotificationShown()
+
+ val statusBarNotification = getNotification(permissionControllerPackageName,
+ SAFETY_LABEL_CHANGES_NOTIFICATION_ID)
+ val contentIntent = statusBarNotification!!.notification.contentIntent
+ contentIntent.send()
+
+ assertDataSharingScreenHasUpdates()
+ }
+
+ @Test
+ fun runNotificationJob_whenNoLocationGrantedApps_doesNotShowNotification() {
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithNoSharing())
+ waitForBroadcasts()
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds())
+ waitForBroadcasts()
+
+ runNotificationJob()
+
+ assertNotificationNotShown()
+ }
+
+ @Test
+ fun runNotificationJob_whenNoLocationSharingUpdates_doesNotShowNotification() {
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithNoSharing())
+ waitForBroadcasts()
+ grantLocationPermission(APP_PACKAGE_NAME)
+
+ runNotificationJob()
+
+ assertNotificationNotShown()
+ }
+
+ @Test
+ fun runNotificationJob_packageSourceUnspecified_updatesSafetyLabelHistoryForApps() {
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithNoSharing(),
+ PACKAGE_SOURCE_UNSPECIFIED)
+ waitForBroadcastReceiverFinished()
+ installPackageNoBroadcast(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds(),
+ PACKAGE_SOURCE_UNSPECIFIED)
+ grantLocationPermission(APP_PACKAGE_NAME)
+
+ // Run the job to check whether the missing safety label for the above app update is
+ // identified and recorded.
+ runNotificationJob()
+
+ assertDataSharingScreenHasUpdates()
+ }
+
+ @Test
+ fun runNotificationJob_packageSourceOther_doesNotShowNotification() {
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithNoSharing(),
+ PACKAGE_SOURCE_OTHER)
+ waitForBroadcastReceiverFinished()
+ installPackageNoBroadcast(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds(),
+ PACKAGE_SOURCE_OTHER)
+ grantLocationPermission(APP_PACKAGE_NAME)
+
+ // Run the job to check whether the missing safety label for the above app update is
+ // identified and recorded.
+ runNotificationJob()
+
+ assertNotificationNotShown()
+ }
+
+ @Test
+ fun runNotificationJob_packageSourceStore_updatesSafetyLabelHistoryForApps() {
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithNoSharing(),
+ PACKAGE_SOURCE_STORE)
+ waitForBroadcastReceiverFinished()
+ installPackageNoBroadcast(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds(),
+ PACKAGE_SOURCE_STORE)
+ grantLocationPermission(APP_PACKAGE_NAME)
+
+ // Run the job to check whether the missing safety label for the above app update is
+ // identified and recorded.
+ runNotificationJob()
+
+ assertDataSharingScreenHasUpdates()
+ }
+
+ @Test
+ fun runNotificationJob_packageSourceLocalFile_doesNotShowNotification() {
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithNoSharing(),
+ PACKAGE_SOURCE_LOCAL_FILE)
+ waitForBroadcastReceiverFinished()
+ installPackageNoBroadcast(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds(),
+ PACKAGE_SOURCE_LOCAL_FILE)
+ grantLocationPermission(APP_PACKAGE_NAME)
+
+ // Run the job to check whether the missing safety label for the above app update is
+ // identified and recorded.
+ runNotificationJob()
+
+ assertNotificationNotShown()
+ }
+
+ @Test
+ fun runNotificationJob_packageSourceDownloadedFile_udoesNotShowNotification() {
+ installPackageViaSession(APP_APK_NAME_31, createAppMetadataWithNoSharing(),
+ PACKAGE_SOURCE_DOWNLOADED_FILE)
+ waitForBroadcastReceiverFinished()
+ installPackageNoBroadcast(APP_APK_NAME_31, createAppMetadataWithLocationSharingNoAds(),
+ PACKAGE_SOURCE_DOWNLOADED_FILE)
+ grantLocationPermission(APP_PACKAGE_NAME)
+
+ // Run the job to check whether the missing safety label for the above app update is
+ // identified and recorded.
+ runNotificationJob()
+
+ assertNotificationNotShown()
+ }
+
+ private fun grantLocationPermission(packageName: String) {
+ uiAutomation.grantRuntimePermission(
+ packageName, android.Manifest.permission.ACCESS_FINE_LOCATION)
+ }
+
+ private fun installPackageNoBroadcast(
+ apkName: String,
+ appMetadata: PersistableBundle? = null,
+ packageSource: Int? = null
+ ) {
+ // Disable the safety labels feature during install to simulate installing an app without
+ // receiving an update about the change to its safety label.
+ setDeviceConfigPrivacyProperty(SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, false.toString())
+ installPackageViaSession(apkName, appMetadata, packageSource)
+ waitForBroadcastReceiverFinished()
+ setDeviceConfigPrivacyProperty(SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, true.toString())
+ }
+
+ private fun assertDataSharingScreenHasUpdates() {
+ startAppDataSharingUpdatesActivity()
+ try {
+ 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(APP_PACKAGE_NAME_SUBSTRING), true)
+ findView(By.textContains(DATA_SHARING_UPDATES_FOOTER_MESSAGE), true)
+ } finally {
+ pressBack()
+ }
+ }
+
+ companion object {
+ private const val TIMEOUT_TIME_MS = 60_000L
+ private const val SHORT_SLEEP_MS = 2000L
+
+ private const val SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID = 8
+ private const val SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID = 9
+ private const val SET_UP_SAFETY_LABEL_CHANGES_JOB =
+ "com.android.permissioncontroller.action.SET_UP_SAFETY_LABEL_CHANGES_JOB"
+ private const val SAFETY_LABEL_CHANGES_JOB_SERVICE_RECEIVER_CLASS =
+ "com.android.permissioncontroller.permission.service.v34" +
+ ".SafetyLabelChangesJobService\$Receiver"
+ private const val SAFETY_LABEL_CHANGES_NOTIFICATION_ID = 5
+ private const val JOB_STATUS_UNKNOWN = "unknown"
+ private const val JOB_STATUS_ACTIVE = "active"
+ private const val JOB_STATUS_WAITING = "waiting"
+
+ private val context: Context = InstrumentationRegistry.getTargetContext()
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private fun uiAutomation(): UiAutomation = instrumentation.uiAutomation
+ private val permissionControllerPackageName =
+ context.packageManager.permissionControllerPackageName
+ private val userId = Process.myUserHandle().identifier
+
+ @get:ClassRule
+ @JvmStatic
+ val ctsNotificationListenerHelper =
+ CtsNotificationListenerHelperRule(
+ InstrumentationRegistry.getInstrumentation().targetContext)
+
+ private fun waitForNotificationShown() {
+ eventually {
+ val notification = getNotification(false)
+ assertThat(notification).isNotNull()
+ }
+ }
+
+ private fun assertNotificationNotShown() {
+ eventually {
+ val notification = getNotification(false)
+ assertThat(notification).isNull()
+ }
+ }
+
+ private fun getNotification(cancelNotification: Boolean) =
+ getNotificationForPackageAndId(
+ permissionControllerPackageName,
+ SAFETY_LABEL_CHANGES_NOTIFICATION_ID,
+ cancelNotification)
+ ?.notification
+
+ private fun cancelJob(jobId: Int) {
+ SystemUtil.runShellCommandOrThrow(
+ "cmd jobscheduler cancel -u $userId $permissionControllerPackageName $jobId")
+ TestUtils.awaitJobUntilRequestedState(
+ permissionControllerPackageName,
+ jobId,
+ TIMEOUT_TIME_MS,
+ uiAutomation(),
+ JOB_STATUS_UNKNOWN)
+ }
+
+ private fun runDetectUpdatesJob() {
+ startJob(SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID)
+ TestUtils.awaitJobUntilRequestedState(
+ permissionControllerPackageName,
+ SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID,
+ TIMEOUT_TIME_MS,
+ uiAutomation(),
+ JOB_STATUS_ACTIVE)
+ TestUtils.awaitJobUntilRequestedState(
+ permissionControllerPackageName,
+ SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID,
+ TIMEOUT_TIME_MS,
+ uiAutomation(),
+ JOB_STATUS_UNKNOWN)
+ }
+
+ private fun runNotificationJob() {
+ startJob(SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID)
+ TestUtils.awaitJobUntilRequestedState(
+ permissionControllerPackageName,
+ SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID,
+ TIMEOUT_TIME_MS,
+ uiAutomation(),
+ JOB_STATUS_ACTIVE)
+ // TODO(b/266449833): In theory we should only have to wait for "waiting" here, but
+ // sometimes jobscheduler returns "unknown".
+ TestUtils.awaitJobUntilRequestedState(
+ permissionControllerPackageName,
+ SAFETY_LABEL_CHANGES_PERIODIC_NOTIFICATION_JOB_ID,
+ TIMEOUT_TIME_MS,
+ uiAutomation(),
+ JOB_STATUS_WAITING,
+ JOB_STATUS_UNKNOWN)
+ }
+
+ private fun startJob(jobId: Int) {
+ val runJobCmd = "cmd jobscheduler run -u $userId -f " +
+ "$permissionControllerPackageName $jobId"
+ try {
+ SystemUtil.runShellCommandOrThrow(runJobCmd)
+ } catch (e: Throwable) {
+ throw RuntimeException(e)
+ }
+ }
+
+ private fun resetPermissionControllerAndSimulateReboot() {
+ PermissionUtils.resetPermissionControllerJob(
+ uiAutomation(),
+ permissionControllerPackageName,
+ SAFETY_LABEL_CHANGES_DETECT_UPDATES_JOB_ID,
+ TIMEOUT_TIME_MS,
+ SET_UP_SAFETY_LABEL_CHANGES_JOB,
+ SAFETY_LABEL_CHANGES_JOB_SERVICE_RECEIVER_CLASS)
+ }
+
+ private fun waitForBroadcastReceiverFinished() {
+ waitForBroadcasts()
+ // Add a short sleep to ensure that the SafetyLabelChangedBroadcastReceiver finishes its
+ // work based according to the current feature flag value before changing the flag value.
+ // While `waitForBroadcasts()` waits for broadcasts to be dispatched, it will not wait for
+ // the receivers' `onReceive` to finish.
+ Thread.sleep(SHORT_SLEEP_MS)
+ }
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/SafetyProtectionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/SafetyProtectionTest.kt
new file mode 100644
index 000000000..f94fa036c
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/SafetyProtectionTest.kt
@@ -0,0 +1,123 @@
+
+/*
+ * 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.permissionui.cts
+
+import android.Manifest.permission.ACCESS_COARSE_LOCATION
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.content.res.Resources
+import android.platform.test.annotations.FlakyTest
+import android.provider.DeviceConfig
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule
+import com.android.modules.utils.build.SdkLevel
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Tests for Safety Protection related features. This feature should only be enabled on T+.
+ */
+@FlakyTest
+class SafetyProtectionTest : BaseUsePermissionTest() {
+ @get:Rule
+ val safetyProtectionEnabled =
+ DeviceConfigStateChangerRule(
+ context,
+ DeviceConfig.NAMESPACE_PRIVACY,
+ SAFETY_PROTECTION_ENABLED_FLAG,
+ true.toString()
+ )
+
+ @Before
+ fun setup() {
+ assumeFalse(isAutomotive)
+ assumeFalse(isTv)
+ assumeFalse(isWatch)
+ }
+
+ @Ignore("b/276944839")
+ @Test
+ fun testSafetyProtectionSectionView_safetyProtection_belowT() {
+ assumeFalse("Safety Protection should only be enabled on T+", SdkLevel.isAtLeastT())
+ installPackageViaSession(APP_APK_NAME_31)
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ findView(By.res(SAFETY_PROTECTION_DISPLAY_TEXT), false)
+ }
+ }
+
+ @Test
+ fun testSafetyProtectionSectionView_safetyProtectionDisabled_aboveT() {
+ assumeTrue("Safety Protection should only be enabled on T+", SdkLevel.isAtLeastT())
+ setDeviceConfigPrivacyProperty(SAFETY_PROTECTION_ENABLED_FLAG, false.toString())
+ installPackageViaSession(APP_APK_NAME_31)
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ findView(By.res(SAFETY_PROTECTION_DISPLAY_TEXT), false)
+ }
+ }
+
+ @Test
+ fun testSafetyProtectionSectionView_safetyProtectionEnabled_aboveT() {
+ assumeTrue("Safety Protection should only be enabled on T+", SdkLevel.isAtLeastT())
+ assumeTrue(safetyProtectionResourcesExist)
+ installPackageViaSession(APP_APK_NAME_31)
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ findView(By.res(SAFETY_PROTECTION_DISPLAY_TEXT), true)
+ }
+ }
+
+ @Test
+ fun testSafetyProtectionSectionView_safetyProtectionResourcesNotExist_aboveT() {
+ assumeTrue("Safety Protection should only be enabled on T+", SdkLevel.isAtLeastT())
+ assumeFalse(safetyProtectionResourcesExist)
+ installPackageViaSession(APP_APK_NAME_31)
+ assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
+ assertAppHasPermission(ACCESS_FINE_LOCATION, false)
+ requestAppPermissionsForNoResult(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION) {
+ findView(By.res(SAFETY_PROTECTION_DISPLAY_TEXT), false)
+ }
+ }
+
+ companion object {
+ private const val SAFETY_PROTECTION_ENABLED_FLAG = "safety_protection_enabled"
+ private const val SAFETY_PROTECTION_DISPLAY_TEXT =
+ "com.android.permissioncontroller:id/safety_protection_display_text"
+ private val safetyProtectionResourcesExist =
+ try {
+ context
+ .getResources()
+ .getBoolean(
+ Resources.getSystem()
+ .getIdentifier("config_safetyProtectionEnabled", "bool", "android")
+ ) &&
+ context.getDrawable(android.R.drawable.ic_safety_protection) != null &&
+ !context.getString(
+ android.R.string.safety_protection_display_text
+ ).isNullOrEmpty()
+ } catch (e: Resources.NotFoundException) {
+ false
+ }
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/SensorBlockedBannerTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/SensorBlockedBannerTest.kt
new file mode 100644
index 000000000..92dc47a05
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/SensorBlockedBannerTest.kt
@@ -0,0 +1,167 @@
+/*
+ * 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 android.permissionui.cts
+
+import android.Manifest.permission_group.CAMERA as CAMERA_PERMISSION_GROUP
+import android.Manifest.permission_group.LOCATION as LOCATION_PERMISSION_GROUP
+import android.Manifest.permission_group.MICROPHONE as MICROPHONE_PERMISSION_GROUP
+import android.content.Intent
+import android.hardware.SensorPrivacyManager
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.location.LocationManager
+import android.os.Build
+import android.platform.test.annotations.FlakyTest
+import android.provider.DeviceConfig
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import java.util.regex.Pattern
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Banner card display tests on sensors being blocked
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+@FlakyTest
+class SensorBlockedBannerTest : BaseUsePermissionTest() {
+ companion object {
+ const val LOCATION = -1
+ const val WARNING_BANNER_ENABLED = "warning_banner_enabled"
+ const val DELAY_MILLIS = 3000L
+ }
+
+ val sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!!
+ val locationManager = context.getSystemService(LocationManager::class.java)!!
+ private val originalEnabledValue = callWithShellPermissionIdentity {
+ DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY,
+ WARNING_BANNER_ENABLED, false.toString())
+ }
+
+ private val sensorToPermissionGroup = mapOf(CAMERA to CAMERA_PERMISSION_GROUP,
+ MICROPHONE to MICROPHONE_PERMISSION_GROUP,
+ LOCATION to LOCATION_PERMISSION_GROUP)
+
+ private val permToTitle = mapOf(CAMERA to "blocked_camera_title",
+ MICROPHONE to "blocked_microphone_title",
+ LOCATION to "blocked_location_title")
+
+ @Before
+ fun setup() {
+ Assume.assumeFalse(isTv)
+ Assume.assumeFalse(isWatch)
+ // TODO(b/203784852) Auto will eventually support the blocked sensor banner, but there won't
+ // be support in T or below
+ Assume.assumeFalse(isAutomotive)
+ installPackage(APP_APK_PATH_31)
+ runWithShellPermissionIdentity {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+ WARNING_BANNER_ENABLED, true.toString(), false)
+ }
+ }
+
+ @After
+ fun restoreWarningBannerState() {
+ runWithShellPermissionIdentity {
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+ WARNING_BANNER_ENABLED, originalEnabledValue, false)
+ }
+ }
+
+ private fun navigateAndTest(sensor: Int) {
+ val permissionGroup = sensorToPermissionGroup.getOrDefault(sensor, "Break")
+ val intent = Intent(Intent.ACTION_MANAGE_PERMISSION_APPS)
+ .putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, permissionGroup)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ runWithShellPermissionIdentity {
+ context.startActivity(intent)
+ }
+ val bannerTitle = permToTitle.getOrDefault(sensor, "Break")
+ waitFindObject(By.text(getPermissionControllerString(bannerTitle)))
+ pressBack()
+ }
+
+ private fun runSensorTest(sensor: Int) {
+ var blocked = false
+ try {
+ blocked = isSensorPrivacyEnabled(sensor)
+ if (!blocked) {
+ setSensor(sensor, true)
+ }
+ navigateAndTest(sensor)
+ } finally {
+ if (!blocked) {
+ setSensor(sensor, false)
+ }
+ }
+ }
+
+ @Test
+ fun testCameraCardDisplayed() {
+ Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(CAMERA))
+ runSensorTest(CAMERA)
+ }
+
+ @Test
+ fun testMicCardDisplayed() {
+ Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(MICROPHONE))
+ runSensorTest(MICROPHONE)
+ }
+
+ @Test
+ fun testLocationCardDisplayed() {
+ runSensorTest(LOCATION)
+ }
+
+ private fun setSensor(sensor: Int, enable: Boolean) {
+ if (sensor == LOCATION) {
+ runWithShellPermissionIdentity {
+ locationManager.setLocationEnabledForUser(!enable,
+ android.os.Process.myUserHandle())
+ if (enable) {
+ try {
+ val closePattern = Pattern.compile("close", Pattern.CASE_INSENSITIVE)
+ waitFindObjectOrNull(By.text(closePattern), DELAY_MILLIS)?.click()
+ } catch (e: Exception) {
+ // Do nothing, warning didn't show up so test can proceed
+ }
+ }
+ }
+ } else {
+ runWithShellPermissionIdentity {
+ sensorPrivacyManager.setSensorPrivacy(SensorPrivacyManager.Sources.OTHER,
+ sensor, enable)
+ }
+ }
+ }
+
+ private fun isSensorPrivacyEnabled(sensor: Int): Boolean {
+ return if (sensor == LOCATION) {
+ callWithShellPermissionIdentity {
+ !locationManager.isLocationEnabled()
+ }
+ } else {
+ callWithShellPermissionIdentity {
+ sensorPrivacyManager.isSensorPrivacyEnabled(sensor)
+ }
+ }
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/StartForFutureActivity.kt b/tests/cts/permissionui/src/android/permissionui/cts/StartForFutureActivity.kt
new file mode 100644
index 000000000..b77da308d
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/StartForFutureActivity.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 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.permissionui.cts
+
+import android.app.Activity
+import android.app.Instrumentation
+import android.content.Intent
+import java.util.concurrent.CompletableFuture
+
+class StartForFutureActivity : Activity() {
+ fun startActivityForFuture(
+ intent: Intent,
+ future: CompletableFuture<Instrumentation.ActivityResult>
+ ) {
+ startActivityForResult(intent, 1)
+ StartForFutureActivity.future = future
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ future!!.complete(Instrumentation.ActivityResult(resultCode, data))
+ future = null
+ finish()
+ }
+
+ companion object {
+ private var future: CompletableFuture<Instrumentation.ActivityResult>? = null
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/TestInstallerActivity.kt b/tests/cts/permissionui/src/android/permissionui/cts/TestInstallerActivity.kt
new file mode 100644
index 000000000..bae332a3c
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/TestInstallerActivity.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.app.Activity
+
+class TestInstallerActivity : Activity()