diff options
Diffstat (limited to 'tests')
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 Binary files differnew file mode 100644 index 000000000..8a9e6984b --- /dev/null +++ b/tests/cts/permission/res/drawable/robot.png 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> + <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" + 	 android:maxSdkVersion="32" /> + </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 + * <privapp-permissions> + * </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 Binary files differnew file mode 100644 index 000000000..d26419604 --- /dev/null +++ b/tests/cts/permissionui/res/raw/lg_g4_iso_800_jpg.jpg diff --git a/tests/cts/permissionui/res/raw/test_video.mp4 b/tests/cts/permissionui/res/raw/test_video.mp4 Binary files differnew file mode 100644 index 000000000..ab95ac07d --- /dev/null +++ b/tests/cts/permissionui/res/raw/test_video.mp4 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 = |