summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/apex/Android.bp1
-rw-r--r--tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt51
-rw-r--r--tests/cts/permission/Android.bp130
-rw-r--r--tests/cts/permission/AndroidManifest.xml100
-rw-r--r--tests/cts/permission/AndroidTest.xml137
-rw-r--r--tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp36
-rw-r--r--tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/AndroidManifest.xml41
-rw-r--r--tests/cts/permission/AppThatAccessesLocationOnCommand/Android.bp41
-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.bp33
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionA/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp33
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionADifferentCert/AndroidManifest.xml25
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp33
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp33
-rw-r--r--tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionA/Android.bp33
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionA/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp32
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp32
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup30/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp32
-rw-r--r--tests/cts/permission/AppThatDefinesPermissionWithPlatformGroup/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp33
-rw-r--r--tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/AndroidManifest.xml38
-rw-r--r--tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/src/android/permission/cts/appthatrequestpermission/RequestPermissions.kt49
-rw-r--r--tests/cts/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp37
-rw-r--r--tests/cts/permission/AppThatDoesNotHaveBgLocationAccess/AndroidManifest.xml24
-rw-r--r--tests/cts/permission/AppThatHasNotificationListener/Android.bp40
-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.bp41
-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.bp34
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermission31/AndroidManifest.xml34
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocation31/Android.bp34
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocation31/AndroidManifest.xml40
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/Android.bp33
-rw-r--r--tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/AndroidManifest.xml36
-rw-r--r--tests/cts/permission/AppThatRequestContactsAndCallLogPermission16/Android.bp32
-rw-r--r--tests/cts/permission/AppThatRequestContactsAndCallLogPermission16/AndroidManifest.xml29
-rw-r--r--tests/cts/permission/AppThatRequestContactsPermission15/Android.bp32
-rw-r--r--tests/cts/permission/AppThatRequestContactsPermission15/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatRequestContactsPermission16/Android.bp32
-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.java85
-rw-r--r--tests/cts/permission/AppThatRequestDevicePermissions/Android.bp33
-rw-r--r--tests/cts/permission/AppThatRequestDevicePermissions/AndroidManifest.xml41
-rw-r--r--tests/cts/permission/AppThatRequestDevicePermissions/src/android/permission/cts/appthatrequestpermission/RevokeSelfPermissionReceiver.kt37
-rw-r--r--tests/cts/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp33
-rw-r--r--tests/cts/permission/AppThatRequestLocationAndBackgroundPermission28/AndroidManifest.xml32
-rw-r--r--tests/cts/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp34
-rw-r--r--tests/cts/permission/AppThatRequestLocationAndBackgroundPermission29/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission22/Android.bp32
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission22/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission28/Android.bp32
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission28/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission29/Android.bp33
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission29/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission29v4/Android.bp33
-rw-r--r--tests/cts/permission/AppThatRequestLocationPermission29v4/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/AppThatRequestMultiplePermissionsWithMinMaxSdk/Android.bp32
-rw-r--r--tests/cts/permission/AppThatRequestMultiplePermissionsWithMinMaxSdk/AndroidManifest.xml30
-rw-r--r--tests/cts/permission/AppThatRequestOneTimePermission/Android.bp36
-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.bp34
-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.bp34
-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.bp32
-rw-r--r--tests/cts/permission/AppThatRequestStoragePermission28/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatRequestStoragePermission29/Android.bp32
-rw-r--r--tests/cts/permission/AppThatRequestStoragePermission29/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/AppThatRequestSystemAlertWindow22/Android.bp33
-rw-r--r--tests/cts/permission/AppThatRequestSystemAlertWindow22/AndroidManifest.xml24
-rw-r--r--tests/cts/permission/AppThatRequestSystemAlertWindow23/Android.bp33
-rw-r--r--tests/cts/permission/AppThatRequestSystemAlertWindow23/AndroidManifest.xml24
-rw-r--r--tests/cts/permission/AppThatRunsRationaleTests/Android.bp36
-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.java49
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp34
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestLocationPermission28/AndroidManifest.xml29
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp34
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestLocationPermission29/AndroidManifest.xml30
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestsNoPermissions/Android.bp31
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestsNoPermissions/AndroidManifest.xml24
-rw-r--r--tests/cts/permission/AppWithSharedUidThatRequestsPermissions/Android.bp31
-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.bp31
-rw-r--r--tests/cts/permission/StorageEscalationApp28/AndroidManifest.xml30
-rw-r--r--tests/cts/permission/StorageEscalationApp29Full/Android.bp31
-rw-r--r--tests/cts/permission/StorageEscalationApp29Full/AndroidManifest.xml30
-rw-r--r--tests/cts/permission/StorageEscalationApp29Scoped/Android.bp31
-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.kt56
-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.kt127
-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.java414
-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.bp34
-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.kt328
-rw-r--r--tests/cts/permission/src/android/permission/cts/AccessibilityTestService.kt22
-rw-r--r--tests/cts/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java135
-rw-r--r--tests/cts/permission/src/android/permission/cts/AppIdleStatePermissionTest.java92
-rw-r--r--tests/cts/permission/src/android/permission/cts/AppWidgetManagerPermissionTest.java62
-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.java95
-rw-r--r--tests/cts/permission/src/android/permission/cts/ConnectivityManagerPermissionTest.java120
-rw-r--r--tests/cts/permission/src/android/permission/cts/ContactsProviderTest.java158
-rw-r--r--tests/cts/permission/src/android/permission/cts/DebuggableTest.java50
-rw-r--r--tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt550
-rw-r--r--tests/cts/permission/src/android/permission/cts/DuplicatePermissionDefinitionsTest.kt195
-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.java806
-rw-r--r--tests/cts/permission/src/android/permission/cts/MainlineNetworkStackPermissionTest.java68
-rw-r--r--tests/cts/permission/src/android/permission/cts/MinMaxSdkVersionTest.kt97
-rw-r--r--tests/cts/permission/src/android/permission/cts/NearbyDevicesPermissionTest.java307
-rw-r--r--tests/cts/permission/src/android/permission/cts/NearbyDevicesRenouncePermissionTest.java315
-rw-r--r--tests/cts/permission/src/android/permission/cts/NfcPermissionTest.java221
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java99
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoAudioPermissionTest.java124
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoBroadcastPackageRemovedPermissionTest.java104
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoCaptureVideoPermissionTest.java106
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoKeyPermissionTest.java100
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoNetworkStatePermissionTest.java99
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoReadLogsPermissionTest.java104
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoRollbackPermissionTest.java48
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java182
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoWakeLockPermissionTest.java119
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoWallpaperPermissionsTest.java190
-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.java396
-rw-r--r--tests/cts/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java124
-rw-r--r--tests/cts/permission/src/android/permission/cts/PermissionControllerTest.java517
-rw-r--r--tests/cts/permission/src/android/permission/cts/PermissionFlagsTest.java254
-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.java273
-rw-r--r--tests/cts/permission/src/android/permission/cts/PlatformPermissionGroupMappingTest.kt62
-rw-r--r--tests/cts/permission/src/android/permission/cts/PowerManagerServicePermissionTest.java57
-rw-r--r--tests/cts/permission/src/android/permission/cts/ProviderPermissionTest.java294
-rw-r--r--tests/cts/permission/src/android/permission/cts/RebootPermissionTest.java49
-rw-r--r--tests/cts/permission/src/android/permission/cts/RecordSensitiveContentPermissionTest.kt62
-rw-r--r--tests/cts/permission/src/android/permission/cts/RemovePermissionTest.java247
-rw-r--r--tests/cts/permission/src/android/permission/cts/RevokePermissionTest.kt183
-rw-r--r--tests/cts/permission/src/android/permission/cts/RevokeSawPermissionTest.kt65
-rw-r--r--tests/cts/permission/src/android/permission/cts/RevokeSelfPermissionTest.java361
-rw-r--r--tests/cts/permission/src/android/permission/cts/RuntimePermissionPresentationInfoTest.java64
-rw-r--r--tests/cts/permission/src/android/permission/cts/SafetyCenterUtils.kt158
-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.java85
-rw-r--r--tests/cts/permission/src/android/permission/cts/SharedUidPermissionsTest.java150
-rw-r--r--tests/cts/permission/src/android/permission/cts/ShellCommandPermissionTest.java61
-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.kt158
-rw-r--r--tests/cts/permission/src/android/permission/cts/TvPermissionTest.java116
-rw-r--r--tests/cts/permission/src/android/permission/cts/UndefinedGroupPermissionTest.kt247
-rw-r--r--tests/cts/permission/src/android/permission/cts/appthataccesseslocation/IAccessLocationOnCommand.aidl22
-rw-r--r--tests/cts/permission/telephony/Android.bp39
-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.bp33
-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.bp33
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/AndroidManifest.xml25
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/Android.bp33
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/AndroidManifest.xml27
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/Android.bp33
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/AndroidManifest.xml28
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/Android.bp33
-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.bp33
-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.bp33
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/AndroidManifest.xml25
-rw-r--r--tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp33
-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/permissionmultidevice/AccessRemoteDeviceCameraApp/Android.bp32
-rw-r--r--tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/AndroidManifest.xml28
-rw-r--r--tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/src/android/permissionmultidevice/cts/accessremotedevicecamera/RequestPermissionActivity.kt22
-rw-r--r--tests/cts/permissionmultidevice/Android.bp51
-rw-r--r--tests/cts/permissionmultidevice/AndroidManifest.xml37
-rw-r--r--tests/cts/permissionmultidevice/AndroidTest.xml75
-rw-r--r--tests/cts/permissionmultidevice/OWNERS3
-rw-r--r--tests/cts/permissionmultidevice/TEST_MAPPING7
-rw-r--r--tests/cts/permissionmultidevice/TestUtils/Android.bp41
-rw-r--r--tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/FakeVirtualDeviceRule.kt80
-rw-r--r--tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/PackageManagementUtils.kt38
-rw-r--r--tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/PermissionUtils.kt29
-rw-r--r--tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/UiAutomatorUtils.kt56
-rw-r--r--tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt405
-rw-r--r--tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt246
-rw-r--r--tests/cts/permissionmultiuser/Android.bp48
-rw-r--r--tests/cts/permissionmultiuser/AndroidManifest.xml34
-rw-r--r--tests/cts/permissionmultiuser/AndroidTest.xml71
-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.kt493
-rw-r--r--tests/cts/permissionpolicy/Android.bp67
-rwxr-xr-xtests/cts/permissionpolicy/AndroidManifest.xml74
-rw-r--r--tests/cts/permissionpolicy/AndroidTest.xml84
-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.kt36
-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/OWNERS8
-rw-r--r--tests/cts/permissionpolicy/res/raw/OWNERS8
-rw-r--r--tests/cts/permissionpolicy/res/raw/android_manifest.xml8788
-rw-r--r--tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml623
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/CommandBroadcastReceiver.java49
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ContactsProviderTest.java95
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/IntelligenceRolesPolicyTest.java99
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoCaptureAudioOutputPermissionTest.java70
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoProcessOutgoingCallPermissionTest.java142
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoReceiveSmsPermissionTest.java293
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoWriteSecureSettingsPermissionTest.java47
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionMaxSdkVersionTest.java59
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionPolicyTest.java559
-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.java745
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedStoragePermissionSharedUidTest.java269
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedStoragePermissionTest.java755
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt192
-rw-r--r--tests/cts/permissionui/Android.bp83
-rw-r--r--tests/cts/permissionui/AndroidManifest.xml89
-rw-r--r--tests/cts/permissionui/AndroidTest.xml101
-rw-r--r--tests/cts/permissionui/AppThatAccessesCameraAndMic/Android.bp35
-rw-r--r--tests/cts/permissionui/AppThatAccessesCameraAndMic/AndroidManifest.xml35
-rw-r--r--tests/cts/permissionui/AppThatAccessesCameraAndMic/src/android/permissionui/cts/appthataccessescameraandmic/AccessCameraOrMicActivity.kt259
-rw-r--r--tests/cts/permissionui/CreateNotificationChannelsApp31/Android.bp33
-rw-r--r--tests/cts/permissionui/CreateNotificationChannelsApp31/AndroidManifest.xml44
-rw-r--r--tests/cts/permissionui/CreateNotificationChannelsApp31/src/android/permissionui/cts/usepermission/CreateNotificationChannelsActivity.kt187
-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.kt112
-rw-r--r--tests/cts/permissionui/StorageApp33/Android.bp33
-rw-r--r--tests/cts/permissionui/StorageApp33/AndroidManifest.xml30
-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.kt39
-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.kt57
-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.kt154
-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.xml56
-rw-r--r--tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/CheckCalendarAccessActivity.kt71
-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.kt93
-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.kt46
-rw-r--r--tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt68
-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.kt60
-rw-r--r--tests/cts/permissionui/UsePermissionAppWithOverlay/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt106
-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.kt591
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/AppMetadata.kt214
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt336
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt511
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt1365
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt698
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt372
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt164
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/LocationProviderInterceptDialogTest.kt149
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionTest.kt183
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionUpgradeTest.kt60
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/NoPermissionTest.kt46
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/NotificationPermissionTest.kt416
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionDecisionsTest.kt131
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionGroupTest.kt90
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionNoOpGtsTest.kt29
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionPolicyTest25.kt64
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt284
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionRationaleTest.kt385
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionReviewTapjackingTest.kt98
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionReviewTest.kt176
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt114
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt150
-rwxr-xr-xtests/cts/permissionui/src/android/permissionui/cts/PermissionTest22.kt66
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionTest23.kt403
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionTest29.kt205
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionTest30.kt93
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionTest30WithBluetooth.kt225
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionUpgradeTest.kt154
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionUsageInfoTest.kt56
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerPermissionTest.kt522
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerUtils.kt128
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/ReviewAccessibilityServicesTest.kt241
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/SafetyLabelChangesJobServiceTest.kt507
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/SafetyProtectionTest.kt120
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/SensorBlockedBannerTest.kt182
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/StartForFutureActivity.kt62
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/TestInstallerActivity.kt21
-rw-r--r--tests/cts/role/Android.bp51
-rw-r--r--tests/cts/role/AndroidManifest.xml40
-rw-r--r--tests/cts/role/AndroidTest.xml51
-rw-r--r--tests/cts/role/CtsRoleTestApp/Android.bp26
-rw-r--r--tests/cts/role/CtsRoleTestApp/AndroidManifest.xml131
-rw-r--r--tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultDialerActivity.java52
-rw-r--r--tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultSmsActivity.java52
-rw-r--r--tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/IsRoleHeldActivity.java46
-rw-r--r--tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java53
-rw-r--r--tests/cts/role/CtsRoleTestApp28/Android.bp27
-rw-r--r--tests/cts/role/CtsRoleTestApp28/AndroidManifest.xml79
-rw-r--r--tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultDialerActivity.java40
-rw-r--r--tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultSmsActivity.java41
-rw-r--r--tests/cts/role/CtsRoleTestApp33WithoutInCallService/Android.bp23
-rw-r--r--tests/cts/role/CtsRoleTestApp33WithoutInCallService/AndroidManifest.xml37
-rw-r--r--tests/cts/role/OWNERS3
-rw-r--r--tests/cts/role/TEST_MAPPING48
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleControllerManagerTest.kt167
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleManagerTest.java1347
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt200
-rw-r--r--tests/cts/role/src/android/app/role/cts/WaitForResultActivity.java75
-rw-r--r--tests/cts/safetycenter/Android.bp3
-rw-r--r--tests/cts/safetycenter/TEST_MAPPING2
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt106
-rw-r--r--tests/functional/safetycenter/multiusers/Android.bp2
-rw-r--r--tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt415
-rw-r--r--tests/functional/safetycenter/safetycenteractivity/Android.bp1
-rw-r--r--tests/functional/safetycenter/safetycenteractivity/TEST_MAPPING2
-rw-r--r--tests/functional/safetycenter/singleuser/Android.bp1
-rw-r--r--tests/functional/safetycenter/singleuser/TEST_MAPPING2
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt17
-rw-r--r--tests/functional/safetycenter/subpages/TEST_MAPPING2
-rw-r--r--tests/hostside/safetycenter/Android.bp3
-rw-r--r--tests/hostside/safetycenter/helper-app/Android.bp3
-rw-r--r--tests/utils/safetycenter/Android.bp2
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt18
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt36
475 files changed, 53157 insertions, 25 deletions
diff --git a/tests/apex/Android.bp b/tests/apex/Android.bp
index 126fcb4b2..83bf4e252 100644
--- a/tests/apex/Android.bp
+++ b/tests/apex/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_android_permissions",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt b/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
index d90ffade9..6500b3926 100644
--- a/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
+++ b/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
@@ -20,7 +20,6 @@ import android.content.ApexEnvironment
import android.content.Context
import android.os.Process
import android.os.UserHandle
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.google.common.truth.Truth.assertThat
@@ -29,6 +28,7 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
@@ -37,27 +37,36 @@ import org.mockito.MockitoAnnotations.initMocks
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
-@RunWith(AndroidJUnit4::class)
+@RunWith(Parameterized::class)
class RolesPersistenceTest {
private val context = InstrumentationRegistry.getInstrumentation().context
private lateinit var mockDataDirectory: File
-
private lateinit var mockitoSession: MockitoSession
@Mock lateinit var apexEnvironment: ApexEnvironment
+ @Parameterized.Parameter(0) lateinit var stateVersion: StateVersion
+ private lateinit var state: RolesState
private val persistence = RolesPersistenceImpl {}
- private val state = RolesState(1, "packagesHash", mapOf("role" to setOf("holder1", "holder2")))
+ private val defaultRoles = mapOf(ROLE_NAME to setOf(HOLDER_1, HOLDER_2))
+ private val stateVersionUndefined = RolesState(VERSION_UNDEFINED, PACKAGE_HASH, defaultRoles)
+ private val stateVersionFallbackMigrated =
+ RolesState(VERSION_FALLBACK_MIGRATED, PACKAGE_HASH, defaultRoles, setOf(ROLE_NAME))
private val user = Process.myUserHandle()
@Before
- fun createMockDataDirectory() {
+ fun setUp() {
+ createMockDataDirectory()
+ mockApexEnvironment()
+ state = getState()
+ }
+
+ private fun createMockDataDirectory() {
mockDataDirectory = context.getDir("mock_data", Context.MODE_PRIVATE)
mockDataDirectory.listFiles()!!.forEach { assertThat(it.deleteRecursively()).isTrue() }
}
- @Before
- fun mockApexEnvironment() {
+ private fun mockApexEnvironment() {
initMocks(this)
mockitoSession =
mockitoSession()
@@ -80,7 +89,7 @@ class RolesPersistenceTest {
persistence.writeForUser(state, user)
val persistedState = persistence.readForUser(user)
- checkPersistedState(persistedState)
+ assertThat(persistedState).isEqualTo(state)
}
@Test
@@ -91,7 +100,7 @@ class RolesPersistenceTest {
.writeText("<roles version=\"-1\"><role name=\"com.foo.bar\"><holder")
val persistedState = persistence.readForUser(user)
- checkPersistedState(persistedState)
+ assertThat(persistedState).isEqualTo(state)
}
@Test
@@ -103,14 +112,28 @@ class RolesPersistenceTest {
assertThat(persistedState).isNull()
}
- private fun checkPersistedState(persistedState: RolesState?) {
- assertThat(persistedState).isEqualTo(state)
- assertThat(persistedState?.version).isEqualTo(state.version)
- assertThat(persistedState?.packagesHash).isEqualTo(state.packagesHash)
- assertThat(persistedState?.roles).isEqualTo(state.roles)
+ private fun getState(): RolesState =
+ when (stateVersion) {
+ StateVersion.VERSION_UNDEFINED -> stateVersionUndefined
+ StateVersion.VERSION_FALLBACK_MIGRATED -> stateVersionFallbackMigrated
+ }
+
+ enum class StateVersion {
+ VERSION_UNDEFINED,
+ VERSION_FALLBACK_MIGRATED
}
companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun data(): Array<StateVersion> = StateVersion.values()
+
+ private const val VERSION_UNDEFINED = -1
+ private const val VERSION_FALLBACK_MIGRATED = 1
private const val APEX_MODULE_NAME = "com.android.permission"
+ private const val PACKAGE_HASH = "packagesHash"
+ private const val ROLE_NAME = "roleName"
+ private const val HOLDER_1 = "holder1"
+ private const val HOLDER_2 = "holder2"
}
}
diff --git a/tests/cts/permission/Android.bp b/tests/cts/permission/Android.bp
new file mode 100644
index 000000000..ed7fcea25
--- /dev/null
+++ b/tests/cts/permission/Android.bp
@@ -0,0 +1,130 @@
+//
+// 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",
+ "mcts-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",
+ "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",
+ "CtsVirtualDeviceCommonLib",
+ "android.permission.flags-aconfig-java",
+ "androidx.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",
+ ":CtsAppThatRequestsDevicePermissions",
+ ],
+ 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..de9504dc7
--- /dev/null
+++ b/tests/cts/permission/AndroidTest.xml
@@ -0,0 +1,137 @@
+<?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-permission" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts-permission"/>
+ </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-permission/CtsAppThatRequestsPermissionAandB.apk" />
+ <option name="push" value="CtsAppThatRequestsPermissionAandC.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsPermissionAandC.apk" />
+ <option name="push" value="CtsAppThatRequestsBluetoothPermission30.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsBluetoothPermission30.apk" />
+ <option name="push" value="CtsAppThatRequestsBluetoothPermission31.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsBluetoothPermission31.apk" />
+ <option name="push" value="CtsAppThatRequestsBluetoothPermissionNeverForLocation31.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsBluetoothPermissionNeverForLocation31.apk" />
+ <option name="push" value="CtsAppThatRequestsBluetoothPermissionNeverForLocationNoProvider.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsBluetoothPermissionNeverForLocationNoProvider.apk" />
+ <option name="push" value="CtsAppThatRequestsContactsPermission16.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsContactsPermission16.apk" />
+ <option name="push" value="CtsAppThatRequestsContactsPermission15.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsContactsPermission15.apk" />
+ <option name="push" value="CtsAppThatRequestsContactsAndCallLogPermission16.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsContactsAndCallLogPermission16.apk" />
+ <option name="push" value="CtsAppThatRequestsLocationPermission29.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsLocationPermission29.apk" />
+ <option name="push" value="CtsAppThatRequestsLocationPermission29v4.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsLocationPermission29v4.apk" />
+ <option name="push" value="CtsAppThatRequestsLocationPermission28.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsLocationPermission28.apk" />
+ <option name="push" value="CtsAppThatRequestsLocationPermission22.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsLocationPermission22.apk" />
+ <option name="push" value="CtsAppThatRequestsStoragePermission29.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsStoragePermission29.apk" />
+ <option name="push" value="CtsAppThatRequestsStoragePermission28.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsStoragePermission28.apk" />
+ <option name="push" value="CtsAppThatRequestsLocationAndBackgroundPermission28.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsLocationAndBackgroundPermission28.apk" />
+ <option name="push" value="CtsAppThatRequestsLocationAndBackgroundPermission29.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsLocationAndBackgroundPermission29.apk" />
+ <option name="push" value="CtsAppThatAccessesLocationOnCommand.apk->/data/local/tmp/cts-permission/CtsAppThatAccessesLocationOnCommand.apk" />
+ <option name="push" value="AppThatDoesNotHaveBgLocationAccess.apk->/data/local/tmp/cts-permission/AppThatDoesNotHaveBgLocationAccess.apk" />
+ <option name="push" value="CtsAppWithSharedUidThatRequestsPermissions.apk->/data/local/tmp/cts-permission/CtsAppWithSharedUidThatRequestsPermissions.apk" />
+ <option name="push" value="CtsAppWithSharedUidThatRequestsNoPermissions.apk->/data/local/tmp/cts-permission/CtsAppWithSharedUidThatRequestsNoPermissions.apk" />
+ <option name="push" value="CtsAppWithSharedUidThatRequestsLocationPermission28.apk->/data/local/tmp/cts-permission/CtsAppWithSharedUidThatRequestsLocationPermission28.apk" />
+ <option name="push" value="CtsAppWithSharedUidThatRequestsLocationPermission29.apk->/data/local/tmp/cts-permission/CtsAppWithSharedUidThatRequestsLocationPermission29.apk" />
+ <option name="push" value="CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk" />
+ <option name="push" value="CtsAppThatRunsRationaleTests.apk->/data/local/tmp/cts-permission/CtsAppThatRunsRationaleTests.apk" />
+ <option name="push" value="CtsAdversarialPermissionUserApp.apk->/data/local/tmp/cts-permission/CtsAdversarialPermissionUserApp.apk" />
+ <option name="push" value="CtsAdversarialPermissionDefinerApp.apk->/data/local/tmp/cts-permission/CtsAdversarialPermissionDefinerApp.apk" />
+ <option name="push" value="CtsVictimPermissionDefinerApp.apk->/data/local/tmp/cts-permission/CtsVictimPermissionDefinerApp.apk" />
+ <option name="push" value="CtsRuntimePermissionDefinerApp.apk->/data/local/tmp/cts-permission/CtsRuntimePermissionDefinerApp.apk" />
+ <option name="push" value="CtsRuntimePermissionUserApp.apk->/data/local/tmp/cts-permission/CtsRuntimePermissionUserApp.apk" />
+ <option name="push" value="CtsInstallPermissionDefinerApp.apk->/data/local/tmp/cts-permission/CtsInstallPermissionDefinerApp.apk" />
+ <option name="push" value="CtsInstallPermissionUserApp.apk->/data/local/tmp/cts-permission/CtsInstallPermissionUserApp.apk" />
+ <option name="push" value="CtsInstallPermissionEscalatorApp.apk->/data/local/tmp/cts-permission/CtsInstallPermissionEscalatorApp.apk" />
+ <option name="push" value="CtsAppThatRequestsOneTimePermission.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsOneTimePermission.apk" />
+ <option name="push" value="CtsAppToTestRevokeSelfPermission.apk->/data/local/tmp/cts-permission/CtsAppToTestRevokeSelfPermission.apk" />
+ <option name="push" value="AppThatDefinesUndefinedPermissionGroupElement.apk->/data/local/tmp/cts-permission/AppThatDefinesUndefinedPermissionGroupElement.apk" />
+ <option name="push" value="CtsAppThatDefinesPermissionA.apk->/data/local/tmp/cts-permission/CtsAppThatDefinesPermissionA.apk" />
+ <option name="push" value="CtsAppThatAlsoDefinesPermissionA.apk->/data/local/tmp/cts-permission/CtsAppThatAlsoDefinesPermissionA.apk" />
+ <option name="push" value="CtsAppThatAlsoDefinesPermissionADifferentCert.apk->/data/local/tmp/cts-permission/CtsAppThatAlsoDefinesPermissionADifferentCert.apk" />
+ <option name="push" value="CtsAppThatAlsoDefinesPermissionGroupADifferentCert.apk->/data/local/tmp/cts-permission/CtsAppThatAlsoDefinesPermissionGroupADifferentCert.apk" />
+ <option name="push" value="CtsAppThatDefinesPermissionInPlatformGroup.apk->/data/local/tmp/cts-permission/CtsAppThatDefinesPermissionInPlatformGroup.apk" />
+ <option name="push" value="CtsAppThatAlsoDefinesPermissionGroupADifferentCert30.apk->/data/local/tmp/cts-permission/CtsAppThatAlsoDefinesPermissionGroupADifferentCert30.apk" />
+ <option name="push" value="CtsAppThatDefinesPermissionWithInvalidGroup.apk->/data/local/tmp/cts-permission/CtsAppThatDefinesPermissionWithInvalidGroup.apk" />
+ <option name="push" value="CtsAppThatDefinesPermissionWithInvalidGroup30.apk->/data/local/tmp/cts-permission/CtsAppThatDefinesPermissionWithInvalidGroup30.apk" />
+ <option name="push" value="CtsStorageEscalationApp28.apk->/data/local/tmp/cts-permission/CtsStorageEscalationApp28.apk" />
+ <option name="push" value="CtsStorageEscalationApp29Full.apk->/data/local/tmp/cts-permission/CtsStorageEscalationApp29Full.apk" />
+ <option name="push" value="CtsStorageEscalationApp29Scoped.apk->/data/local/tmp/cts-permission/CtsStorageEscalationApp29Scoped.apk" />
+ <option name="push" value="CtsAppThatHasNotificationListener.apk->/data/local/tmp/cts-permission/CtsAppThatHasNotificationListener.apk" />
+ <option name="push" value="CtsAppThatRequestsMultiplePermissionsWithMinMaxSdk.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsMultiplePermissionsWithMinMaxSdk.apk" />
+ <option name="push" value="CtsAppThatRequestsSystemAlertWindow22.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsSystemAlertWindow22.apk" />
+ <option name="push" value="CtsAppThatRequestsSystemAlertWindow23.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsSystemAlertWindow23.apk" />
+ <option name="push" value="CtsAppThatRequestCustomCameraPermission.apk->/data/local/tmp/cts-permission/CtsAppThatRequestCustomCameraPermission.apk" />
+ <option name="push" value="CtsAppThatRequestsDevicePermissions.apk->/data/local/tmp/cts-permission/CtsAppThatRequestsDevicePermissions.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..df6bdf5c3
--- /dev/null
+++ b/tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp
@@ -0,0 +1,36 @@
+//
+// 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",
+ "mcts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/AndroidManifest.xml b/tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/AndroidManifest.xml
new file mode 100644
index 000000000..ece3ba1c7
--- /dev/null
+++ b/tests/cts/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?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.RECORD_AUDIO" />
+ <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..d4573d264
--- /dev/null
+++ b/tests/cts/permission/AppThatAccessesLocationOnCommand/Android.bp
@@ -0,0 +1,41 @@
+//
+// 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",
+ "mcts-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..af002df2b
--- /dev/null
+++ b/tests/cts/permission/AppThatAlsoDefinesPermissionA/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: "CtsAppThatAlsoDefinesPermissionA",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ certificate: ":cts-testkey1",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+}
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..6a622ca46
--- /dev/null
+++ b/tests/cts/permission/AppThatAlsoDefinesPermissionADifferentCert/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: "CtsAppThatAlsoDefinesPermissionADifferentCert",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ certificate: ":cts-testkey2",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+}
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..98f0a9aeb
--- /dev/null
+++ b/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/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: "CtsAppThatAlsoDefinesPermissionGroupADifferentCert",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ certificate: ":cts-testkey2",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+}
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..dd46066e9
--- /dev/null
+++ b/tests/cts/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/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: "CtsAppThatAlsoDefinesPermissionGroupADifferentCert30",
+ defaults: ["cts_defaults"],
+ sdk_version: "30",
+ certificate: ":cts-testkey2",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+}
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..e8125cd76
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesPermissionA/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: "CtsAppThatDefinesPermissionA",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ certificate: ":cts-testkey1",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+}
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..06f181f40
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup/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: "CtsAppThatDefinesPermissionWithInvalidGroup",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+}
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..527594589
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesPermissionWithInvalidGroup30/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: "CtsAppThatDefinesPermissionWithInvalidGroup30",
+ defaults: ["cts_defaults"],
+ sdk_version: "30",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+}
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..8d6279704
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesPermissionWithPlatformGroup/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: "CtsAppThatDefinesPermissionInPlatformGroup",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+}
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..684fd0559
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/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: "AppThatDefinesUndefinedPermissionGroupElement",
+ defaults: ["cts_defaults"],
+ sdk_version: "test_current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-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..bbf9066f2
--- /dev/null
+++ b/tests/cts/permission/AppThatDefinesUndefinedPermissionGroupElement/src/android/permission/cts/appthatrequestpermission/RequestPermissions.kt
@@ -0,0 +1,49 @@
+/*
+ * 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
+import android.os.Bundle
+import android.util.Log
+
+class RequestPermissions : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (savedInstanceState != null) {
+ Log.w(TAG, "Activity was recreated. (Perhaps due to a configuration change?)")
+ return
+ }
+
+ 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"
+ private val TAG = RequestPermissions::class.simpleName
+ }
+}
diff --git a/tests/cts/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp b/tests/cts/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp
new file mode 100644
index 000000000..6e9936996
--- /dev/null
+++ b/tests/cts/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp
@@ -0,0 +1,37 @@
+//
+// 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",
+ "mcts-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..3f3563e87
--- /dev/null
+++ b/tests/cts/permission/AppThatHasNotificationListener/Android.bp
@@ -0,0 +1,40 @@
+//
+// 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",
+ "mcts-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..5909e12b0
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestBluetoothPermission30/Android.bp
@@ -0,0 +1,41 @@
+//
+// 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",
+ "mcts-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..c5abe6c18
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestBluetoothPermission31/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: "CtsAppThatRequestsBluetoothPermission31",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ "mcts-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..f5a0faa3a
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocation31/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: "CtsAppThatRequestsBluetoothPermissionNeverForLocation31",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ "mcts-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..88a2765ca
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/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: "CtsAppThatRequestsBluetoothPermissionNeverForLocationNoProvider",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ "mcts-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..a1364df32
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestContactsAndCallLogPermission16/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: "CtsAppThatRequestsContactsAndCallLogPermission16",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-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..bb9ea42dc
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestContactsPermission15/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: "CtsAppThatRequestsContactsPermission15",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-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..a378c0c7f
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestContactsPermission16/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: "CtsAppThatRequestsContactsPermission16",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-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..288e7e1b3
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestCustomCameraPermission/src/android/permission/cts/appthatrequestcustomcamerapermission/RequestCameraPermission.java
@@ -0,0 +1,85 @@
+/*
+ * 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);
+
+ if (savedInstanceState != null) {
+ Log.w(LOG_TAG, "Activity was recreated. (Perhaps due to a configuration change?)");
+ return;
+ }
+
+ 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/AppThatRequestDevicePermissions/Android.bp b/tests/cts/permission/AppThatRequestDevicePermissions/Android.bp
new file mode 100644
index 000000000..10ee18ed6
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestDevicePermissions/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: "CtsAppThatRequestsDevicePermissions",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ srcs: ["src/**/*.kt"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestDevicePermissions/AndroidManifest.xml b/tests/cts/permission/AppThatRequestDevicePermissions/AndroidManifest.xml
new file mode 100644
index 000000000..a96342706
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestDevicePermissions/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?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.permission.cts.appthatrequestpermission">
+
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <permission android:name="android.permission.cts.CUSTOM_SIGNATURE_PERMISSION"
+ android:protectionLevel="signature"/>
+ <uses-permission android:name="android.permission.cts.CUSTOM_SIGNATURE_PERMISSION" />
+
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+ <application>
+ <receiver
+ android:name="android.permission.cts.appthatrequestpermission.RevokeSelfPermissionReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.permission.cts.appthatrequestpermission.REVOKE_SELF_PERMISSION" />
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
diff --git a/tests/cts/permission/AppThatRequestDevicePermissions/src/android/permission/cts/appthatrequestpermission/RevokeSelfPermissionReceiver.kt b/tests/cts/permission/AppThatRequestDevicePermissions/src/android/permission/cts/appthatrequestpermission/RevokeSelfPermissionReceiver.kt
new file mode 100644
index 000000000..4ce6a2aaa
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestDevicePermissions/src/android/permission/cts/appthatrequestpermission/RevokeSelfPermissionReceiver.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.appthatrequestpermission
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.os.Handler
+import android.os.Process
+
+/** Revokes permission for a device provided in the intent. */
+class RevokeSelfPermissionReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val permissionName = intent.getStringExtra("permissionName")!!
+ val deviceId = intent.getIntExtra("deviceID", Context.DEVICE_ID_INVALID)
+ val deviceContext = context.createDeviceContext(deviceId)
+ deviceContext.revokeSelfPermissionOnKill(permissionName)
+
+ // revokeSelfPermissionOnKill is an async API, and the work is executed by main
+ // thread, so we add the kill to the queue to be executed after revoke call.
+ val handler = Handler.createAsync(context.mainLooper)
+ handler.postDelayed({ Process.killProcess(Process.myPid()) }, 1000)
+ }
+}
diff --git a/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp b/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp
new file mode 100644
index 000000000..eaf10f50c
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission28/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: "CtsAppThatRequestsLocationAndBackgroundPermission28",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ "mts-permission",
+ "mcts-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..095c24093
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp
@@ -0,0 +1,34 @@
+//
+// 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",
+ "mcts-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..32d8505fb
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationPermission22/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: "CtsAppThatRequestsLocationPermission22",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-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..29ade84d2
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationPermission28/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: "CtsAppThatRequestsLocationPermission28",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-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..240795b30
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationPermission29/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: "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",
+ "mcts-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..262438a04
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestLocationPermission29v4/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: "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",
+ "mcts-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..4a8cddd5c
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestMultiplePermissionsWithMinMaxSdk/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: "CtsAppThatRequestsMultiplePermissionsWithMinMaxSdk",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-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..50a347928
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestOneTimePermission/Android.bp
@@ -0,0 +1,36 @@
+//
+// 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",
+ "mcts-permission",
+ ],
+ 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..b583b4603
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestPermissionAandB/Android.bp
@@ -0,0 +1,34 @@
+//
+// 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",
+ "mcts-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..ae309b0bb
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestPermissionAandC/Android.bp
@@ -0,0 +1,34 @@
+//
+// 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",
+ "mcts-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..a2bd97b3c
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestStoragePermission28/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: "CtsAppThatRequestsStoragePermission28",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-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..5b1c73f3a
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestStoragePermission29/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: "CtsAppThatRequestsStoragePermission29",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-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..8f0ff524f
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestSystemAlertWindow22/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: "CtsAppThatRequestsSystemAlertWindow22",
+ target_sdk_version: "22",
+ certificate: ":cts-testkey2",
+ min_sdk_version: "22",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+}
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..73c5c8813
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestSystemAlertWindow23/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: "CtsAppThatRequestsSystemAlertWindow23",
+ target_sdk_version: "23",
+ certificate: ":cts-testkey2",
+ min_sdk_version: "23",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+}
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..f6826784c
--- /dev/null
+++ b/tests/cts/permission/AppThatRunsRationaleTests/Android.bp
@@ -0,0 +1,36 @@
+//
+// 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",
+ "mcts-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..b9a0ed7bb
--- /dev/null
+++ b/tests/cts/permission/AppToTestRevokeSelfPermission/src/android/permission/cts/apptotestselfrevokepermission/RevokePermission.java
@@ -0,0 +1,49 @@
+/*
+ * 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 android.util.Log;
+
+import java.util.Arrays;
+
+public class RevokePermission extends Activity {
+ private static final String TAG = "RevokePermission";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState != null) {
+ Log.w(TAG, "Activity was recreated. (Perhaps due to a configuration change?)");
+ return;
+ }
+
+ 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..43e40d887
--- /dev/null
+++ b/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp
@@ -0,0 +1,34 @@
+//
+// 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",
+ "mcts-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..c76088043
--- /dev/null
+++ b/tests/cts/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp
@@ -0,0 +1,34 @@
+//
+// 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",
+ "mcts-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..a00cf0df1
--- /dev/null
+++ b/tests/cts/permission/AppWithSharedUidThatRequestsNoPermissions/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: "CtsAppWithSharedUidThatRequestsNoPermissions",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-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..ff95c1b86
--- /dev/null
+++ b/tests/cts/permission/AppWithSharedUidThatRequestsPermissions/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: "CtsAppWithSharedUidThatRequestsPermissions",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-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..f0c86433b
--- /dev/null
+++ b/tests/cts/permission/StorageEscalationApp28/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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",
+ "mcts-permission",
+ ],
+}
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..810032059
--- /dev/null
+++ b/tests/cts/permission/StorageEscalationApp29Full/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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",
+ "mcts-permission",
+ ],
+}
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..12a5310e2
--- /dev/null
+++ b/tests/cts/permission/StorageEscalationApp29Scoped/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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",
+ "mcts-permission",
+ ],
+}
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..f477231ef
--- /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="permissions" />
+ <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..acdf55ef1
--- /dev/null
+++ b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerHelperRule.kt
@@ -0,0 +1,56 @@
+/*
+ * 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..15d091f72
--- /dev/null
+++ b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/CtsNotificationListenerServiceUtils.kt
@@ -0,0 +1,127 @@
+/*
+ * 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..6bbf8b52e
--- /dev/null
+++ b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java
@@ -0,0 +1,414 @@
+/*
+ * 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.runShellCommandOrThrow;
+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;
+ runShellCommandOrThrow("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..2bdffabe5
--- /dev/null
+++ b/tests/cts/permission/sdk28/Android.bp
@@ -0,0 +1,34 @@
+//
+// 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",
+ "mcts-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..391142964
--- /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="permissions" />
+ <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..42b9067f3
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/AccessibilityPrivacySourceTest.kt
@@ -0,0 +1,328 @@
+/*
+ * 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.rule.ScreenRecordRule
+import android.provider.DeviceConfig
+import android.safetycenter.SafetyCenterManager
+import androidx.test.filters.FlakyTest
+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 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)
+ assertEmptyNotification(permissionControllerPackage, ACCESSIBILITY_NOTIFICATION_ID)
+ runWithShellPermissionIdentity { safetyCenterManager?.clearAllSafetySourceDataForTests() }
+ assertSafetyCenterIssueDoesNotExist(
+ SC_ACCESSIBILITY_SOURCE_ID,
+ safetyCenterIssueId,
+ SC_ACCESSIBILITY_ISSUE_TYPE_ID
+ )
+ }
+
+ @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 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 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..74e8a74ae
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/AccessibilityTestService.kt
@@ -0,0 +1,22 @@
+/*
+ * 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..47f011a34
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.runShellCommandOrThrow;
+
+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-permission/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() {
+ runShellCommandOrThrow("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..fa43bfb18
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/AppWidgetManagerPermissionTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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 androidx.test.filters.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..f3f47631c
--- /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-permission/";
+ 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..42da8c830
--- /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-permission/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..981735388
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/CameraPermissionTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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 androidx.test.filters.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..69b64d790
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/ContactsProviderTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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 androidx.test.filters.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/DevicePermissionsTest.kt b/tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt
new file mode 100644
index 000000000..3af2f895f
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt
@@ -0,0 +1,550 @@
+/*
+ * 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.Manifest
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
+import android.app.Instrumentation
+import android.companion.virtual.VirtualDeviceManager
+import android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
+import android.companion.virtual.VirtualDeviceManager.VirtualDevice
+import android.companion.virtual.VirtualDeviceParams
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME
+import android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED
+import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
+import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET
+import android.content.pm.PackageManager.PERMISSION_DENIED
+import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.os.Build
+import android.os.UserHandle
+import android.permission.PermissionManager
+import android.permission.flags.Flags
+import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.virtualdevice.cts.common.FakeAssociationRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.AdoptShellPermissionsRule
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.android.compatibility.common.util.SystemUtil.waitForBroadcasts
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assume.assumeNotNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@AppModeFull(reason = " cannot be accessed by instant apps")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName = "VanillaIceCream")
+class DevicePermissionsTest {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val defaultDeviceContext = instrumentation.targetContext
+
+ private lateinit var virtualDevice: VirtualDevice
+ private lateinit var virtualDeviceContext: Context
+ private lateinit var persistentDeviceId: String
+
+ private lateinit var permissionManager: PermissionManager
+
+ @get:Rule var mFakeAssociationRule = FakeAssociationRule()
+
+ @get:Rule
+ val mAdoptShellPermissionsRule =
+ AdoptShellPermissionsRule(
+ instrumentation.uiAutomation,
+ Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ Manifest.permission.GET_RUNTIME_PERMISSIONS
+ )
+
+ @Rule @JvmField val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Before
+ fun setup() {
+ val virtualDeviceManager =
+ defaultDeviceContext.getSystemService(VirtualDeviceManager::class.java)
+ assumeNotNull(virtualDeviceManager)
+ virtualDevice =
+ virtualDeviceManager!!.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ VirtualDeviceParams.Builder().build()
+ )
+ virtualDeviceContext = defaultDeviceContext.createDeviceContext(virtualDevice.deviceId)
+ permissionManager = virtualDeviceContext.getSystemService(PermissionManager::class.java)!!
+ persistentDeviceId = virtualDevice.persistentDeviceId!!
+ runShellCommandOrThrow("pm install -r $TEST_APK")
+ }
+
+ @After
+ fun cleanup() {
+ runShellCommandOrThrow("pm uninstall $TEST_PACKAGE_NAME")
+ virtualDevice.close()
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun testDeviceAwareRuntimePermissionIsGranted() {
+ grantPermissionAndAssertGranted(Manifest.permission.CAMERA, virtualDeviceContext)
+ }
+
+ @RequiresFlagsDisabled(Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED)
+ @Test
+ fun testDeviceAwareRuntimePermissionGrantIsInherited() {
+ grantPermissionAndAssertGranted(Manifest.permission.CAMERA, defaultDeviceContext)
+
+ assertPermission(Manifest.permission.CAMERA, PERMISSION_GRANTED, virtualDeviceContext)
+ }
+
+ @Test
+ fun testNonDeviceAwareRuntimePermissionGrantIsInherited() {
+ grantPermissionAndAssertGranted(NON_DEVICE_AWARE_PERMISSION, defaultDeviceContext)
+
+ assertPermission(NON_DEVICE_AWARE_PERMISSION, PERMISSION_GRANTED, virtualDeviceContext)
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun testDeviceAwareRuntimePermissionIsRevoked() {
+ grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
+
+ revokePermissionAndAssertDenied(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
+ }
+
+ @Test
+ fun testNonDeviceAwareRuntimePermissionIsRevokedForDefaultDevice() {
+ grantPermissionAndAssertGranted(NON_DEVICE_AWARE_PERMISSION, defaultDeviceContext)
+ assertPermission(NON_DEVICE_AWARE_PERMISSION, PERMISSION_GRANTED, virtualDeviceContext)
+ // Revoke call from virtualDeviceContext should revoke for default device as well.
+ revokePermissionAndAssertDenied(NON_DEVICE_AWARE_PERMISSION, virtualDeviceContext)
+ assertPermission(NON_DEVICE_AWARE_PERMISSION, PERMISSION_DENIED, defaultDeviceContext)
+ }
+
+ @Test
+ fun testNormalPermissionGrantIsInherited() {
+ assertPermission(Manifest.permission.INTERNET, PERMISSION_GRANTED, virtualDeviceContext)
+ }
+
+ @Test
+ fun testSignaturePermissionGrantIsInherited() {
+ assertPermission(CUSTOM_SIGNATURE_PERMISSION, PERMISSION_GRANTED, virtualDeviceContext)
+ }
+
+ @Test
+ fun testOneTimePermissionIsRevoked() {
+ grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
+ virtualDeviceContext.packageManager.updatePermissionFlags(
+ DEVICE_AWARE_PERMISSION,
+ TEST_PACKAGE_NAME,
+ FLAG_PERMISSION_ONE_TIME,
+ FLAG_PERMISSION_ONE_TIME,
+ UserHandle.of(virtualDeviceContext.userId)
+ )
+
+ permissionManager.startOneTimePermissionSession(
+ TEST_PACKAGE_NAME,
+ 0,
+ 0,
+ IMPORTANCE_FOREGROUND,
+ IMPORTANCE_FOREGROUND_SERVICE
+ )
+ eventually {
+ assertPermission(DEVICE_AWARE_PERMISSION, PERMISSION_DENIED, virtualDeviceContext)
+ }
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun testRevokeSelfPermissionOnKill() {
+ grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
+
+ revokeSelfPermission(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
+ eventually {
+ assertPermission(DEVICE_AWARE_PERMISSION, PERMISSION_DENIED, virtualDeviceContext)
+ }
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun testGrantAndRevokeDeviceAwarePermissionByPersistentDeviceId() {
+ val deviceAwarePermission = DEVICE_AWARE_PERMISSION
+
+ permissionManager.grantRuntimePermission(
+ TEST_PACKAGE_NAME,
+ deviceAwarePermission,
+ persistentDeviceId
+ )
+
+ assertThat(
+ permissionManager.checkPermission(
+ deviceAwarePermission,
+ TEST_PACKAGE_NAME,
+ virtualDevice.persistentDeviceId!!
+ )
+ )
+ .isEqualTo(PERMISSION_GRANTED)
+
+ assertThat(
+ permissionManager.checkPermission(
+ deviceAwarePermission,
+ TEST_PACKAGE_NAME,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
+ )
+ )
+ .isEqualTo(PERMISSION_DENIED)
+
+ permissionManager.revokeRuntimePermission(
+ TEST_PACKAGE_NAME,
+ deviceAwarePermission,
+ persistentDeviceId,
+ "test"
+ )
+
+ assertThat(
+ permissionManager.checkPermission(
+ deviceAwarePermission,
+ TEST_PACKAGE_NAME,
+ virtualDevice.persistentDeviceId!!
+ )
+ )
+ .isEqualTo(PERMISSION_DENIED)
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun testUpdateAndGetPermissionFlagsByPersistentDeviceId() {
+ val deviceAwarePermission = DEVICE_AWARE_PERMISSION
+ val flagMask = FLAG_PERMISSION_USER_SET or FLAG_PERMISSION_USER_FIXED
+ val flag = FLAG_PERMISSION_USER_SET
+
+ assertThat(
+ permissionManager.getPermissionFlags(
+ TEST_PACKAGE_NAME,
+ deviceAwarePermission,
+ persistentDeviceId
+ )
+ )
+ .isEqualTo(0)
+
+ permissionManager.updatePermissionFlags(
+ TEST_PACKAGE_NAME,
+ deviceAwarePermission,
+ persistentDeviceId,
+ flagMask,
+ flag
+ )
+
+ assertThat(
+ permissionManager.getPermissionFlags(
+ TEST_PACKAGE_NAME,
+ deviceAwarePermission,
+ persistentDeviceId
+ )
+ )
+ .isEqualTo(FLAG_PERMISSION_USER_SET)
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun testAllPermissionStatesApiGrantForVirtualDevice() {
+ assertThat(
+ permissionManager
+ .getAllPermissionStates(TEST_PACKAGE_NAME, persistentDeviceId)
+ .isEmpty()
+ )
+ .isTrue()
+
+ permissionManager.grantRuntimePermission(
+ TEST_PACKAGE_NAME,
+ DEVICE_AWARE_PERMISSION,
+ persistentDeviceId
+ )
+
+ val permissionStateMap =
+ permissionManager.getAllPermissionStates(TEST_PACKAGE_NAME, persistentDeviceId)
+ assertThat(permissionStateMap.size).isEqualTo(1)
+ assertThat(permissionStateMap[DEVICE_AWARE_PERMISSION]!!.isGranted).isTrue()
+ assertThat(permissionStateMap[DEVICE_AWARE_PERMISSION]!!.flags).isEqualTo(0)
+
+ assertThat(
+ permissionManager
+ .getAllPermissionStates(TEST_PACKAGE_NAME, PERSISTENT_DEVICE_ID_DEFAULT)
+ .contains(DEVICE_AWARE_PERMISSION)
+ )
+ .isFalse()
+
+ permissionManager.revokeRuntimePermission(
+ TEST_PACKAGE_NAME,
+ DEVICE_AWARE_PERMISSION,
+ persistentDeviceId,
+ "test"
+ )
+
+ assertThat(
+ permissionManager
+ .getAllPermissionStates(TEST_PACKAGE_NAME, persistentDeviceId)
+ .contains(DEVICE_AWARE_PERMISSION)
+ )
+ .isFalse()
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun testAllPermissionStatesApiFlagsForVirtualDevice() {
+ val flagMask = FLAG_PERMISSION_USER_SET or FLAG_PERMISSION_USER_FIXED
+ val flag = FLAG_PERMISSION_USER_SET
+
+ assertThat(permissionManager.getAllPermissionStates(TEST_PACKAGE_NAME, persistentDeviceId))
+ .isEmpty()
+
+ permissionManager.updatePermissionFlags(
+ TEST_PACKAGE_NAME,
+ DEVICE_AWARE_PERMISSION,
+ persistentDeviceId,
+ flagMask,
+ flag
+ )
+
+ assertThat(
+ hasPermission(
+ permissionManager
+ .getAllPermissionStates(TEST_PACKAGE_NAME, persistentDeviceId)[
+ DEVICE_AWARE_PERMISSION]!!
+ .flags,
+ flag
+ )
+ )
+ .isTrue()
+
+ assertThat(
+ hasPermission(
+ permissionManager
+ .getAllPermissionStates(TEST_PACKAGE_NAME, persistentDeviceId)[
+ DEVICE_AWARE_PERMISSION]!!
+ .flags,
+ FLAG_PERMISSION_USER_FIXED
+ )
+ )
+ .isFalse()
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ @Test
+ fun testAllPermissionStatesApiGrantForDefaultDevice() {
+ // Setting a flag explicitly so that the permission consistently stays in the state upon
+ // revoke
+ permissionManager.updatePermissionFlags(
+ TEST_PACKAGE_NAME,
+ DEVICE_AWARE_PERMISSION,
+ PERSISTENT_DEVICE_ID_DEFAULT,
+ FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED,
+ FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
+ )
+
+ permissionManager.grantRuntimePermission(
+ TEST_PACKAGE_NAME,
+ DEVICE_AWARE_PERMISSION,
+ PERSISTENT_DEVICE_ID_DEFAULT
+ )
+
+ assertThat(
+ permissionManager
+ .getAllPermissionStates(TEST_PACKAGE_NAME, PERSISTENT_DEVICE_ID_DEFAULT)[
+ DEVICE_AWARE_PERMISSION]!!
+ .isGranted
+ )
+ .isTrue()
+
+ assertThat(
+ permissionManager
+ .getAllPermissionStates(TEST_PACKAGE_NAME, persistentDeviceId)
+ .contains(DEVICE_AWARE_PERMISSION)
+ )
+ .isFalse()
+
+ permissionManager.revokeRuntimePermission(
+ TEST_PACKAGE_NAME,
+ DEVICE_AWARE_PERMISSION,
+ PERSISTENT_DEVICE_ID_DEFAULT,
+ "test"
+ )
+
+ assertThat(
+ permissionManager
+ .getAllPermissionStates(TEST_PACKAGE_NAME, PERSISTENT_DEVICE_ID_DEFAULT)[
+ DEVICE_AWARE_PERMISSION]!!
+ .isGranted
+ )
+ .isFalse()
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ @Test
+ fun testAllPermissionStatesApiFlagsForDefaultDevice() {
+ val flagMask = FLAG_PERMISSION_USER_SET or FLAG_PERMISSION_USER_FIXED
+ val flag = FLAG_PERMISSION_USER_SET
+
+ assertThat(
+ permissionManager
+ .getAllPermissionStates(TEST_PACKAGE_NAME, PERSISTENT_DEVICE_ID_DEFAULT)
+ .contains(DEVICE_AWARE_PERMISSION)
+ )
+ .isFalse()
+
+ permissionManager.updatePermissionFlags(
+ TEST_PACKAGE_NAME,
+ DEVICE_AWARE_PERMISSION,
+ PERSISTENT_DEVICE_ID_DEFAULT,
+ flagMask,
+ flag
+ )
+
+ assertThat(
+ hasPermission(
+ permissionManager
+ .getAllPermissionStates(TEST_PACKAGE_NAME, PERSISTENT_DEVICE_ID_DEFAULT)[
+ DEVICE_AWARE_PERMISSION]!!
+ .flags,
+ flag
+ )
+ )
+ .isTrue()
+
+ assertThat(
+ hasPermission(
+ permissionManager
+ .getAllPermissionStates(TEST_PACKAGE_NAME, PERSISTENT_DEVICE_ID_DEFAULT)[
+ DEVICE_AWARE_PERMISSION]!!
+ .flags,
+ FLAG_PERMISSION_USER_FIXED
+ )
+ )
+ .isFalse()
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ @Test
+ fun testAllPermissionStatesApiThatNonDeviceAwareRuntimePermissionGrantIsNotInherited() {
+ permissionManager.grantRuntimePermission(
+ TEST_PACKAGE_NAME,
+ NON_DEVICE_AWARE_PERMISSION,
+ PERSISTENT_DEVICE_ID_DEFAULT
+ )
+
+ assertThat(
+ permissionManager
+ .getAllPermissionStates(TEST_PACKAGE_NAME, PERSISTENT_DEVICE_ID_DEFAULT)[
+ NON_DEVICE_AWARE_PERMISSION]!!
+ .isGranted
+ )
+ .isTrue()
+
+ assertThat(
+ permissionManager
+ .getAllPermissionStates(TEST_PACKAGE_NAME, persistentDeviceId)
+ .contains(NON_DEVICE_AWARE_PERMISSION)
+ )
+ .isFalse()
+ }
+
+ private fun hasPermission(permissionFlags: Int, permissionBit: Int): Boolean =
+ permissionFlags and permissionBit != 0
+
+ private fun revokeSelfPermission(permissionName: String, context: Context) {
+ val intent = Intent(PERMISSION_SELF_REVOKE_INTENT)
+ intent.setClassName(TEST_PACKAGE_NAME, PERMISSION_SELF_REVOKE_RECEIVER)
+ intent.putExtra("permissionName", permissionName)
+ intent.putExtra("deviceID", context.deviceId)
+ context.sendBroadcast(intent)
+ waitForBroadcasts()
+ }
+
+ private fun grantPermissionAndAssertGranted(permissionName: String, context: Context) {
+ context.packageManager.grantRuntimePermission(
+ TEST_PACKAGE_NAME,
+ permissionName,
+ UserHandle.of(context.userId)
+ )
+ assertPermission(permissionName, PERMISSION_GRANTED, context)
+ }
+
+ private fun revokePermissionAndAssertDenied(permissionName: String, context: Context) {
+ context.packageManager.revokeRuntimePermission(
+ TEST_PACKAGE_NAME,
+ permissionName,
+ UserHandle.of(context.userId)
+ )
+ assertPermission(permissionName, PERMISSION_DENIED, context)
+ }
+
+ private fun assertPermission(
+ permissionName: String,
+ permissionState: Int,
+ context: Context,
+ ) {
+ assertThat(context.packageManager.checkPermission(permissionName, TEST_PACKAGE_NAME))
+ .isEqualTo(permissionState)
+ }
+
+ companion object {
+ private const val TEST_PACKAGE_NAME = "android.permission.cts.appthatrequestpermission"
+ private const val TEST_APK =
+ "/data/local/tmp/cts-permission/CtsAppThatRequestsDevicePermissions.apk"
+
+ private const val CUSTOM_SIGNATURE_PERMISSION =
+ "android.permission.cts.CUSTOM_SIGNATURE_PERMISSION"
+
+ private const val PERMISSION_SELF_REVOKE_INTENT =
+ "android.permission.cts.appthatrequestpermission.REVOKE_SELF_PERMISSION"
+ private const val PERMISSION_SELF_REVOKE_RECEIVER =
+ "android.permission.cts.appthatrequestpermission.RevokeSelfPermissionReceiver"
+
+ private const val DEVICE_AWARE_PERMISSION = Manifest.permission.RECORD_AUDIO
+ private const val NON_DEVICE_AWARE_PERMISSION = Manifest.permission.READ_CONTACTS
+ }
+}
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..ceb797b80
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/DuplicatePermissionDefinitionsTest.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+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-permission/"
+
+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) {
+ runShellCommandOrThrow("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..f59883921
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/LocationAccessCheckTest.java
@@ -0,0 +1,806 @@
+/*
+ * 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.runShellCommandOrThrow;
+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.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.FlakyTest;
+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-permission/CtsAppThatAccessesLocationOnCommand.apk";
+ private static final String TEST_APP_LOCATION_FG_ACCESS_APK =
+ "/data/local/tmp/cts-permission/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;
+
+ 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 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 =
+ runShellCommandOrThrow("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 static void installForegroundAccessApp() throws Exception {
+ unbindService();
+ runShellCommandOrThrow("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
+ runLocationCheck();
+
+ 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");
+ }
+
+ /**
+ * 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 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
+ @AsbSecurityTest(cveBugId = 141028068)
+ public void noNotificationWhenLocationNeverAccessed() throws Throwable {
+ assumeNotPlayManaged();
+
+ // Reset to clear property location_access_check_enabled_time has been already happened
+ // when resetPermissionController() invoked from before test method
+
+ runLocationCheck();
+
+ // Not expecting notification as location is not accessed and previously set
+ // LOCATION_ACCESS_CHECK_ENABLED_TIME if any is cleaned up
+ assertNull(getNotification(false));
+ }
+
+ @Test
+ @AsbSecurityTest(cveBugId = 141028068)
+ public void notificationWhenLocationAccessed() throws Throwable {
+ assumeNotPlayManaged();
+
+ // Reset to clear property location_access_check_enabled_time has been already happened
+ // when resetPermissionController() invoked from before test method
+
+ accessLocation();
+ runLocationCheck();
+
+ // Expecting notification as accessing the location causes
+ // LOCATION_ACCESS_CHECK_ENABLED_TIME to be set
+ eventually(() -> assertNotNull(getNotification(true)), EXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ @AsbSecurityTest(cveBugId = 141028068)
+ public void noNotificationWhenLocationAccessedPriorToEnableTime() throws Throwable {
+ assumeNotPlayManaged();
+
+ accessLocation();
+
+ // Reset to clear the property location_access_check_enabled_time
+ resetPermissionController();
+
+ runLocationCheck();
+
+ // Not expecting the notification as the location
+ // access was prior to LOCATION_ACCESS_CHECK_ENABLED_TIME (No notification for prior events)
+ 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..4679fbee8
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/MinMaxSdkVersionTest.kt
@@ -0,0 +1,97 @@
+/*
+ * 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-permission/"
+ 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..d4ad2ad04
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NearbyDevicesPermissionTest.java
@@ -0,0 +1,307 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.bluetooth.cts.EnableBluetoothRule;
+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 com.android.compatibility.common.util.EnableLocationRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+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-permission/";
+ 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 final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ @ClassRule
+ public static final EnableBluetoothRule sEnableBluetoothRule = new EnableBluetoothRule(true);
+
+ @Rule
+ public final EnableLocationRule enableLocationRule = new EnableLocationRule();
+
+ public void uninstallTestApp() {
+ uninstallApp(TEST_APP_PKG);
+ uninstallApp(DISAVOWAL_APP_PKG);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ uninstallTestApp();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ uninstallTestApp();
+ }
+
+ @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 supportsBluetoothLe() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
+ }
+
+}
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..aeb4d1d28
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NearbyDevicesRenouncePermissionTest.java
@@ -0,0 +1,315 @@
+/*
+ * 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.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.AppOpsManager;
+import android.app.AsyncNotedAppOp;
+import android.app.SyncNotedAppOp;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.cts.EnableBluetoothRule;
+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.provider.DeviceConfig;
+import android.util.ArraySet;
+import android.util.Base64;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
+import com.android.compatibility.common.util.EnableLocationRule;
+import com.android.compatibility.common.util.SystemUtil;
+
+import com.google.common.util.concurrent.Uninterruptibles;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 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 final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+ @ClassRule
+ public static final EnableBluetoothRule sEnableBluetoothRule = new EnableBluetoothRule(true);
+
+ @Rule
+ public DeviceConfigStateChangerRule safetyLabelChangeNotificationsEnabledConfig =
+ new DeviceConfigStateChangerRule(
+ mContext,
+ DeviceConfig.NAMESPACE_BLUETOOTH,
+ "scan_quota_count",
+ Integer.toString(1000)
+ );
+
+ @Rule
+ public final EnableLocationRule enableLocationRule = new EnableLocationRule();
+
+ private AppOpsManager mAppOpsManager;
+
+ private volatile long mTestStartTimestamp;
+ private final AtomicInteger mLocationNoteCount = new AtomicInteger(0);
+ private final AtomicInteger mScanNoteCount = new AtomicInteger(0);
+
+ private enum Result {
+ UNKNOWN, EXCEPTION, EMPTY, FILTERED, FULL
+ }
+
+ private enum Scenario {
+ DEFAULT, RENOUNCE, RENOUNCE_MIDDLE, RENOUNCE_END
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ // Sleep to guarantee that past noteOp timestamps are less than mTestStartTimestamp
+ Uninterruptibles.sleepUninterruptibly(2, TimeUnit.MILLISECONDS);
+ mTestStartTimestamp = System.currentTimeMillis();
+
+ mLocationNoteCount.set(0);
+ mScanNoteCount.set(0);
+
+ 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:
+ logNoteOp(op);
+ mLocationNoteCount.incrementAndGet();
+ break;
+ case OPSTR_BLUETOOTH_SCAN:
+ logNoteOp(op);
+ mScanNoteCount.incrementAndGet();
+ break;
+ default:
+ }
+ }
+
+ @Override
+ public void onSelfNoted(SyncNotedAppOp op) {
+ }
+
+ @Override
+ public void onAsyncNoted(AsyncNotedAppOp asyncOp) {
+ switch (asyncOp.getOp()) {
+ case OPSTR_FINE_LOCATION:
+ logNoteOp(asyncOp);
+ if (asyncOp.getTime() < mTestStartTimestamp) {
+ Log.i(TAG, "ignoring asyncOp that originated before test "
+ + "start");
+ return;
+ }
+ mLocationNoteCount.incrementAndGet();
+ break;
+ case OPSTR_BLUETOOTH_SCAN:
+ logNoteOp(asyncOp);
+ if (asyncOp.getTime() < mTestStartTimestamp) {
+ Log.i(TAG, "ignoring asyncOp that originated before test "
+ + "start");
+ return;
+ }
+ mScanNoteCount.incrementAndGet();
+ break;
+ default:
+ }
+ }
+ });
+ }
+
+ private void logNoteOp(SyncNotedAppOp op) {
+ Log.i(TAG, "OnOpNotedCallback::onNoted(op=" + op.getOp() + ")");
+ }
+
+ private void logNoteOp(AsyncNotedAppOp asyncOp) {
+ Log.i(TAG, "OnOpNotedCallback::"
+ + "onAsyncNoted(op=" + asyncOp.getOp()
+ + ", testStartTimestamp=" + mTestStartTimestamp
+ + ", noteOpTimestamp=" + asyncOp.getTime() + ")");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mAppOpsManager.setOnOpNotedCallback(null, null);
+ }
+
+ @AppModeFull
+ @Test
+ public void scanWithoutRenouncingNotesBluetoothAndLocation() throws Exception {
+ assumeTrue(supportsBluetoothLe());
+
+ assertThat(performScan(Scenario.DEFAULT)).isEqualTo(Result.FULL);
+ SystemUtil.eventually(() -> {
+ assertThat(mLocationNoteCount.get()).isGreaterThan(0);
+ assertThat(mScanNoteCount.get()).isGreaterThan(0);
+ });
+ }
+
+ @AppModeFull
+ @Test
+ public void scanRenouncingLocationNotesBluetoothButNotLocation() throws Exception {
+ assumeTrue(supportsBluetoothLe());
+
+ assertThat(performScan(Scenario.RENOUNCE)).isEqualTo(Result.FILTERED);
+ SystemUtil.eventually(() -> {
+ assertThat(mLocationNoteCount.get()).isEqualTo(0);
+ assertThat(mScanNoteCount.get()).isGreaterThan(0);
+ });
+ }
+
+ @AppModeFull
+ @Test
+ public void scanRenouncingInMiddleOfChainNotesBluetoothButNotLocation() throws Exception {
+ assumeTrue(supportsBluetoothLe());
+
+ assertThat(performScan(Scenario.RENOUNCE_MIDDLE)).isEqualTo(Result.FILTERED);
+ SystemUtil.eventually(() -> {
+ assertThat(mLocationNoteCount.get()).isEqualTo(0);
+ assertThat(mScanNoteCount.get()).isGreaterThan(0);
+ });
+ }
+
+ @AppModeFull
+ @Test
+ public void scanRenouncingAtEndOfChainNotesBluetoothButNotLocation() throws Exception {
+ assertThat(performScan(Scenario.RENOUNCE_END)).isEqualTo(Result.FILTERED);
+ SystemUtil.eventually(() -> {
+ assertThat(mLocationNoteCount.get()).isEqualTo(0);
+ assertThat(mScanNoteCount.get()).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 supportsBluetoothLe() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
+ }
+}
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..3d9ba8214
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.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.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.test.ActivityInstrumentationTestCase2;
+
+import androidx.test.filters.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..50498b1d5
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoAudioPermissionTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.util.Log;
+
+import androidx.test.filters.SmallTest;
+
+/**
+ * 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..1a46842b2
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoBroadcastPackageRemovedPermissionTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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 androidx.test.filters.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..e0573044c
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoCaptureVideoPermissionTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.util.DisplayMetrics;
+
+import androidx.test.filters.SmallTest;
+
+/**
+ * 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..ac77947d9
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoKeyPermissionTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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 androidx.test.filters.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..5dc73d520
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoNetworkStatePermissionTest.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.content.Context;
+import android.net.ConnectivityManager;
+import android.test.AndroidTestCase;
+
+import androidx.test.filters.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..f0d70b2ce
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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 androidx.test.filters.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..437aa19c4
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.os.VibratorManager;
+import android.platform.test.annotations.AppModeFull;
+import android.telephony.gsm.SmsManager;
+import android.test.AndroidTestCase;
+
+import androidx.test.filters.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 = mContext.getSystemService(VibratorManager.class).getDefaultVibrator();
+
+ if (!vibrator.hasVibrator()) {
+ // Run the test only if a vibrator is present.
+ return;
+ }
+
+ if (!vibrator.hasVibrator()) {
+ // If the test device does not have a vibrator, then abort test.
+ return;
+ }
+
+ 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..95c4da727
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoWakeLockPermissionTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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 androidx.test.filters.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..fc1d6b59f
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/NoWallpaperPermissionsTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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 androidx.test.filters.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..19fc20de6
--- /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.rule.ScreenRecordRule;
+import android.service.notification.StatusBarNotification;
+
+import androidx.test.filters.FlakyTest;
+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..f5f222f80
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/OneTimePermissionTest.java
@@ -0,0 +1,396 @@
+/*
+ * 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.runShellCommandOrThrow;
+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.rule.ScreenRecordRule;
+import android.provider.DeviceConfig;
+
+import androidx.test.filters.FlakyTest;
+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-permission/CtsAppThatRequestsOneTimePermission.apk";
+ private static final String CUSTOM_CAMERA_PERM_APK =
+ "/data/local/tmp/cts-permission/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() {
+ runShellCommandOrThrow("pm install -r " + APK);
+ runShellCommandOrThrow("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)), 30000);
+
+ // This checks the vulnerability
+ eventually(() -> Assert.assertEquals(PackageManager.PERMISSION_DENIED,
+ mContext.getPackageManager()
+ .checkPermission(CUSTOM_PERMISSION, CUSTOM_CAMERA_PERM_APP_PKG_NAME)),
+ 30000);
+
+ }
+
+ 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..7ebb09f98
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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 androidx.test.filters.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..4367d2bf6
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PermissionControllerTest.java
@@ -0,0 +1,517 @@
+/*
+ * 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.content.pm.PackageManager.PERMISSION_GRANTED;
+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.runShellCommandOrThrow;
+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-permission/CtsAppThatAccessesLocationOnCommand.apk";
+ private static final String APP = "android.permission.cts.appthataccesseslocation";
+ private static final String APK2 =
+ "/data/local/tmp/cts-permission/"
+ + "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() {
+ runShellCommandOrThrow("pm install -r -g " + APK);
+ runShellCommandOrThrow("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 {
+ assertThat(sContext.getPackageManager().checkPermission(ACCESS_FINE_LOCATION,
+ APP)).isEqualTo(PERMISSION_GRANTED);
+
+ 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(6);
+ 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..d03e215ed
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PermissionFlagsTest.java
@@ -0,0 +1,254 @@
+/*
+ * 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 android.platform.test.annotations.PlatinumTest;
+
+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.")
+@PlatinumTest
+@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-permission/";
+ 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..46fe167c0
--- /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.runShellCommandOrThrow(
+ "pm install -r /data/local/tmp/cts-permission/" + 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..33975f91c
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PermissionUpdateListenerTest.java
@@ -0,0 +1,273 @@
+/*
+ * 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.runShellCommandOrThrow;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.OnPermissionsChangedListener;
+import android.permission.flags.Flags;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.virtualdevice.cts.common.FakeAssociationRule;
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+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 LOG_TAG = PermissionUpdateListenerTest.class.getSimpleName();
+ private static final String APK =
+ "/data/local/tmp/cts-permission/"
+ + "CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk";
+ private static final String PACKAGE_NAME =
+ "android.permission.cts.appthatrequestcustompermission";
+ private static final String PERMISSION_NAME = "android.permission.RECORD_AUDIO";
+ private static final int TIMEOUT = 10000;
+
+ private final Context mDefaultContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+ private final PackageManager mPackageManager = mDefaultContext.getPackageManager();
+
+ private int mTestAppUid;
+
+ private VirtualDeviceManager mVirtualDeviceManager;
+
+ @Rule
+ public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
+
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE);
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ public void setup() throws PackageManager.NameNotFoundException, InterruptedException {
+ runShellCommandOrThrow("pm install " + APK);
+ // permission change events are generated for a package install, the wait helps prevent
+ // those permission change events interfere with the test.
+ SystemUtil.waitForBroadcasts();
+ Thread.sleep(1000);
+ mTestAppUid = mPackageManager.getPackageUid(PACKAGE_NAME, 0);
+ mVirtualDeviceManager = mDefaultContext.getSystemService(VirtualDeviceManager.class);
+ }
+
+ @After
+ public void cleanup() {
+ runShellCommand("pm uninstall " + PACKAGE_NAME);
+ }
+
+ @Test
+ public void testGrantPermissionInvokesOldCallback() throws InterruptedException {
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ OnPermissionsChangedListener permissionsChangedListener =
+ uid -> {
+ if (mTestAppUid == uid) {
+ countDownLatch.countDown();
+ }
+ };
+
+ runWithShellPermissionIdentity(() -> {
+ mPackageManager.addOnPermissionsChangeListener(permissionsChangedListener);
+ mPackageManager.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME,
+ mDefaultContext.getUser());
+ });
+ countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+ runWithShellPermissionIdentity(() -> {
+ mPackageManager.removeOnPermissionsChangeListener(permissionsChangedListener);
+ });
+
+ assertThat(countDownLatch.getCount()).isEqualTo(0);
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED})
+ public void testVirtualDeviceGrantPermissionNotifyListener() throws InterruptedException {
+ VirtualDeviceManager.VirtualDevice virtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().build());
+ Context deviceContext = mDefaultContext.createDeviceContext(virtualDevice.getDeviceId());
+ testGrantPermissionNotifyListener(deviceContext, virtualDevice.getPersistentDeviceId());
+ }
+
+ @Test
+ public void testDefaultDeviceGrantPermissionNotifyListener() throws InterruptedException {
+ testGrantPermissionNotifyListener(
+ mDefaultContext, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+
+ private void testGrantPermissionNotifyListener(
+ Context context, String expectedDeviceId) throws InterruptedException {
+ final PackageManager packageManager = context.getPackageManager();
+ TestOnPermissionsChangedListener permissionsChangedListener =
+ new TestOnPermissionsChangedListener(1);
+ runWithShellPermissionIdentity(() -> {
+ packageManager.addOnPermissionsChangeListener(permissionsChangedListener);
+ packageManager.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME,
+ mDefaultContext.getUser());
+ });
+
+ permissionsChangedListener.waitForPermissionChangedCallbacks();
+ runWithShellPermissionIdentity(() -> {
+ packageManager.removeOnPermissionsChangeListener(permissionsChangedListener);
+ });
+
+ String deviceId = permissionsChangedListener.getNotifiedDeviceId(mTestAppUid);
+ assertThat(deviceId).isEqualTo(expectedDeviceId);
+ }
+
+ @Test
+ public void testDefaultDeviceRevokePermissionNotifyListener() throws InterruptedException {
+ testRevokePermissionNotifyListener(
+ mDefaultContext, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED})
+ public void testVirtualDeviceRevokePermissionNotifyListener() throws InterruptedException {
+ VirtualDeviceManager.VirtualDevice virtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().build());
+ Context deviceContext = mDefaultContext.createDeviceContext(virtualDevice.getDeviceId());
+ testRevokePermissionNotifyListener(
+ deviceContext, virtualDevice.getPersistentDeviceId());
+ }
+
+ private void testRevokePermissionNotifyListener(
+ Context context, String expectedDeviceId) throws InterruptedException {
+ final PackageManager packageManager = context.getPackageManager();
+ TestOnPermissionsChangedListener permissionsChangedListener =
+ new TestOnPermissionsChangedListener(1);
+ runWithShellPermissionIdentity(() -> {
+ packageManager.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME,
+ mDefaultContext.getUser());
+ packageManager.addOnPermissionsChangeListener(permissionsChangedListener);
+ packageManager.revokeRuntimePermission(PACKAGE_NAME, PERMISSION_NAME,
+ mDefaultContext.getUser());
+ });
+ permissionsChangedListener.waitForPermissionChangedCallbacks();
+ runWithShellPermissionIdentity(() -> {
+ packageManager.removeOnPermissionsChangeListener(permissionsChangedListener);
+ });
+
+ String deviceId = permissionsChangedListener.getNotifiedDeviceId(mTestAppUid);
+ assertThat(deviceId).isEqualTo(expectedDeviceId);
+ }
+
+ @Test
+ public void testDefaultDeviceUpdatePermissionFlagsNotifyListener() throws InterruptedException {
+ testUpdatePermissionFlagsNotifyListener(
+ mDefaultContext, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT);
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED})
+ public void testVirtualDeviceUpdatePermissionFlagsNotifyListener() throws InterruptedException {
+ VirtualDeviceManager.VirtualDevice virtualDevice =
+ mVirtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ new VirtualDeviceParams.Builder().build());
+ Context deviceContext = mDefaultContext.createDeviceContext(virtualDevice.getDeviceId());
+ testUpdatePermissionFlagsNotifyListener(
+ deviceContext, virtualDevice.getPersistentDeviceId());
+ }
+
+ private void testUpdatePermissionFlagsNotifyListener(
+ Context context, String expectedDeviceId) throws InterruptedException {
+ TestOnPermissionsChangedListener permissionsChangedListener =
+ new TestOnPermissionsChangedListener(1);
+ final PackageManager packageManager = context.getPackageManager();
+ runWithShellPermissionIdentity(() -> {
+ packageManager.addOnPermissionsChangeListener(permissionsChangedListener);
+ int flag = PackageManager.FLAG_PERMISSION_USER_SET;
+ packageManager.updatePermissionFlags(PERMISSION_NAME, PACKAGE_NAME, flag, flag,
+ mDefaultContext.getUser());
+ });
+ permissionsChangedListener.waitForPermissionChangedCallbacks();
+ runWithShellPermissionIdentity(() -> {
+ packageManager.removeOnPermissionsChangeListener(permissionsChangedListener);
+ });
+
+ String deviceId = permissionsChangedListener.getNotifiedDeviceId(mTestAppUid);
+ assertThat(deviceId).isEqualTo(expectedDeviceId);
+ }
+
+ private class TestOnPermissionsChangedListener
+ implements OnPermissionsChangedListener {
+ // map of uid and persistentDeviceID
+ private final Map<Integer, String> mUidDeviceIdsMap = new ConcurrentHashMap<>();
+ private final CountDownLatch mCountDownLatch;
+
+ TestOnPermissionsChangedListener(int expectedCallbackCount) {
+ mCountDownLatch = new CountDownLatch(expectedCallbackCount);
+ }
+
+ @Override
+ public void onPermissionsChanged(int uid) {
+ // ignored when we implement the new callback.
+ }
+
+ @Override
+ public void onPermissionsChanged(int uid, String deviceId) {
+ if (uid == mTestAppUid) {
+ mCountDownLatch.countDown();
+ mUidDeviceIdsMap.put(uid, deviceId);
+ }
+ }
+
+ String getNotifiedDeviceId(int uid) {
+ return mUidDeviceIdsMap.get(uid);
+ }
+
+ void waitForPermissionChangedCallbacks() throws InterruptedException {
+ mCountDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+ assertThat(mCountDownLatch.getCount()).isEqualTo(0);
+ }
+ }
+}
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..2816bbb5a
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/PlatformPermissionGroupMappingTest.kt
@@ -0,0 +1,62 @@
+/*
+ * 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..83c2ffaee
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/ProviderPermissionTest.java
@@ -0,0 +1,294 @@
+/*
+ * 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.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+
+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..13f17dce8
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/RebootPermissionTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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 androidx.test.filters.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/RecordSensitiveContentPermissionTest.kt b/tests/cts/permission/src/android/permission/cts/RecordSensitiveContentPermissionTest.kt
new file mode 100644
index 000000000..b80f89938
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/RecordSensitiveContentPermissionTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION
+import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SdkSuppress
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+@AppModeFull(reason = "Instant apps cannot install packages")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName = "VanillaIceCream")
+class RecordSensitiveContentPermissionTest {
+ @Rule
+ @JvmField
+ val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
+ fun testRecordSensitiveContentDuringProjection() {
+ val packageManager = InstrumentationRegistry.getContext().getPackageManager()
+ val packagesHoldingPermission =
+ packageManager
+ .getPackagesHoldingPermissions(
+ arrayOf(android.Manifest.permission.RECORD_SENSITIVE_CONTENT),
+ 0
+ )
+ .map { it.packageName }
+
+ if (packagesHoldingPermission.size > 1) {
+ Assert.fail(
+ "Only one system app on the device is allowed to hold the " +
+ "RECORD_SENSITIVE_CONTENT_DURING_PROJECTION permission, " +
+ "packages holding the permissions are: " +
+ packagesHoldingPermission
+ )
+ }
+ }
+}
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..915918f71
--- /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.runShellCommandOrThrow(
+ "pm install -r -d /data/local/tmp/cts-permission/" + 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..579b03f9c
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/RevokePermissionTest.kt
@@ -0,0 +1,183 @@
+/*
+ * 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-permission/" +
+ "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 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 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..57a2f7fcb
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/RevokeSawPermissionTest.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.AppModeFull
+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-permission/" + "CtsAppThatRequestsSystemAlertWindow22.apk"
+private val APK_23 = "/data/local/tmp/cts-permission/" + "CtsAppThatRequestsSystemAlertWindow23.apk"
+
+@AppModeFull
+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..674fa2d12
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/RevokeSelfPermissionTest.java
@@ -0,0 +1,361 @@
+/*
+ * 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.runShellCommandOrThrow;
+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-permission/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() {
+ runShellCommandOrThrow("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..7ca9b138d
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/SafetyCenterUtils.kt
@@ -0,0 +1,158 @@
+/*
+ * 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..0c1c885be
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/ServicesInstantAppsCannotAccessTests.java
@@ -0,0 +1,85 @@
+/*
+ * 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.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 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..4966a870b
--- /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 androidx.test.filters.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-permission/";
+ 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..54562d5ee
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/ShellCommandPermissionTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test shell command capability enforcement
+ */
+@RunWith(AndroidJUnit4.class)
+public class ShellCommandPermissionTest {
+
+ static final int EXPECTED_ERROR_CODE = 255;
+
+ /**
+ * Runs the given command, waits for it to exit, and verifies the return
+ * code indicates failure.
+ */
+ private void executeShellCommandAndWaitForError(String command)
+ throws Exception {
+ try {
+ java.lang.Process proc = Runtime.getRuntime().exec(command);
+ assertThat(proc.waitFor()).isEqualTo(EXPECTED_ERROR_CODE);
+ } catch (InterruptedException e) {
+ fail("Unsuccessful shell command");
+ }
+ }
+
+ @Test
+ public void testTraceIpc() throws Exception {
+ executeShellCommandAndWaitForError(
+ "cmd activity trace-ipc stop --dump-file /data/system/last-fstrim");
+ }
+
+ @Test
+ public void testDumpheap() throws Exception {
+ executeShellCommandAndWaitForError(
+ "cmd activity dumpheap system_server /data/system/last-fstrim");
+ }
+}
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..a509b3bfe
--- /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.SystemUserOnly;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+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-permission/";
+ 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..2458baeb2
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/StorageEscalationTest.kt
@@ -0,0 +1,158 @@
+/*
+ * 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-permission"
+ 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.runShellCommandOrThrow("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..4414402ff
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/UndefinedGroupPermissionTest.kt
@@ -0,0 +1,247 @@
+/*
+ * 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.runShellCommandOrThrow(
+ "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 if (
+ mContext?.packageManager?.hasSystemFeature(PackageManager.FEATURE_WATCH) == true
+ ) {
+ waitFindObject(By.res(ALLOW_BUTTON), 2000)
+ } else {
+ waitFindObject(By.res(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-permission/AppThatDefinesUndefinedPermissionGroupElement.apk"
+ private const val APP_PKG_NAME = "android.permission.cts.appthatrequestpermission"
+ private const val EXTRA_PERMISSIONS =
+ "android.permission.cts.appthatrequestpermission.extra.PERMISSIONS"
+ private const val GRANT_DIALOG = "com.android.permissioncontroller:id/grant_dialog"
+ private const val ALLOW_BUTTON =
+ "com.android.permissioncontroller:id/permission_allow_button"
+ 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..5ded57ab3
--- /dev/null
+++ b/tests/cts/permission/telephony/Android.bp
@@ -0,0 +1,39 @@
+//
+// 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",
+ "mcts-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..344787045
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/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: "CtsAdversarialPermissionDefinerApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ "mcts-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..4265d0924
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/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: "CtsAdversarialPermissionUserApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ "mcts-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..987d29039
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/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: "CtsInstallPermissionDefinerApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+ 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..9fcebb09b
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/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: "CtsInstallPermissionEscalatorApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+ 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..a153e39c5
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/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: "CtsInstallPermissionUserApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+ 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..6efcfdd01
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/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: "CtsRuntimePermissionDefinerApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+ 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..ab0b8a7b5
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/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: "CtsRuntimePermissionUserApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "sts",
+ "mcts-permission",
+ ],
+ 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..41333cb57
--- /dev/null
+++ b/tests/cts/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/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: "CtsVictimPermissionDefinerApp",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "sts",
+ "mts-permission",
+ "mcts-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/permissionmultidevice/AccessRemoteDeviceCameraApp/Android.bp b/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/Android.bp
new file mode 100644
index 000000000..3651b6b7d
--- /dev/null
+++ b/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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: "CtsAccessRemoteDeviceCamera",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "compatibility-device-util-axt",
+ ],
+ min_sdk_version: "34",
+ target_sdk_version: "35",
+}
diff --git a/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/AndroidManifest.xml b/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/AndroidManifest.xml
new file mode 100644
index 000000000..211e415bd
--- /dev/null
+++ b/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.permissionmultidevice.cts.accessremotedevicecamera">
+
+ <uses-permission android:name="android.permission.CAMERA" />
+
+ <application>
+ <activity android:name=".RequestPermissionActivity" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/src/android/permissionmultidevice/cts/accessremotedevicecamera/RequestPermissionActivity.kt b/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/src/android/permissionmultidevice/cts/accessremotedevicecamera/RequestPermissionActivity.kt
new file mode 100644
index 000000000..773dfa778
--- /dev/null
+++ b/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/src/android/permissionmultidevice/cts/accessremotedevicecamera/RequestPermissionActivity.kt
@@ -0,0 +1,22 @@
+package android.permissionmultidevice.cts.accessremotedevicecamera
+
+import android.Manifest
+import android.app.Activity
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Bundle
+
+class RequestPermissionActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val deviceId =
+ intent.getIntExtra(
+ PackageManager.EXTRA_REQUEST_PERMISSIONS_DEVICE_ID,
+ Context.DEVICE_ID_DEFAULT
+ )
+
+ requestPermissions(arrayOf(Manifest.permission.CAMERA), 1001, deviceId)
+ }
+}
diff --git a/tests/cts/permissionmultidevice/Android.bp b/tests/cts/permissionmultidevice/Android.bp
new file mode 100644
index 000000000..fdd115b5e
--- /dev/null
+++ b/tests/cts/permissionmultidevice/Android.bp
@@ -0,0 +1,51 @@
+//
+// 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: "CtsPermissionMultiDeviceTestCases",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ min_sdk_version: "30",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "modules-utils-build_system",
+ "cts-wm-util",
+ "flag-junit",
+ "android.companion.virtual.flags-aconfig-java",
+ "permission-test-util-lib",
+ "permission-multidevice-test-util-lib",
+ "android.permission.flags-aconfig-java",
+ ],
+ data: [
+ ":CtsAccessRemoteDeviceCamera",
+ ],
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-permission",
+ ],
+}
diff --git a/tests/cts/permissionmultidevice/AndroidManifest.xml b/tests/cts/permissionmultidevice/AndroidManifest.xml
new file mode 100644
index 000000000..9bad85813
--- /dev/null
+++ b/tests/cts/permissionmultidevice/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.permissionmultidevice.cts">
+
+ <uses-feature android:name="android.software.companion_device_setup" />
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <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.permissionmultidevice.cts"
+ android:label="CTS permission multi-device tests">
+ </instrumentation>
+</manifest>
diff --git a/tests/cts/permissionmultidevice/AndroidTest.xml b/tests/cts/permissionmultidevice/AndroidTest.xml
new file mode 100644
index 000000000..d86bea2d5
--- /dev/null
+++ b/tests/cts/permissionmultidevice/AndroidTest.xml
@@ -0,0 +1,75 @@
+<?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 permission multi-device test cases">
+
+ <option name="test-suite-tag" value="cts" />
+
+ <option name="config-descriptor:metadata" key="component" value="permissions" />
+ <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.Sdk34ModuleController" />
+
+ <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>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsPermissionMultiDeviceTestCases.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="CtsAccessRemoteDeviceCamera.apk->/data/local/tmp/cts-permissionmultidevice/CtsAccessRemoteDeviceCamera.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="appops set android.permissionmultidevice.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" />
+ <!-- Ensure all broadcasts are dispatched prior to running our tests, to make sure they
+ aren't polluted by `BOOT_COMPLETED` or similar broadcasts still being delivered, which
+ causes our `ActivityManager#waitForBroadcastIdle()` calls to timeout. -->
+ <option name="run-command" value="am wait-for-broadcast-idle" />
+ <option name="run-command" value="am wait-for-broadcast-barrier" />
+ <!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
+ <option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
+ </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-permissionmultidevice" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts-permissionmultidevice"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.permissionmultidevice.cts" />
+ <option name="runtime-hint" value="5m" />
+ </test>
+
+</configuration>
diff --git a/tests/cts/permissionmultidevice/OWNERS b/tests/cts/permissionmultidevice/OWNERS
new file mode 100644
index 000000000..fb6099cf7
--- /dev/null
+++ b/tests/cts/permissionmultidevice/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/tests/cts/permissionmultidevice/TEST_MAPPING b/tests/cts/permissionmultidevice/TEST_MAPPING
new file mode 100644
index 000000000..f56ee5b1c
--- /dev/null
+++ b/tests/cts/permissionmultidevice/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "CtsPermissionMultiDeviceTestCases"
+ }
+ ]
+}
diff --git a/tests/cts/permissionmultidevice/TestUtils/Android.bp b/tests/cts/permissionmultidevice/TestUtils/Android.bp
new file mode 100644
index 000000000..aeef5d134
--- /dev/null
+++ b/tests/cts/permissionmultidevice/TestUtils/Android.bp
@@ -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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+ name: "permission-multidevice-test-util-lib",
+ sdk_version: "test_current",
+ min_sdk_version: "34",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "androidx.test.core",
+ "compatibility-device-util-axt",
+ "kotlinx-coroutines-android",
+ "CtsVirtualDeviceCommonLib",
+ ],
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ installable: false,
+ visibility: [
+ "//packages/modules/Permission:__subpackages__",
+ ],
+}
diff --git a/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/FakeVirtualDeviceRule.kt b/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/FakeVirtualDeviceRule.kt
new file mode 100644
index 000000000..e8d35e614
--- /dev/null
+++ b/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/FakeVirtualDeviceRule.kt
@@ -0,0 +1,80 @@
+package android.permissionmultidevice.cts
+
+import android.companion.virtual.VirtualDeviceManager
+import android.companion.virtual.VirtualDeviceParams
+import android.content.Context
+import android.graphics.PixelFormat
+import android.hardware.display.DisplayManager
+import android.hardware.display.VirtualDisplayConfig
+import android.media.ImageReader
+import android.virtualdevice.cts.common.FakeAssociationRule
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.android.compatibility.common.util.SystemUtil
+import com.google.common.truth.Truth
+import org.junit.Assume
+
+/** A test rule that creates a virtual device for the duration of the test. */
+class FakeVirtualDeviceRule : FakeAssociationRule() {
+
+ private val imageReader =
+ ImageReader.newInstance(
+ /* width= */ DISPLAY_WIDTH,
+ /* height= */ DISPLAY_HEIGHT,
+ PixelFormat.RGBA_8888,
+ /* maxImages= */ 1
+ )
+
+ private lateinit var virtualDeviceManager: VirtualDeviceManager
+ lateinit var virtualDevice: VirtualDeviceManager.VirtualDevice
+ lateinit var deviceDisplayName: String
+ var virtualDisplayId: Int = -1
+
+ override fun before() {
+ // Call FakeAssociationRule#before() to create a CDM association to be used by VDM
+ super.before()
+
+ SystemUtil.callWithShellPermissionIdentity {
+ val virtualDeviceManager =
+ getApplicationContext<Context>().getSystemService(VirtualDeviceManager::class.java)
+ Assume.assumeNotNull(virtualDeviceManager)
+ this.virtualDeviceManager = virtualDeviceManager!!
+ virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ associationInfo.id,
+ VirtualDeviceParams.Builder().build()
+ )
+ val display =
+ virtualDevice.createVirtualDisplay(
+ VirtualDisplayConfig.Builder("testDisplay", DISPLAY_WIDTH, DISPLAY_HEIGHT, 240)
+ .setSurface(imageReader.surface)
+ .setFlags(
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC or
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED or
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+ )
+ .build(),
+ Runnable::run,
+ null
+ )
+ Truth.assertThat(display).isNotNull()
+ virtualDisplayId = display!!.display.displayId
+ deviceDisplayName =
+ virtualDeviceManager
+ .getDisplayNameForPersistentDeviceId(virtualDevice.persistentDeviceId!!)
+ .toString()
+ }
+ }
+
+ override fun after() {
+ // Call FakeAssociationRule#after() to remote CDM association
+ super.after()
+
+ SystemUtil.callWithShellPermissionIdentity { virtualDevice.close() }
+ imageReader.close()
+ }
+
+ companion object {
+ private const val DISPLAY_HEIGHT = 1920
+ private const val DISPLAY_WIDTH = 1080
+ }
+}
diff --git a/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/PackageManagementUtils.kt b/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/PackageManagementUtils.kt
new file mode 100644
index 000000000..9294bcdd9
--- /dev/null
+++ b/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/PackageManagementUtils.kt
@@ -0,0 +1,38 @@
+package android.permissionmultidevice.cts
+
+import com.android.compatibility.common.util.SystemUtil
+import com.android.modules.utils.build.SdkLevel
+import org.junit.Assert
+
+object PackageManagementUtils {
+ fun installPackage(
+ apkPath: String,
+ reinstall: Boolean = false,
+ grantRuntimePermissions: Boolean = false,
+ expectSuccess: Boolean = true,
+ installSource: String? = null
+ ) {
+ val output =
+ SystemUtil.runShellCommandOrThrow(
+ "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) {
+ Assert.assertEquals("Success", output)
+ } else {
+ Assert.assertNotEquals("Success", output)
+ }
+ }
+
+ fun uninstallPackage(packageName: String, requireSuccess: Boolean = true) {
+ val output = SystemUtil.runShellCommand("pm uninstall $packageName").trim()
+ if (requireSuccess) {
+ Assert.assertEquals("Success", output)
+ }
+ }
+}
diff --git a/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/PermissionUtils.kt b/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/PermissionUtils.kt
new file mode 100644
index 000000000..cffc0617c
--- /dev/null
+++ b/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/PermissionUtils.kt
@@ -0,0 +1,29 @@
+package android.permissionmultidevice.cts
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.permission.PermissionManager
+import android.permission.PermissionManager.PermissionState
+import com.android.compatibility.common.util.SystemUtil
+
+object PermissionUtils {
+ fun getAllPermissionStates(
+ context: Context,
+ packageName: String,
+ companionDeviceId: String
+ ): Map<String, PermissionState> {
+ val permissionManager = context.getSystemService(PermissionManager::class.java)!!
+ return SystemUtil.runWithShellPermissionIdentity<Map<String, PermissionState>> {
+ permissionManager.getAllPermissionStates(packageName, companionDeviceId)
+ }
+ }
+
+ fun isAutomotive(context: Context): Boolean =
+ context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+
+ fun isTv(context: Context): Boolean =
+ context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+
+ fun isWatch(context: Context): Boolean =
+ context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
+}
diff --git a/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/UiAutomatorUtils.kt b/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/UiAutomatorUtils.kt
new file mode 100644
index 000000000..f3b8eecbf
--- /dev/null
+++ b/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/UiAutomatorUtils.kt
@@ -0,0 +1,56 @@
+package android.permissionmultidevice.cts
+
+import android.os.SystemClock
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.StaleObjectException
+import androidx.test.uiautomator.UiObject2
+import com.android.compatibility.common.util.UiAutomatorUtils2
+import com.google.common.truth.Truth
+
+object UiAutomatorUtils {
+ fun waitFindObject(selector: BySelector): UiObject2 {
+ return findObjectWithRetry({ t -> UiAutomatorUtils2.waitFindObject(selector, t) })!!
+ }
+
+ fun waitFindObject(selector: BySelector, timeoutMillis: Long): UiObject2 {
+ return findObjectWithRetry(
+ { t -> UiAutomatorUtils2.waitFindObject(selector, t) },
+ timeoutMillis
+ )!!
+ }
+
+ fun click(selector: BySelector, timeoutMillis: Long = 20_000) {
+ waitFindObject(selector, timeoutMillis).click()
+ }
+
+ fun findTextForView(selector: BySelector): String {
+ val timeoutMs = 10000L
+
+ var exception: Exception? = null
+ var view: UiObject2? = null
+ try {
+ view = waitFindObject(selector, timeoutMs)
+ } catch (e: Exception) {
+ exception = e
+ }
+ Truth.assertThat(exception).isNull()
+ Truth.assertThat(view).isNotNull()
+ return view!!.text
+ }
+
+ private fun findObjectWithRetry(
+ automatorMethod: (timeoutMillis: Long) -> UiObject2?,
+ timeoutMillis: Long = 20_000L
+ ): UiObject2? {
+ val startTime = SystemClock.elapsedRealtime()
+ return try {
+ automatorMethod(timeoutMillis)
+ } catch (e: StaleObjectException) {
+ val remainingTime = timeoutMillis - (SystemClock.elapsedRealtime() - startTime)
+ if (remainingTime <= 0) {
+ throw e
+ }
+ automatorMethod(remainingTime)
+ }
+ }
+}
diff --git a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt
new file mode 100644
index 000000000..243eeb884
--- /dev/null
+++ b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.permissionmultidevice.cts
+
+import android.Manifest
+import android.app.Instrumentation
+import android.companion.virtual.VirtualDeviceManager
+import android.companion.virtual.VirtualDeviceParams
+import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM
+import android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Build
+import android.permission.PermissionManager
+import android.permission.flags.Flags
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.provider.Settings
+import android.virtualdevice.cts.common.VirtualDeviceRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.UiScrollable
+import androidx.test.uiautomator.UiSelector
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.UiAutomatorUtils2
+import com.android.modules.utils.build.SdkLevel
+import org.junit.After
+import org.junit.Assert.assertEquals
+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
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName = "VanillaIceCream")
+class AppPermissionsTest {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val defaultDeviceContext = instrumentation.targetContext
+
+ @get:Rule
+ var virtualDeviceRule =
+ VirtualDeviceRule.withAdditionalPermissions(
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ Manifest.permission.CREATE_VIRTUAL_DEVICE
+ )
+
+ private lateinit var persistentDeviceId: String
+ private lateinit var externalDeviceCameraText: String
+ private lateinit var permissionMessage: String
+
+ private val permissionManager =
+ defaultDeviceContext.getSystemService(PermissionManager::class.java)!!
+
+ @Before
+ fun setup() {
+ assumeTrue(SdkLevel.isAtLeastV())
+ assumeFalse(PermissionUtils.isAutomotive(defaultDeviceContext))
+ assumeFalse(PermissionUtils.isTv(defaultDeviceContext))
+ assumeFalse(PermissionUtils.isWatch(defaultDeviceContext))
+
+ PackageManagementUtils.installPackage(APP_APK_PATH_STREAMING)
+
+ val virtualDeviceManager =
+ defaultDeviceContext.getSystemService(VirtualDeviceManager::class.java)!!
+ val virtualDevice =
+ virtualDeviceRule.createManagedVirtualDevice(
+ VirtualDeviceParams.Builder()
+ .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_CUSTOM)
+ .build()
+ )
+
+ val mDeviceDisplayName =
+ virtualDeviceManager.getVirtualDevice(virtualDevice.deviceId)!!.displayName.toString()
+
+ persistentDeviceId = virtualDevice.persistentDeviceId!!
+ externalDeviceCameraText = "Camera on $mDeviceDisplayName"
+ permissionMessage = "Camera access for this app on $mDeviceDisplayName"
+ }
+
+ @After
+ fun cleanup() {
+ PackageManagementUtils.uninstallPackage(APP_PACKAGE_NAME, requireSuccess = false)
+ UiAutomatorUtils2.getUiDevice().pressHome()
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun externalDevicePermissionGrantTest() {
+ grantRunTimePermission()
+
+ openAppPermissionsScreen()
+
+ val expectedGrantInfoMap =
+ mapOf(
+ "Allowed" to listOf(externalDeviceCameraText),
+ "Ask every time" to emptyList(),
+ "Not allowed" to listOf("Camera")
+ )
+ assertEquals(expectedGrantInfoMap, getGrantInfoMap())
+
+ clickPermissionItem(externalDeviceCameraText)
+
+ verifyPermissionMessage()
+
+ val radioButtons = getRadioButtons()
+ assertEquals(true, radioButtons["ALLOW_FOREGROUND_ONLY_RADIO_BUTTON"]!!.isChecked)
+ assertEquals(false, radioButtons["ASK_RADIO_BUTTON"]!!.isChecked)
+ assertEquals(false, radioButtons["DENY_RADIO_BUTTON"]!!.isChecked)
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun externalDevicePermissionChangeToAskTest() {
+ grantRunTimePermission()
+ openAppPermissionsScreen()
+
+ clickPermissionItem(externalDeviceCameraText)
+ getRadioButtons()["ASK_RADIO_BUTTON"]!!.click()
+ verifyAskSelection()
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun externalDevicePermissionChangeToDenyTest() {
+ grantRunTimePermission()
+ openAppPermissionsScreen()
+
+ clickPermissionItem(externalDeviceCameraText)
+ getRadioButtons()["DENY_RADIO_BUTTON"]!!.click()
+ verifyDenySelection()
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun externalDevicePermissionChangeToAllowTest() {
+ grantRunTimePermission()
+ openAppPermissionsScreen()
+
+ clickPermissionItem(externalDeviceCameraText)
+ getRadioButtons()["ASK_RADIO_BUTTON"]!!.click()
+ val radioButtons = getRadioButtons()
+ assertEquals(false, radioButtons["ALLOW_FOREGROUND_ONLY_RADIO_BUTTON"]!!.isChecked)
+ assertEquals(true, radioButtons["ASK_RADIO_BUTTON"]!!.isChecked)
+ assertEquals(false, radioButtons["DENY_RADIO_BUTTON"]!!.isChecked)
+
+ radioButtons["ALLOW_FOREGROUND_ONLY_RADIO_BUTTON"]!!.click()
+ verifyAllowedSelection()
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun externalDevicePermissionNotDisplayedInitiallyTest() {
+ openAppPermissionsScreen()
+
+ // External device permission does not show initially (until requested)
+ val expectedGrantInfoMap =
+ mapOf(
+ "Allowed" to emptyList(),
+ "Ask every time" to emptyList(),
+ "Not allowed" to listOf("Camera")
+ )
+ assertEquals(expectedGrantInfoMap, getGrantInfoMap())
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun externalDevicePermissionStickyOnGrantTest() {
+ grantRunTimePermission()
+ openAppPermissionsScreen()
+
+ clickPermissionItem(externalDeviceCameraText)
+
+ val radioButtons = getRadioButtons()
+ assertEquals(true, radioButtons["ALLOW_FOREGROUND_ONLY_RADIO_BUTTON"]!!.isChecked)
+ assertEquals(false, radioButtons["ASK_RADIO_BUTTON"]!!.isChecked)
+ assertEquals(false, radioButtons["DENY_RADIO_BUTTON"]!!.isChecked)
+
+ radioButtons["DENY_RADIO_BUTTON"]!!.click()
+
+ UiAutomatorUtils2.getUiDevice().pressBack()
+
+ // Verify the permission continue to show (sticky) after revoking, keeps option for users
+ // to change in future
+ val expectedGrantInfoMap =
+ mapOf(
+ "Allowed" to emptyList(),
+ "Ask every time" to emptyList(),
+ "Not allowed" to listOf("Camera", externalDeviceCameraText)
+ )
+ assertEquals(expectedGrantInfoMap, getGrantInfoMap())
+ }
+
+ private fun verifyAskSelection() {
+ verifyPermissionMessage()
+
+ val radioButtons = getRadioButtons()
+ assertEquals(false, radioButtons["ALLOW_FOREGROUND_ONLY_RADIO_BUTTON"]!!.isChecked)
+ assertEquals(true, radioButtons["ASK_RADIO_BUTTON"]!!.isChecked)
+ assertEquals(false, radioButtons["DENY_RADIO_BUTTON"]!!.isChecked)
+
+ UiAutomatorUtils2.getUiDevice().pressBack()
+
+ val expectedGrantInfoMap =
+ mapOf(
+ "Allowed" to emptyList(),
+ "Ask every time" to listOf(externalDeviceCameraText),
+ "Not allowed" to listOf("Camera")
+ )
+ assertEquals(expectedGrantInfoMap, getGrantInfoMap())
+
+ val permState = getPermState()
+ assertEquals(false, permState[DEVICE_AWARE_PERMISSION]!!.isGranted)
+ assertTrue(
+ permState[DEVICE_AWARE_PERMISSION]!!.flags and
+ PackageManager.FLAG_PERMISSION_ONE_TIME != 0
+ )
+ }
+
+ private fun verifyDenySelection() {
+ verifyPermissionMessage()
+
+ val radioButtons = getRadioButtons()
+ assertEquals(false, radioButtons["ALLOW_FOREGROUND_ONLY_RADIO_BUTTON"]!!.isChecked)
+ assertEquals(false, radioButtons["ASK_RADIO_BUTTON"]!!.isChecked)
+ assertEquals(true, radioButtons["DENY_RADIO_BUTTON"]!!.isChecked)
+
+ UiAutomatorUtils2.getUiDevice().pressBack()
+
+ val expectedGrantInfoMap =
+ mapOf(
+ "Allowed" to emptyList(),
+ "Ask every time" to emptyList(),
+ "Not allowed" to listOf("Camera", externalDeviceCameraText)
+ )
+ assertEquals(expectedGrantInfoMap, getGrantInfoMap())
+
+ val permState = getPermState()
+ assertEquals(false, permState[DEVICE_AWARE_PERMISSION]!!.isGranted)
+ assertTrue(
+ permState[DEVICE_AWARE_PERMISSION]!!.flags and
+ PackageManager.FLAG_PERMISSION_USER_SET != 0
+ )
+ }
+
+ private fun verifyAllowedSelection() {
+ verifyPermissionMessage()
+
+ val radioButtons = getRadioButtons()
+ assertEquals(true, radioButtons["ALLOW_FOREGROUND_ONLY_RADIO_BUTTON"]!!.isChecked)
+ assertEquals(false, radioButtons["ASK_RADIO_BUTTON"]!!.isChecked)
+ assertEquals(false, radioButtons["DENY_RADIO_BUTTON"]!!.isChecked)
+
+ UiAutomatorUtils2.getUiDevice().pressBack()
+
+ val expectedGrantInfoMap =
+ mapOf(
+ "Allowed" to listOf(externalDeviceCameraText),
+ "Ask every time" to emptyList(),
+ "Not allowed" to listOf("Camera")
+ )
+ assertEquals(expectedGrantInfoMap, getGrantInfoMap())
+
+ val permState = getPermState()
+ assertEquals(true, permState[DEVICE_AWARE_PERMISSION]!!.isGranted)
+ assertTrue(
+ permState[DEVICE_AWARE_PERMISSION]!!.flags and
+ PackageManager.FLAG_PERMISSION_USER_SET != 0
+ )
+ }
+
+ private fun verifyPermissionMessage() {
+ val actualText = UiAutomatorUtils2.waitFindObject(By.res(PERMISSION_MESSAGE_ID)).text
+ assertEquals(permissionMessage, actualText)
+ }
+
+ private fun getGrantInfoMap(): Map<String, List<String>> {
+ val grantInfoMap =
+ mapOf(
+ "Allowed" to mutableListOf<String>(),
+ "Ask every time" to mutableListOf(),
+ "Not allowed" to mutableListOf()
+ )
+ val outOfScopeTitles = setOf("Unused app settings", "Manage app if unused")
+
+ val titleSelector = UiSelector().resourceId(TITLE)
+ var currentGrantText = ""
+
+ val scrollable = getScrollableRecyclerView()
+
+ // Scrolling to end inorder to have the scrollable object loaded with all child element data
+ // ready to be read. If the scroll happens in the middle of the reading process, it has been
+ // observed that child items will be skipped during the reading (could be a bug). Hence this
+ // solution is to scroll to the bottom in the beginning and be more efficient as well.
+ scrollable.scrollToEnd(1)
+
+ for (i in 0..scrollable.childCount) {
+ val child = scrollable.getChild(UiSelector().index(i))
+ val titleText = child.getChild(titleSelector).text
+ if (outOfScopeTitles.contains(titleText)) {
+ break
+ }
+ if (grantInfoMap.contains(titleText)) {
+ currentGrantText = titleText
+ } else if (!titleText.startsWith("No permissions")) {
+ grantInfoMap[currentGrantText]!!.add(titleText)
+ }
+ }
+ return grantInfoMap
+ }
+
+ private fun getRadioButtons(): Map<String, UiObject2> =
+ mapOf(
+ "ALLOW_FOREGROUND_ONLY_RADIO_BUTTON" to
+ UiAutomatorUtils2.waitFindObject(By.res(ALLOW_FOREGROUND_ONLY_RADIO_BUTTON)),
+ "ASK_RADIO_BUTTON" to UiAutomatorUtils2.waitFindObject(By.res(ASK_RADIO_BUTTON)),
+ "DENY_RADIO_BUTTON" to UiAutomatorUtils2.waitFindObject(By.res(DENY_RADIO_BUTTON))
+ )
+
+ private fun openAppPermissionsScreen() {
+ instrumentation.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)
+ }
+ )
+ eventually { UiAutomatorUtils.click(By.text("Permissions")) }
+ }
+
+ private fun getScrollableRecyclerView(): UiScrollable =
+ UiScrollable(UiSelector().resourceId(RECYCLER_VIEW))
+
+ private fun clickPermissionItem(permissionItemName: String) =
+ UiAutomatorUtils2.waitFindObject(By.text(permissionItemName)).click()
+
+ private fun grantRunTimePermission() =
+ permissionManager.grantRuntimePermission(
+ APP_PACKAGE_NAME,
+ DEVICE_AWARE_PERMISSION,
+ persistentDeviceId
+ )
+
+ private fun getPermState(): Map<String, PermissionManager.PermissionState> =
+ permissionManager.getAllPermissionStates(APP_PACKAGE_NAME, persistentDeviceId)
+
+ companion object {
+ private const val APK_DIRECTORY = "/data/local/tmp/cts-permissionmultidevice"
+ private const val APP_APK_PATH_STREAMING =
+ "${APK_DIRECTORY}/CtsAccessRemoteDeviceCamera.apk"
+ private const val APP_PACKAGE_NAME =
+ "android.permissionmultidevice.cts.accessremotedevicecamera"
+ private const val DEVICE_AWARE_PERMISSION = Manifest.permission.CAMERA
+
+ private const val ALLOW_FOREGROUND_ONLY_RADIO_BUTTON =
+ "com.android.permissioncontroller:id/allow_foreground_only_radio_button"
+ private const val ASK_RADIO_BUTTON = "com.android.permissioncontroller:id/ask_radio_button"
+ private const val DENY_RADIO_BUTTON =
+ "com.android.permissioncontroller:id/deny_radio_button"
+ private const val TITLE = "android:id/title"
+ private const val RECYCLER_VIEW = "com.android.permissioncontroller:id/recycler_view"
+ private const val PERMISSION_MESSAGE_ID =
+ "com.android.permissioncontroller:id/permission_message"
+ }
+}
diff --git a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
new file mode 100644
index 000000000..191e69367
--- /dev/null
+++ b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
@@ -0,0 +1,246 @@
+/*
+ * 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.permissionmultidevice.cts
+
+import android.Manifest
+import android.app.Instrumentation
+import android.companion.virtual.VirtualDeviceManager
+import android.companion.virtual.VirtualDeviceParams
+import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM
+import android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS
+import android.hardware.display.DisplayManager
+import android.hardware.display.VirtualDisplay
+import android.os.Build
+import android.permission.flags.Flags
+import android.permissionmultidevice.cts.PackageManagementUtils.installPackage
+import android.permissionmultidevice.cts.PackageManagementUtils.uninstallPackage
+import android.permissionmultidevice.cts.UiAutomatorUtils.click
+import android.permissionmultidevice.cts.UiAutomatorUtils.findTextForView
+import android.permissionmultidevice.cts.UiAutomatorUtils.waitFindObject
+import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.view.Display
+import android.virtualdevice.cts.common.VirtualDeviceRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil
+import com.google.common.truth.Truth
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName = "VanillaIceCream")
+@AppModeFull(reason = "VirtualDeviceManager cannot be accessed by instant apps")
+class DeviceAwarePermissionGrantTest {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val defaultDeviceContext = instrumentation.targetContext
+ private lateinit var mVirtualDeviceManager: VirtualDeviceManager
+ private lateinit var mVirtualDevice: VirtualDeviceManager.VirtualDevice
+ private lateinit var mVirtualDisplay: VirtualDisplay
+ private lateinit var mDeviceDisplayName: String
+
+ @get:Rule var mVirtualDeviceRule = VirtualDeviceRule.createDefault()
+
+ @Before
+ fun setup() {
+ assumeFalse(PermissionUtils.isAutomotive(defaultDeviceContext))
+ assumeFalse(PermissionUtils.isTv(defaultDeviceContext))
+ assumeFalse(PermissionUtils.isWatch(defaultDeviceContext))
+
+ installPackage(APP_APK_PATH_STREAMING)
+ mVirtualDeviceManager =
+ defaultDeviceContext.getSystemService(VirtualDeviceManager::class.java)!!
+ mVirtualDevice =
+ mVirtualDeviceRule.createManagedVirtualDevice(
+ VirtualDeviceParams.Builder()
+ .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_CUSTOM)
+ .build()
+ )
+
+ val displayConfig =
+ VirtualDeviceRule.createDefaultVirtualDisplayConfigBuilder(
+ DISPLAY_WIDTH,
+ DISPLAY_HEIGHT
+ )
+ .setFlags(
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC or
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED or
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+ )
+ .build()
+
+ mVirtualDisplay =
+ mVirtualDeviceRule.createManagedVirtualDisplay(mVirtualDevice, displayConfig)!!
+ mDeviceDisplayName =
+ mVirtualDeviceManager.getVirtualDevice(mVirtualDevice.deviceId)!!.displayName.toString()
+ }
+
+ @After
+ fun cleanup() {
+ uninstallPackage(APP_PACKAGE_NAME, requireSuccess = false)
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun onHostDevice_requestPermissionForHostDevice_shouldGrantPermission() {
+ testGrantPermissionForDevice(
+ Display.DEFAULT_DISPLAY,
+ DEVICE_ID_DEFAULT,
+ false,
+ "",
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnRemoteDevice = false
+ )
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun onHostDevice_requestPermissionForRemoteDevice_shouldGrantPermission() {
+ testGrantPermissionForDevice(
+ Display.DEFAULT_DISPLAY,
+ mVirtualDevice.deviceId,
+ true,
+ mDeviceDisplayName,
+ expectPermissionGrantedOnDefaultDevice = false,
+ expectPermissionGrantedOnRemoteDevice = true
+ )
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun onRemoteDevice_requestPermissionForHostDevice_shouldShowWarningDialog() {
+ requestPermissionOnDevice(mVirtualDisplay.display.displayId, DEVICE_ID_DEFAULT)
+
+ val displayId = mVirtualDisplay.display.displayId
+ waitFindObject(By.displayId(displayId).textContains("Permission request suppressed"))
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ )
+ @Test
+ fun onRemoteDevice_requestPermissionForRemoteDevice_shouldGrantPermission() {
+ testGrantPermissionForDevice(
+ mVirtualDisplay.display.displayId,
+ mVirtualDevice.deviceId,
+ true,
+ mDeviceDisplayName,
+ expectPermissionGrantedOnDefaultDevice = false,
+ expectPermissionGrantedOnRemoteDevice = true
+ )
+ }
+
+ private fun testGrantPermissionForDevice(
+ displayId: Int,
+ targetDeviceId: Int,
+ showDeviceName: Boolean,
+ expectedDeviceNameInDialog: String,
+ expectPermissionGrantedOnDefaultDevice: Boolean,
+ expectPermissionGrantedOnRemoteDevice: Boolean
+ ) {
+ // Assert no permission granted to either default device or virtual device
+ assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
+ assertAppHasPermissionForDevice(mVirtualDevice.deviceId, false)
+
+ requestPermissionOnDevice(displayId, targetDeviceId)
+ mVirtualDeviceRule.waitAndAssertActivityResumed(getPermissionDialogComponentName())
+
+ if (showDeviceName) {
+ assertPermissionMessageContainsDeviceName(displayId, expectedDeviceNameInDialog)
+ }
+
+ SystemUtil.eventually { click(By.displayId(displayId).res(ALLOW_BUTTON)) }
+
+ assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, expectPermissionGrantedOnDefaultDevice)
+ assertAppHasPermissionForDevice(
+ mVirtualDevice.deviceId,
+ expectPermissionGrantedOnRemoteDevice
+ )
+ }
+
+ private fun requestPermissionOnDevice(displayId: Int, targetDeviceId: Int) {
+ val intent =
+ Intent()
+ .setComponent(
+ ComponentName(APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.RequestPermissionActivity")
+ )
+ .putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_DEVICE_ID, targetDeviceId)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ mVirtualDeviceRule.sendIntentToDisplay(intent, displayId)
+ }
+
+ private fun assertPermissionMessageContainsDeviceName(displayId: Int, deviceName: String) {
+ waitFindObject(By.displayId(displayId).res(PERMISSION_MESSAGE_ID))
+ val text = findTextForView(By.displayId(displayId).res(PERMISSION_MESSAGE_ID))
+ Truth.assertThat(text).contains(deviceName)
+ }
+
+ private fun assertAppHasPermissionForDevice(deviceId: Int, expectPermissionGranted: Boolean) {
+ val checkPermissionResult =
+ defaultDeviceContext
+ .createDeviceContext(deviceId)
+ .packageManager
+ .checkPermission(DEVICE_AWARE_PERMISSION, APP_PACKAGE_NAME)
+
+ if (expectPermissionGranted) {
+ Assert.assertEquals(PackageManager.PERMISSION_GRANTED, checkPermissionResult)
+ } else {
+ Assert.assertEquals(PackageManager.PERMISSION_DENIED, checkPermissionResult)
+ }
+ }
+
+ private fun getPermissionDialogComponentName(): ComponentName {
+ val intent = Intent(ACTION_REQUEST_PERMISSIONS)
+ intent.setPackage(defaultDeviceContext.packageManager.getPermissionControllerPackageName())
+ return intent.resolveActivity(defaultDeviceContext.packageManager)
+ }
+
+ companion object {
+ const val APK_DIRECTORY = "/data/local/tmp/cts-permissionmultidevice"
+ const val APP_APK_PATH_STREAMING = "${APK_DIRECTORY}/CtsAccessRemoteDeviceCamera.apk"
+ const val APP_PACKAGE_NAME = "android.permissionmultidevice.cts.accessremotedevicecamera"
+ const val PERMISSION_MESSAGE_ID = "com.android.permissioncontroller:id/permission_message"
+ const val ALLOW_BUTTON =
+ "com.android.permissioncontroller:id/permission_allow_foreground_only_button"
+ const val DEVICE_ID_DEFAULT = 0
+ const val PERSISTENT_DEVICE_ID_DEFAULT = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
+ const val DEVICE_AWARE_PERMISSION = Manifest.permission.CAMERA
+ private const val DISPLAY_HEIGHT = 1920
+ private const val DISPLAY_WIDTH = 1080
+ }
+}
diff --git a/tests/cts/permissionmultiuser/Android.bp b/tests/cts/permissionmultiuser/Android.bp
new file mode 100644
index 000000000..b86b02205
--- /dev/null
+++ b/tests/cts/permissionmultiuser/Android.bp
@@ -0,0 +1,48 @@
+//
+// 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",
+ "mcts-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..10fd4e7a5
--- /dev/null
+++ b/tests/cts/permissionmultiuser/AndroidTest.xml
@@ -0,0 +1,71 @@
+<?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="permissions" />
+ <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-permissionmultiuser"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.permissionmultiuser.cts" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
+ <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..beade31ac
--- /dev/null
+++ b/tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt
@@ -0,0 +1,493 @@
+/*
+ * 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_NO_UPDATES_MESSAGE), true)
+ findView(By.textContains(LOCATION_PACKAGE_NAME_SUBSTRING), false)
+ }
+
+ 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) {
+ 20000L
+ } 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
+ }
+ val actual = exception == null
+ val message =
+ if (expected) {
+ "Expected view $selector not found"
+ } else {
+ "Unexpected view found: $selector"
+ }
+ Assert.assertTrue(message, actual == expected)
+ }
+ }
+}
diff --git a/tests/cts/permissionpolicy/Android.bp b/tests/cts/permissionpolicy/Android.bp
new file mode 100644
index 000000000..8f3c42b0e
--- /dev/null
+++ b/tests/cts/permissionpolicy/Android.bp
@@ -0,0 +1,67 @@
+//
+// 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",
+ ],
+ libs: ["android.test.base"],
+ static_libs: [
+ "androidx.test.core",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "guava",
+ "androidx.test.ext.junit-nodeps",
+ "truth",
+ "permission-test-util-lib",
+ "androidx.test.rules",
+ "flag-junit",
+ ],
+ 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..7fddaca52
--- /dev/null
+++ b/tests/cts/permissionpolicy/AndroidTest.xml
@@ -0,0 +1,84 @@
+<?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="permissions" />
+ <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>
+
+ <!-- Create place to store apks -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts-permissionpolicy" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts-permissionpolicy-permissionpolicy"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="CtsLocationPermissionsUserSdk22.apk->/data/local/tmp/cts-permissionpolicy/CtsLocationPermissionsUserSdk22.apk" />
+ <option name="push" value="CtsLocationPermissionsUserSdk29.apk->/data/local/tmp/cts-permissionpolicy/CtsLocationPermissionsUserSdk29.apk" />
+ <option name="push" value="CtsSMSCallLogPermissionsUserSdk22.apk->/data/local/tmp/cts-permissionpolicy/CtsSMSCallLogPermissionsUserSdk22.apk" />
+ <option name="push" value="CtsSMSCallLogPermissionsUserSdk29.apk->/data/local/tmp/cts-permissionpolicy/CtsSMSCallLogPermissionsUserSdk29.apk" />
+ <option name="push" value="CtsStoragePermissionsUserDefaultSdk22.apk->/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsUserDefaultSdk22.apk" />
+ <option name="push" value="CtsStoragePermissionsUserDefaultSdk28.apk->/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsUserDefaultSdk28.apk" />
+ <option name="push" value="CtsStoragePermissionsUserDefaultSdk29.apk->/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsUserDefaultSdk29.apk" />
+ <option name="push" value="CtsStoragePermissionsUserOptInSdk22.apk->/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsUserOptInSdk22.apk" />
+ <option name="push" value="CtsStoragePermissionsUserOptInSdk28.apk->/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsUserOptInSdk28.apk" />
+ <option name="push" value="CtsStoragePermissionsUserOptOutSdk29.apk->/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsUserOptOutSdk29.apk" />
+ <option name="push" value="CtsLegacyStorageNotIsolatedWithSharedUid.apk->/data/local/tmp/cts-permissionpolicy/CtsLegacyStorageNotIsolatedWithSharedUid.apk" />
+ <option name="push" value="CtsLegacyStorageIsolatedWithSharedUid.apk->/data/local/tmp/cts-permissionpolicy/CtsLegacyStorageIsolatedWithSharedUid.apk" />
+ <option name="push" value="CtsLegacyStorageRestrictedWithSharedUid.apk->/data/local/tmp/cts-permissionpolicy/CtsLegacyStorageRestrictedWithSharedUid.apk" />
+ <option name="push" value="CtsLegacyStorageRestrictedSdk28WithSharedUid.apk->/data/local/tmp/cts-permissionpolicy/CtsLegacyStorageRestrictedSdk28WithSharedUid.apk" />
+ <option name="push" value="CtsStoragePermissionsUserOptOutSdk30.apk->/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsUserOptOutSdk30.apk" />
+ <option name="push" value="CtsStoragePermissionsPreservedUserOptOutSdk30.apk->/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsPreservedUserOptOutSdk30.apk" />
+ <option name="push" value="CtsSMSRestrictedWithSharedUid.apk->/data/local/tmp/cts-permissionpolicy/CtsSMSRestrictedWithSharedUid.apk" />
+ <option name="push" value="CtsSMSNotRestrictedWithSharedUid.apk->/data/local/tmp/cts-permissionpolicy/CtsSMSNotRestrictedWithSharedUid.apk" />
+ <option name="push" value="CtsProcessOutgoingCalls.apk->/data/local/tmp/cts-permissionpolicy/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..901810aa1
--- /dev/null
+++ b/tests/cts/permissionpolicy/CtsProcessOutgoingCalls/src/android/permissionpolicy/cts/receivecallbroadcast/ProcessOutgoingCallReceiver.kt
@@ -0,0 +1,36 @@
+/*
+ * 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..3f0256275
--- /dev/null
+++ b/tests/cts/permissionpolicy/OWNERS
@@ -0,0 +1,8 @@
+# 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
+per-file NoReceiveSmsPermissionTest.java = sasindran@google.com
diff --git a/tests/cts/permissionpolicy/res/raw/OWNERS b/tests/cts/permissionpolicy/res/raw/OWNERS
new file mode 100644
index 000000000..6e1a91b88
--- /dev/null
+++ b/tests/cts/permissionpolicy/res/raw/OWNERS
@@ -0,0 +1,8 @@
+hackbod@google.com
+patb@google.com
+yamasani@google.com
+michaelwr@google.com
+narayan@google.com
+roosa@google.com
+per-file automotive_android_manifest.xml = skeys@google.com
+per-file automotive_android_manifest.xml = file:platform/packages/services/Car:/OWNERS
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..d80c23ba5
--- /dev/null
+++ b/tests/cts/permissionpolicy/res/raw/android_manifest.xml
@@ -0,0 +1,8788 @@
+<?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_UNSTOPPED" />
+ <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.intent.action.UNARCHIVE_PACKAGE" />
+
+ <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.appwidget.action.APPWIDGET_ENABLE_AND_UPDATE" />
+
+ <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.device.action.REMOTE_ISSUE_OCCURRED" />
+ <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.headsetclient.profile.action.NETWORK_SERVICE_STATE_CHANGED" />
+ <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.HAP_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_PORT_COMPLIANCE_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="com.android.server.audio.action.CHECK_MUSIC_ACTIVE" />
+
+ <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.internal.action.EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS" />
+ <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.aware.action.WIFI_AWARE_RESOURCE_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.content.action.PERMISSION_RESPONSE_RECEIVED" />
+ <protected-broadcast android:name="android.content.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.app.action.MANAGED_PROFILE_PROVISIONED" />
+
+ <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="android.app.action.KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED" />
+ <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" />
+
+ <protected-broadcast android:name="com.android.content.pm.action.CAN_INTERACT_ACROSS_PROFILES_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" />
+
+ <!-- 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="com.android.internal.telephony.ACTION_VOWIFI_ENABLED" />
+ <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.safetycenter.action.REFRESH_SAFETY_SOURCES" />
+ <protected-broadcast android:name="android.safetycenter.action.SAFETY_CENTER_ENABLED_CHANGED" />
+ <protected-broadcast android:name="android.app.action.DEVICE_POLICY_RESOURCE_UPDATED" />
+ <protected-broadcast android:name="android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER" />
+ <protected-broadcast android:name="android.service.autofill.action.DELAYED_FILL" />
+ <protected-broadcast android:name="android.app.action.PROVISIONING_COMPLETED" />
+ <protected-broadcast android:name="android.app.action.LOST_MODE_LOCATION_UPDATE" />
+
+ <!-- Added in U -->
+ <protected-broadcast android:name="android.intent.action.PROFILE_ADDED" />
+ <protected-broadcast android:name="android.intent.action.PROFILE_REMOVED" />
+ <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_SENT_ACTION" />
+ <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_DELIVERY_ACTION" />
+ <protected-broadcast android:name="com.android.internal.telephony.data.ACTION_RETRY" />
+ <protected-broadcast android:name="android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED" />
+ <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW" />
+ <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW" />
+ <protected-broadcast android:name="android.app.admin.action.DEVICE_FINANCING_STATE_CHANGED" />
+ <protected-broadcast android:name="android.app.admin.action.DEVICE_POLICY_SET_RESULT" />
+ <protected-broadcast android:name="android.app.admin.action.DEVICE_POLICY_CHANGED" />
+
+ <!-- Added in V -->
+ <protected-broadcast android:name="android.intent.action.PROFILE_AVAILABLE" />
+ <protected-broadcast android:name="android.intent.action.PROFILE_UNAVAILABLE" />
+ <protected-broadcast android:name="android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED" />
+
+ <!-- ====================================================================== -->
+ <!-- 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" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+ <!-- 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 app to update the verification status of E2EE contact keys owned by other apps.
+ <p>This permission is only granted to system apps.
+ <p>Protection level: signature|privileged
+ @SystemApi
+ @hide
+ @FlaggedApi("android.provider.user_keys")
+ -->
+ <permission android:name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_writeVerificationStateE2eeContactKeys"
+ android:description="@string/permdesc_writeVerificationStateE2eeContactKeys"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.provider.user_keys" />
+
+ <!-- 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 allowlists 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 allowlists 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 allowlists 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 allowlists 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 allowlists 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 allowlists 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 or privileged app. -->
+ <permission android:name="android.permission.SATELLITE_COMMUNICATION"
+ android:protectionLevel="role|signature|privileged" />
+
+ <!-- ====================================================================== -->
+ <!-- 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 class="note"><strong>Note: </strong>Starting in API level 33, this permission has no
+ effect. If your app accesses other apps' media files, request one or more of these permissions
+ instead: <a href="#READ_MEDIA_IMAGES"><code>READ_MEDIA_IMAGES</code></a>,
+ <a href="#READ_MEDIA_VIDEO"><code>READ_MEDIA_VIDEO</code></a>,
+ <a href="#READ_MEDIA_AUDIO"><code>READ_MEDIA_AUDIO</code></a>. Learn more about the
+ <a href="{@docRoot}training/data-storage/shared/media#storage-permission">storage
+ permissions</a> that are associated with media files.</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 <b>Developer options</b> 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 or write files in your application-specific directories returned by
+ {@link android.content.Context#getExternalFilesDir} and
+ {@link android.content.Context#getExternalCacheDir}.</p>
+ <p>Starting in API level 29, apps don't need to request this permission to access files in
+ their app-specific directory on external storage, or their own files in the
+ <a href="{@docRoot}reference/android/provider/MediaStore"><code>MediaStore</code></a>. Apps
+ shouldn't request this permission unless they need to access other apps' files in the
+ <code>MediaStore</code>. Read more about these changes in the
+ <a href="{@docRoot}training/data-storage#scoped-storage">scoped storage</a> section of the
+ developer documentation.</p>
+ <p>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>
+
+ <p> This is a soft restricted permission which cannot be held by an app it its
+ full form until the installer on record allowlists 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}. An app which targets
+ {@link android.os.Build.VERSION_CODES#TIRAMISU} or higher and needs to read audio files from
+ external storage must hold this permission; {@link #READ_EXTERNAL_STORAGE} is not required.
+ 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_V2} or lower, the
+ {@link #READ_EXTERNAL_STORAGE} permission is required, instead, to read audio files.
+ <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 video files from external storage.
+ <p>This permission is enforced starting in API level
+ {@link android.os.Build.VERSION_CODES#TIRAMISU}. An app which targets
+ {@link android.os.Build.VERSION_CODES#TIRAMISU} or higher and needs to read video files from
+ external storage must hold this permission; {@link #READ_EXTERNAL_STORAGE} is not required.
+ 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_V2} or lower, the
+ {@link #READ_EXTERNAL_STORAGE} permission is required, instead, to read video files.
+ <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}. An app which targets
+ {@link android.os.Build.VERSION_CODES#TIRAMISU} or higher and needs to read image files from
+ external storage must hold this permission; {@link #READ_EXTERNAL_STORAGE} is not required.
+ 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_V2} or lower, the
+ {@link #READ_EXTERNAL_STORAGE} permission is required, instead, to read image files.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.READ_MEDIA_IMAGES"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_readMediaImages"
+ android:description="@string/permdesc_readMediaImages"
+ 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 {@link #READ_MEDIA_VIDEO}. It does not prevent apps from
+ accessing the standard photo picker manually. This permission should be requested alongside
+ {@link #READ_MEDIA_IMAGES} and/or {@link #READ_MEDIA_VIDEO}, depending on which type of media
+ is desired.
+ <p> This permission will be automatically added to an app's manifest if the app requests
+ {@link #READ_MEDIA_IMAGES}, {@link #READ_MEDIA_VIDEO}, or {@link #ACCESS_MEDIA_LOCATION}
+ regardless of target SDK. If an app does not request this permission, then the grant dialog
+ will return `PERMISSION_GRANTED` for {@link #READ_MEDIA_IMAGES} and/or
+ {@link #READ_MEDIA_VIDEO}, but the app will only have access to the media selected by the
+ user. This false grant state will persist until the app goes into the background.
+ <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><strong>Note: </strong>If your app targets {@link android.os.Build.VERSION_CODES#R} or
+ higher, this permission has no effect.
+
+ <p>If your app is on a device that runs API level 19 or higher, you don't need to declare
+ this permission to read and write files in your application-specific directories returned
+ by {@link android.content.Context#getExternalFilesDir} and
+ {@link android.content.Context#getExternalCacheDir}.
+
+ <p>Learn more about how to
+ <a href="{@docRoot}training/data-storage/shared/media#update-other-apps-files">modify media
+ files</a> that your app doesn't own, and how to
+ <a href="{@docRoot}training/data-storage/shared/documents-files">modify non-media files</a>
+ that your app doesn't own.
+
+ <p>If your app is a file manager and needs broad access to external storage files, then
+ the system must place your app on an allowlist so that you can successfully request the
+ <a href="#MANAGE_EXTERNAL_STORAGE><code>MANAGE_EXTERNAL_STORAGE</code></a> permission.
+ Learn more about the appropriate use cases for
+ <a href="{@docRoot}training/data-storage/manage-all-files>managing all files on a storage
+ device</a>.
+
+ <p>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</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 allowlists 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 allowlists 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 and 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 allowlists 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 allowlists 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 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" />
+
+ <!-- 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 class="note"><b>Note:</b> An app holding this permission can also call carrier MMI
+ codes to change settings such as call forwarding or call waiting preferences.</p>
+ <p>Protection level: dangerous</p>
+ -->
+ <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" />
+
+ <!-- @SystemApi Allows camera access by Headless System User 0 when device is running in
+ HSUM Mode.
+ @FlaggedApi("com.android.internal.camera.flags.camera_hsum_permission")
+ @hide -->
+ <permission android:name="android.permission.CAMERA_HEADLESS_SYSTEM_USER"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_cameraHeadlessSystemUser"
+ android:description="@string/permdesc_cameraHeadlessSystemUser"
+ android:protectionLevel="signature"
+ android:featureFlag="com.android.internal.camera.flags.camera_hsum_permission" />
+
+
+ <!-- @SystemApi Allows camera access of allowlisted driver assistance apps
+ to be controlled separately.
+ <p> Not for use by third-party applications.
+ @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist")
+ @hide
+ -->
+ <permission android:name="android.permission.CAMERA_PRIVACY_ALLOWLIST"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- ====================================================================== -->
+ <!-- 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" />
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+
+ <!-- ====================================================================== -->
+ <!-- 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"/>
+
+ <!-- @SystemApi @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" />
+
+ <!-- @SystemApi 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" />
+
+ <!-- @hide @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
+ 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" />
+
+ <!-- Allows direct access to the <RemoteAuth>Service interfaces.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_REMOTE_AUTH"
+ android:protectionLevel="signature" />
+
+ <!-- Allows direct access to the <RemoteAuth>Service authentication methods.
+ @hide -->
+ <permission android:name="android.permission.USE_REMOTE_AUTH"
+ 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" />
+
+ <!-- @deprecated Allows applications to act as network scorers. @hide @SystemApi-->
+ <permission android:name="android.permission.SCORE_NETWORKS"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @deprecated 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 changing Thread network state and access to Thread network
+ credentials such as Network Key and PSKc.
+ <p>Not for use by third-party applications.
+ @FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform") -->
+ <permission android:name="android.permission.THREAD_NETWORK_PRIVILEGED"
+ 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" />
+
+ <!-- Allows system apps to call methods to register itself as a mDNS offload engine.
+ <p>Not for use by third-party or privileged applications.
+ @SystemApi
+ @FlaggedApi("android.net.platform.flags.register_nsd_offload_engine")
+ @hide This should only be used by system apps.
+ -->
+ <permission android:name="android.permission.REGISTER_NSD_OFFLOAD_ENGINE"
+ android:protectionLevel="signature"
+ android:featureFlag="android.net.platform.flags.register_nsd_offload_engine" />
+
+ <!-- ======================================= -->
+ <!-- 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|verifier" />
+
+ <!-- @SystemApi
+ @hide
+ @FlaggedApi("android.content.pm.quarantined_enabled")
+ Allows an application to quarantine other apps, which will prevent
+ them from running without explicit user action.
+ -->
+ <permission android:name="android.permission.QUARANTINE_APPS"
+ android:protectionLevel="signature|verifier"
+ android:featureFlag="android.content.pm.quarantined_enabled" />
+
+ <!-- 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
+ This should only be granted to the Bluetooth apk.
+ @hide @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
+ -->
+ <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:description="@string/permdesc_nfcTransactionEvent"
+ android:label="@string/permlab_nfcTransactionEvent"
+ 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" />
+
+ <!-- @SystemApi Allows internal management of Bluetooth state when on wireless consent mode.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <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" />
+
+ <!-- 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" />
+
+ <!-- ==================================================== -->
+ <!-- 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 get notified when a screen capture of its windows 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" />
+
+ <!-- Allows an application to get notified when it is being recorded.
+ <p>Protection level: normal
+ @FlaggedApi("com.android.window.flags.screen_recording_callbacks")
+ -->
+ <permission android:name="android.permission.DETECT_SCREEN_RECORDING"
+ android:protectionLevel="normal"
+ android:featureFlag="com.android.window.flags.screen_recording_callbacks" />
+
+ <!-- ======================================== -->
+ <!-- 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 Allows configuration of factory reset protection
+ @FlaggedApi("android.security.frp_enforcement")
+ @hide <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CONFIGURE_FACTORY_RESET_PROTECTION"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @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" />
+
+ <!-- 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" />
+
+ <!-- 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.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 {@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 a DomainSelectionService to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature
+ @SystemApi
+ @hide
+ @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service")
+ -->
+ <permission android:name="android.permission.BIND_DOMAIN_SELECTION_SERVICE"
+ 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 SatelliteService to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature|privileged|vendorPrivileged
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_SATELLITE_SERVICE"
+ android:protectionLevel="signature|privileged|vendorPrivileged" />
+
+ <!-- Must be required by a SatelliteGatewayService to ensure that only the
+ system can bind to it.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_SATELLITE_GATEWAY_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- 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" />
+
+ <!-- Used to provide the Telecom framework with access to the last known call ID.
+ <p>Protection level: signature
+ @SystemApi
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
+ @hide
+ -->
+ <permission android:name="android.permission.ACCESS_LAST_KNOWN_CELL_ID"
+ android:protectionLevel="signature"
+ android:label="@string/permlab_accessLastKnownCellId"
+ android:description="@string/permdesc_accessLastKnownCellId"/>
+
+ <!-- ================================== -->
+ <!-- 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|module|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" />
+
+ <!-- Allows applications to access profiles with ACCESS_HIDDEN_PROFILES user property
+ <p>Protection level: normal
+ @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") -->
+ <permission android:name="android.permission.ACCESS_HIDDEN_PROFILES"
+ android:label="@string/permlab_accessHiddenProfile"
+ android:description="@string/permdesc_accessHiddenProfile"
+ android:protectionLevel="normal" />
+
+ <!-- @SystemApi @hide Allows privileged applications to get details about hidden profile
+ users.
+ @FlaggedApi("android.multiuser.flags.enable_permission_to_access_hidden_profiles") -->
+ <permission
+ android:name="android.permission.ACCESS_HIDDEN_PROFILES_FULL"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @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 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 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 <a
+ href="https://www.threadgroup.org">Thread</a> network.
+ @FlaggedApi("com.android.net.thread.platform.flags.thread_user_restriction_enabled")
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to sending assist content to a
+ privileged app such as the Assistant app.
+ @FlaggedApi("android.app.admin.flags.assist_content_user_restriction_enabled")
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT"
+ 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 use audit logging API.
+ @hide
+ @SystemApi
+ @FlaggedApi("android.app.admin.flags.security_log_v2_enabled")
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_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 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 query the device stolen state.
+ @FlaggedApi("android.app.admin.flags.device_theft_api_enabled")
+ @hide
+ @SystemApi
+ -->
+ <permission android:name="android.permission.QUERY_DEVICE_STOLEN_STATE"
+ 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 manage policy related to content protection.
+ <p>Protection level: internal|role
+ @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled")
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to set policy related to subscriptions downloaded by an admin.
+ <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.
+ @FlaggedApi("android.app.admin.flags.esim_management_enabled") -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy related to block package uninstallation.
+ @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled")
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy related to camera toggle.
+ @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled")
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE"
+ android:protectionLevel="internal|role" />
+
+ <!-- Allows an application to manage policy related to microphone toggle.
+ @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled")
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE"
+ 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" />
+
+ <!-- Allows an application to access EnhancedConfirmationManager.
+ @SystemApi
+ @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled")
+ @hide This is not a third-party API (intended for OEMs and system apps). -->
+ <permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES"
+ android:protectionLevel="signature|installer" />
+ <uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
+
+ <!-- @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" />
+
+ <!-- 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 embed any other apps in untrusted embedding mode without the need
+ for the embedded app to consent.
+ <p>For now, this permission is only granted to the Assistant application selected by
+ the user.
+ {@see https://developer.android.com/guide/topics/large-screens/activity-embedding#trust_model}
+ @SystemApi
+ @FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission")
+ @hide
+ -->
+ <permission android:name="android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE"
+ android:protectionLevel="internal|role" />
+
+ <!-- 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" />
+
+ <!-- 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. If the caller has
+ the system permission {@code KILL_ALL_BACKGROUND_PROCESSES}, other processes will be
+ killed too.
+
+ <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:label="@string/permlab_startForegroundServicesFromBackground"
+ android:description="@string/permdesc_startForegroundServicesFromBackground"
+ 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:label="@string/permlab_companionProfileWatch"
+ android:description="@string/permdesc_companionProfileWatch"
+ 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 stream content from an Android host to a nearby device
+ ({@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:label="@string/permlab_hideOverlayWindows"
+ android:description="@string/permdesc_hideOverlayWindows"
+ 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 and lock wallpaper images.
+ <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
+ @FlaggedApi("com.android.window.flags.always_update_wallpaper_permission")
+ -->
+ <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 @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 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 @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" />
+
+ <!-- @SystemApi @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
+ @deprecated Vestigial permission declaration. No longer used. -->
+ <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
+ @deprecated Vestigial permission declaration. No longer used. -->
+ <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" />
+
+ <!-- Allows an application to get enabled 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.
+ @hide @SystemApi -->
+ <permission android:name="android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- 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 a browser to invoke credential manager APIs on behalf of another RP.
+ <p>Protection level: normal -->
+ <permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ORIGIN"
+ android:protectionLevel="normal" />
+
+ <!-- Allows a browser to invoke the set of query apis to get metadata about credential
+ candidates prepared during the CredentialManager.prepareGetCredential API.
+ <p>Protection level: normal -->
+ <permission android:name="android.permission.CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS"
+ 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 application to be able to store and retrieve credentials from a remote
+ device.
+ <p>Protection level: signature|privileged|role -->
+ <permission android:name="android.permission.PROVIDE_REMOTE_CREDENTIALS"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- ========================================= -->
+ <!-- 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 access the data in Dropbox.
+ <p>Not for use by third-party applications.
+ @FlaggedApi("com.android.server.feature.flags.enable_read_dropbox_permission") -->
+ <permission android:name="android.permission.READ_DROPBOX_DATA"
+ 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|recents" />
+
+ <!-- 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" />
+
+ <!-- @TestApi 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|recents" />
+
+ <!-- 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" />
+
+ <!-- @FlaggedApi("com.android.server.accessibility.motion_event_observing")
+ @hide
+ @TestApi
+ Allows an accessibility service to observe motion events without consuming them. -->
+ <permission android:name="android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING"
+ 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
+ @TestApi -->
+ <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|module" />
+
+ <!-- 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" />
+
+ <!-- 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
+ @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 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" />
+
+ <!-- @SystemApi Must be required by a {@link android.service.voice.VisualQueryDetectionService},
+ 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" />
+
+ <!-- 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|module|role"/>
+
+ <!-- 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" />
+
+ <!-- Must be required by a 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" />
+
+ <!-- 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 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 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 @SystemApi Intended for OEM and system apps.
+ <p>Protection level: signature|privileged
+ -->
+ <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 @SystemApi Intended for OEM and system apps.
+ <p>Protection level: signature|privileged
+ -->
+ <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 android.media.tv.ad.TvAdService to ensure that only the system can
+ bind to it.
+ <p>Protection level: signature|privileged
+ @FlaggedApi("android.media.tv.flags.enable_ad_service_fw")
+ -->
+ <permission android:name="android.permission.BIND_TV_AD_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- 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
+ 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|privileged|vendorPrivileged
+ <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 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>This is a special access permission that can be revoked by the system or the user.
+ It should only be used to enable <b>user-facing features</b> that require exact alarms.
+ For more details, please go through the associated
+ <a href="{@docRoot}training/scheduling/alarms#exact">developer docs</a>.
+ <p>Apps need to target API {@link android.os.Build.VERSION_CODES#S} or above to be able to
+ request this permission. Note that apps targeting lower API levels do not need this
+ permission to use exact alarm APIs.
+ <p>Apps that hold this permission and target API
+ {@link android.os.Build.VERSION_CODES#TIRAMISU} and below always stay in the
+ {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_WORKING_SET WORKING_SET} or
+ lower standby bucket.
+ <p>If your app relies on exact alarms for core functionality, it can instead request
+ {@link android.Manifest.permission#USE_EXACT_ALARM} once it targets API
+ {@link android.os.Build.VERSION_CODES#TIRAMISU}. All apps using exact alarms for secondary
+ features (which should still be user facing) should continue using this permission.
+ <p>Protection level: signature|privileged|appop
+ -->
+ <permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+ android:label="@string/permlab_schedule_exact_alarm"
+ android:description="@string/permdesc_schedule_exact_alarm"
+ android:protectionLevel="signature|privileged|appop"/>
+
+ <!-- Allows apps to use exact alarms just like with {@link
+ android.Manifest.permission#SCHEDULE_EXACT_ALARM} but without needing to request this
+ permission from the user.
+ <p><b> This is only intended for use by apps that rely on exact alarms for their core
+ functionality.</b> You should continue using {@code SCHEDULE_EXACT_ALARM} if your app needs
+ exact alarms for a secondary feature that users may or may not use within your app.
+ <p> Keep in mind that this is a powerful permission and app stores may enforce policies to
+ audit and review the use of this permission. Such audits may involve removal from the app
+ store if the app is found to be misusing this permission.
+ <p> Apps need to target API {@link android.os.Build.VERSION_CODES#TIRAMISU} or above to be
+ able to request this permission. Note that only one of {@code USE_EXACT_ALARM} or
+ {@code SCHEDULE_EXACT_ALARM} should be requested on a device. If your app is already using
+ {@code SCHEDULE_EXACT_ALARM} on older SDKs but needs {@code USE_EXACT_ALARM} on SDK 33 and
+ above, then {@code SCHEDULE_EXACT_ALARM} should be declared with a max-sdk attribute, like:
+ <pre>
+ &lt;uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+ &Tab; android:maxSdkVersion="32" /&gt;
+ </pre>
+ <p>Apps that hold this permission, always stay in the
+ {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_WORKING_SET WORKING_SET} or
+ lower standby bucket.
+ -->
+ <permission android:name="android.permission.USE_EXACT_ALARM"
+ android:label="@string/permlab_use_exact_alarm"
+ android:description="@string/permdesc_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
+ @FlaggedApi("android.content.pm.get_resolved_apk_path")
+ -->
+ <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 @TestApi iAllows 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 @TestApi 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 allowlists.
+ @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|module" />
+ <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
+
+ <!-- @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|module" />
+
+ <!-- Allows an application to manage the companion devices.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_COMPANION_DEVICES"
+ android:protectionLevel="module|signature|role" />
+
+ <!-- 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:label="@string/permlab_observeCompanionDevicePresence"
+ android:description="@string/permdesc_observeCompanionDevicePresence"
+ android:protectionLevel="normal" />
+
+ <!-- Allows an application to subscribe to notifications about the nearby devices' presence
+ status change base on the UUIDs.
+ <p>Not for use by third-party applications.</p>
+ @FlaggedApi("android.companion.flags.device_presence")
+ -->
+ <permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows an application to deliver companion messages to system
+ -->
+ <permission android:name="android.permission.DELIVER_COMPANION_MESSAGES"
+ android:label="@string/permlab_deliverCompanionMessages"
+ android:description="@string/permdesc_deliverCompanionMessages"
+ android:protectionLevel="normal" />
+
+ <!-- @hide @FlaggedApi("android.companion.flags.companion_transport_apis")
+ Allows an application to send and receive messages via CDM transports.
+ -->
+ <permission android:name="android.permission.USE_COMPANION_TRANSPORTS"
+ android:protectionLevel="signature" />
+
+ <!-- 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" />
+
+ <!-- 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 change the touch mode state.
+ Without this permission, an app can only change the touch mode
+ if it currently has focus.
+ @hide -->
+ <permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE"
+ android:protectionLevel="signature" />
+
+ <!-- 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 -->
+ <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 information 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 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" />
+
+ <!-- @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" />
+
+ <!-- @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" />
+
+ <!-- Allows an application to control the routing of media apps.
+ <p>Only for use by role COMPANION_DEVICE_WATCH</p>
+ @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control")
+ -->
+ <permission android:name="android.permission.MEDIA_ROUTING_CONTROL"
+ android:protectionLevel="signature|appop" />
+
+ <!-- @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" />
+ <uses-permission android:name="android.permission.CALL_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" />
+
+ <!-- @SystemApi @hide
+ @FlaggedApi("android.app.usage.report_usage_stats_permission")
+ Allows trusted system components to report events to UsageStatsManager -->
+ <permission android:name="android.permission.REPORT_USAGE_STATS"
+ android:protectionLevel="signature|module" />
+
+ <!-- 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 @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" />
+
+ <!-- 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 the 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"/>
+
+ <!-- @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 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 set the advanced features on BiometricDialog (SystemUI), including
+ logo, logo description.
+ <p>Not for use by third-party applications.
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")
+ -->
+ <permission android:name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- 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" />
+
+ <!-- @hide @TestApi Allows an application to record sensitive content during media
+ projection. This is intended for on device screen recording system app.
+ @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") -->
+ <permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"
+ android:protectionLevel="signature"
+ android:featureFlag="android.permission.flags.sensitive_notification_app_protection"/>
+
+ <!-- @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}.
+ @SystemApi
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
+ @hide -->
+ <permission android:name="android.permission.READ_BLOCKED_NUMBERS"
+ android:protectionLevel="signature" />
+
+ <!-- Allows the holder to write blocked numbers. See
+ {@link android.provider.BlockedNumberContract}.
+ @SystemApi
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
+ @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 cloudsearch service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_CLOUDSEARCH"
+ android:protectionLevel="signature|privileged|role" />
+
+ <!-- @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 interact with 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|role" />
+
+ <!-- @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 access the smartspace service as a client.
+ @FlaggedApi(android.app.smartspace.flags.Flags.FLAG_ACCESS_SMARTSPACE)
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.ACCESS_SMARTSPACE"
+ android:protectionLevel="signature|privileged|development" />
+
+ <!-- @SystemApi Allows an application to access the contextual search
+ service as a client.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.ACCESS_CONTEXTUAL_SEARCH"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.app.contextualsearch.flags.enable_service"/>
+
+ <!-- @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" />
+
+ <!-- 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:description="@string/permdesc_foregroundServiceCamera"
+ android:label="@string/permlab_foregroundServiceCamera"
+ 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:description="@string/permdesc_foregroundServiceConnectedDevice"
+ android:label="@string/permlab_foregroundServiceConnectedDevice"
+ 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:description="@string/permdesc_foregroundServiceDataSync"
+ android:label="@string/permlab_foregroundServiceDataSync"
+ 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:description="@string/permdesc_foregroundServiceLocation"
+ android:label="@string/permlab_foregroundServiceLocation"
+ 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:description="@string/permdesc_foregroundServiceMediaPlayback"
+ android:label="@string/permlab_foregroundServiceMediaPlayback"
+ 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:description="@string/permdesc_foregroundServiceMediaProjection"
+ android:label="@string/permlab_foregroundServiceMediaProjection"
+ 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:description="@string/permdesc_foregroundServiceMicrophone"
+ android:label="@string/permlab_foregroundServiceMicrophone"
+ 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:description="@string/permdesc_foregroundServicePhoneCall"
+ android:label="@string/permlab_foregroundServicePhoneCall"
+ 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:description="@string/permdesc_foregroundServiceHealth"
+ android:label="@string/permlab_foregroundServiceHealth"
+ 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:description="@string/permdesc_foregroundServiceRemoteMessaging"
+ android:label="@string/permlab_foregroundServiceRemoteMessaging"
+ 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:description="@string/permdesc_foregroundServiceSystemExempted"
+ android:label="@string/permlab_foregroundServiceSystemExempted"
+ 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" />
+
+ <!-- @FlaggedApi("android.content.pm.introduce_media_processing_type")
+ Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "mediaProcessing".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING"
+ android:description="@string/permdesc_foregroundServiceMediaProcessing"
+ android:label="@string/permlab_foregroundServiceMediaProcessing"
+ 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:description="@string/permdesc_foregroundServiceSpecialUse"
+ android:label="@string/permlab_foregroundServiceSpecialUse"
+ 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|recents" />
+
+ <!-- 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.
+ <p> The only approved role for this permission is COMPANION_DEVICE_APP_STREAMING.
+ @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 Required for the privileged assistant apps targeting
+ {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
+ that receive voice trigger from a sandboxed {@link HotwordDetectionService}.
+ <p>Protection level: signature|privileged|appop
+ @FlaggedApi("android.permission.flags.voice_activation_permission_apis")
+ @hide -->
+ <permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO"
+ android:protectionLevel="signature|privileged|appop" />
+
+ <!-- @SystemApi Required for the privileged assistant apps targeting
+ {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
+ that receive training data from a sandboxed {@link HotwordDetectionService} or
+ {@link VisualQueryDetectionService}.
+ <p>Protection level: internal|appop
+ @FlaggedApi("android.permission.flags.voice_activation_permission_apis")
+ @hide -->
+ <permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA"
+ android:protectionLevel="internal|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" />
+ <!-- @SystemApi 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 ThemeOverlayController to delay launch of Home / SetupWizard on boot, ensuring
+ Theme Palettes and Colors are ready -->
+ <permission android:name="android.permission.SET_THEME_OVERLAY_CONTROLLER_READY"
+ android:protectionLevel="signature|setup" />
+
+ <!-- @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" />
+
+ <!-- @TestApi Allows setting the game service provider, meant for tests only.
+ @hide -->
+ <permission android:name="android.permission.SET_GAME_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @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 the GameService provider to create GameSession and call GameSession
+ APIs and overlay a view on top of the game's Activity.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_GAME_ACTIVITY"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @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 the holder to launch an Intent Resolver flow with custom presentation
+ and/or targets.
+ @FlaggedApi("android.nfc.enable_nfc_mainline")
+ @hide -->
+ <permission android:name="android.permission.SHOW_CUSTOMIZED_RESOLVER"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @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.
+ Applications that are not the device owner will need this permission to call
+ {@link android.app.admin.DevicePolicyManager#getNearbyNotificationStreamingPolicy} or
+ {@link android.app.admin.DevicePolicyManager#getNearbyAppStreamingPolicy}. -->
+ <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" />
+
+ <!-- @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 application to access the AmbientContextEvent service.
+ @hide
+ -->
+ <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
+ -->
+ <permission android:name="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"
+ android:protectionLevel="signature" />
+
+ <!-- @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" />
+
+ <!-- @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 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 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 @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+ <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" />
+
+ <!-- Allows low-level access to 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 to monitor keyboard backlight changes.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT"
+ android:protectionLevel="signature" />
+
+ <!-- Allows low-level access to monitor sticky modifier state changes when A11Y Sticky keys
+ feature is enabled.
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE"
+ 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
+ <p>Protection level: internal|role
+ <p>Intended for use by the FINANCED_DEVICE_KIOSK role only.
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE"
+ android:protectionLevel="internal|role" />
+
+ <!-- @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" />
+
+ <!-- @SystemApi Allows an app to use the on-device intelligence service.
+ <p>Protection level: signature|privileged
+ @hide
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")
+ -->
+ <permission android:name="android.permission.USE_ON_DEVICE_INTELLIGENCE"
+ android:protectionLevel="signature|privileged" />
+
+
+ <!-- @SystemApi Allows an app to bind the on-device intelligence service.
+ <p>Protection level: signature|privileged
+ @hide
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")
+ -->
+ <permission android:name="android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE"
+ android:protectionLevel="signature|privileged" />
+
+
+ <!-- @SystemApi Allows an app to bind the on-device sandboxed service.
+ <p>Protection level: signature|privileged
+ @hide
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")
+ -->
+ <permission android:name="android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE"
+ android:protectionLevel="signature"/>
+
+
+ <!-- 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"/>
+
+ <!-- @FlaggedApi("android.app.job.backup_jobs_exemption")
+ Gives applications with a <b>major use case</b> of backing-up or syncing content increased
+ job execution allowance in order to complete the related work. The jobs must have a valid
+ content URI trigger and network constraint set.
+ <p>This is a special access permission that can be revoked by the system or the user.
+ <p>Protection level: signature|privileged|appop
+ @hide
+ -->
+ <permission android:name="android.permission.RUN_BACKUP_JOBS"
+ android:protectionLevel="signature|privileged|appop"/>
+
+ <!-- Allows an app access to the installer provided app metadata.
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.GET_APP_METADATA"
+ android:protectionLevel="signature|installer|verifier" />
+
+ <!-- @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" />
+
+ <!-- @hide @TestApi Allows tests running in CTS-in-sandbox mode to launch activities -->
+ <permission android:name="android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX"
+ 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" />
+
+ <!-- @hide @SystemApi
+ @FlaggedApi("android.app.get_binding_uid_importance")
+ Allows to get the importance of an UID that has a service
+ binding to the app.
+ <p>Protection level: signature|privileged
+ -->
+ <permission android:name="android.permission.GET_BINDING_UID_IMPORTANCE"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @hide Allows internal applications to manage displays.
+ <p>This means intercept internal signals about displays being (dis-)connected
+ and being able to enable or disable the external displays.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.MANAGE_DISPLAYS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi Allows an app to track all preparations for a complete factory reset.
+ <p>Protection level: signature|privileged
+ @FlaggedApi("android.permission.flags.factory_reset_prep_permission_apis")
+ @hide -->
+ <permission android:name="android.permission.PREPARE_FACTORY_RESET"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- @SystemApi Allows focused window to override the default behavior of supported system keys.
+ The following keycodes are supported:
+ <p> KEYCODE_STEM_PRIMARY
+ <p>If an app is granted this permission and has a focused window, it will be allowed to
+ receive supported key events that are otherwise handled by the system. The app can choose
+ to consume the key events and trigger its own behavior, in which case the default key
+ behavior will be skipped.
+ <p>For example, KEYCODE_STEM_PRIMARY by default opens recent app launcher. If the foreground
+ fitness app is granted this permission, it can repurpose the KEYCODE_STEM_PRIMARY button
+ to pause/resume the current fitness session.
+ <p>Protection level: signature|privileged
+ @FlaggedApi("com.android.input.flags.override_key_behavior_permission_apis")
+ @hide -->
+ <permission android:name="android.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Allows internal applications to restrict display modes
+ <p>Protection level: signature
+ @FlaggedApi("com.android.server.display.feature.flags.enable_restrict_display_modes")
+ @hide
+ -->
+ <permission android:name="android.permission.RESTRICT_DISPLAY_MODES"
+ android:protectionLevel="signature" />
+
+ <!-- @hide @SystemApi
+ @FlaggedApi("com.android.server.notification.flags.redact_otp_notifications_from_untrusted_listeners")
+ Allows apps with a NotificationListenerService to receive notifications with sensitive
+ information
+ <p>Apps with a NotificationListenerService without this permission will not be able
+ to view certain types of sensitive information contained in notifications
+ <p>Protection level: signature|role
+ -->
+ <permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi
+ @FlaggedApi("android.app.bic_client")
+ Allows app to call BackgroundInstallControlManager API to retrieve silently installed apps
+ for all users on device.
+ <p>Apps with a BackgroundInstallControlManager client will not be able to call any API without
+ this permission.
+ <p>Protection level: signature|role
+ @hide
+ -->
+ <permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an application to read the system grammatical gender.
+ @FlaggedApi("android.app.system_terms_of_address_enabled")
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"
+ android:protectionLevel="signature|privileged"/>
+
+ <!-- @SystemApi
+ @FlaggedApi("android.content.pm.emergency_install_permission")
+ Allows each app store in the system image to designate another app in the system image to
+ update the app store
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.EMERGENCY_INSTALL_PACKAGES"
+ android:protectionLevel="signature|privileged"/>
+
+ <!-- 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"/>
+ <!-- Attribution for Device Policy Manager service. -->
+ <attribution android:tag="DevicePolicyManagerService"
+ android:label="@string/device_policy_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.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.NfcResolverActivity"
+ android:theme="@style/Theme.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true"
+ android:multiprocess="true"
+ android:permission="android.permission.SHOW_CUSTOMIZED_RESOLVER"
+ android:exported="true">
+ <intent-filter android:priority="100" >
+ <action android:name="android.nfc.action.SHOW_NFC_RESOLVER" />
+ <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.DeviceDefault.Resolver"
+ android:excludeFromRecents="true"
+ android:documentLaunchMode="never"
+ android:relinquishTaskIdentity="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ 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.NoTitleBar.Fullscreen"
+ android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
+ android:enableOnBackInvokedCallback="true"
+ 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.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.SetScreenLockDialogActivity"
+ 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.BlockedAppStreamingActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true"
+ 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>
+
+ <activity android:name="android.service.games.GameSessionTrampolineActivity"
+ android:excludeFromRecents="true"
+ android:exported="true"
+ android:permission="android.permission.MANAGE_GAME_ACTIVITY"
+ android:theme="@style/Theme.GameSessionTrampoline">
+ </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>
+
+ <!-- Broadcast Receiver listens to sufficient verifier broadcast from Package Manager
+ when installing new SDK. Verification of SDK code during installation time is run
+ to determine compatibility with privacy sandbox restrictions. -->
+ <receiver android:name="com.android.server.sdksandbox.SdkSandboxVerifierReceiver"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/>
+ </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.SmartStorageMaintIdler"
+ 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.selinux.SelinuxAuditLogsService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
+ <service android:name="com.android.server.compos.IsolatedCompilationJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
+ <service android:name="com.android.system.virtualmachine.SecretkeeperJobService"
+ 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.pm.GentleUpdateHelper$Service"
+ 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.companion.association.InactiveAssociationsRemovalService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
+ <service android:name="com.android.server.appsearch.contactsindexer.ContactsIndexerMaintenanceService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
+ <service android:name="com.android.server.BinaryTransparencyService$UpdateMeasurementsJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
+ <service android:name="com.android.server.notification.ReviewNotificationPermissionsJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
+ <service android:name="com.android.server.notification.NotificationHistoryJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.notification.NotificationBitmapJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.healthconnect.HealthConnectDailyService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.healthconnect.migration.MigrationBroadcastJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
+ <service android:name="com.android.server.healthconnect.backuprestore.BackupRestore$BackupRestoreJobService"
+ 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>
+
+ <!-- TODO: Move to ExtServices or relevant component. -->
+ <service android:name="android.service.selectiontoolbar.DefaultSelectionToolbarRenderService"
+ android:permission="android.permission.BIND_SELECTION_TOOLBAR_RENDER_SERVICE"
+ android:process=":ui"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.service.selectiontoolbar.SelectionToolbarRenderService"/>
+ </intent-filter>
+ </service>
+
+ <service android:name="com.android.server.art.BackgroundDexoptJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
+ <service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncInCallService"
+ android:permission="android.permission.BIND_INCALL_SERVICE"
+ android:exported="true">
+ <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+ android:value="true" />
+ <intent-filter>
+ <action android:name="android.telecom.InCallService"/>
+ </intent-filter>
+ </service>
+
+ <service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncConnectionService"
+ android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.telecom.ConnectionService"/>
+ </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>
+
+ <meta-data
+ android:name="com.android.server.patch.25239169"
+ android:value="true" />
+
+ </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..783cd7f6b
--- /dev/null
+++ b/tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml
@@ -0,0 +1,623 @@
+<?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.READ_CAR_SEAT_BELTS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_read_car_seat_belts"
+ android:description="@string/car_permission_desc_read_car_seat_belts"/>
+ <permission android:name="android.car.permission.CONTROL_CAR_DYNAMICS_STATE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_dynamics_state"
+ android:description="@string/car_permission_desc_control_car_dynamics_state"/>
+ <permission android:name="android.car.permission.READ_IMPACT_SENSORS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_read_impact_sensors"
+ android:description="@string/car_permission_desc_read_impact_sensors"/>
+ <permission android:name="android.car.permission.READ_HEAD_UP_DISPLAY_STATUS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_read_head_up_display_status"
+ android:description="@string/car_permission_desc_read_head_up_display_status"/>
+ <permission android:name="android.car.permission.CONTROL_HEAD_UP_DISPLAY"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_head_up_display"
+ android:description="@string/car_permission_desc_control_head_up_display"/>
+ <permission android:name="android.car.permission.READ_VALET_MODE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_read_valet_mode"
+ android:description="@string/car_permission_desc_read_valet_mode"/>
+ <permission android:name="android.car.permission.CONTROL_VALET_MODE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_valet_mode"
+ android:description="@string/car_permission_desc_control_valet_mode"/>
+ <permission android:name="android.car.permission.READ_CAR_AIRBAGS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_read_car_airbags"
+ android:description="@string/car_permission_desc_read_car_airbags"/>
+ <permission android:name="android.car.permission.READ_ULTRASONICS_SENSOR_DATA"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_read_ultrasonics_sensor_data"
+ android:description="@string/car_permission_desc_read_ultrasonics_sensor_data"/>
+ <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"/>
+ <permission android:name="android.car.permission.MANAGE_DISPLAY_COMPATIBILITY"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_manage_display_compatibility"
+ android:description="@string/car_permission_desc_manage_display_compatibility"/>
+ <permission
+ android:name="android.car.permission.READ_PERSIST_TETHERING_SETTINGS"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_read_persist_tethering_settings"
+ android:description="@string/car_permission_desc_read_persist_tethering_settings" />
+ <permission
+ android:name="android.car.permission.BIND_APP_CARD_PROVIDER"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_bind_app_card_provider"
+ android:description="@string/car_permission_desc_bind_app_card_provider" />
+</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..f34170a9b
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ContactsProviderTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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 androidx.test.filters.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..ef38573ab
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoCaptureAudioOutputPermissionTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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 androidx.test.filters.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..989c927e1
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoProcessOutgoingCallPermissionTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
+
+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-permissionpolicy/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 = runShellCommandOrThrow("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..ecd31e620
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoReceiveSmsPermissionTest.java
@@ -0,0 +1,293 @@
+/*
+ * 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.telephony.TelephonyManager;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.List;
+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 int SMS_DELIVERED_WAIT_TIME_MILLIS = 4000;
+ 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";
+
+ // List of carrier-id that does not support loop back messages
+ // This is copied from
+ // cts/tests/tests/telephony/current/src/android/telephony/cts/CarrierCapability.java
+ public static final List<Integer> UNSUPPORT_LOOP_BACK_MESSAGES =
+ Arrays.asList(
+ 1 // "T-Mobile - US"
+ );
+
+ 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;
+ }
+
+ int carrierId = getContext().getSystemService(TelephonyManager.class).getSimCarrierId();
+ assertFalse("[RERUN] Carrier [carrier-id: " + carrierId + "] does not support "
+ + "loop back messages. Use another carrier.",
+ UNSUPPORT_LOOP_BACK_MESSAGES.contains(carrierId));
+
+ 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(SMS_DELIVERED_WAIT_TIME_MILLIS, 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..8bf3e83a4
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionMaxSdkVersionTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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 androidx.test.filters.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..c28b5d560
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionPolicyTest.java
@@ -0,0 +1,559 @@
+/*
+* 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 android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
+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.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Xml;
+
+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 String ATTR_FEATURE_FLAG = "featureFlag";
+
+ 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);
+ }
+
+ boolean filterFlaggedPermissions = sContext.getPackageManager()
+ .getApplicationInfo(PLATFORM_PACKAGE_NAME, 0).minSdkVersion <= UPSIDE_DOWN_CAKE;
+
+ Set<String> expectedPermissionGroups = loadExpectedPermissionGroupNames(
+ R.raw.android_manifest);
+ List<ExpectedPermissionInfo> expectedPermissions = loadExpectedPermissions(
+ R.raw.android_manifest, filterFlaggedPermissions);
+
+ if (sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ expectedPermissions.addAll(loadExpectedPermissions(R.raw.automotive_android_manifest,
+ filterFlaggedPermissions));
+ 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,
+ boolean filterFlaggedPermissions) throws Exception {
+ List<ExpectedPermissionInfo> permissions = new ArrayList<>();
+ DeviceFlagsValueProvider flagsValueProvider = new DeviceFlagsValueProvider();
+ flagsValueProvider.setUp();
+ 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())) {
+ if (filterFlaggedPermissions) {
+ String featureFlag = parser.getAttributeValue(null, ATTR_FEATURE_FLAG);
+ if (featureFlag != null) {
+ featureFlag = featureFlag.trim();
+ boolean invert = featureFlag.startsWith("!");
+ if (invert) {
+ featureFlag = featureFlag.substring(1).trim();
+ }
+ boolean flagEnabled =
+ invert != flagsValueProvider.getBoolean(featureFlag);
+ if (!flagEnabled) {
+ continue;
+ }
+ }
+ }
+
+ 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());
+ }
+ }
+ } finally {
+ flagsValueProvider.tearDownBeforeTest();
+ }
+
+ 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..5f396c49c
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedPermissionsTest.java
@@ -0,0 +1,745 @@
+/*
+ * 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.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
+
+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.SystemUserOnly;
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.FlakyTest;
+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-permissionpolicy/CtsLocationPermissionsUserSdk22.apk";
+
+ private static final String APK_USES_LOCATION_29 =
+ "/data/local/tmp/cts-permissionpolicy/CtsLocationPermissionsUserSdk29.apk";
+
+ private static final String APK_USES_SMS_CALL_LOG_22 =
+ "/data/local/tmp/cts-permissionpolicy/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-permissionpolicy/CtsSMSCallLogPermissionsUserSdk29.apk";
+
+ private static final String APK_USES_STORAGE_DEFAULT_29 =
+ "/data/local/tmp/cts-permissionpolicy/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-permissionpolicy/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-permissionpolicy/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
+ runShellCommandOrThrow("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()));
+
+ runShellCommandOrThrow(
+ "pm install -g --force-queryable --restrict-permissions "
+ + APK_USES_SMS_RESTRICTED_SHARED_UID);
+ runShellCommandOrThrow("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 = runShellCommandOrThrow("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..d6ee7a66b
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedStoragePermissionSharedUidTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.runShellCommandOrThrow;
+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) {
+ runShellCommandOrThrow(
+ "pm install -g --force-queryable --restrict-permissions " + mApk);
+ } else {
+ runShellCommandOrThrow("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-permissionpolicy/";
+ 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..6a3b0711d
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RestrictedStoragePermissionTest.java
@@ -0,0 +1,755 @@
+/*
+ * 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.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
+
+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-permissionpolicy/CtsStoragePermissionsUserDefaultSdk22.apk";
+
+ private static final String APK_USES_STORAGE_DEFAULT_28 =
+ "/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsUserDefaultSdk28.apk";
+
+ private static final String APK_USES_STORAGE_DEFAULT_29 =
+ "/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsUserDefaultSdk29.apk";
+
+ private static final String APK_USES_STORAGE_OPT_IN_22 =
+ "/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsUserOptInSdk22.apk";
+
+ private static final String APK_USES_STORAGE_OPT_IN_28 =
+ "/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsUserOptInSdk28.apk";
+
+ private static final String APK_USES_STORAGE_OPT_OUT_29 =
+ "/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsUserOptOutSdk29.apk";
+
+ private static final String APK_USES_STORAGE_OPT_OUT_30 =
+ "/data/local/tmp/cts-permissionpolicy/CtsStoragePermissionsUserOptOutSdk30.apk";
+
+ private static final String APK_USES_STORAGE_PRESERVED_OPT_OUT_30 =
+ "/data/local/tmp/cts-permissionpolicy/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 = runShellCommandOrThrow("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..6b3ae5f2e
--- /dev/null
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt
@@ -0,0 +1,192 @@
+/*
+ * 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.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..07167284d
--- /dev/null
+++ b/tests/cts/permissionui/Android.bp
@@ -0,0 +1,83 @@
+//
+// 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",
+ "flag-junit",
+ "CtsAccessibilityCommon",
+ "platform-test-rules",
+ "platform-test-annotations",
+ "android.permission.flags-aconfig-java",
+ ],
+ 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",
+ ":CtsUsePermissionAppStorage33",
+ ],
+ test_suites: [
+ "cts",
+ "sts",
+ "general-tests",
+ "mts-permission",
+ "automotive-tests",
+ "automotive-general-tests",
+ "mcts-permission",
+ ],
+}
diff --git a/tests/cts/permissionui/AndroidManifest.xml b/tests/cts/permissionui/AndroidManifest.xml
new file mode 100644
index 000000000..3b80b8d8b
--- /dev/null
+++ b/tests/cts/permissionui/AndroidManifest.xml
@@ -0,0 +1,89 @@
+<?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"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"/>
+
+ <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..eefa0018d
--- /dev/null
+++ b/tests/cts/permissionui/AndroidTest.xml
@@ -0,0 +1,101 @@
+<?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="permissions" />
+ <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" />
+ <option name="push" value="CtsUsePermissionAppStorage33.apk->/data/local/tmp/cts-permissionui/CtsUsePermissionAppStorage33.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-permissionui"/>
+ </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..e1130fbaa
--- /dev/null
+++ b/tests/cts/permissionui/AppThatAccessesCameraAndMic/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ 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..ca0c458a3
--- /dev/null
+++ b/tests/cts/permissionui/AppThatAccessesCameraAndMic/src/android/permissionui/cts/appthataccessescameraandmic/AccessCameraOrMicActivity.kt
@@ -0,0 +1,259 @@
+/*
+ * 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.Bundle
+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 onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (savedInstanceState != null) {
+ throw RuntimeException(
+ "Activity was recreated (perhaps due to a configuration change?) " +
+ "and this activity doesn't currently know how to gracefully handle " +
+ "configuration changes."
+ )
+ }
+ }
+
+ 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..b342319f3
--- /dev/null
+++ b/tests/cts/permissionui/CreateNotificationChannelsApp31/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?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:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ 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..302d021ca
--- /dev/null
+++ b/tests/cts/permissionui/CreateNotificationChannelsApp31/src/android/permissionui/cts/usepermission/CreateNotificationChannelsActivity.kt
@@ -0,0 +1,187 @@
+/*
+ * 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)
+
+ if (savedInstanceState != null) {
+ throw RuntimeException(
+ "Activity was recreated (perhaps due to a configuration change?) " +
+ "and this activity doesn't currently know how to gracefully handle " +
+ "configuration changes."
+ )
+ }
+
+ 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)
+ )
+ }
+
+ companion object {
+ private val TAG = CreateNotificationChannelsActivity::class.simpleName
+ }
+}
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..c341125fd
--- /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: "34",
+ min_sdk_version: "34",
+}
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..4bc81e7a9
--- /dev/null
+++ b/tests/cts/permissionui/PermissionPolicyApp25/src/android/permissionui/cts/permissionpolicy/TestProtectionFlagsActivity.kt
@@ -0,0 +1,112 @@
+/*
+ * 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 ?: emptyArray()) {
+ 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/StorageApp33/Android.bp b/tests/cts/permissionui/StorageApp33/Android.bp
new file mode 100644
index 000000000..35c79fada
--- /dev/null
+++ b/tests/cts/permissionui/StorageApp33/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: "CtsUsePermissionAppStorage33",
+ 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/StorageApp33/AndroidManifest.xml b/tests/cts/permissionui/StorageApp33/AndroidManifest.xml
new file mode 100644
index 000000000..212bf1508
--- /dev/null
+++ b/tests/cts/permissionui/StorageApp33/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/TEST_MAPPING b/tests/cts/permissionui/TEST_MAPPING
new file mode 100644
index 000000000..c703c539d
--- /dev/null
+++ b/tests/cts/permissionui/TEST_MAPPING
@@ -0,0 +1,32 @@
+{
+ "presubmit-large": [
+ {
+ "name": "CtsPermissionUiTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "CtsPermissionUiTestCases[com.google.android.permission.apex]",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.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..563f5255d
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp22CalendarOnly/src/android/permissionui/cts/usepermission/CheckPermissionService.kt
@@ -0,0 +1,39 @@
+/*
+ * 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..31ff0fa04
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp30WithBackground/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt
@@ -0,0 +1,57 @@
+/*
+ * 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
+import android.util.Log
+
+class RequestPermissionsActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (savedInstanceState != null) {
+ Log.w(TAG, "Activity was recreated. (Perhaps due to a configuration change?)")
+ return
+ }
+
+ 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()
+ }
+
+ companion object {
+ private val TAG = RequestPermissionsActivity::class.simpleName
+ }
+}
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..b19f62362
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionApp30WithBluetooth/src/android/permissionui/cts/usepermission/AccessBluetoothOnCommand.kt
@@ -0,0 +1,154 @@
+/*
+ * 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..0b92f5ef1
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLatest/AndroidManifest.xml
@@ -0,0 +1,56 @@
+<?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-permission android:name="android.permission.READ_PHONE_STATE"/>
+ <uses-permission android:name="android.permission.CALL_PHONE"/>
+
+ <!-- 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..55fd104cb
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/CheckCalendarAccessActivity.kt
@@ -0,0 +1,71 @@
+/*
+ * 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..3eea77340
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLatest/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.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.usepermission
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+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) {
+ Log.w(TAG, "Activity was recreated. (Perhaps due to a configuration change?)")
+ return
+ }
+
+ 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()
+ }
+
+ companion object {
+ private val TAG = RequestPermissionsActivity::class.simpleName
+ }
+}
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..b4bd16f05
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
@@ -0,0 +1,46 @@
+/*
+ * 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..897949b95
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt
@@ -0,0 +1,68 @@
+/*
+ * 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..bcd5496dc
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppWithOverlay/src/android/permissionui/cts/usepermission/OverlayActivity.kt
@@ -0,0 +1,60 @@
+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..c13a02392
--- /dev/null
+++ b/tests/cts/permissionui/UsePermissionAppWithOverlay/src/android/permissionui/cts/usepermission/RequestPermissionsActivity.kt
@@ -0,0 +1,106 @@
+/*
+ * 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
+import android.util.Log
+
+class RequestPermissionsActivity : Activity() {
+
+ var paused = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (savedInstanceState != null) {
+ Log.w(TAG, "Activity was recreated. (Perhaps due to a configuration change?)")
+ return
+ }
+
+ 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"
+ private val TAG = RequestPermissionsActivity::class.simpleName
+ }
+}
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..cd002ebfc
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/AppDataSharingUpdatesTest.kt
@@ -0,0 +1,591 @@
+/*
+ * 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.provider.DeviceConfig
+import android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED
+import android.util.Log
+import androidx.test.filters.FlakyTest
+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)
+
+ clickAndWaitForWindowTransition(By.textContains(LEARN_ABOUT_DATA_SHARING))
+
+ 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)
+
+ clickAndWaitForWindowTransition(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..8c61f0366
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/AppMetadata.kt
@@ -0,0 +1,214 @@
+/*
+ * 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..cb356f6a3
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt
@@ -0,0 +1,336 @@
+/*
+ * 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_group.PHONE
+import android.Manifest.permission_group.SMS
+import android.os.Build
+import android.permission.flags.Flags
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.provider.DeviceConfig
+import android.provider.Settings
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+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()
+ )
+
+ @get:Rule
+ val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @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)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
+ "VanillaIceCream")
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun installFromTrustedSource_enabledAllowRadioButtonAndIfClickedAndChecked() {
+ installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_LATEST)
+
+ navigateToIndividualPermissionSetting(PHONE)
+
+ assertAllowButtonIsEnabledAndClickAndChecked()
+
+ pressBack()
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
+ "VanillaIceCream")
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun installFromDownloadedFile_disabledAllowRadioButtonAndIfClickedAndRestrictedSettingDialog_PhonePermGroup() {
+ installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
+ APP_APK_NAME_LATEST
+ )
+
+ navigateToIndividualPermissionSetting(PHONE)
+
+ assertAllowButtonIsDisabledAndRestrictedSettingDialogPoppedUp()
+
+ pressBack()
+
+ pressBack()
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
+ "VanillaIceCream")
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun installFromDownloadedFile_disabledAllowRadioButtonAndIfClickedAndRestrictedSettingDialog_SMSPermGroup() {
+ installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
+ APP_APK_NAME_LATEST
+ )
+
+ navigateToIndividualPermissionSetting(SMS)
+
+ assertAllowButtonIsDisabledAndRestrictedSettingDialogPoppedUp()
+
+ pressBack()
+
+ pressBack()
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
+ "VanillaIceCream")
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun installFromLocalFile_disabledAllowRadioButtonAndIfClickedAndRestrictedSettingDialog_PhonePermGroup() {
+ installPackageWithInstallSourceAndMetadataFromLocalFile(APP_APK_NAME_LATEST)
+
+ navigateToIndividualPermissionSetting(PHONE)
+
+ assertAllowButtonIsDisabledAndRestrictedSettingDialogPoppedUp()
+
+ pressBack()
+
+ pressBack()
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
+ "VanillaIceCream")
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun installFromLocalFile_disabledAllowRadioButtonAndIfClickedAndRestrictedSettingDialog_SMSPermGroup() {
+ installPackageWithInstallSourceAndMetadataFromLocalFile(APP_APK_NAME_LATEST)
+
+ navigateToIndividualPermissionSetting(SMS)
+
+ assertAllowButtonIsDisabledAndRestrictedSettingDialogPoppedUp()
+
+ pressBack()
+
+ pressBack()
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
+ "VanillaIceCream")
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun installWithUnspecifiedSource_disabledAllowRadioButtonAndIfClickedAndRestrictedSettingDialog_SMSPermGroup() {
+ installPackageViaSession(APP_APK_NAME_LATEST)
+
+ navigateToIndividualPermissionSetting(SMS)
+
+ assertAllowButtonIsDisabledAndRestrictedSettingDialogPoppedUp()
+
+ pressBack()
+
+ pressBack()
+ }
+
+ private fun assertAllowButtonIsEnabledAndClickAndChecked() {
+ waitFindObject(By.res(ALLOW_RADIO_BUTTON).enabled(true).checked(false))
+ .click()
+ waitFindObject(By.res(ALLOW_RADIO_BUTTON).checked(true))
+ }
+
+ private fun assertAllowButtonIsDisabledAndRestrictedSettingDialogPoppedUp() {
+ waitFindObject(By.res(ALLOW_RADIO_BUTTON).enabled(false))
+ .clickAndWait(Until.newWindow(), TIMEOUT_MILLIS)
+
+ waitFindObject(ENHANCED_CONFIRMATION_DIALOG_SELECTOR, TIMEOUT_MILLIS)
+ }
+
+ 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"
+ private val ENHANCED_CONFIRMATION_DIALOG_SELECTOR = By.res(
+ "com.android.permissioncontroller:id/enhanced_confirmation_dialog_title");
+ }
+}
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..8c300e328
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt
@@ -0,0 +1,511 @@
+/*
+ * 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 androidx.test.uiautomator.Until
+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.runShellCommandOrThrow
+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
+ const val NEW_WINDOW_TIMEOUT_MILLIS: Long = 20_000
+
+ @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)
+ @JvmStatic
+ protected val isAutomotiveSplitscreen = isAutomotive &&
+ packageManager.hasSystemFeature(
+ /* PackageManager.FEATURE_CAR_SPLITSCREEN_MULTITASKING */
+ "android.software.car.splitscreen_multitasking")
+ }
+
+ @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 =
+ runShellCommandOrThrow(
+ "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,
+ allowlistedRestrictedPermissions: Set<String>? = null
+ ) {
+ val (sessionId, session) = createPackageInstallerSession(
+ packageSource,
+ allowlistedRestrictedPermissions
+ )
+ 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 {
+ return findObjectWithRetry({ t -> UiAutomatorUtils2.waitFindObject(selector, t) })!!
+ }
+
+ protected fun waitFindObject(selector: BySelector, timeoutMillis: Long): UiObject2 {
+ return findObjectWithRetry(
+ { t -> UiAutomatorUtils2.waitFindObject(selector, t) },
+ timeoutMillis
+ )!!
+ }
+
+ protected fun waitFindObjectOrNull(selector: BySelector): UiObject2? {
+ return findObjectWithRetry({ t -> UiAutomatorUtils2.waitFindObjectOrNull(selector, t) })
+ }
+
+ protected fun waitFindObjectOrNull(selector: BySelector, timeoutMillis: Long): UiObject2? {
+ return findObjectWithRetry(
+ { t -> UiAutomatorUtils2.waitFindObjectOrNull(selector, t) },
+ timeoutMillis
+ )
+ }
+
+ private fun findObjectWithRetry(
+ automatorMethod: (timeoutMillis: Long) -> UiObject2?,
+ timeoutMillis: Long = 20_000L
+ ): UiObject2? {
+ 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()
+ }
+
+ protected fun clickAndWaitForWindowTransition(
+ selector: BySelector,
+ timeoutMillis: Long = 20_000
+ ) {
+ waitFindObject(selector, timeoutMillis)
+ .clickAndWait(Until.newWindow(), NEW_WINDOW_TIMEOUT_MILLIS)
+ }
+
+ 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()
+ }
+
+ protected fun pressHome() {
+ uiDevice.pressHome()
+ }
+
+ 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,
+ allowlistedRestrictedPermissions: Set<String>? = null
+ ): Pair<Int, PackageInstaller.Session> {
+ // Create session
+ val sessionParam = SessionParams(SessionParams.MODE_FULL_INSTALL)
+ allowlistedRestrictedPermissions?.let {
+ sessionParam.setWhitelistedRestrictedPermissions(it)
+ }
+
+ 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..6babd1c06
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt
@@ -0,0 +1,1365 @@
+/*
+ * 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.PackageInstaller.SessionParams
+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 androidx.test.uiautomator.Until
+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.CompletableFuture
+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.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_NAME_LATEST = "CtsUsePermissionAppLatest.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_STORAGE_33 = "$APK_DIRECTORY/CtsUsePermissionAppStorage33.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_FRAME =
+ "com.android.permissioncontroller:id/allow_radio_button_frame"
+ 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 EDIT_PHOTOS_BUTTON = "com.android.permissioncontroller:id/edit_selected_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_group.SMS to "@android:string/permgrouplab_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_group.PHONE to "@android:string/permgrouplab_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
+ }
+
+ 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) {
+ clickAndWaitForWindowTransition(
+ By.text(getPermissionControllerString("review_button_continue")),
+ TIMEOUT_MILLIS * 2
+ )
+ } else {
+ clickAndWaitForWindowTransition(
+ By.res("com.android.permissioncontroller:id/continue_button")
+ )
+ }
+ }
+
+ protected fun clickPermissionReviewContinueAndClearSdkWarning() {
+ clickPermissionReviewContinue()
+ 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 installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
+ apkName: String
+ ) {
+ installPackageViaSession(
+ apkName,
+ AppMetadata.createDefaultAppMetadata(),
+ PACKAGE_SOURCE_DOWNLOADED_FILE,
+ allowlistedRestrictedPermissions = SessionParams.RESTRICTED_PERMISSIONS_ALL
+ )
+ }
+
+ 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) {
+ clickAndWaitForWindowTransition(
+ By.text(getPermissionControllerString("review_button_cancel"))
+ )
+ } else {
+ clickAndWaitForWindowTransition(
+ By.res("com.android.permissioncontroller:id/cancel_button")
+ )
+ }
+ }
+
+ protected fun approvePermissionReview() {
+ startAppActivityAndAssertResultCode(Activity.RESULT_OK) {
+ clickPermissionReviewContinueAndClearSdkWarning()
+ }
+ }
+
+ 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?,
+ crossinline block: () -> Unit
+ ) {
+ // Request the permissions
+ doAndWaitForWindowTransition {
+ 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)
+ }
+ )
+ }
+ // Perform the post-request action
+ block()
+ }
+
+ protected inline fun requestAppPermissions(
+ vararg permissions: String?,
+ askTwice: Boolean = false,
+ waitForWindowTransition: Boolean = true,
+ crossinline block: () -> Unit
+ ): Instrumentation.ActivityResult {
+ // Request the permissions
+ lateinit var future: CompletableFuture<Instrumentation.ActivityResult>
+ doAndWaitForWindowTransition {
+ 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)
+ }
+ )
+ }
+
+ // Notification permission prompt is shown first, so get it out of the way
+ clickNotificationPermissionRequestAllowButtonIfAvailable()
+ // Perform the post-request action
+ if (waitForWindowTransition) {
+ doAndWaitForWindowTransition { block() }
+ } else {
+ 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,
+ waitForWindowTransition: Boolean = !isWatch,
+ crossinline block: () -> Unit
+ ) {
+ var shouldWaitForWindowTransition = waitForWindowTransition
+ // Do not wait for windowTransition after action is performed on auto, when permissions
+ // are being denied. The click deny function explicitly waits for window to transition
+ if (isAutomotive) {
+ var somePermissionsTrue = false
+ // http://go/nl-kt-best-practices#for-loop-vs-foreach
+ for (it in permissionAndExpectedGrantResults) {
+ somePermissionsTrue = somePermissionsTrue || it.second
+ }
+ // When all permissions being requested are to be denied
+ // do not wait for windowTransition
+ if (!somePermissionsTrue) {
+ shouldWaitForWindowTransition = false
+ }
+ }
+ val result =
+ requestAppPermissions(
+ *permissions,
+ askTwice = askTwice,
+ waitForWindowTransition = shouldWaitForWindowTransition,
+ block = block
+ )
+ assertEquals(
+ "Permission request result had unexpected resultCode:",
+ Activity.RESULT_OK,
+ result.resultCode
+ )
+
+ val responseSize: Int =
+ result.resultData!!.getStringArrayExtra("$APP_PACKAGE_NAME.PERMISSIONS")!!.size
+ assertEquals(
+ "Permission request result had unexpected number of grant results:",
+ 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(
+ "Permission request result had fewer permissions than request",
+ permissions.size >= responseSize
+ )
+ assertEquals(
+ "Permission request result had unexpected grant results:",
+ 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,
+ waitForWindowTransition: Boolean = !isWatch,
+ crossinline block: () -> Unit
+ ) {
+ requestAppPermissionsAndAssertResult(
+ permissionAndExpectedGrantResults.map { it.first }.toTypedArray(),
+ permissionAndExpectedGrantResults,
+ askTwice,
+ waitForWindowTransition,
+ block
+ )
+ }
+
+ // Perform the requested action, then wait both for the action to complete, and for at least
+ // one window transition to occur since the moment the action begins executing.
+ protected inline fun doAndWaitForWindowTransition(crossinline block: () -> Unit) {
+ val timeoutOccurred =
+ !uiDevice.performActionAndWait(
+ { block() },
+ Until.newWindow(),
+ NEW_WINDOW_TIMEOUT_MILLIS
+ )
+
+ if (timeoutOccurred) {
+ throw RuntimeException("Timed out waiting for window transition.")
+ }
+ }
+
+ protected fun findPermissionRequestAllowButton(timeoutMillis: Long = 20000) {
+ if (isAutomotive || isWatch) {
+ waitFindObject(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)), timeoutMillis)
+ } else {
+ waitFindObject(By.res(ALLOW_BUTTON), timeoutMillis)
+ }
+ }
+
+ protected fun clickPermissionRequestAllowButton(timeoutMillis: Long = 20000) {
+ if (isAutomotive || isWatch) {
+ 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 || isWatch) {
+ 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 || isWatch) {
+ click(
+ By.text(getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT)),
+ timeoutMillis
+ )
+ } else {
+ click(By.res(ALLOW_FOREGROUND_BUTTON), timeoutMillis)
+ }
+ }
+
+ protected fun clickPermissionRequestDenyButton() {
+ if (isAutomotive) {
+ clickAndWaitForWindowTransition(
+ By.text(getPermissionControllerString(DENY_BUTTON_TEXT))
+ )
+ } else if (isWatch || isTv) {
+ click(By.text(getPermissionControllerString(DENY_BUTTON_TEXT)))
+ } else {
+ click(By.res(DENY_BUTTON))
+ }
+ }
+
+ protected fun clickPermissionRequestSettingsLinkAndDeny() {
+ clickPermissionRequestSettingsLink()
+ eventually({ clicksDenyInSettings() }, TIMEOUT_MILLIS * 2)
+ pressBack()
+ }
+
+ protected fun clickPermissionRequestSettingsLink() {
+ 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.
+ doAndWaitForWindowTransition { clickableSpan.onClick(View(context)) }
+ }
+ }
+
+ protected fun clickPermissionRequestDenyAndDontAskAgainButton() {
+ if (isAutomotive) {
+ clickAndWaitForWindowTransition(
+ 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() {
+ clickAndWaitForWindowTransition(By.res(APP_PERMISSION_RATIONALE_CONTENT_VIEW))
+ }
+
+ protected fun clickPermissionRationaleViewInGrantDialog() {
+ clickAndWaitForWindowTransition(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
+ doAndWaitForWindowTransition {
+ 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) {
+ pressDPadDown()
+ pressDPadDown()
+ pressDPadDown()
+ pressDPadDown()
+ }
+ // Open the permissions UI
+ clickAndWaitForWindowTransition(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) {
+ clickAndWaitForWindowTransition(By.text(permissionLabel), 40_000)
+ } else {
+ clickPermissionControllerUi(By.text(permissionLabel))
+ }
+ return
+ }
+ doAndWaitForWindowTransition {
+ 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() {
+ doAndWaitForWindowTransition {
+ 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 {
+ doAndWaitForWindowTransition {
+ 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) {
+ if (isWatch) {
+ click(
+ By.desc(
+ getPermissionControllerString("media_confirm_dialog_positive_button")
+ )
+ )
+ } else {
+ 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.desc(getPermissionControllerString("ok")))
+ } 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) {
+ val checkPermissionResult = packageManager.checkPermission(permissionName, APP_PACKAGE_NAME)
+ assertTrue(
+ "Invalid permission check result: $checkPermissionResult",
+ checkPermissionResult == PackageManager.PERMISSION_GRANTED ||
+ checkPermissionResult == PackageManager.PERMISSION_DENIED
+ )
+ if (!expectPermission && checkPermissionResult == PackageManager.PERMISSION_GRANTED) {
+ Assert.fail(
+ "Unexpected permission check result for $permissionName: " +
+ "expected -1 (PERMISSION_DENIED) but was 0 (PERMISSION_GRANTED)"
+ )
+ }
+ if (expectPermission && checkPermissionResult == PackageManager.PERMISSION_DENIED) {
+ Assert.fail(
+ "Unexpected permission check result for $permissionName: " +
+ "expected 0 (PERMISSION_GRANTED) but was -1 (PERMISSION_DENIED)"
+ )
+ }
+ }
+
+ protected fun assertAppHasCalendarAccess(expectAccess: Boolean) {
+ val future =
+ startActivityForFuture(
+ Intent().apply {
+ component =
+ ComponentName(
+ APP_PACKAGE_NAME,
+ "$APP_PACKAGE_NAME.CheckCalendarAccessActivity"
+ )
+ }
+ )
+ 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..47fe17bac
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt
@@ -0,0 +1,698 @@
+/*
+ * 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.provider.DeviceConfig
+import android.provider.Settings
+import android.safetycenter.SafetyCenterManager
+import android.server.wm.WindowManagerStateHelper
+import androidx.annotation.RequiresApi
+import androidx.test.filters.FlakyTest
+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.runShellCommandOrThrow
+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 = runShellCommandOrThrow("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_ANY))
+ 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_ANY))
+ 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_ANY))
+ }
+ 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()
+ }
+
+ private fun pressHome() {
+ uiDevice.pressHome()
+ }
+
+ 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? {
+ return findObjectWithRetry({ t -> UiAutomatorUtils2.waitFindObject(selector, t) })
+ }
+
+ private fun findObjectWithRetry(
+ automatorMethod: (timeoutMillis: Long) -> UiObject2?,
+ timeoutMillis: Long = TIMEOUT_MILLIS
+ ): UiObject2? {
+ 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/EnhancedConfirmationManagerTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt
new file mode 100644
index 000000000..a55a48cf2
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.AppOpsManager
+import android.app.Instrumentation
+import android.app.ecm.EnhancedConfirmationManager
+import android.content.Context
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Process
+import android.permission.flags.Flags
+import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By
+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 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
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+@AppModeFull(reason = "Instant apps cannot install packages")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName = "VanillaIceCream")
+@RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context: Context = instrumentation.targetContext
+ private val ecm by lazy { context.getSystemService(EnhancedConfirmationManager::class.java)!! }
+ private val appOpsManager by lazy { context.getSystemService(AppOpsManager::class.java)!! }
+ private val packageManager by lazy { context.packageManager }
+
+ @Rule
+ @JvmField
+ val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Before
+ fun assumeNotAutoTvOrWear() {
+ Assume.assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
+ Assume.assumeFalse(
+ packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ )
+ Assume.assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun installedAppStartsWithModeDefault() {
+ installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_LATEST)
+ runWithShellPermissionIdentity {
+ assertEquals(
+ getAppEcmState(context, appOpsManager, APP_PACKAGE_NAME),
+ AppOpsManager.MODE_DEFAULT
+ )
+ }
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun givenStoreAppThenIsNotRestrictedFromProtectedSetting() {
+ installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_LATEST)
+ runWithShellPermissionIdentity {
+ eventually { assertFalse(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) }
+ }
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun givenLocalAppThenIsRestrictedFromProtectedSetting() {
+ installPackageWithInstallSourceAndMetadataFromLocalFile(APP_APK_NAME_LATEST)
+ runWithShellPermissionIdentity {
+ eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) }
+ }
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun givenDownloadedThenAppIsRestrictedFromProtectedSetting() {
+ installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST)
+ runWithShellPermissionIdentity {
+ eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) }
+ }
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun givenExplicitlyRestrictedAppThenIsRestrictedFromProtectedSetting() {
+ installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_LATEST)
+ runWithShellPermissionIdentity {
+ eventually { assertFalse(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) }
+ setAppEcmState(context, appOpsManager, APP_PACKAGE_NAME, AppOpsManager.MODE_ERRORED)
+ eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) }
+ }
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun givenRestrictedAppThenIsNotRestrictedFromNonProtectedSetting() {
+ installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST)
+ runWithShellPermissionIdentity {
+ eventually { assertFalse(ecm.isRestricted(APP_PACKAGE_NAME, NON_PROTECTED_SETTING)) }
+ }
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun givenRestrictedAppThenClearRestrictionNotAllowedByDefault() {
+ installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST)
+ runWithShellPermissionIdentity {
+ eventually { assertFalse(ecm.isClearRestrictionAllowed(APP_PACKAGE_NAME)) }
+ }
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun givenRestrictedAppWhenClearRestrictionThenNotRestrictedFromProtectedSetting() {
+ installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST)
+ runWithShellPermissionIdentity {
+ eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) }
+ ecm.setClearRestrictionAllowed(APP_PACKAGE_NAME)
+ eventually { assertTrue(ecm.isClearRestrictionAllowed(APP_PACKAGE_NAME)) }
+ ecm.clearRestriction(APP_PACKAGE_NAME)
+ eventually { assertFalse(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) }
+ }
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun createRestrictedSettingDialogIntentReturnsIntent() {
+ installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST)
+
+ val intent = ecm.createRestrictedSettingDialogIntent(APP_PACKAGE_NAME, PROTECTED_SETTING)
+
+ assertNotNull(intent)
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun grantDialogBlocksRestrictedPermissionsOfSameGroupTogether() {
+ installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
+ APP_APK_NAME_LATEST
+ )
+ val permissionAndExpectedGrantResults =
+ arrayOf(
+ GROUP_1_PERMISSION_1_RESTRICTED to false,
+ GROUP_1_PERMISSION_2_RESTRICTED to false
+ )
+
+ requestAppPermissionsAndAssertResult(*permissionAndExpectedGrantResults) {
+ click(By.res(ALERT_DIALOG_OK_BUTTON), TIMEOUT_MILLIS)
+ }
+ assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME))
+
+ requestAppPermissionsAndAssertResult(
+ *permissionAndExpectedGrantResults,
+ waitForWindowTransition = false
+ ) {
+ assertNoEcmDialogShown()
+ }
+ assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME))
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun grantDialogBlocksRestrictedPermissionsOfDifferentGroupsIndividually() {
+ installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
+ APP_APK_NAME_LATEST
+ )
+ val permissionAndExpectedGrantResults =
+ arrayOf(
+ GROUP_1_PERMISSION_1_RESTRICTED to false,
+ GROUP_2_PERMISSION_1_RESTRICTED to false
+ )
+
+ requestAppPermissionsAndAssertResult(
+ *permissionAndExpectedGrantResults,
+ waitForWindowTransition = false
+ ) {
+ doAndWaitForWindowTransition { click(By.res(ALERT_DIALOG_OK_BUTTON), TIMEOUT_MILLIS) }
+ doAndWaitForWindowTransition { click(By.res(ALERT_DIALOG_OK_BUTTON), TIMEOUT_MILLIS) }
+ }
+ assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME))
+
+ requestAppPermissionsAndAssertResult(
+ *permissionAndExpectedGrantResults,
+ waitForWindowTransition = false
+ ) {
+ assertNoEcmDialogShown()
+ }
+ assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME))
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun grantDialogBlocksRestrictedGroupsThenRequestsUnrestrictedGroupsDespiteOutOfOrderRequest() {
+ installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
+ APP_APK_NAME_LATEST
+ )
+
+ requestAppPermissionsAndAssertResult(
+ GROUP_3_PERMISSION_1_UNRESTRICTED to false,
+ GROUP_2_PERMISSION_1_RESTRICTED to false,
+ GROUP_3_PERMISSION_2_UNRESTRICTED to false,
+ GROUP_2_PERMISSION_2_RESTRICTED to false,
+ waitForWindowTransition = false
+ ) {
+ doAndWaitForWindowTransition { click(By.res(ALERT_DIALOG_OK_BUTTON), TIMEOUT_MILLIS) }
+ doAndWaitForWindowTransition { clickPermissionRequestDenyButton() }
+ }
+ assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME))
+
+ requestAppPermissionsAndAssertResult(
+ GROUP_3_PERMISSION_1_UNRESTRICTED to true,
+ GROUP_3_PERMISSION_2_UNRESTRICTED to true,
+ waitForWindowTransition = false
+ ) {
+ doAndWaitForWindowTransition { clickPermissionRequestAllowForegroundButton() }
+ }
+ assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME))
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun grantDialogBlocksRestrictedGroupsThenRequestsUnrestrictedHighPriorityGroups() {
+ installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
+ APP_APK_NAME_LATEST
+ )
+
+ requestAppPermissionsAndAssertResult(
+ GROUP_3_PERMISSION_1_UNRESTRICTED to true,
+ GROUP_2_PERMISSION_1_RESTRICTED to false,
+ GROUP_1_PERMISSION_1_RESTRICTED to false,
+ waitForWindowTransition = false
+ ) {
+ doAndWaitForWindowTransition { click(By.res(ALERT_DIALOG_OK_BUTTON), TIMEOUT_MILLIS) }
+ doAndWaitForWindowTransition { click(By.res(ALERT_DIALOG_OK_BUTTON), TIMEOUT_MILLIS) }
+ doAndWaitForWindowTransition { clickPermissionRequestAllowForegroundButton() }
+ }
+ assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME))
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun grantDialogBlocksRestrictedGroupsThenRequestsUnrestrictedLowPriorityGroups() {
+ installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
+ APP_APK_NAME_LATEST
+ )
+
+ requestAppPermissionsAndAssertResult(
+ GROUP_4_PERMISSION_1_UNRESTRICTED to true,
+ GROUP_2_PERMISSION_1_RESTRICTED to false,
+ GROUP_1_PERMISSION_1_RESTRICTED to false,
+ waitForWindowTransition = false
+ ) {
+ doAndWaitForWindowTransition { click(By.res(ALERT_DIALOG_OK_BUTTON), TIMEOUT_MILLIS) }
+ doAndWaitForWindowTransition { click(By.res(ALERT_DIALOG_OK_BUTTON), TIMEOUT_MILLIS) }
+ doAndWaitForWindowTransition { clickPermissionRequestAllowForegroundButton() }
+ }
+ assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME))
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Test
+ fun givenPackagesSourceUnspecifiedAndInstallerTargetVersionAtLeastVThenIsRestricted() {
+ val installingApplicationInfo = getApplicationInfoAsUser(context,
+ TEST_INSTALLER_PACKAGE_NAME)
+ assertTrue(installingApplicationInfo.targetSdkVersion >=
+ Build.VERSION_CODES.VANILLA_ICE_CREAM)
+
+ installPackageViaSession(APP_APK_NAME_LATEST)
+
+ val installSource = packageManager.getInstallSourceInfo(APP_PACKAGE_NAME)
+ assertEquals(installSource.installingPackageName, TEST_INSTALLER_PACKAGE_NAME)
+ assertEquals(installSource.packageSource, PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED)
+
+ runWithShellPermissionIdentity {
+ assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING))
+ }
+ }
+
+ private fun isClearRestrictionAllowed(packageName: String) = callWithShellPermissionIdentity {
+ ecm.isClearRestrictionAllowed(packageName)
+ }
+
+ private fun assertNoEcmDialogShown() {
+ assertNull(
+ "expected to not see dialog",
+ waitFindObjectOrNull(By.res(ALERT_DIALOG_OK_BUTTON), UNEXPECTED_TIMEOUT_MILLIS.toLong())
+ )
+ }
+
+ companion object {
+ private const val GROUP_1_PERMISSION_1_RESTRICTED = Manifest.permission.CALL_PHONE
+ private const val GROUP_1_PERMISSION_2_RESTRICTED = Manifest.permission.READ_PHONE_STATE
+ private const val GROUP_2_PERMISSION_1_RESTRICTED = Manifest.permission.SEND_SMS
+ private const val GROUP_2_PERMISSION_2_RESTRICTED = Manifest.permission.READ_SMS
+ private const val GROUP_3_PERMISSION_1_UNRESTRICTED =
+ Manifest.permission.ACCESS_FINE_LOCATION
+ private const val GROUP_3_PERMISSION_2_UNRESTRICTED =
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ private const val GROUP_4_PERMISSION_1_UNRESTRICTED = Manifest.permission.BODY_SENSORS
+
+ private const val NON_PROTECTED_SETTING = "example_setting_which_is_not_protected"
+ private const val PROTECTED_SETTING = "android:bind_accessibility_service"
+
+ @Throws(PackageManager.NameNotFoundException::class)
+ private fun setAppEcmState(
+ context: Context,
+ appOpsManager: AppOpsManager,
+ packageName: String,
+ mode: Int
+ ) =
+ appOpsManager.setMode(
+ AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS,
+ getPackageUid(context, packageName),
+ packageName,
+ mode
+ )
+
+ @Throws(PackageManager.NameNotFoundException::class)
+ private fun getAppEcmState(
+ context: Context,
+ appOpsManager: AppOpsManager,
+ packageName: String
+ ) =
+ appOpsManager.noteOpNoThrow(
+ AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS,
+ getPackageUid(context, packageName),
+ packageName,
+ context.attributionTag,
+ /* message */ null
+ )
+
+ @Throws(PackageManager.NameNotFoundException::class)
+ private fun getPackageUid(context: Context, packageName: String) =
+ getApplicationInfoAsUser(context, packageName).uid
+
+ @Throws(PackageManager.NameNotFoundException::class)
+ private fun getApplicationInfoAsUser(context: Context, packageName: String) =
+ packageManager.getApplicationInfoAsUser(
+ packageName,
+ /* flags */ 0,
+ Process.myUserHandle()
+ )
+ }
+}
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..176010cf5
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt
@@ -0,0 +1,164 @@
+/*
+ * 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 androidx.test.filters.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,
+ waitForWindowTransition = false
+ ) {}
+ }
+
+ 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..e7920edfd
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/LocationProviderInterceptDialogTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.MtsIgnore
+import android.permission.cts.PermissionUtils
+import androidx.test.filters.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.Ignore
+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
+ @Ignore("b/288471744")
+ @MtsIgnore(bugId = 288471744)
+ fun clickLocationPermission_showDialog_clickOk() {
+ openPermissionScreenForApp()
+ clickAndWaitForWindowTransition(By.text("Location"))
+ findView(By.textContains("Location access can be modified from location settings"), true)
+ click(By.res(OK_BUTTON_RES))
+ }
+
+ @Test
+ @Ignore("b/288471744")
+ @MtsIgnore(bugId = 288471744)
+ fun clickLocationPermission_showDialog_clickLocationAccess() {
+ openPermissionScreenForApp()
+ clickAndWaitForWindowTransition(By.text("Location"))
+ findView(By.textContains("Location access can be modified from location settings"), true)
+ clickAndWaitForWindowTransition(By.res(LOCATION_ACCESS_BUTTON_RES))
+ findView(By.res(USE_LOCATION_LABEL_ID), true)
+ }
+
+ @Test
+ @Ignore("b/288471744")
+ @MtsIgnore(bugId = 288471744)
+ 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()
+ doAndWaitForWindowTransition {
+ 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)
+ }
+ )
+ }
+ }
+ }
+
+ 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..d41c7454f
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionTest.kt
@@ -0,0 +1,183 @@
+/*
+ * 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 androidx.test.filters.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,
+ waitForWindowTransition = false
+ ) {}
+ 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,
+ waitForWindowTransition = false
+ ) {
+ // 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..40c09ea8c
--- /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 androidx.test.filters.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,
+ waitForWindowTransition = !isWatch
+ ) {
+ 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,
+ waitForWindowTransition = false
+ ) {
+ // Don't click any grant dialog buttons because no grant dialog should appear
+ }
+ }
+}
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..a5d428812
--- /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 androidx.test.filters.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..9b72d1706
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/NotificationPermissionTest.kt
@@ -0,0 +1,416 @@
+/*
+ * 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.provider.Settings
+import androidx.test.filters.FlakyTest
+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 || isWatch) {
+ 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
+ doAndWaitForWindowTransition { context.startActivity(intent, options.toBundle()) }
+
+ // Watch does not have app bar
+ if (!isWatch) {
+ waitFindObject(By.textContains(ACTIVITY_LABEL))
+ }
+ }
+
+ 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..495648b55
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionDecisionsTest.kt
@@ -0,0 +1,131 @@
+/*
+ * 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 androidx.test.filters.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() {
+ doAndWaitForWindowTransition {
+ 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..b27d9ea69
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionGroupTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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 androidx.test.filters.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,
+ waitForWindowTransition = false
+ ) {}
+
+ 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,
+ waitForWindowTransition = !isWatch
+ ) {
+ 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..1ca319a30
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionNoOpGtsTest.kt
@@ -0,0 +1,29 @@
+/*
+ * 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..3d03b669a
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionPolicyTest25.kt
@@ -0,0 +1,64 @@
+/*
+ * 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 androidx.test.filters.FlakyTest
+import java.util.concurrent.TimeUnit
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+
+/** 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..73faaa7f6
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt
@@ -0,0 +1,284 @@
+/*
+ * 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.provider.DeviceConfig
+import android.safetylabel.SafetyLabelConstants.PERMISSION_RATIONALE_ENABLED
+import androidx.test.filters.FlakyTest
+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()
+ assertPermissionRationaleDialogIsVisible(true)
+ pressBack()
+ 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..e20fdeffd
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationaleTest.kt
@@ -0,0 +1,385 @@
+/*
+ * 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.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.FlakyTest
+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() }, NEW_WINDOW_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.
+ doAndWaitForWindowTransition { clickableSpan.onClick(View(context)) }
+ }
+ }
+
+ 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.
+ doAndWaitForWindowTransition { clickableSpan.onClick(View(context)) }
+ }
+ }
+
+ 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.
+ doAndWaitForWindowTransition { clickableSpan.onClick(View(context)) }
+ }
+ }
+
+ 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"
+ }
+}
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..a75f08916
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionReviewTapjackingTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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 androidx.test.filters.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")),
+ TIMEOUT_MILLIS * 2
+ )
+ } 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..5ca23fea2
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionReviewTest.kt
@@ -0,0 +1,176 @@
+/*
+ * 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 androidx.test.filters.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..927b9833b
--- /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 androidx.test.filters.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,
+ waitForWindowTransition = false
+ ) {
+ if (expectSplit) {
+ clickPermissionRequestSettingsLinkAndAllowAlways()
+ } else {
+ doAndWaitForWindowTransition { 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,
+ waitForWindowTransition = false
+ ) {
+ if (expectSplit) {
+ clickPermissionRequestSettingsLinkAndAllowAlways()
+ } else {
+ doAndWaitForWindowTransition { 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..b81432369
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt
@@ -0,0 +1,150 @@
+/*
+ * 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 androidx.test.filters.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)
+
+ // Automotive split-screen multitasking uses multi-window mode
+ assumeFalse(isAutomotiveSplitscreen)
+
+ 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)
+
+ // Automotive split-screen multitasking uses multi-window mode
+ assumeFalse(isAutomotiveSplitscreen)
+
+ 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..b6d5887d6
--- /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 androidx.test.filters.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(),
+ waitForWindowTransition = false
+ ) {}
+ }
+}
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..01993adc5
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest23.kt
@@ -0,0 +1,403 @@
+/*
+ * 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 androidx.test.filters.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,
+ waitForWindowTransition = false
+ ) {}
+ }
+
+ @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,
+ waitForWindowTransition = 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()
+ 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,
+ waitForWindowTransition = 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,
+ waitForWindowTransition = 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,
+ waitForWindowTransition = false
+ ) {}
+ }
+
+ @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,
+ waitForWindowTransition = 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..892cae5c6
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest29.kt
@@ -0,0 +1,205 @@
+/*
+ * 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 androidx.test.filters.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,
+ waitForWindowTransition = 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,
+ waitForWindowTransition = false
+ ) {
+ 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,
+ waitForWindowTransition = false
+ ) {
+ 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,
+ waitForWindowTransition = false
+ ) {
+ 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,
+ waitForWindowTransition = 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,
+ waitForWindowTransition = false
+ ) {
+ openSettingsThenDoNothingThenLeave()
+
+ assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, false)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
+
+ doAndWaitForWindowTransition { 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,
+ waitForWindowTransition = false
+ ) {
+ openSettingsThenDoNothingThenLeave()
+
+ assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, true)
+ assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
+
+ doAndWaitForWindowTransition { 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,
+ waitForWindowTransition = false
+ ) {
+ clickPermissionRequestSettingsLinkAndDeny()
+ 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..25bdab298
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest30.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.Manifest.permission.ACCESS_BACKGROUND_LOCATION
+import android.Manifest.permission.ACCESS_COARSE_LOCATION
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import androidx.test.filters.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,
+ waitForWindowTransition = 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,
+ waitForWindowTransition = false
+ ) {
+ clickAllowAlwaysInSettings()
+ 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
+ )
+ // Close dialog
+ clickPermissionRequestDenyButton()
+ }
+ }
+}
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..493aa60fb
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTest30WithBluetooth.kt
@@ -0,0 +1,225 @@
+/*
+ * 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.util.Log
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.FlakyTest
+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..3948e984f
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionUpgradeTest.kt
@@ -0,0 +1,154 @@
+/*
+ * 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 androidx.test.filters.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..80e3dfaed
--- /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 androidx.test.filters.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() {
+ doAndWaitForWindowTransition {
+ 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..1f1aba1e7
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerPermissionTest.kt
@@ -0,0 +1,522 @@
+/*
+ * 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.app.UiAutomation.ROTATION_FREEZE_270
+import android.app.UiAutomation.ROTATION_UNFREEZE
+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.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_PRIVACY
+import androidx.test.filters.FlakyTest
+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 com.android.compatibility.common.util.UiAutomatorUtils2
+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),
+ waitForWindowTransition = false
+ ) {
+ doAndWaitForWindowTransition { 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),
+ waitForWindowTransition = false
+ ) {
+ doAndWaitForWindowTransition { 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, waitForWindowTransition = false) {
+ 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),
+ waitForWindowTransition = false
+ ) {
+ doAndWaitForWindowTransition { 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, waitForWindowTransition = false) {
+ doAndWaitForWindowTransition { click(By.res(SELECT_BUTTON)) }
+ clickImageOrVideo()
+ doAndWaitForWindowTransition { clickAllow() }
+ }
+
+ requestAppPermissions(READ_MEDIA_IMAGES, waitForWindowTransition = false) {
+ doAndWaitForWindowTransition { click(By.res(SELECT_BUTTON)) }
+ clickImageOrVideo()
+ doAndWaitForWindowTransition { clickAllow() }
+ }
+
+ assertPermissionFlags(READ_MEDIA_VISUAL_USER_SELECTED, FLAG_PERMISSION_USER_FIXED to true)
+
+ requestAppPermissions(READ_MEDIA_IMAGES, waitForWindowTransition = false) {
+ findImageOrVideo(expected = true)
+ uiDevice.pressBack()
+ }
+ }
+
+ @Test
+ fun testRequestedPermsFilterMediaType() {
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissions(READ_MEDIA_IMAGES, waitForWindowTransition = false) {
+ doAndWaitForWindowTransition { click(By.res(SELECT_BUTTON)) }
+ findImageOrVideo(expected = true)
+ findVideo(expected = false)
+ uiDevice.pressBack()
+ }
+
+ requestAppPermissions(READ_MEDIA_VIDEO, waitForWindowTransition = false) {
+ doAndWaitForWindowTransition { 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, waitForWindowTransition = false) {
+ findView(By.res(SELECT_BUTTON), expected = false)
+ pressBack()
+ }
+
+ navigateToIndividualPermissionSetting(READ_MEDIA_IMAGES)
+ findView(By.res(SELECT_RADIO_BUTTON), expected = false)
+ }
+
+ @Test
+ fun test33AppWithImplicitUserSelectDoesntShowSelect() {
+ installPackage(APP_APK_PATH_STORAGE_33)
+
+ 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, waitForWindowTransition = false) {
+ 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,
+ waitForWindowTransition = false
+ ) {}
+ uninstallPackage(APP_PACKAGE_NAME)
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissionsAndAssertResult(
+ READ_MEDIA_VISUAL_USER_SELECTED to false,
+ ACCESS_MEDIA_LOCATION to false,
+ waitForWindowTransition = 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,
+ waitForWindowTransition = false
+ ) {}
+ }
+
+ @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,
+ waitForWindowTransition = false
+ ) {
+ doAndWaitForWindowTransition { 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,
+ waitForWindowTransition = 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,
+ waitForWindowTransition = false
+ ) {
+ doAndWaitForWindowTransition { click(By.res(SELECT_BUTTON)) }
+ clickImageOrVideo()
+ doAndWaitForWindowTransition { 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,
+ waitForWindowTransition = false
+ ) {
+ doAndWaitForWindowTransition { click(By.res(SELECT_BUTTON)) }
+ clickImageOrVideo()
+ doAndWaitForWindowTransition { clickAllow() }
+ }
+ assertAppHasPermission(ACCESS_MEDIA_LOCATION, false)
+ }
+
+ @Test
+ fun testDismissAfterActivityRecreatedWithPickerOpen() {
+ installPackage(APP_APK_PATH_LATEST)
+ requestAppPermissionsAndAssertResult(
+ READ_MEDIA_IMAGES to false,
+ READ_MEDIA_VISUAL_USER_SELECTED to true,
+ waitForWindowTransition = false
+ ) {
+ doAndWaitForWindowTransition { click(By.res(SELECT_BUTTON)) }
+ clickImageOrVideo()
+ try {
+ doAndWaitForWindowTransition { uiAutomation.setRotation(ROTATION_FREEZE_270) }
+ clickImageOrVideo()
+ doAndWaitForWindowTransition { clickAllow() }
+ } finally {
+ uiAutomation.setRotation(ROTATION_UNFREEZE)
+ }
+ }
+ }
+
+ @Test
+ fun testCanSelectPhotosInSettings() {
+ installPackage(APP_APK_PATH_LATEST)
+ navigateToIndividualPermissionSetting(READ_MEDIA_IMAGES)
+ click(By.res(SELECT_RADIO_BUTTON))
+ doAndWaitForWindowTransition { click(By.res(EDIT_PHOTOS_BUTTON)) }
+ clickImageOrVideo()
+ clickAllow()
+ }
+
+ @Test
+ fun testEditButtonNotShownInSettingsWhenNoPhotosRequested() {
+ installPackage(APP_APK_PATH_LATEST)
+ navigateToIndividualPermissionSetting(READ_MEDIA_IMAGES)
+ UiAutomatorUtils2.waitUntilObjectGone(By.res(EDIT_PHOTOS_BUTTON))
+ }
+
+ private fun clickImageOrVideo() {
+ click(By.res(PhotoPickerUtils.getImageOrVideoResId(context)))
+ }
+
+ private fun clickAllow() {
+ click(By.res(PhotoPickerUtils.getAllowId(context)))
+ }
+
+ 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..9a6de1514
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerUtils.kt
@@ -0,0 +1,128 @@
+/*
+ * 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..03151c9fe
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/ReviewAccessibilityServicesTest.kt
@@ -0,0 +1,241 @@
+/*
+ * 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 androidx.test.filters.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 androidx.test.uiautomator.Until
+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)
+ private val packageName = context.packageManager.permissionControllerPackageName
+
+ companion object {
+ private const val EXPECTED_TIMEOUT_MS = 500L
+ private const val NEW_WINDOW_TIMEOUT_MILLIS: Long = 20_000
+ }
+
+ @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()
+ findTestService2(true)!!.click()
+ waitForSettingsButtonToDisappear()
+ findTestService2(true)
+ findTestService(false)
+ }
+
+ private fun startAccessibilityActivity() {
+ val automan =
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES)
+ doAndWaitForWindowTransition {
+ 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 inline fun doAndWaitForWindowTransition(crossinline block: () -> Unit) {
+ val timeoutOccurred: Boolean =
+ !uiDevice.performActionAndWait(
+ { block() },
+ Until.newWindow(),
+ NEW_WINDOW_TIMEOUT_MILLIS
+ )
+
+ if (timeoutOccurred) {
+ throw RuntimeException("Timed out waiting for window transition.")
+ }
+ }
+
+ 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 {
+ findPCObjectByClassAndText(false,
+ "android.widget.Button",
+ "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)
+ }
+ }
+
+ private fun findPCObjectByClassAndText(
+ shouldBePresent: Boolean,
+ className: String,
+ text: String
+ ): UiObject2? {
+ val selector = By.pkg(packageName)
+ .clazz(className)
+ .text(text)
+ val view = waitFindObjectOrNull(selector)
+ assertEquals(
+ "Expected to find view with packageName '$packageName' className '$className' " +
+ "text '$text' : $shouldBePresent", shouldBePresent, view != null)
+ return view
+ }
+}
+
+/** 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..690d76729
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/SafetyLabelChangesJobServiceTest.kt
@@ -0,0 +1,507 @@
+/*
+ * 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.provider.DeviceConfig
+import android.safetylabel.SafetyLabelConstants
+import android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.FlakyTest
+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..541ea9d16
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/SafetyProtectionTest.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.provider.DeviceConfig
+import androidx.test.filters.FlakyTest
+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..614b59f3c
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/SensorBlockedBannerTest.kt
@@ -0,0 +1,182 @@
+/*
+ * 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.safetycenter.SafetyCenterManager
+import androidx.test.filters.FlakyTest
+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 com.android.modules.utils.build.SdkLevel
+import java.util.regex.Pattern
+import org.junit.Assert.assertTrue
+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 DELAY_MILLIS = 3000L
+ private const val CHANGE_BUTTON = "com.android.permissioncontroller:id/button_id"
+ private const val CAMERA_TOGGLE_LABEL = "Camera access"
+ }
+
+ private val sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!!
+ private val locationManager = context.getSystemService(LocationManager::class.java)!!
+
+ 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)
+ }
+
+ 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)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ runWithShellPermissionIdentity { context.startActivity(intent) }
+ val bannerTitle = permToTitle.getOrDefault(sensor, "Break")
+ waitFindObject(By.text(getPermissionControllerString(bannerTitle)))
+ }
+
+ 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)
+ }
+
+ @Test
+ fun testCardClickOpenPrivacyControls() {
+ Assume.assumeTrue(SdkLevel.isAtLeastT())
+ Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(CAMERA))
+ val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)
+ Assume.assumeNotNull(safetyCenterManager)
+
+ var isSafetyCenterEnabled = false
+ runWithShellPermissionIdentity {
+ isSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled
+ }
+ Assume.assumeTrue(isSafetyCenterEnabled)
+ // Disable global camera toggle
+ val blocked = isSensorPrivacyEnabled(CAMERA)
+ if (!blocked) {
+ setSensor(CAMERA, true)
+ }
+ // verify sensor card is shown for blocked camera
+ navigateAndTest(CAMERA)
+ click(By.res(CHANGE_BUTTON))
+ // Enable global camera toggle and verify
+ waitFindObject(By.text(CAMERA_TOGGLE_LABEL)).click()
+ assertTrue(!isSensorPrivacyEnabled(CAMERA))
+ }
+
+ 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..f9f9e8cd2
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/StartForFutureActivity.kt
@@ -0,0 +1,62 @@
+/*
+ * 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 android.os.Bundle
+import android.util.Log
+import android.util.LruCache
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.atomic.AtomicInteger
+
+class StartForFutureActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (savedInstanceState != null) {
+ Log.w(TAG, "Activity was recreated. (Perhaps due to a configuration change?)")
+ }
+ }
+
+ fun startActivityForFuture(
+ intent: Intent,
+ future: CompletableFuture<Instrumentation.ActivityResult>
+ ) {
+ val requestCode = nextRequestCode.getAndIncrement()
+ futures.put(requestCode, future)
+ startActivityForResult(intent, requestCode)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ val future =
+ futures.remove(requestCode)
+ ?: throw IllegalStateException(
+ "StartForFutureActivity received an activity result with an unknown requestCode"
+ )
+ future.complete(Instrumentation.ActivityResult(resultCode, data))
+ finish()
+ }
+
+ companion object {
+ private val TAG = StartForFutureActivity::class.simpleName
+ private var nextRequestCode = AtomicInteger(1)
+ private val futures = LruCache<Int, CompletableFuture<Instrumentation.ActivityResult>>(10)
+ }
+}
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()
diff --git a/tests/cts/role/Android.bp b/tests/cts/role/Android.bp
new file mode 100644
index 000000000..2a312976b
--- /dev/null
+++ b/tests/cts/role/Android.bp
@@ -0,0 +1,51 @@
+// 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: "CtsRoleTestCases",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ min_sdk_version: "30",
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "android.permission.flags-aconfig-java",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "platform-test-annotations",
+ "truth",
+ ],
+
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-permission",
+ ],
+
+ data: [
+ ":CtsRoleTestApp",
+ ":CtsRoleTestApp28",
+ ":CtsRoleTestApp33WithoutInCallService",
+ ],
+}
diff --git a/tests/cts/role/AndroidManifest.xml b/tests/cts/role/AndroidManifest.xml
new file mode 100644
index 000000000..a8c8c8e3d
--- /dev/null
+++ b/tests/cts/role/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?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.app.role.cts">
+
+ <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" />
+
+ <activity
+ android:name=".WaitForResultActivity"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"/>
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.app.role.cts"
+ android:label="CTS tests of android.app.role">
+ </instrumentation>
+</manifest>
diff --git a/tests/cts/role/AndroidTest.xml b/tests/cts/role/AndroidTest.xml
new file mode 100644
index 000000000..527ac3d32
--- /dev/null
+++ b/tests/cts/role/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?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 role test cases">
+
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="permissions" />
+ <option name="config-descriptor:metadata" key="parameter" value="all_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" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsRoleTestCases.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts-role" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts-role"/>
+ </target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsRoleTestApp.apk->/data/local/tmp/cts-role/CtsRoleTestApp.apk" />
+ <option name="push" value="CtsRoleTestApp28.apk->/data/local/tmp/cts-role/CtsRoleTestApp28.apk" />
+ <option name="push" value="CtsRoleTestApp33WithoutInCallService.apk->/data/local/tmp/cts-role/CtsRoleTestApp33WithoutInCallService.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.app.role.cts" />
+ <option name="runtime-hint" value="5m" />
+ </test>
+</configuration>
diff --git a/tests/cts/role/CtsRoleTestApp/Android.bp b/tests/cts/role/CtsRoleTestApp/Android.bp
new file mode 100644
index 000000000..1270e490b
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp/Android.bp
@@ -0,0 +1,26 @@
+// 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: "CtsRoleTestApp",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "30",
+ srcs: [
+ "src/**/*.java"
+ ],
+}
diff --git a/tests/cts/role/CtsRoleTestApp/AndroidManifest.xml b/tests/cts/role/CtsRoleTestApp/AndroidManifest.xml
new file mode 100644
index 000000000..b2dfca961
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp/AndroidManifest.xml
@@ -0,0 +1,131 @@
+<?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.app.role.cts.app">
+
+ <uses-permission android:name="android.permission.SEND_SMS" />
+
+ <application android:label="CtsRoleTestApp">
+
+ <activity
+ android:name=".RequestRoleActivity"
+ android:exported="true" />
+
+ <activity
+ android:name=".IsRoleHeldActivity"
+ android:exported="true" />
+
+ <activity
+ android:name=".ChangeDefaultDialerActivity"
+ android:exported="true" />
+
+ <activity
+ android:name=".ChangeDefaultSmsActivity"
+ android:exported="true" />
+
+ <!-- Dialer -->
+ <activity
+ android:name=".DialerDialActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="tel" />
+ </intent-filter>
+ </activity>
+ <service
+ android:name=".DialerInCallService"
+ android:permission="android.permission.BIND_INCALL_SERVICE"
+ android:exported="true">
+ <meta-data
+ android:name="android.telecom.IN_CALL_SERVICE_UI"
+ android:value="true"/>
+ <meta-data
+ android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
+ android:value="false"/>
+ <intent-filter>
+ <action android:name="android.telecom.InCallService" />
+ </intent-filter>
+ </service>
+ <!-- Sms -->
+ <activity
+ android:name=".SmsSendToActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.SENDTO" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="smsto" />
+ </intent-filter>
+ </activity>
+ <service
+ android:name=".SmsRespondViaMessageService"
+ android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="smsto" />
+ </intent-filter>
+ </service>
+ <receiver
+ android:name=".SmsDelieverReceiver"
+ android:permission="android.permission.BROADCAST_SMS"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_DELIVER" />
+ </intent-filter>
+ </receiver>
+ <receiver
+ android:name=".SmsWapPushDelieverReceiver"
+ android:permission="android.permission.BROADCAST_WAP_PUSH"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+ <data android:mimeType="application/vnd.wap.mms-message" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Browser -->
+ <activity
+ android:name=".BrowserActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="http" />
+ </intent-filter>
+ </activity>
+
+ <!-- Assistant -->
+ <activity
+ android:name=".AssistantActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.ASSIST" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultDialerActivity.java b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultDialerActivity.java
new file mode 100644
index 000000000..89cafa001
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultDialerActivity.java
@@ -0,0 +1,52 @@
+/*
+ * 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.app.role.cts.app;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.TelecomManager;
+
+/**
+ * An activity that tries to change the default dialer app.
+ */
+public class ChangeDefaultDialerActivity extends Activity {
+
+ private static final int REQUEST_CODE_CHANGE_DEFAULT_DIALER = 1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER)
+ .putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName);
+ startActivityForResult(intent, REQUEST_CODE_CHANGE_DEFAULT_DIALER);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_CHANGE_DEFAULT_DIALER) {
+ setResult(resultCode, data);
+ finish();
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+}
diff --git a/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultSmsActivity.java b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultSmsActivity.java
new file mode 100644
index 000000000..00559bf44
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultSmsActivity.java
@@ -0,0 +1,52 @@
+/*
+ * 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.app.role.cts.app;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Telephony;
+
+/**
+ * An activity that tries to change the default SMS app.
+ */
+public class ChangeDefaultSmsActivity extends Activity {
+
+ private static final int REQUEST_CODE_CHANGE_DEFAULT_SMS = 1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT)
+ .putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, packageName);
+ startActivityForResult(intent, REQUEST_CODE_CHANGE_DEFAULT_SMS);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_CHANGE_DEFAULT_SMS) {
+ setResult(resultCode, data);
+ finish();
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+}
diff --git a/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/IsRoleHeldActivity.java b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/IsRoleHeldActivity.java
new file mode 100644
index 000000000..8e97f9f24
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/IsRoleHeldActivity.java
@@ -0,0 +1,46 @@
+/*
+ * 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.app.role.cts.app;
+
+import android.app.Activity;
+import android.app.role.RoleManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+/**
+ * An activity that checks whether a role is held.
+ */
+public class IsRoleHeldActivity extends Activity {
+
+ private static final String EXTRA_IS_ROLE_HELD = "android.app.role.cts.app.extra.IS_ROLE_HELD";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ String roleName = getIntent().getStringExtra(Intent.EXTRA_ROLE_NAME);
+ if (TextUtils.isEmpty(roleName)) {
+ throw new IllegalArgumentException("Role name in extras cannot be null or empty");
+ }
+
+ RoleManager roleManager = getSystemService(RoleManager.class);
+ setResult(RESULT_OK, new Intent()
+ .putExtra(EXTRA_IS_ROLE_HELD, roleManager.isRoleHeld(roleName)));
+ finish();
+ }
+}
diff --git a/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java
new file mode 100644
index 000000000..b2d69e044
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java
@@ -0,0 +1,53 @@
+/*
+ * 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.app.role.cts.app;
+
+import android.app.Activity;
+import android.app.role.RoleManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+/**
+ * An activity that requests a role.
+ */
+public class RequestRoleActivity extends Activity {
+
+ private static final int REQUEST_CODE_REQUEST_ROLE = 1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ String roleName = getIntent().getStringExtra(Intent.EXTRA_ROLE_NAME);
+ RoleManager roleManager = getSystemService(RoleManager.class);
+ Intent intent = roleManager.createRequestRoleIntent(roleName);
+ startActivityForResult(intent, REQUEST_CODE_REQUEST_ROLE);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_REQUEST_ROLE) {
+ setResult(resultCode, data);
+ finish();
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+}
diff --git a/tests/cts/role/CtsRoleTestApp28/Android.bp b/tests/cts/role/CtsRoleTestApp28/Android.bp
new file mode 100644
index 000000000..dc8239edb
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp28/Android.bp
@@ -0,0 +1,27 @@
+// 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: "CtsRoleTestApp28",
+ min_sdk_version: "28",
+ target_sdk_version: "28",
+
+ srcs: [
+ "src/**/*.java"
+ ],
+}
diff --git a/tests/cts/role/CtsRoleTestApp28/AndroidManifest.xml b/tests/cts/role/CtsRoleTestApp28/AndroidManifest.xml
new file mode 100644
index 000000000..8113b2676
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp28/AndroidManifest.xml
@@ -0,0 +1,79 @@
+<?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.app.role.cts.app28">
+
+ <uses-permission android:name="android.permission.SEND_SMS"/>
+
+ <application android:label="CtsRoleTestApp28">
+
+ <activity android:name=".ChangeDefaultDialerActivity"
+ android:exported="true"/>
+
+ <activity android:name=".ChangeDefaultSmsActivity"
+ android:exported="true"/>
+
+ <!-- Dialer -->
+ <activity android:name=".DialerDialActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="tel"/>
+ </intent-filter>
+ </activity>
+
+ <!-- Sms -->
+ <activity android:name=".SmsSendToActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.SENDTO"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="smsto"/>
+ </intent-filter>
+ </activity>
+ <service android:name=".SmsRespondViaMessageService"
+ android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="smsto"/>
+ </intent-filter>
+ </service>
+ <receiver android:name=".SmsDelieverReceiver"
+ android:permission="android.permission.BROADCAST_SMS"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_DELIVER"/>
+ </intent-filter>
+ </receiver>
+ <receiver android:name=".SmsWapPushDelieverReceiver"
+ android:permission="android.permission.BROADCAST_WAP_PUSH"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+ <data android:mimeType="application/vnd.wap.mms-message"/>
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
diff --git a/tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultDialerActivity.java b/tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultDialerActivity.java
new file mode 100644
index 000000000..5d1c47cfc
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultDialerActivity.java
@@ -0,0 +1,40 @@
+/*
+ * 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.app.role.cts.app28;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.TelecomManager;
+
+/**
+ * An activity that tries to change the default dialer app.
+ */
+public class ChangeDefaultDialerActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER)
+ .putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName);
+ startActivity(intent);
+ }
+ }
+}
diff --git a/tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultSmsActivity.java b/tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultSmsActivity.java
new file mode 100644
index 000000000..37819bbec
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultSmsActivity.java
@@ -0,0 +1,41 @@
+/*
+ * 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.app.role.cts.app28;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Telephony;
+import android.telecom.TelecomManager;
+
+/**
+ * An activity that tries to change the default SMS app.
+ */
+public class ChangeDefaultSmsActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT)
+ .putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, packageName);
+ startActivity(intent);
+ }
+ }
+}
diff --git a/tests/cts/role/CtsRoleTestApp33WithoutInCallService/Android.bp b/tests/cts/role/CtsRoleTestApp33WithoutInCallService/Android.bp
new file mode 100644
index 000000000..7cce565af
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp33WithoutInCallService/Android.bp
@@ -0,0 +1,23 @@
+// 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: "CtsRoleTestApp33WithoutInCallService",
+ min_sdk_version: "30",
+ target_sdk_version: "33",
+}
diff --git a/tests/cts/role/CtsRoleTestApp33WithoutInCallService/AndroidManifest.xml b/tests/cts/role/CtsRoleTestApp33WithoutInCallService/AndroidManifest.xml
new file mode 100644
index 000000000..a6504adae
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp33WithoutInCallService/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.app.role.cts.app33WithoutInCallService">
+ <application android:label="CtsRoleTestApp33WithoutInCallService">
+ <!-- Dialer -->
+ <activity
+ android:name=".DialerDialActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="tel" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/cts/role/OWNERS b/tests/cts/role/OWNERS
new file mode 100644
index 000000000..fb6099cf7
--- /dev/null
+++ b/tests/cts/role/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/tests/cts/role/TEST_MAPPING b/tests/cts/role/TEST_MAPPING
new file mode 100644
index 000000000..01d04bea0
--- /dev/null
+++ b/tests/cts/role/TEST_MAPPING
@@ -0,0 +1,48 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsRoleTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "mainline-presubmit": [
+ {
+ "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"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.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/tests/cts/role/src/android/app/role/cts/RoleControllerManagerTest.kt b/tests/cts/role/src/android/app/role/cts/RoleControllerManagerTest.kt
new file mode 100644
index 000000000..0fd7c0152
--- /dev/null
+++ b/tests/cts/role/src/android/app/role/cts/RoleControllerManagerTest.kt
@@ -0,0 +1,167 @@
+/*
+ * 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.app.role.cts
+
+import android.app.Instrumentation
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Process
+import android.provider.Settings
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SdkSuppress
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.ThrowingSupplier
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests RoleControllerManager APIs exposed on [RoleManager]. */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+class RoleControllerManagerTest {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context: Context = instrumentation.context
+ private val packageManager: PackageManager = context.packageManager
+ private val roleManager: RoleManager = context.getSystemService(RoleManager::class.java)!!
+
+ @Before
+ fun installApp() {
+ installPackage(APP_APK_PATH)
+ }
+
+ @After
+ fun uninstallApp() {
+ uninstallPackage(APP_PACKAGE_NAME)
+ }
+
+ @Test
+ fun appIsVisibleForRole() {
+ assumeRoleIsVisible()
+ assertAppIsVisibleForRole(APP_PACKAGE_NAME, ROLE_NAME, true)
+ }
+
+ @Test
+ fun settingsIsNotVisibleForHomeRole() {
+ // Settings should never show as a possible home app even if qualified.
+ val settingsPackageName =
+ packageManager
+ .resolveActivity(
+ Intent(Settings.ACTION_SETTINGS),
+ PackageManager.MATCH_DEFAULT_ONLY or
+ PackageManager.MATCH_DIRECT_BOOT_AWARE or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ )!!
+ .activityInfo
+ .packageName
+ assertAppIsVisibleForRole(settingsPackageName, RoleManager.ROLE_HOME, false)
+ }
+
+ @Test
+ fun appIsNotVisibleForInvalidRole() {
+ assertAppIsVisibleForRole(APP_PACKAGE_NAME, "invalid", false)
+ }
+
+ @Test
+ fun invalidAppIsNotVisibleForRole() {
+ assertAppIsVisibleForRole("invalid", ROLE_NAME, false)
+ }
+
+ private fun assertAppIsVisibleForRole(
+ packageName: String,
+ roleName: String,
+ expectedIsVisible: Boolean
+ ) {
+ runWithShellPermissionIdentity {
+ val future = CompletableFuture<Boolean>()
+ roleManager.isApplicationVisibleForRole(
+ roleName,
+ packageName,
+ context.mainExecutor,
+ Consumer { future.complete(it) }
+ )
+ val isVisible = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ assertThat(isVisible).isEqualTo(expectedIsVisible)
+ }
+ }
+
+ private fun assumeRoleIsVisible() {
+ assumeTrue(isRoleVisible(ROLE_NAME))
+ }
+
+ @Test
+ fun systemGalleryRoleIsNotVisible() {
+ // The system gallery role should always be hidden.
+ assertThat(isRoleVisible(SYSTEM_GALLERY_ROLE_NAME)).isEqualTo(false)
+ }
+
+ @Test
+ fun invalidRoleIsNotVisible() {
+ assertThat(isRoleVisible("invalid")).isEqualTo(false)
+ }
+
+ private fun isRoleVisible(roleName: String): Boolean =
+ runWithShellPermissionIdentity(
+ ThrowingSupplier {
+ val future = CompletableFuture<Boolean>()
+ roleManager.isRoleVisible(
+ roleName,
+ context.mainExecutor,
+ Consumer { future.complete(it) }
+ )
+ future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ }
+ )
+
+ private fun installPackage(apkPath: String) {
+ assertEquals(
+ "Success",
+ runShellCommandOrThrow(
+ "pm install -r --user ${Process.myUserHandle().identifier} $apkPath"
+ )
+ .trim()
+ )
+ }
+
+ private fun uninstallPackage(packageName: String) {
+ assertEquals(
+ "Success",
+ runShellCommand("pm uninstall --user ${Process.myUserHandle().identifier} $packageName")
+ .trim()
+ )
+ }
+
+ companion object {
+ private const val ROLE_NAME = RoleManager.ROLE_BROWSER
+ private const val APP_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestApp.apk"
+ private const val APP_PACKAGE_NAME = "android.app.role.cts.app"
+ private const val SYSTEM_GALLERY_ROLE_NAME = "android.app.role.SYSTEM_GALLERY"
+ private const val TIMEOUT_MILLIS = 15 * 1000L
+ }
+}
diff --git a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
new file mode 100644
index 000000000..e6b27382f
--- /dev/null
+++ b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
@@ -0,0 +1,1347 @@
+/*
+ * 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.app.role.cts;
+
+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.runShellCommandOrThrow;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject;
+import static com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObjectOrNull;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.AppOpsManager;
+import android.app.Instrumentation;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.permission.flags.Flags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.Settings;
+import android.provider.Telephony;
+import android.telephony.TelephonyManager;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.Until;
+
+import com.android.compatibility.common.util.DisableAnimationRule;
+import com.android.compatibility.common.util.FreezeRotationRule;
+import com.android.compatibility.common.util.TestUtils;
+import com.android.compatibility.common.util.ThrowingRunnable;
+import com.android.compatibility.common.util.UiAutomatorUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+
+/**
+ * Tests {@link RoleManager}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RoleManagerTest {
+
+ private static final long TIMEOUT_MILLIS = 15 * 1000;
+
+ private static final long UNEXPECTED_TIMEOUT_MILLIS = 1000;
+
+ private static final String ROLE_NAME = RoleManager.ROLE_BROWSER;
+ private static final String ROLE_PHONE_NAME = RoleManager.ROLE_DIALER;
+ private static final String ROLE_SMS_NAME = RoleManager.ROLE_SMS;
+ private static final String ROLE_SHORT_LABEL = "Browser app";
+
+ private static final String APP_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestApp.apk";
+ private static final String APP_PACKAGE_NAME = "android.app.role.cts.app";
+ private static final String APP_LABEL = "CtsRoleTestApp";
+ private static final String APP_IS_ROLE_HELD_ACTIVITY_NAME = APP_PACKAGE_NAME
+ + ".IsRoleHeldActivity";
+ private static final String APP_IS_ROLE_HELD_EXTRA_IS_ROLE_HELD = APP_PACKAGE_NAME
+ + ".extra.IS_ROLE_HELD";
+ private static final String APP_REQUEST_ROLE_ACTIVITY_NAME = APP_PACKAGE_NAME
+ + ".RequestRoleActivity";
+ private static final String APP_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME = APP_PACKAGE_NAME
+ + ".ChangeDefaultDialerActivity";
+ private static final String APP_CHANGE_DEFAULT_SMS_ACTIVITY_NAME = APP_PACKAGE_NAME
+ + ".ChangeDefaultSmsActivity";
+
+ private static final String APP_28_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestApp28.apk";
+ private static final String APP_28_PACKAGE_NAME = "android.app.role.cts.app28";
+ private static final String APP_28_LABEL = "CtsRoleTestApp28";
+ private static final String APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME = APP_28_PACKAGE_NAME
+ + ".ChangeDefaultDialerActivity";
+ private static final String APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME = APP_28_PACKAGE_NAME
+ + ".ChangeDefaultSmsActivity";
+
+ private static final String APP_33_WITHOUT_INCALLSERVICE_APK_PATH =
+ "/data/local/tmp/cts-role/CtsRoleTestApp33WithoutInCallService.apk";
+ private static final String APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME =
+ "android.app.role.cts.app33WithoutInCallService";
+
+ private static final String PERMISSION_MANAGE_ROLES_FROM_CONTROLLER =
+ "com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER";
+
+ private static final Instrumentation sInstrumentation =
+ InstrumentationRegistry.getInstrumentation();
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+ private static final PackageManager sPackageManager = sContext.getPackageManager();
+ private static final RoleManager sRoleManager = sContext.getSystemService(RoleManager.class);
+ private static final boolean sIsAutomotive = sPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE);
+ private static final boolean sIsTelevision = sPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_TELEVISION);
+ private static final boolean sIsWatch = sPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_WATCH);
+
+ private static final BySelector ENHANCED_CONFIRMATION_DIALOG_SELECTOR =
+ By.res("com.android.permissioncontroller:id/enhanced_confirmation_dialog_title");
+ // TODO(b/327528959): consider using resource selectors for Wear too, once the underlying
+ // issue is handled.
+ private static final BySelector NEGATIVE_BUTTON_SELECTOR =
+ sIsWatch ? By.text("Cancel") : By.res("android:id/button2");
+ private static final BySelector POSITIVE_BUTTON_SELECTOR =
+ sIsWatch ? By.text("Set as default") : By.res("android:id/button1");
+ private static final BySelector DONT_ASK_AGAIN_TOGGLE_SELECTOR =
+ sIsWatch
+ ? By.text("Don\u2019t ask again")
+ : By.res("com.android.permissioncontroller:id/dont_ask_again");
+
+ @Rule
+ public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public DisableAnimationRule mDisableAnimationRule = new DisableAnimationRule();
+
+ @Rule
+ public FreezeRotationRule mFreezeRotationRule = new FreezeRotationRule();
+
+ @Rule
+ public ActivityTestRule<WaitForResultActivity> mActivityRule =
+ new ActivityTestRule<>(WaitForResultActivity.class);
+
+ private String mRoleHolder;
+
+ @Before
+ public void saveRoleHolder() throws Exception {
+ List<String> roleHolders = getRoleHolders(ROLE_NAME);
+ mRoleHolder = !roleHolders.isEmpty() ? roleHolders.get(0) : null;
+
+ if (Objects.equals(mRoleHolder, APP_PACKAGE_NAME)) {
+ removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+ mRoleHolder = null;
+ }
+ }
+
+ @After
+ public void restoreRoleHolder() throws Exception {
+ removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ if (mRoleHolder != null) {
+ addRoleHolder(ROLE_NAME, mRoleHolder);
+ }
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ }
+
+ @Before
+ public void installApp() throws Exception {
+ installPackage(APP_APK_PATH);
+ installPackage(APP_28_APK_PATH);
+ installPackage(APP_33_WITHOUT_INCALLSERVICE_APK_PATH);
+ }
+
+ @After
+ public void uninstallApp() throws Exception {
+ uninstallPackage(APP_PACKAGE_NAME);
+ uninstallPackage(APP_28_PACKAGE_NAME);
+ uninstallPackage(APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME);
+ }
+
+ @Before
+ public void wakeUpScreen() throws IOException {
+ runShellCommand(sInstrumentation, "input keyevent KEYCODE_WAKEUP");
+ }
+
+ @Before
+ public void closeNotificationShade() {
+ sContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ }
+
+ @Test
+ public void requestRoleIntentHasPermissionControllerPackage() throws Exception {
+ Intent intent = sRoleManager.createRequestRoleIntent(ROLE_NAME);
+
+ assertThat(intent.getPackage()).isEqualTo(
+ sPackageManager.getPermissionControllerPackageName());
+ }
+
+ @Test
+ public void requestRoleIntentHasExtraRoleName() throws Exception {
+ Intent intent = sRoleManager.createRequestRoleIntent(ROLE_NAME);
+
+ assertThat(intent.getStringExtra(Intent.EXTRA_ROLE_NAME)).isEqualTo(ROLE_NAME);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void requestRoleAndDenyThenIsNotRoleHolder() throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(false);
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void requestRoleAndAllowThenIsRoleHolder() throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(true);
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
+ "VanillaIceCream")
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void requestRoleThenBlockRequestRoleDialogByRestrictedSettingDialog() throws Exception {
+ assumeFalse(sIsWatch || sIsAutomotive || sIsTelevision);
+ runWithShellPermissionIdentity(
+ () -> setEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME,
+ AppOpsManager.MODE_ERRORED));
+
+ requestRole(ROLE_SMS_NAME);
+ waitFindObject(ENHANCED_CONFIRMATION_DIALOG_SELECTOR, TIMEOUT_MILLIS);
+
+ pressBack();
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void requestRoleFirstTimeNoDontAskAgain() throws Exception {
+ requestRole(ROLE_NAME);
+ UiObject2 dontAskAgainCheck = findDontAskAgainCheck(false);
+
+ assertThat(dontAskAgainCheck).isNull();
+
+ respondToRoleRequest(false);
+ }
+
+ @Test
+ @FlakyTest
+ public void requestRoleAndDenyThenHasDontAskAgain() throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(false);
+
+ requestRole(ROLE_NAME);
+ UiObject2 dontAskAgainCheck = findDontAskAgainCheck();
+
+ assertThat(dontAskAgainCheck).isNotNull();
+
+ respondToRoleRequest(false);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void requestRoleAndDenyWithDontAskAgainReturnsCanceled() throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(false);
+
+ requestRole(ROLE_NAME);
+ findDontAskAgainCheck().click();
+ Pair<Integer, Intent> result = clickButtonAndWaitForResult(true);
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ @FlakyTest
+ public void requestRoleAndDenyWithDontAskAgainThenDeniedAutomatically() throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(false);
+
+ requestRole(ROLE_NAME);
+ findDontAskAgainCheck().click();
+ clickButtonAndWaitForResult(true);
+
+ requestRole(ROLE_NAME);
+ Pair<Integer, Intent> result = waitForResult();
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ @FlakyTest
+ public void requestRoleAndDenyWithDontAskAgainAndClearDataThenShowsUiWithoutDontAskAgain()
+ throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(false);
+
+ requestRole(ROLE_NAME);
+ findDontAskAgainCheck().click();
+ clickButtonAndWaitForResult(true);
+ // Wait for the RequestRoleActivity inside the test app to be removed from our task so that
+ // when the test app is force stopped, our task isn't force finished and our
+ // WaitForResultActivity can survive.
+ Thread.sleep(5000);
+
+ clearPackageData(APP_PACKAGE_NAME);
+ // Wait for the don't ask again to be forgotten.
+ Thread.sleep(10000);
+
+ TestUtils.waitUntil("Find and respond to request role UI", () -> {
+ requestRole(ROLE_NAME);
+ UiObject2 cancelButton = waitFindObjectOrNull(NEGATIVE_BUTTON_SELECTOR);
+ if (cancelButton == null) {
+ // Dialog not found, try again later.
+ return false;
+ }
+ UiObject2 dontAskAgainCheck = findDontAskAgainCheck(false);
+
+ assertThat(dontAskAgainCheck).isNull();
+
+ respondToRoleRequest(false);
+ return true;
+ });
+ }
+
+ @Test
+ @FlakyTest
+ public void requestRoleAndDenyWithDontAskAgainAndReinstallThenShowsUiWithoutDontAskAgain()
+ throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(false);
+
+ requestRole(ROLE_NAME);
+ findDontAskAgainCheck().click();
+ clickButtonAndWaitForResult(true);
+ // Wait for the RequestRoleActivity inside the test app to be removed from our task so that
+ // when the test app is uninstalled, our task isn't force finished and our
+ // WaitForResultActivity can survive.
+ Thread.sleep(5000);
+
+ uninstallPackage(APP_PACKAGE_NAME);
+ // Wait for the don't ask again to be forgotten.
+ Thread.sleep(10000);
+ installPackage(APP_APK_PATH);
+
+ TestUtils.waitUntil("Find and respond to request role UI", () -> {
+ requestRole(ROLE_NAME);
+ UiObject2 cancelButton = waitFindObjectOrNull(NEGATIVE_BUTTON_SELECTOR);
+ if (cancelButton == null) {
+ // Dialog not found, try again later.
+ return false;
+ }
+ UiObject2 dontAskAgainCheck = findDontAskAgainCheck(false);
+
+ assertThat(dontAskAgainCheck).isNull();
+
+ respondToRoleRequest(false);
+ return true;
+ });
+ }
+
+ @Test
+ public void requestInvalidRoleThenDeniedAutomatically() throws Exception {
+ requestRole("invalid");
+ Pair<Integer, Intent> result = waitForResult();
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void requestUnqualifiedRoleThenDeniedAutomatically() throws Exception {
+ requestRole(RoleManager.ROLE_HOME);
+ Pair<Integer, Intent> result = waitForResult();
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void requestAssistantRoleThenDeniedAutomatically() throws Exception {
+ requestRole(RoleManager.ROLE_ASSISTANT);
+ Pair<Integer, Intent> result = waitForResult();
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void requestHoldingRoleThenAllowedAutomatically() throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(true);
+
+ requestRole(ROLE_NAME);
+ Pair<Integer, Intent> result = waitForResult();
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_OK);
+ }
+
+ private void requestRole(@NonNull String roleName) {
+ Intent intent = new Intent()
+ .setComponent(new ComponentName(APP_PACKAGE_NAME, APP_REQUEST_ROLE_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_ROLE_NAME, roleName);
+ mActivityRule.getActivity().startActivityToWaitForResult(intent);
+ }
+
+ private void respondToRoleRequest(boolean allow)
+ throws InterruptedException, UiObjectNotFoundException {
+ if (allow) {
+ waitFindObject(By.text(APP_LABEL)).click();
+ }
+ Pair<Integer, Intent> result = clickButtonAndWaitForResult(allow);
+ int expectedResult = allow ? Activity.RESULT_OK : Activity.RESULT_CANCELED;
+
+ assertThat(result.first).isEqualTo(expectedResult);
+ }
+
+ @Nullable
+ private UiObject2 findDontAskAgainCheck(boolean expected) throws UiObjectNotFoundException {
+ return expected
+ ? waitFindObject(DONT_ASK_AGAIN_TOGGLE_SELECTOR)
+ : waitFindObjectOrNull(DONT_ASK_AGAIN_TOGGLE_SELECTOR, UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Nullable
+ private UiObject2 findDontAskAgainCheck() throws UiObjectNotFoundException {
+ return findDontAskAgainCheck(true);
+ }
+
+ @NonNull
+ private Pair<Integer, Intent> clickButtonAndWaitForResult(boolean positive)
+ throws InterruptedException, UiObjectNotFoundException {
+ waitFindObject(positive ? POSITIVE_BUTTON_SELECTOR : NEGATIVE_BUTTON_SELECTOR).click();
+ return waitForResult();
+ }
+
+ @NonNull
+ private Pair<Integer, Intent> waitForResult() throws InterruptedException {
+ return mActivityRule.getActivity().waitForActivityResult(TIMEOUT_MILLIS);
+ }
+
+ private void clearPackageData(@NonNull String packageName) {
+ runShellCommand("pm clear --user " + Process.myUserHandle().getIdentifier() + " "
+ + packageName);
+ }
+
+ private void installPackage(@NonNull String apkPath) {
+ runShellCommandOrThrow(
+ "pm install -r --user " + Process.myUserHandle().getIdentifier() + " " + apkPath);
+ }
+
+ private void uninstallPackage(@NonNull String packageName) {
+ runShellCommand("pm uninstall --user " + Process.myUserHandle().getIdentifier() + " "
+ + packageName);
+ }
+
+ @Test
+ public void targetCurrentSdkAndChangeDefaultDialerThenDeniedAutomatically() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
+
+ WaitForResultActivity activity = mActivityRule.getActivity();
+ activity.startActivityToWaitForResult(new Intent()
+ .setComponent(new ComponentName(APP_PACKAGE_NAME,
+ APP_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME));
+ Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void targetCurrentSdkAndChangeDefaultSmsThenDeniedAutomatically() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ WaitForResultActivity activity = mActivityRule.getActivity();
+ activity.startActivityToWaitForResult(new Intent()
+ .setComponent(new ComponentName(APP_PACKAGE_NAME,
+ APP_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME));
+ Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void targetSdk28AndChangeDefaultDialerAndAllowThenIsDefaultDialer() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
+
+ sContext.startActivity(new Intent()
+ .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
+ APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_28_PACKAGE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ waitFindObject(By.text(APP_28_LABEL)).click();
+ waitFindObject(POSITIVE_BUTTON_SELECTOR).click();
+
+ // TODO(b/149037075): Use TelecomManager.getDefaultDialerPackage() once the bug is fixed.
+ //TelecomManager telecomManager = sContext.getSystemService(TelecomManager.class);
+ //TestUtils.waitUntil("App is not set as default dialer app", () -> Objects.equals(
+ // telecomManager.getDefaultDialerPackage(), APP_28_PACKAGE_NAME));
+ TestUtils.waitUntil("App is not set as default dialer app", () ->
+ getRoleHolders(RoleManager.ROLE_DIALER).contains(APP_28_PACKAGE_NAME));
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void targetSdk28AndChangeDefaultSmsAndAllowThenIsDefaultSms() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ sContext.startActivity(new Intent()
+ .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
+ APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_28_PACKAGE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ waitFindObject(By.text(APP_28_LABEL)).click();
+ waitFindObject(POSITIVE_BUTTON_SELECTOR).click();
+
+ TestUtils.waitUntil("App is not set as default sms app", () -> Objects.equals(
+ Telephony.Sms.getDefaultSmsPackage(sContext), APP_28_PACKAGE_NAME));
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void targetSdk28AndChangeDefaultDialerForAnotherAppThenDeniedAutomatically()
+ throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
+
+ WaitForResultActivity activity = mActivityRule.getActivity();
+ activity.startActivityToWaitForResult(new Intent()
+ .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
+ APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME));
+ Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void targetSdk28AndChangeDefaultSmsForAnotherAppThenDeniedAutomatically()
+ throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ WaitForResultActivity activity = mActivityRule.getActivity();
+ activity.startActivityToWaitForResult(new Intent()
+ .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
+ APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME));
+ Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void
+ targetSdk28AndChangeDefaultDialerForAnotherAppAsHolderAndAllowThenTheOtherAppIsDefaultDialer()
+ throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
+
+ addRoleHolder(RoleManager.ROLE_DIALER, APP_28_PACKAGE_NAME);
+ sContext.startActivity(new Intent()
+ .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
+ APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ waitFindObject(By.text(APP_LABEL)).click();
+ waitFindObject(POSITIVE_BUTTON_SELECTOR).click();
+
+ // TODO(b/149037075): Use TelecomManager.getDefaultDialerPackage() once the bug is fixed.
+ //TelecomManager telecomManager = sContext.getSystemService(TelecomManager.class);
+ //TestUtils.waitUntil("App is not set as default dialer app", () -> Objects.equals(
+ // telecomManager.getDefaultDialerPackage(), APP_PACKAGE_NAME));
+ TestUtils.waitUntil("App is not set as default dialer app", () ->
+ getRoleHolders(RoleManager.ROLE_DIALER).contains(APP_PACKAGE_NAME));
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ public void testHoldDialerRoleRequirementWithInCallServiceAndSdk()
+ throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
+ // target below sdk 33 without InCallService component can hold dialer role
+ addRoleHolder(
+ RoleManager.ROLE_DIALER, APP_28_PACKAGE_NAME, true);
+ assertIsRoleHolder(
+ RoleManager.ROLE_DIALER, APP_28_PACKAGE_NAME, true);
+ // target sdk 33 without InCallService component cannot hold dialer role
+ addRoleHolder(
+ RoleManager.ROLE_DIALER, APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME, false);
+ assertIsRoleHolder(
+ RoleManager.ROLE_DIALER, APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME, false);
+ // target sdk 33 with InCallService component can hold dialer role
+ addRoleHolder(
+ RoleManager.ROLE_DIALER, APP_PACKAGE_NAME, true);
+ assertIsRoleHolder(
+ RoleManager.ROLE_DIALER, APP_PACKAGE_NAME, true);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void
+ targetSdk28AndChangeDefaultSmsForAnotherAppAsHolderAndAllowThenTheOtherAppIsDefaultSms()
+ throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ addRoleHolder(RoleManager.ROLE_SMS, APP_28_PACKAGE_NAME);
+ sContext.startActivity(new Intent()
+ .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
+ APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ waitFindObject(By.text(APP_LABEL)).click();
+ waitFindObject(POSITIVE_BUTTON_SELECTOR).click();
+
+ TestUtils.waitUntil("App is not set as default sms app", () -> Objects.equals(
+ Telephony.Sms.getDefaultSmsPackage(sContext), APP_PACKAGE_NAME));
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void openDefaultAppDetailsThenIsNotDefaultApp() throws Exception {
+ runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
+ Intent.ACTION_MANAGE_DEFAULT_APP)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK)));
+
+ if (sIsWatch) {
+ waitFindObject(By.clickable(true).checked(false).hasDescendant(By.text(APP_LABEL)));
+ } else {
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_LABEL)));
+ }
+
+ pressBack();
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
+ "VanillaIceCream")
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void openDefaultAppDetailsOnHandHeldThenRestrictedAppIsNotSelectableAsDefaultApp()
+ throws Exception {
+ assumeFalse(sIsWatch || sIsAutomotive || sIsTelevision);
+ runWithShellPermissionIdentity(
+ () -> setEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME,
+ AppOpsManager.MODE_ERRORED));
+
+ runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
+ Intent.ACTION_MANAGE_DEFAULT_APP)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .putExtra(Intent.EXTRA_ROLE_NAME, ROLE_PHONE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK)));
+
+ waitFindObject(By.text(APP_LABEL).enabled(false)).clickAndWait(Until.newWindow(),
+ TIMEOUT_MILLIS);
+
+ waitFindObject(ENHANCED_CONFIRMATION_DIALOG_SELECTOR, TIMEOUT_MILLIS);
+ pressBack();
+
+ pressBack();
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void openDefaultAppDetailsAndSetDefaultAppThenIsDefaultApp() throws Exception {
+ runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
+ Intent.ACTION_MANAGE_DEFAULT_APP)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK)));
+ waitForIdle();
+ if (sIsWatch) {
+ waitFindObject(By.clickable(true).checked(false).hasDescendant(
+ By.text(APP_LABEL))).click();
+ } else {
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_LABEL))).click();
+ }
+
+ if (sIsWatch) {
+ waitFindObject(By.clickable(true).checked(true).hasDescendant(By.text(APP_LABEL)));
+ } else {
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(APP_LABEL)));
+ }
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+
+ pressBack();
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void openDefaultAppDetailsAndSetDefaultAppAndSetAnotherThenIsNotDefaultApp()
+ throws Exception {
+ runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
+ Intent.ACTION_MANAGE_DEFAULT_APP)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK)));
+ waitForIdle();
+ if (sIsWatch) {
+ waitFindObject(By.clickable(true).checked(false).hasDescendant(
+ By.text(APP_LABEL))).click();
+ waitFindObject(By.clickable(true).checked(true).hasDescendant(By.text(APP_LABEL)));
+ } else {
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_LABEL))).click();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(APP_LABEL)));
+ }
+ waitForIdle();
+ if (sIsWatch) {
+ waitFindObject(By.clickable(true).checked(false)).click();
+ } else {
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).enabled(true)
+ .checked(false))).click();
+ }
+
+ if (sIsWatch) {
+ waitFindObject(By.clickable(true).checked(false).hasDescendant(By.text(APP_LABEL)));
+ } else {
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_LABEL)));
+ }
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+
+ pressBack();
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void openDefaultAppListThenHasDefaultApp() throws Exception {
+ sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+
+ waitFindObject(By.text(ROLE_SHORT_LABEL));
+
+ pressBack();
+ }
+
+ @Test
+ public void openDefaultAppListThenIsNotDefaultAppInList() throws Exception {
+ sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+
+ assertThat(waitFindObjectOrNull(By.text(APP_LABEL), UNEXPECTED_TIMEOUT_MILLIS))
+ .isNull();
+
+ pressBack();
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void openDefaultAppListAndSetDefaultAppThenIsDefaultApp() throws Exception {
+ sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+ waitForIdle();
+ waitFindObject(By.text(ROLE_SHORT_LABEL)).click();
+ waitForIdle();
+ if (sIsWatch) {
+ waitFindObject(By.clickable(true).checked(false).hasDescendant(
+ By.text(APP_LABEL))).click();
+ } else {
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_LABEL))).click();
+ }
+
+ if (sIsWatch) {
+ waitFindObject(By.clickable(true).checked(true).hasDescendant(By.text(APP_LABEL)));
+ } else {
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(APP_LABEL)));
+ }
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+
+ pressBack();
+ pressBack();
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void openDefaultAppListAndSetDefaultAppThenIsDefaultAppInList() throws Exception {
+ sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+ waitForIdle();
+ waitFindObject(By.text(ROLE_SHORT_LABEL)).click();
+ waitForIdle();
+ if (sIsWatch) {
+ waitFindObject(By.clickable(true).checked(false).hasDescendant(
+ By.text(APP_LABEL))).click();
+ waitFindObject(By.clickable(true).checked(true).hasDescendant(By.text(APP_LABEL)));
+ } else {
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_LABEL))).click();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(APP_LABEL)));
+ }
+ pressBack();
+
+ waitFindObject(By.text(APP_LABEL));
+
+ pressBack();
+ }
+
+ private void setEnhancedConfirmationRestrictedAppOpMode(@NonNull Context context,
+ @NonNull String packageName, int mode)
+ throws PackageManager.NameNotFoundException {
+ AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ appOpsManager.setMode(AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS,
+ context.getPackageManager().getApplicationInfo(packageName, 0).uid,
+ packageName, mode);
+ }
+
+ private static void waitForIdle() {
+ UiAutomatorUtils.getUiDevice().waitForIdle();
+ }
+
+ private static void pressBack() {
+ UiAutomatorUtils.getUiDevice().pressBack();
+ waitForIdle();
+ }
+
+ @Test
+ public void roleIsAvailable() {
+ assertThat(sRoleManager.isRoleAvailable(ROLE_NAME)).isTrue();
+ }
+
+ @Test
+ public void dontAddRoleHolderThenRoleIsNotHeld() throws Exception {
+ assertRoleIsHeld(ROLE_NAME, false);
+ }
+
+ @Test
+ public void addRoleHolderThenRoleIsHeld() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertRoleIsHeld(ROLE_NAME, true);
+ }
+
+ @Test
+ public void addAndRemoveRoleHolderThenRoleIsNotHeld() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+ removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertRoleIsHeld(ROLE_NAME, false);
+ }
+
+ private void assertRoleIsHeld(@NonNull String roleName, boolean isHeld)
+ throws InterruptedException {
+ Intent intent = new Intent()
+ .setComponent(new ComponentName(APP_PACKAGE_NAME, APP_IS_ROLE_HELD_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_ROLE_NAME, roleName);
+ WaitForResultActivity activity = mActivityRule.getActivity();
+ activity.startActivityToWaitForResult(intent);
+ Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_OK);
+ assertThat(result.second).isNotNull();
+ assertThat(result.second.hasExtra(APP_IS_ROLE_HELD_EXTRA_IS_ROLE_HELD)).isTrue();
+ assertThat(result.second.getBooleanExtra(APP_IS_ROLE_HELD_EXTRA_IS_ROLE_HELD, false))
+ .isEqualTo(isHeld);
+ }
+
+ @Test
+ public void dontAddRoleHolderThenIsNotRoleHolder() throws Exception {
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void addRoleHolderThenIsRoleHolder() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+ }
+
+ @Test
+ public void addAndRemoveRoleHolderThenIsNotRoleHolder() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+ removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void addAndClearRoleHoldersThenIsNotRoleHolder() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+ clearRoleHolders(ROLE_NAME);
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void addInvalidRoleHolderThenFails() throws Exception {
+ addRoleHolder("invalid", APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void addUnqualifiedRoleHolderThenFails() throws Exception {
+ addRoleHolder(RoleManager.ROLE_HOME, APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void removeInvalidRoleHolderThenFails() throws Exception {
+ removeRoleHolder("invalid", APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void clearInvalidRoleHoldersThenFails() throws Exception {
+ clearRoleHolders("invalid", false);
+ }
+
+ @Test
+ public void addOnRoleHoldersChangedListenerAndAddRoleHolderThenIsNotified() throws Exception {
+ assertOnRoleHoldersChangedListenerIsNotified(() -> addRoleHolder(ROLE_NAME,
+ APP_PACKAGE_NAME));
+ }
+
+ @Test
+ public void addOnRoleHoldersChangedListenerAndRemoveRoleHolderThenIsNotified()
+ throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertOnRoleHoldersChangedListenerIsNotified(() -> removeRoleHolder(ROLE_NAME,
+ APP_PACKAGE_NAME));
+ }
+
+ @Test
+ public void addOnRoleHoldersChangedListenerAndClearRoleHoldersThenIsNotified()
+ throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertOnRoleHoldersChangedListenerIsNotified(() -> clearRoleHolders(ROLE_NAME));
+ }
+
+ private void assertOnRoleHoldersChangedListenerIsNotified(@NonNull ThrowingRunnable runnable)
+ throws Exception {
+ ListenerFuture future = new ListenerFuture();
+ UserHandle user = Process.myUserHandle();
+ runWithShellPermissionIdentity(() -> sRoleManager.addOnRoleHoldersChangedListenerAsUser(
+ sContext.getMainExecutor(), future, user));
+ Pair<String, UserHandle> result;
+ try {
+ runnable.run();
+ result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } finally {
+ runWithShellPermissionIdentity(() ->
+ sRoleManager.removeOnRoleHoldersChangedListenerAsUser(future, user));
+ }
+
+ assertThat(result.first).isEqualTo(ROLE_NAME);
+ assertThat(result.second).isEqualTo(user);
+ }
+
+ @Test
+ public void addAndRemoveOnRoleHoldersChangedListenerAndAddRoleHolderThenIsNotNotified()
+ throws Exception {
+ ListenerFuture future = new ListenerFuture();
+ UserHandle user = Process.myUserHandle();
+ runWithShellPermissionIdentity(() -> {
+ sRoleManager.addOnRoleHoldersChangedListenerAsUser(sContext.getMainExecutor(), future,
+ user);
+ sRoleManager.removeOnRoleHoldersChangedListenerAsUser(future, user);
+ });
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ try {
+ future.get(UNEXPECTED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ // Expected
+ return;
+ }
+ throw new AssertionError("OnRoleHoldersChangedListener was notified after removal");
+ }
+
+ @Test
+ public void setRoleNamesFromControllerShouldRequireManageRolesFromControllerPermission() {
+ assertRequiresManageRolesFromControllerPermission(
+ () -> sRoleManager.setRoleNamesFromController(Collections.emptyList()),
+ "setRoleNamesFromController");
+ }
+
+ @Test
+ public void addRoleHolderFromControllerShouldRequireManageRolesFromControllerPermission() {
+ assertRequiresManageRolesFromControllerPermission(
+ () -> sRoleManager.addRoleHolderFromController(ROLE_NAME, APP_PACKAGE_NAME),
+ "addRoleHolderFromController");
+ }
+
+ @Test
+ public void removeRoleHolderFromControllerShouldRequireManageRolesFromControllerPermission() {
+ assertRequiresManageRolesFromControllerPermission(
+ () -> sRoleManager.removeRoleHolderFromController(ROLE_NAME, APP_PACKAGE_NAME),
+ "removeRoleHolderFromController");
+ }
+
+ @Test
+ public void getHeldRolesFromControllerShouldRequireManageRolesFromControllerPermission() {
+ assertRequiresManageRolesFromControllerPermission(
+ () -> sRoleManager.getHeldRolesFromController(APP_PACKAGE_NAME),
+ "getHeldRolesFromController");
+ }
+
+ private void assertRequiresManageRolesFromControllerPermission(@NonNull Runnable runnable,
+ @NonNull String methodName) {
+ try {
+ runnable.run();
+ } catch (SecurityException e) {
+ if (e.getMessage().contains(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)) {
+ // Expected
+ return;
+ }
+ throw e;
+ }
+ fail("RoleManager." + methodName + "() should require "
+ + PERMISSION_MANAGE_ROLES_FROM_CONTROLLER);
+ }
+
+ @Test
+ public void manageRolesFromControllerPermissionShouldBeDeclaredByPermissionController()
+ throws PackageManager.NameNotFoundException {
+ PermissionInfo permissionInfo = sPackageManager.getPermissionInfo(
+ PERMISSION_MANAGE_ROLES_FROM_CONTROLLER, 0);
+
+ assertThat(permissionInfo.packageName).isEqualTo(
+ sPackageManager.getPermissionControllerPackageName());
+ assertThat(permissionInfo.getProtection()).isEqualTo(PermissionInfo.PROTECTION_SIGNATURE);
+ assertThat(permissionInfo.getProtectionFlags()).isEqualTo(0);
+ }
+
+ @Test
+ public void smsRoleHasHolder() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ assertThat(getRoleHolders(RoleManager.ROLE_SMS)).isNotEmpty();
+ }
+
+ @Test
+ public void addSmsRoleHolderThenPermissionIsGranted() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+
+ assertThat(sPackageManager.checkPermission(android.Manifest.permission.SEND_SMS,
+ APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_GRANTED);
+ }
+
+ @Test
+ public void removeSmsRoleHolderThenPermissionIsRevoked() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ String smsRoleHolder = getRoleHolders(RoleManager.ROLE_SMS).get(0);
+ addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+ addRoleHolder(RoleManager.ROLE_SMS, smsRoleHolder);
+
+ assertThat(sPackageManager.checkPermission(android.Manifest.permission.SEND_SMS,
+ APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_DENIED);
+ }
+
+ @Test
+ public void removeSmsRoleHolderThenDialerRolePermissionIsRetained() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER)
+ && sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ addRoleHolder(RoleManager.ROLE_DIALER, APP_PACKAGE_NAME);
+ String smsRoleHolder = getRoleHolders(RoleManager.ROLE_SMS).get(0);
+ addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+ addRoleHolder(RoleManager.ROLE_SMS, smsRoleHolder);
+
+ assertThat(sPackageManager.checkPermission(android.Manifest.permission.SEND_SMS,
+ APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_GRANTED);
+ }
+
+ @Test
+ public void packageManagerGetDefaultBrowserBackedByRole() throws Exception {
+ addRoleHolder(RoleManager.ROLE_BROWSER, APP_PACKAGE_NAME);
+
+ assertThat(sPackageManager.getDefaultBrowserPackageNameAsUser(UserHandle.myUserId()))
+ .isEqualTo(APP_PACKAGE_NAME);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void packageManagerSetDefaultBrowserBackedByRole() throws Exception {
+ callWithShellPermissionIdentity(() -> sPackageManager.setDefaultBrowserPackageNameAsUser(
+ APP_PACKAGE_NAME, UserHandle.myUserId()));
+
+ assertIsRoleHolder(RoleManager.ROLE_BROWSER, APP_PACKAGE_NAME, true);
+ }
+
+ @Test
+ public void telephonySmsGetDefaultSmsPackageBackedByRole() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+
+ assertThat(Telephony.Sms.getDefaultSmsPackage(sContext)).isEqualTo(APP_PACKAGE_NAME);
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ codeName = "VanillaIceCream")
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_GET_EMERGENCY_ROLE_HOLDER_API_ENABLED)
+ public void telephonyManagerGetEmergencyAssistancePackageNameBackedByRole() throws Exception {
+ TelephonyManager telephonyManager = sContext.getSystemService(TelephonyManager.class);
+ List<String> emergencyRoleHolders = getRoleHolders(RoleManager.ROLE_EMERGENCY);
+
+ if (telephonyManager.isVoiceCapable()
+ && callWithShellPermissionIdentity(() ->
+ telephonyManager.isEmergencyAssistanceEnabled())) {
+ String emergencyAssistancePackageName = callWithShellPermissionIdentity(() ->
+ telephonyManager.getEmergencyAssistancePackageName());
+ if (emergencyRoleHolders.isEmpty()) {
+ assertThat(emergencyAssistancePackageName).isNull();
+ } else {
+ assertThat(emergencyRoleHolders).hasSize(1);
+ assertThat(emergencyAssistancePackageName).isEqualTo(emergencyRoleHolders.get(0));
+ }
+ } else {
+ assertThrows(IllegalStateException.class, () ->
+ callWithShellPermissionIdentity(() ->
+ telephonyManager.getEmergencyAssistancePackageName()));
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+ @Test
+ public void cannotBypassRoleQualificationWithoutPermission() throws Exception {
+ assertThrows(SecurityException.class, () ->
+ sRoleManager.setBypassingRoleQualification(true));
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+ @Test
+ public void bypassRoleQualificationThenCanAddUnqualifiedRoleHolder() throws Exception {
+ assertThat(sRoleManager.isRoleAvailable(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER))
+ .isTrue();
+
+ runWithShellPermissionIdentity(() -> sRoleManager.setBypassingRoleQualification(true));
+ try {
+ assertThat(callWithShellPermissionIdentity(() ->
+ sRoleManager.isBypassingRoleQualification())).isTrue();
+
+ // The System Activity Recognizer role requires a system app, so this won't succeed
+ // without bypassing role qualification.
+ addRoleHolder(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER, APP_PACKAGE_NAME);
+
+ assertThat(getRoleHolders(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER))
+ .contains(APP_PACKAGE_NAME);
+ } finally {
+ runWithShellPermissionIdentity(() -> sRoleManager.setBypassingRoleQualification(false));
+ }
+ assertThat(callWithShellPermissionIdentity(() ->
+ sRoleManager.isBypassingRoleQualification())).isFalse();
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ public void cannotGetDefaultApplicationWithoutPermission() throws Exception {
+ assertThrows(SecurityException.class, ()->
+ sRoleManager.getDefaultApplication(
+ RoleManager.ROLE_SMS));
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ public void getDefaultApplicationChecksRoles() throws Exception {
+ runWithShellPermissionIdentity(() ->
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.getDefaultApplication(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER)));
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ public void getDefaultApplicationReadsRole() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+ runWithShellPermissionIdentity(() -> {
+ assertThat(sRoleManager.getDefaultApplication(RoleManager.ROLE_SMS))
+ .isEqualTo(APP_PACKAGE_NAME);
+ });
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ public void cannotSetDefaultApplicationWithoutPermission() throws Exception {
+ CallbackFuture future = new CallbackFuture();
+ assertThrows(SecurityException.class, ()->
+ sRoleManager.setDefaultApplication(
+ RoleManager.ROLE_SMS, APP_PACKAGE_NAME, 0,
+ sContext.getMainExecutor(), future));
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ public void setDefaultApplicationChecksRoles() throws Exception {
+ CallbackFuture future = new CallbackFuture();
+ runWithShellPermissionIdentity(() ->
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.setDefaultApplication(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER, APP_PACKAGE_NAME, 0,
+ sContext.getMainExecutor(), future)));
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ public void setDefaultApplicationSetsRole() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ CallbackFuture future = new CallbackFuture();
+ runWithShellPermissionIdentity(() -> {
+ sRoleManager.setDefaultApplication(
+ RoleManager.ROLE_SMS, APP_PACKAGE_NAME, 0,
+ sContext.getMainExecutor(), future);
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(sRoleManager.getRoleHolders(RoleManager.ROLE_SMS))
+ .containsExactly(APP_PACKAGE_NAME);
+ });
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ codeName = "VanillaIceCream")
+ @Test
+ public void testSetAndGetRoleFallbackEnabled() {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ runWithShellPermissionIdentity(() -> {
+ sRoleManager.setRoleFallbackEnabled(RoleManager.ROLE_SMS, true);
+ assertThat(sRoleManager.isRoleFallbackEnabled(RoleManager.ROLE_SMS)).isTrue();
+ });
+ }
+
+ @NonNull
+ private List<String> getRoleHolders(@NonNull String roleName) throws Exception {
+ return callWithShellPermissionIdentity(() -> sRoleManager.getRoleHolders(roleName));
+ }
+
+ private void assertIsRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ boolean shouldBeRoleHolder) throws Exception {
+ List<String> packageNames = getRoleHolders(roleName);
+
+ if (shouldBeRoleHolder) {
+ assertThat(packageNames).contains(packageName);
+ } else {
+ assertThat(packageNames).doesNotContain(packageName);
+ }
+ }
+
+ private void addRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ boolean expectSuccess) throws Exception {
+ CallbackFuture future = new CallbackFuture();
+ runWithShellPermissionIdentity(() -> sRoleManager.addRoleHolderAsUser(roleName,
+ packageName, 0, Process.myUserHandle(), sContext.getMainExecutor(), future));
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isEqualTo(expectSuccess);
+ }
+
+ private void addRoleHolder(@NonNull String roleName, @NonNull String packageName)
+ throws Exception {
+ addRoleHolder(roleName, packageName, true);
+ }
+
+ private void removeRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ boolean expectSuccess) throws Exception {
+ CallbackFuture future = new CallbackFuture();
+ runWithShellPermissionIdentity(() -> sRoleManager.removeRoleHolderAsUser(roleName,
+ packageName, 0, Process.myUserHandle(), sContext.getMainExecutor(), future));
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isEqualTo(expectSuccess);
+ }
+
+ private void removeRoleHolder(@NonNull String roleName, @NonNull String packageName)
+ throws Exception {
+ removeRoleHolder(roleName, packageName, true);
+ }
+
+ private void clearRoleHolders(@NonNull String roleName, boolean expectSuccess)
+ throws Exception {
+ CallbackFuture future = new CallbackFuture();
+ runWithShellPermissionIdentity(() -> sRoleManager.clearRoleHoldersAsUser(roleName, 0,
+ Process.myUserHandle(), sContext.getMainExecutor(), future));
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isEqualTo(expectSuccess);
+ }
+
+ private void clearRoleHolders(@NonNull String roleName) throws Exception {
+ clearRoleHolders(roleName, true);
+ }
+
+ private static class ListenerFuture extends CompletableFuture<Pair<String, UserHandle>>
+ implements OnRoleHoldersChangedListener {
+
+ @Override
+ public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+ complete(new Pair<>(roleName, user));
+ }
+ }
+
+ private static class CallbackFuture extends CompletableFuture<Boolean>
+ implements Consumer<Boolean> {
+
+ @Override
+ public void accept(Boolean successful) {
+ complete(successful);
+ }
+ }
+}
diff --git a/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt b/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt
new file mode 100644
index 000000000..7e58e1848
--- /dev/null
+++ b/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.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.app.role.cts
+
+import android.app.role.RoleManager
+import android.os.Build
+import android.os.UserHandle
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SdkSuppress
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests role shell commands. */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+class RoleShellCommandTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context = instrumentation.context
+ private val roleManager = context.getSystemService(RoleManager::class.java)!!
+ private val userId = UserHandle.myUserId()
+
+ private var roleHolder: String? = null
+ private var wasBypassingRoleQualification: Boolean = false
+
+ @Before
+ fun saveRoleHolder() {
+ roleHolder = getRoleHolders().firstOrNull()
+ if (roleHolder == APP_PACKAGE_NAME) {
+ removeRoleHolder()
+ roleHolder = null
+ }
+ }
+
+ @Before
+ fun saveBypassingRoleQualification() {
+ wasBypassingRoleQualification = isBypassingRoleQualification()
+ }
+
+ @After
+ fun restoreRoleHolder() {
+ removeRoleHolder()
+ roleHolder?.let { addRoleHolder(it) }
+ assertIsRoleHolder(false)
+ }
+
+ @After
+ fun restoreBypassingRoleQualification() {
+ setBypassingRoleQualification(wasBypassingRoleQualification)
+ }
+
+ @Before
+ fun installApp() {
+ installPackage(APP_APK_PATH)
+ }
+
+ @After
+ fun uninstallApp() {
+ uninstallPackage(APP_PACKAGE_NAME)
+ }
+
+ @Test
+ fun helpPrintsNonEmpty() {
+ assertThat(runShellCommandOrThrow("cmd role help")).isNotEmpty()
+ }
+
+ @Test
+ fun dontAddRoleHolderThenIsNotRoleHolder() {
+ assertIsRoleHolder(false)
+ }
+
+ @Test
+ fun addRoleHolderThenIsRoleHolder() {
+ addRoleHolder()
+
+ assertIsRoleHolder(true)
+ }
+
+ @Test
+ fun addAndRemoveRoleHolderThenIsNotRoleHolder() {
+ addRoleHolder()
+ removeRoleHolder()
+
+ assertIsRoleHolder(false)
+ }
+
+ @Test
+ fun addAndClearRoleHolderThenIsNotRoleHolder() {
+ addRoleHolder()
+ clearRoleHolders()
+
+ assertIsRoleHolder(false)
+ }
+
+ @Test
+ fun addInvalidRoleHolderThenFails() {
+ assertThrows(AssertionError::class.java) {
+ runShellCommandOrThrow("cmd role add-role-holder --user $userId $ROLE_NAME invalid")
+ }
+ }
+
+ @Test
+ fun addRoleHolderThenAppearsInDumpsys() {
+ addRoleHolder()
+
+ assertThat(runShellCommandOrThrow("dumpsys role")).contains(APP_PACKAGE_NAME)
+ }
+
+ @Test
+ fun setBypassingRoleQualificationToTrueThenSetsToTrue() {
+ setBypassingRoleQualification(false)
+
+ runShellCommandOrThrow("cmd role set-bypassing-role-qualification true")
+
+ assertThat(isBypassingRoleQualification()).isTrue()
+ }
+
+ @Test
+ fun setBypassingRoleQualificationToFalseThenSetsToFalse() {
+ setBypassingRoleQualification(true)
+
+ runShellCommandOrThrow("cmd role set-bypassing-role-qualification false")
+
+ assertThat(isBypassingRoleQualification()).isFalse()
+ }
+
+ private fun addRoleHolder(packageName: String = APP_PACKAGE_NAME) {
+ runShellCommandOrThrow("cmd role add-role-holder --user $userId $ROLE_NAME $packageName")
+ }
+
+ private fun removeRoleHolder(packageName: String = APP_PACKAGE_NAME) {
+ runShellCommandOrThrow("cmd role remove-role-holder --user $userId $ROLE_NAME $packageName")
+ }
+
+ private fun clearRoleHolders() {
+ runShellCommandOrThrow("cmd role clear-role-holders --user $userId $ROLE_NAME")
+ }
+
+ private fun getRoleHolders(): List<String> =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ runShellCommandOrThrow("cmd role get-role-holders --user $userId $ROLE_NAME")
+ .trim()
+ .let { if (it.isNotEmpty()) it.split(";") else emptyList() }
+ } else {
+ callWithShellPermissionIdentity { roleManager.getRoleHolders(ROLE_NAME) }
+ }
+
+ private fun assertIsRoleHolder(shouldBeRoleHolder: Boolean) {
+ val packageNames = getRoleHolders()
+ if (shouldBeRoleHolder) {
+ assertThat(packageNames).contains(APP_PACKAGE_NAME)
+ } else {
+ assertThat(packageNames).doesNotContain(APP_PACKAGE_NAME)
+ }
+ }
+
+ private fun installPackage(apkPath: String) {
+ assertThat(runShellCommandOrThrow("pm install -r --user $userId $apkPath").trim())
+ .isEqualTo("Success")
+ }
+
+ private fun uninstallPackage(packageName: String) {
+ assertThat(runShellCommandOrThrow("pm uninstall --user $userId $packageName").trim())
+ .isEqualTo("Success")
+ }
+
+ private fun isBypassingRoleQualification(): Boolean = callWithShellPermissionIdentity {
+ roleManager.isBypassingRoleQualification()
+ }
+
+ private fun setBypassingRoleQualification(value: Boolean) {
+ callWithShellPermissionIdentity { roleManager.setBypassingRoleQualification(value) }
+ }
+
+ companion object {
+ private const val ROLE_NAME = RoleManager.ROLE_BROWSER
+ private const val APP_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestApp.apk"
+ private const val APP_PACKAGE_NAME = "android.app.role.cts.app"
+ }
+}
diff --git a/tests/cts/role/src/android/app/role/cts/WaitForResultActivity.java b/tests/cts/role/src/android/app/role/cts/WaitForResultActivity.java
new file mode 100644
index 000000000..fb13423d4
--- /dev/null
+++ b/tests/cts/role/src/android/app/role/cts/WaitForResultActivity.java
@@ -0,0 +1,75 @@
+/*
+ * 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.app.role.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An Activity that can start another Activity and wait for its result.
+ */
+public class WaitForResultActivity extends Activity {
+
+ private static final int REQUEST_CODE_WAIT_FOR_RESULT = 1;
+
+ private CountDownLatch mLatch;
+ private int mResultCode;
+ private Intent mData;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState != null) {
+ throw new RuntimeException(
+ "Activity was recreated (perhaps due to a configuration change?) "
+ + "and this activity doesn't currently know how to gracefully handle "
+ + "configuration changes.");
+ }
+ }
+
+ public void startActivityToWaitForResult(@NonNull Intent intent) {
+ mLatch = new CountDownLatch(1);
+ startActivityForResult(intent, REQUEST_CODE_WAIT_FOR_RESULT);
+ }
+
+ @NonNull
+ public Pair<Integer, Intent> waitForActivityResult(long timeoutMillis)
+ throws InterruptedException {
+ mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
+ return new Pair<>(mResultCode, mData);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ if (requestCode == REQUEST_CODE_WAIT_FOR_RESULT) {
+ mResultCode = resultCode;
+ mData = data;
+ mLatch.countDown();
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+}
diff --git a/tests/cts/safetycenter/Android.bp b/tests/cts/safetycenter/Android.bp
index 78e43bedd..e49587c39 100644
--- a/tests/cts/safetycenter/Android.bp
+++ b/tests/cts/safetycenter/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_safety_center",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -41,7 +42,7 @@ android_test {
"safety-center-pending-intents",
"safety-center-test-util-lib",
"modules-utils-build",
- "truth-prebuilt",
+ "truth",
],
test_suites: [
"cts",
diff --git a/tests/cts/safetycenter/TEST_MAPPING b/tests/cts/safetycenter/TEST_MAPPING
index e8c210a5d..b1f60307b 100644
--- a/tests/cts/safetycenter/TEST_MAPPING
+++ b/tests/cts/safetycenter/TEST_MAPPING
@@ -9,7 +9,7 @@
"name": "CtsSafetyCenterTestCases[com.google.android.permission.apex]",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
index 59cc6547a..4b6f0f6f9 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
@@ -18,11 +18,14 @@ package android.safetycenter.cts.config
import android.content.res.Resources
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM
+import android.platform.test.annotations.RequiresFlagsEnabled
import android.safetycenter.config.SafetySource
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.truth.os.ParcelableSubject.assertThat
import androidx.test.filters.SdkSuppress
import com.android.modules.utils.build.SdkLevel
+import com.android.permission.flags.Flags
import com.android.safetycenter.testing.EqualsHashCodeToStringTester
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
@@ -126,6 +129,35 @@ class SafetySourceTest {
}
}
+ @RequiresFlagsEnabled(Flags.FLAG_PRIVATE_PROFILE_TITLE_API)
+ @SdkSuppress(minSdkVersion = VANILLA_ICE_CREAM, codeName = "VanillaIceCream")
+ @Test
+ fun getTitleForPrivateProfileResId_returnsTitleForPrivateProfileResIdOrThrows() {
+ if (!Flags.privateProfileTitleApi()) {
+ return
+ }
+ assertThrows(UnsupportedOperationException::class.java) {
+ DYNAMIC_BAREBONE.titleForPrivateProfileResId
+ }
+ assertThat(dynamicAllOptional().titleForPrivateProfileResId).isEqualTo(REFERENCE_RES_ID)
+ assertThrows(UnsupportedOperationException::class.java) {
+ DYNAMIC_DISABLED.titleForPrivateProfileResId
+ }
+ assertThat(DYNAMIC_HIDDEN.titleForPrivateProfileResId).isEqualTo(Resources.ID_NULL)
+ assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.titleForPrivateProfileResId)
+ .isEqualTo(REFERENCE_RES_ID)
+ assertThrows(UnsupportedOperationException::class.java) {
+ STATIC_BAREBONE.titleForPrivateProfileResId
+ }
+ assertThat(STATIC_ALL_OPTIONAL.titleForPrivateProfileResId).isEqualTo(REFERENCE_RES_ID)
+ assertThrows(UnsupportedOperationException::class.java) {
+ ISSUE_ONLY_BAREBONE.titleForPrivateProfileResId
+ }
+ assertThrows(UnsupportedOperationException::class.java) {
+ issueOnlyAllOptional().titleForPrivateProfileResId
+ }
+ }
+
@Test
fun getSummaryResId_returnsSummaryResIdOrThrows() {
assertThat(DYNAMIC_BAREBONE.summaryResId).isEqualTo(REFERENCE_RES_ID)
@@ -360,6 +392,9 @@ class SafetySourceTest {
setDeduplicationGroup(DEDUPLICATION_GROUP)
addPackageCertificateHash(HASH1)
}
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
}
.build()
)
@@ -390,6 +425,9 @@ class SafetySourceTest {
setDeduplicationGroup(DEDUPLICATION_GROUP)
addPackageCertificateHash(HASH1)
}
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
}
.build()
)
@@ -413,6 +451,9 @@ class SafetySourceTest {
setDeduplicationGroup(DEDUPLICATION_GROUP)
addPackageCertificateHash(HASH1)
}
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
}
.build()
)
@@ -436,6 +477,9 @@ class SafetySourceTest {
setDeduplicationGroup(DEDUPLICATION_GROUP)
addPackageCertificateHash(HASH1)
}
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
}
.build()
)
@@ -459,6 +503,9 @@ class SafetySourceTest {
setDeduplicationGroup(DEDUPLICATION_GROUP)
addPackageCertificateHash(HASH1)
}
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
}
.build()
)
@@ -482,6 +529,9 @@ class SafetySourceTest {
setDeduplicationGroup(DEDUPLICATION_GROUP)
addPackageCertificateHash(HASH1)
}
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
}
.build()
)
@@ -505,6 +555,9 @@ class SafetySourceTest {
setDeduplicationGroup(DEDUPLICATION_GROUP)
addPackageCertificateHash(HASH1)
}
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
}
.build()
)
@@ -536,6 +589,9 @@ class SafetySourceTest {
setDeduplicationGroup(DEDUPLICATION_GROUP)
addPackageCertificateHash(HASH1)
}
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
}
.build()
)
@@ -559,6 +615,9 @@ class SafetySourceTest {
setDeduplicationGroup(DEDUPLICATION_GROUP)
addPackageCertificateHash(HASH1)
}
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
}
.build()
)
@@ -582,6 +641,9 @@ class SafetySourceTest {
setDeduplicationGroup(DEDUPLICATION_GROUP)
addPackageCertificateHash(HASH1)
}
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
}
.build()
)
@@ -605,6 +667,9 @@ class SafetySourceTest {
setDeduplicationGroup(DEDUPLICATION_GROUP)
addPackageCertificateHash(HASH1)
}
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
}
.build()
)
@@ -628,6 +693,9 @@ class SafetySourceTest {
setDeduplicationGroup(DEDUPLICATION_GROUP)
addPackageCertificateHash(HASH1)
}
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
}
.build()
)
@@ -650,6 +718,11 @@ class SafetySourceTest {
.setNotificationsAllowed(false)
.setDeduplicationGroup(DEDUPLICATION_GROUP)
.addPackageCertificateHash(HASH1)
+ .apply {
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
+ }
.build()
)
addEqualityGroup(
@@ -669,6 +742,11 @@ class SafetySourceTest {
.setNotificationsAllowed(true)
.setDeduplicationGroup("other_deduplication_group")
.addPackageCertificateHash(HASH1)
+ .apply {
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
+ }
.build()
)
// With no package cert hashes provided
@@ -688,6 +766,11 @@ class SafetySourceTest {
.setRefreshOnPageOpenAllowed(true)
.setNotificationsAllowed(true)
.setDeduplicationGroup(DEDUPLICATION_GROUP)
+ .apply {
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
+ }
.build()
)
// With longer package cert hash list
@@ -709,6 +792,11 @@ class SafetySourceTest {
.setDeduplicationGroup(DEDUPLICATION_GROUP)
.addPackageCertificateHash(HASH1)
.addPackageCertificateHash(HASH2)
+ .apply {
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
+ }
.build()
)
// With package cert hash list with different value
@@ -729,6 +817,11 @@ class SafetySourceTest {
.setNotificationsAllowed(true)
.setDeduplicationGroup(DEDUPLICATION_GROUP)
.addPackageCertificateHash(HASH2)
+ .apply {
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
+ }
.build()
)
}
@@ -785,6 +878,9 @@ class SafetySourceTest {
setDeduplicationGroup(DEDUPLICATION_GROUP)
addPackageCertificateHash(HASH1)
}
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
}
.build()
@@ -817,6 +913,11 @@ class SafetySourceTest {
.setProfile(SafetySource.PROFILE_ALL)
.setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_HIDDEN)
.setSearchTermsResId(REFERENCE_RES_ID)
+ .apply {
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
+ }
.build()
internal val STATIC_BAREBONE =
@@ -837,6 +938,11 @@ class SafetySourceTest {
.setIntentAction(INTENT_ACTION)
.setProfile(SafetySource.PROFILE_ALL)
.setSearchTermsResId(REFERENCE_RES_ID)
+ .apply {
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(REFERENCE_RES_ID)
+ }
+ }
.build()
internal val ISSUE_ONLY_BAREBONE =
diff --git a/tests/functional/safetycenter/multiusers/Android.bp b/tests/functional/safetycenter/multiusers/Android.bp
index c1c7a95e7..2f1cda9ed 100644
--- a/tests/functional/safetycenter/multiusers/Android.bp
+++ b/tests/functional/safetycenter/multiusers/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_safety_center",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -35,6 +36,7 @@ android_test {
"Harrier",
"Nene",
"TestApp",
+ "permissions-aconfig-flags-lib",
],
test_suites: [
"general-tests",
diff --git a/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt b/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt
index acbc5cfc0..8a54ccf26 100644
--- a/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt
+++ b/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt
@@ -21,6 +21,8 @@ import android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
import android.app.PendingIntent
import android.content.Context
import android.os.UserHandle
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
import android.safetycenter.SafetyCenterData
import android.safetycenter.SafetyCenterEntry
import android.safetycenter.SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING
@@ -43,6 +45,7 @@ import com.android.bedstead.harrier.DeviceState
import com.android.bedstead.harrier.annotations.EnsureHasAdditionalUser
import com.android.bedstead.harrier.annotations.EnsureHasCloneProfile
import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile
+import com.android.bedstead.harrier.annotations.EnsureHasPrivateProfile
import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner
@@ -227,6 +230,13 @@ class SafetyCenterMultiUsersTest {
.setEnabled(false)
.build()
+ private val dynamicDisabledForPrivateUpdated: SafetyCenterEntry
+ get() =
+ safetyCenterEntryOkForPrivate(DYNAMIC_DISABLED_ID, deviceState.privateProfile().id())
+
+ private val dynamicHiddenForPrivateUpdated: SafetyCenterEntry
+ get() = safetyCenterEntryOkForPrivate(DYNAMIC_HIDDEN_ID, deviceState.privateProfile().id())
+
private val staticGroupBuilder =
SafetyCenterEntryGroup.Builder(STATIC_GROUP_ID, "OK")
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
@@ -267,6 +277,24 @@ class SafetyCenterMultiUsersTest {
.setEnabled(false)
.build()
+ private val staticAllOptionalForPrivateBuilder
+ get() =
+ safetyCenterTestData
+ .safetyCenterEntryDefaultStaticBuilder(
+ STATIC_ALL_OPTIONAL_ID,
+ userId = deviceState.privateProfile().id(),
+ title = "Unknown"
+ )
+ .setPendingIntent(
+ createTestActivityRedirectPendingIntentForUser(
+ deviceState.privateProfile().userHandle(),
+ explicit = false
+ )
+ )
+
+ private val staticAllOptionalForPrivate
+ get() = staticAllOptionalForPrivateBuilder.build()
+
private fun createStaticEntry(explicit: Boolean = true): SafetyCenterStaticEntry =
SafetyCenterStaticEntry.Builder("OK")
.setSummary("OK")
@@ -292,9 +320,25 @@ class SafetyCenterMultiUsersTest {
)
)
+ private fun staticEntryForPrivateBuilder(
+ title: CharSequence = "Unknown",
+ explicit: Boolean = true
+ ) =
+ SafetyCenterStaticEntry.Builder(title)
+ .setSummary("OK")
+ .setPendingIntent(
+ createTestActivityRedirectPendingIntentForUser(
+ deviceState.privateProfile().userHandle(),
+ explicit
+ )
+ )
+
private fun createStaticEntryForWork(explicit: Boolean = true): SafetyCenterStaticEntry =
staticEntryForWorkBuilder(explicit = explicit).build()
+ private fun createStaticEntryForPrivate(explicit: Boolean = true): SafetyCenterStaticEntry =
+ staticEntryForPrivateBuilder(explicit = explicit).build()
+
private fun createStaticEntryForWorkPaused(): SafetyCenterStaticEntry =
staticEntryForWorkBuilder(explicit = false)
.setSummary(safetyCenterResourcesApk.getStringByName("work_profile_paused"))
@@ -313,6 +357,13 @@ class SafetyCenterMultiUsersTest {
.setPendingIntent(safetySourceTestData.createTestActivityRedirectPendingIntent())
.build()
+ private val staticEntryForPrivateUpdated: SafetyCenterStaticEntry
+ get() =
+ SafetyCenterStaticEntry.Builder("Unspecified title for Private")
+ .setSummary("Unspecified summary")
+ .setPendingIntent(safetySourceTestData.createTestActivityRedirectPendingIntent())
+ .build()
+
private val safetyCenterDataForAdditionalUser
get() =
SafetyCenterData(
@@ -657,7 +708,7 @@ class SafetyCenterMultiUsersTest {
@Test
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
- fun getSafetyCenterData_withComplexConfigWithAllDataProvided_returnsAllDataProvided() {
+ fun getSafetyCenterData_withComplexConfigWithExtraWorkOnlyWithAllDataProvided_returnsAllDataProvided() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexAllProfileConfig)
updatePrimaryProfileSources()
updateWorkProfileSources()
@@ -765,6 +816,267 @@ class SafetyCenterMultiUsersTest {
}
@Test
+ @RequiresFlagsDisabled(com.android.permission.flags.Flags.FLAG_PRIVATE_PROFILE_SUPPORTED)
+ @EnsureHasWorkProfile(installInstrumentedApp = TRUE)
+ @EnsureHasPrivateProfile(installInstrumentedApp = TRUE)
+ fun getSafetyCenterData_withComplexConfigWithPrivateProfileDisallowedWithAllDataProvided_returnsAllDataProvided() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexAllProfileConfig)
+ updatePrimaryProfileSources()
+ updateWorkProfileSources()
+ updatePrivateProfileSources()
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+
+ val managedUserId = deviceState.workProfile().id()
+ val safetyCenterDataFromComplexConfig =
+ SafetyCenterData(
+ safetyCenterTestData.safetyCenterStatusCritical(11),
+ listOf(
+ safetyCenterTestData.safetyCenterIssueCritical(
+ DYNAMIC_BAREBONE_ID,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueCritical(
+ ISSUE_ONLY_BAREBONE_ID,
+ attributionTitle = null,
+ groupId = ISSUE_ONLY_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueRecommendation(
+ DYNAMIC_DISABLED_ID,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueRecommendation(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ attributionTitle = null,
+ groupId = ISSUE_ONLY_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ DYNAMIC_IN_STATELESS_ID,
+ groupId = MIXED_STATELESS_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ ISSUE_ONLY_IN_STATELESS_ID,
+ groupId = MIXED_STATELESS_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ DYNAMIC_DISABLED_ID,
+ managedUserId,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ DYNAMIC_HIDDEN_ID,
+ managedUserId,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ managedUserId,
+ attributionTitle = null,
+ groupId = ISSUE_ONLY_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ DYNAMIC_IN_STATELESS_ID,
+ managedUserId,
+ groupId = MIXED_STATELESS_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ ISSUE_ONLY_IN_STATELESS_ID,
+ managedUserId,
+ groupId = MIXED_STATELESS_GROUP_ID
+ )
+ ),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ SafetyCenterEntryGroup.Builder(DYNAMIC_GROUP_ID, "OK")
+ .setSeverityLevel(ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .setSummary("Critical summary")
+ .setEntries(
+ listOf(
+ dynamicBareboneUpdated,
+ dynamicDisabledUpdated,
+ dynamicDisabledForWorkUpdated,
+ dynamicHiddenUpdated,
+ dynamicHiddenForWorkUpdated
+ )
+ )
+ .setSeverityUnspecifiedIconType(
+ SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION
+ )
+ .build()
+ ),
+ SafetyCenterEntryOrGroup(
+ staticGroupBuilder
+ .setEntries(
+ listOf(staticBarebone, staticAllOptional, staticAllOptionalForWork)
+ )
+ .build()
+ )
+ ),
+ listOf(
+ SafetyCenterStaticEntryGroup(
+ "OK",
+ listOf(
+ staticEntryUpdated,
+ staticEntryForWorkUpdated,
+ createStaticEntry(explicit = false),
+ createStaticEntryForWork(explicit = false)
+ )
+ )
+ )
+ )
+ assertThat(apiSafetyCenterData.withoutExtras()).isEqualTo(safetyCenterDataFromComplexConfig)
+ }
+
+ // TODO(b/286539356) add the os feature flag requirement when available.
+ @Test
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_PRIVATE_PROFILE_SUPPORTED)
+ @EnsureHasWorkProfile(installInstrumentedApp = TRUE)
+ @EnsureHasPrivateProfile(installInstrumentedApp = TRUE)
+ fun getSafetyCenterData_withComplexConfigWithAllDataProvided_returnsAllDataProvided() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexAllProfileConfig)
+ updatePrimaryProfileSources()
+ updateWorkProfileSources()
+ updatePrivateProfileSources()
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+
+ val managedUserId = deviceState.workProfile().id()
+ val privateProfileId = deviceState.privateProfile().id()
+ val safetyCenterDataFromComplexConfig =
+ SafetyCenterData(
+ safetyCenterTestData.safetyCenterStatusCritical(11),
+ listOf(
+ safetyCenterTestData.safetyCenterIssueCritical(
+ DYNAMIC_BAREBONE_ID,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueCritical(
+ ISSUE_ONLY_BAREBONE_ID,
+ attributionTitle = null,
+ groupId = ISSUE_ONLY_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueRecommendation(
+ DYNAMIC_DISABLED_ID,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueRecommendation(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ attributionTitle = null,
+ groupId = ISSUE_ONLY_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ DYNAMIC_IN_STATELESS_ID,
+ groupId = MIXED_STATELESS_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ ISSUE_ONLY_IN_STATELESS_ID,
+ groupId = MIXED_STATELESS_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ DYNAMIC_DISABLED_ID,
+ managedUserId,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ DYNAMIC_HIDDEN_ID,
+ managedUserId,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ managedUserId,
+ attributionTitle = null,
+ groupId = ISSUE_ONLY_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ DYNAMIC_IN_STATELESS_ID,
+ managedUserId,
+ groupId = MIXED_STATELESS_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ ISSUE_ONLY_IN_STATELESS_ID,
+ managedUserId,
+ groupId = MIXED_STATELESS_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ DYNAMIC_DISABLED_ID,
+ privateProfileId,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ DYNAMIC_HIDDEN_ID,
+ privateProfileId,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ privateProfileId,
+ attributionTitle = null,
+ groupId = ISSUE_ONLY_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ DYNAMIC_IN_STATELESS_ID,
+ privateProfileId,
+ groupId = MIXED_STATELESS_GROUP_ID
+ ),
+ safetyCenterTestData.safetyCenterIssueInformation(
+ ISSUE_ONLY_IN_STATELESS_ID,
+ privateProfileId,
+ groupId = MIXED_STATELESS_GROUP_ID
+ )
+ ),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ SafetyCenterEntryGroup.Builder(DYNAMIC_GROUP_ID, "OK")
+ .setSeverityLevel(ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .setSummary("Critical summary")
+ .setEntries(
+ listOf(
+ dynamicBareboneUpdated,
+ dynamicDisabledUpdated,
+ dynamicDisabledForWorkUpdated,
+ dynamicDisabledForPrivateUpdated,
+ dynamicHiddenUpdated,
+ dynamicHiddenForWorkUpdated,
+ dynamicHiddenForPrivateUpdated
+ )
+ )
+ .setSeverityUnspecifiedIconType(
+ SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION
+ )
+ .build()
+ ),
+ SafetyCenterEntryOrGroup(
+ staticGroupBuilder
+ .setEntries(
+ listOf(
+ staticBarebone,
+ staticAllOptional,
+ staticAllOptionalForWork,
+ staticAllOptionalForPrivate
+ )
+ )
+ .build()
+ )
+ ),
+ listOf(
+ SafetyCenterStaticEntryGroup(
+ "OK",
+ listOf(
+ staticEntryUpdated,
+ staticEntryForWorkUpdated,
+ staticEntryForPrivateUpdated,
+ createStaticEntry(explicit = false),
+ createStaticEntryForWork(explicit = false),
+ createStaticEntryForPrivate(explicit = false)
+ )
+ )
+ )
+ )
+ assertThat(apiSafetyCenterData.withoutExtras()).isEqualTo(safetyCenterDataFromComplexConfig)
+ }
+
+ @Test
@EnsureHasWorkProfile(installInstrumentedApp = TRUE)
fun getSafetyCenterData_withQuietMode_shouldHaveWorkProfilePausedSummaryAndNoWorkIssues() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexAllProfileConfig)
@@ -920,6 +1232,77 @@ class SafetyCenterMultiUsersTest {
}
@Test
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_PRIVATE_PROFILE_SUPPORTED)
+ @EnsureHasPrivateProfile(installInstrumentedApp = TRUE)
+ fun getSafetyCenterData_afterPrivateProfileRemoved_returnsDefaultData() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
+ val privateSafetyCenterManager =
+ getSafetyCenterManagerForUser(deviceState.privateProfile().userHandle())
+ val safetyCenterDataWithPrivateProfile =
+ SafetyCenterData(
+ safetyCenterTestData.safetyCenterStatusUnknown,
+ emptyList(),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ SafetyCenterEntryGroup.Builder(SINGLE_SOURCE_GROUP_ID, "OK")
+ .setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNKNOWN)
+ .setSummary(
+ safetyCenterResourcesApk.getStringByName("group_unknown_summary")
+ )
+ .setEntries(
+ listOf(
+ safetyCenterTestData.safetyCenterEntryDefault(
+ SINGLE_SOURCE_ALL_PROFILE_ID
+ ),
+ safetyCenterTestData.safetyCenterEntryDefault(
+ SINGLE_SOURCE_ALL_PROFILE_ID,
+ deviceState.privateProfile().id(),
+ title = "Unknown",
+ pendingIntent =
+ createTestActivityRedirectPendingIntentForUser(
+ deviceState.privateProfile().userHandle()
+ )
+ )
+ )
+ )
+ .setSeverityUnspecifiedIconType(
+ SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION
+ )
+ .build()
+ )
+ ),
+ emptyList()
+ )
+
+ assertThat(safetyCenterManager.getSafetyCenterDataWithPermission())
+ .isEqualTo(safetyCenterDataWithPrivateProfile)
+ assertThat(
+ privateSafetyCenterManager.getSafetyCenterDataWithInteractAcrossUsersPermission()
+ )
+ .isEqualTo(safetyCenterDataWithPrivateProfile)
+
+ deviceState.privateProfile().remove()
+
+ val safetyCenterDataForPrimaryUser =
+ SafetyCenterData(
+ safetyCenterTestData.safetyCenterStatusUnknown,
+ emptyList(),
+ listOf(
+ SafetyCenterEntryOrGroup(
+ safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ALL_PROFILE_ID)
+ )
+ ),
+ emptyList()
+ )
+ assertThat(safetyCenterManager.getSafetyCenterDataWithPermission())
+ .isEqualTo(safetyCenterDataForPrimaryUser)
+ assertThat(
+ privateSafetyCenterManager.getSafetyCenterDataWithInteractAcrossUsersPermission()
+ )
+ .isEqualTo(SafetyCenterTestData.DEFAULT)
+ }
+
+ @Test
@EnsureHasAdditionalUser(installInstrumentedApp = TRUE)
fun getSafetyCenterData_afterAdditionalUserRemoved_returnsDefaultData() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceAllProfileConfig)
@@ -1290,6 +1673,11 @@ class SafetyCenterMultiUsersTest {
.safetyCenterEntryOkBuilder(sourceId, managedUserId, title = "Ok title for Work")
.build()
+ private fun safetyCenterEntryOkForPrivate(sourceId: String, managedUserId: Int) =
+ safetyCenterTestData
+ .safetyCenterEntryOkBuilder(sourceId, managedUserId, title = "Ok title for Private")
+ .build()
+
private fun updatePrimaryProfileSources() {
safetyCenterTestHelper.setData(
DYNAMIC_BAREBONE_ID,
@@ -1342,4 +1730,29 @@ class SafetyCenterMultiUsersTest {
SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
)
}
+
+ private fun updatePrivateProfileSources() {
+ val privateSafetyCenterManager =
+ getSafetyCenterManagerForUser(deviceState.privateProfile().userHandle())
+ privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
+ DYNAMIC_DISABLED_ID,
+ safetySourceTestData.informationWithIssueForPrivate
+ )
+ privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
+ DYNAMIC_HIDDEN_ID,
+ safetySourceTestData.informationWithIssueForPrivate
+ )
+ privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
+ )
+ privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
+ DYNAMIC_IN_STATELESS_ID,
+ safetySourceTestData.unspecifiedWithIssueForPrivate
+ )
+ privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
+ ISSUE_ONLY_IN_STATELESS_ID,
+ SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
+ )
+ }
}
diff --git a/tests/functional/safetycenter/safetycenteractivity/Android.bp b/tests/functional/safetycenter/safetycenteractivity/Android.bp
index af0020e91..ea5f9f286 100644
--- a/tests/functional/safetycenter/safetycenteractivity/Android.bp
+++ b/tests/functional/safetycenter/safetycenteractivity/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_safety_center",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/functional/safetycenter/safetycenteractivity/TEST_MAPPING b/tests/functional/safetycenter/safetycenteractivity/TEST_MAPPING
index 533b4d2a5..dcc2f817f 100644
--- a/tests/functional/safetycenter/safetycenteractivity/TEST_MAPPING
+++ b/tests/functional/safetycenter/safetycenteractivity/TEST_MAPPING
@@ -9,7 +9,7 @@
"name": "SafetyCenterActivityFunctionalTestCases[com.google.android.permission.apex]",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
diff --git a/tests/functional/safetycenter/singleuser/Android.bp b/tests/functional/safetycenter/singleuser/Android.bp
index 995f3ca40..fa7b6475b 100644
--- a/tests/functional/safetycenter/singleuser/Android.bp
+++ b/tests/functional/safetycenter/singleuser/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_safety_center",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/tests/functional/safetycenter/singleuser/TEST_MAPPING b/tests/functional/safetycenter/singleuser/TEST_MAPPING
index 8285ecd5a..9ba98a87a 100644
--- a/tests/functional/safetycenter/singleuser/TEST_MAPPING
+++ b/tests/functional/safetycenter/singleuser/TEST_MAPPING
@@ -9,7 +9,7 @@
"name": "SafetyCenterFunctionalTestCases[com.google.android.permission.apex]",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
index ef217a199..4f06c0f3f 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
@@ -16,6 +16,7 @@
package android.safetycenter.functional
+import android.Manifest.permission.MANAGE_SAFETY_CENTER
import android.content.Context
import android.content.Intent
import android.os.Build.VERSION_CODES.TIRAMISU
@@ -786,6 +787,18 @@ class SafetyCenterManagerTest {
@get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
@Test
+ fun getSafetySourceData_differentPackageWithManageSafetyCenterPermission_returnsData() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexConfig)
+
+ val data =
+ callWithShellPermissionIdentity(MANAGE_SAFETY_CENTER) {
+ safetyCenterManager.getSafetySourceData(DYNAMIC_OTHER_PACKAGE_ID)
+ }
+
+ assertThat(data).isNull()
+ }
+
+ @Test
fun refreshSafetySources_timeout_marksSafetySourceAsError() {
SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -3823,9 +3836,9 @@ class SafetyCenterManagerTest {
companion object {
private val RESURFACE_DELAY = Duration.ofMillis(500)
- // Wait 1.5 times the RESURFACE_DELAY before asserting whether an issue has or has not
+ // Wait 3 times the RESURFACE_DELAY before asserting whether an issue has or has not
// resurfaced. Use a constant additive error buffer if we increase the delay considerably.
- private val RESURFACE_TIMEOUT = RESURFACE_DELAY.multipliedBy(3).dividedBy(2)
+ private val RESURFACE_TIMEOUT = RESURFACE_DELAY.multipliedBy(3)
// Check more than once during a RESURFACE_DELAY before asserting whether an issue has or
// has not resurfaced. Use a different check logic (focused at the expected resurface time)
// if we increase the delay considerably.
diff --git a/tests/functional/safetycenter/subpages/TEST_MAPPING b/tests/functional/safetycenter/subpages/TEST_MAPPING
index 455ad21d0..e6802586e 100644
--- a/tests/functional/safetycenter/subpages/TEST_MAPPING
+++ b/tests/functional/safetycenter/subpages/TEST_MAPPING
@@ -9,7 +9,7 @@
"name": "SafetyCenterSubpagesTestCases[com.google.android.permission.apex]",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
diff --git a/tests/hostside/safetycenter/Android.bp b/tests/hostside/safetycenter/Android.bp
index c66cae23a..10258f95b 100644
--- a/tests/hostside/safetycenter/Android.bp
+++ b/tests/hostside/safetycenter/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_safety_center",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -37,4 +38,4 @@ java_test_host {
"general-tests",
"mts-permission",
],
-} \ No newline at end of file
+}
diff --git a/tests/hostside/safetycenter/helper-app/Android.bp b/tests/hostside/safetycenter/helper-app/Android.bp
index a05f8d2f3..04e660134 100644
--- a/tests/hostside/safetycenter/helper-app/Android.bp
+++ b/tests/hostside/safetycenter/helper-app/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_safety_center",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -33,4 +34,4 @@ android_test_helper_app {
"safety-center-pending-intents",
"safety-center-test-util-lib",
],
-} \ No newline at end of file
+}
diff --git a/tests/utils/safetycenter/Android.bp b/tests/utils/safetycenter/Android.bp
index 1c76dc775..8514b0662 100644
--- a/tests/utils/safetycenter/Android.bp
+++ b/tests/utils/safetycenter/Android.bp
@@ -14,6 +14,7 @@
// limitations under the License.
//
package {
+ default_team: "trendy_team_android_permissions",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -35,6 +36,7 @@ android_library {
"kotlinx-coroutines-android",
"safety-center-internal-data",
"safety-center-resources-lib",
+ "permissions-aconfig-flags-lib",
],
apex_available: [
"com.android.permission",
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt
index b0d209fcc..0e31b2934 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt
@@ -31,6 +31,7 @@ import android.safetycenter.config.SafetySource.SAFETY_SOURCE_TYPE_STATIC
import android.safetycenter.config.SafetySourcesGroup
import androidx.annotation.RequiresApi
import com.android.modules.utils.build.SdkLevel
+import com.android.permission.flags.Flags
import com.android.safetycenter.testing.SettingsPackage.getSettingsPackageName
import java.security.MessageDigest
@@ -49,7 +50,7 @@ class SafetyCenterTestConfigs(private val context: Context) {
context.packageName,
PackageInfoFlags.of(GET_SIGNING_CERTIFICATES.toLong())
)
- .signingInfo
+ .signingInfo!!
.apkContentsSigners[0]
.toByteArray()
)
@@ -686,6 +687,11 @@ class SafetyCenterTestConfigs(private val context: Context) {
.setSummaryResId(Resources.ID_NULL)
.setIntentAction(null)
.setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_HIDDEN)
+ .apply {
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(Resources.ID_NULL)
+ }
+ }
.build()
)
.build()
@@ -788,6 +794,11 @@ class SafetyCenterTestConfigs(private val context: Context) {
dynamicSafetySourceBuilder(id)
.setProfile(SafetySource.PROFILE_ALL)
.setTitleForWorkResId(android.R.string.paste)
+ .apply {
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(android.R.string.unknownName)
+ }
+ }
private fun staticSafetySource(id: String) = staticSafetySourceBuilder(id).build()
@@ -803,6 +814,11 @@ class SafetyCenterTestConfigs(private val context: Context) {
staticSafetySourceBuilder(id)
.setProfile(SafetySource.PROFILE_ALL)
.setTitleForWorkResId(android.R.string.paste)
+ .apply {
+ if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
+ setTitleForPrivateProfileResId(android.R.string.unknownName)
+ }
+ }
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private fun issueOnlySafetySourceWithDuplicationInfo(id: String, deduplicationGroup: String) =
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt
index c35d02a52..7e77c0827 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt
@@ -175,6 +175,24 @@ class SafetySourceTestData(private val context: Context) {
.addIssue(informationIssue)
.build()
+ /**
+ * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and
+ * a [SEVERITY_LEVEL_UNSPECIFIED] [SafetySourceStatus], to be used for a private profile entry.
+ */
+ val unspecifiedWithIssueForPrivate =
+ SafetySourceData.Builder()
+ .setStatus(
+ SafetySourceStatus.Builder(
+ "Unspecified title for Private",
+ "Unspecified summary",
+ SEVERITY_LEVEL_UNSPECIFIED
+ )
+ .setPendingIntent(createTestActivityRedirectPendingIntent())
+ .build()
+ )
+ .addIssue(informationIssue)
+ .build()
+
/** A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] [SafetySourceStatus]. */
val information =
SafetySourceData.Builder()
@@ -284,6 +302,24 @@ class SafetySourceTestData(private val context: Context) {
/**
* A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and
+ * [SafetySourceStatus], to be used for a private profile entry.
+ */
+ val informationWithIssueForPrivate =
+ SafetySourceData.Builder()
+ .setStatus(
+ SafetySourceStatus.Builder(
+ "Ok title for Private",
+ "Ok summary",
+ SEVERITY_LEVEL_INFORMATION
+ )
+ .setPendingIntent(createTestActivityRedirectPendingIntent())
+ .build()
+ )
+ .addIssue(informationIssue)
+ .build()
+
+ /**
+ * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and
* [SafetySourceStatus].
*/
val informationWithSubtitleIssue =