summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt182
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java3
-rw-r--r--tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/AndroidManifest.xml1
-rw-r--r--tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/src/android/permissionmultidevice/cts/accessremotedevicecamera/RequestPermissionActivity.kt18
-rw-r--r--tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt50
-rw-r--r--tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt585
-rw-r--r--tests/cts/permissionpolicy/res/raw/android_manifest.xml22
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt323
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt22
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionDecisionsTest.kt25
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt36
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt10
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleManagerSecurityTest.kt148
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleManagerTest.java21
-rw-r--r--tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt236
-rw-r--r--tests/cts/safetycenter/AndroidTest.xml4
-rw-r--r--tests/functional/safetycenter/multiusers/AndroidTest.xml4
-rw-r--r--tests/functional/safetycenter/safetycenteractivity/Android.bp1
-rw-r--r--tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml4
-rw-r--r--tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt34
-rw-r--r--tests/functional/safetycenter/singleuser/AndroidTest.xml4
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt23
-rw-r--r--tests/functional/safetycenter/subpages/AndroidTest.xml4
-rw-r--r--tests/hostside/safetycenter/AndroidTest.xml4
-rw-r--r--tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt8
-rw-r--r--tests/utils/safetycenter/Android.bp1
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt30
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt140
28 files changed, 1380 insertions, 563 deletions
diff --git a/tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt b/tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt
index ff333c6a0..145936382 100644
--- a/tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt
+++ b/tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt
@@ -22,6 +22,10 @@ import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_S
import android.app.Instrumentation
import android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
import android.companion.virtual.VirtualDeviceManager.VirtualDevice
+import android.companion.virtual.VirtualDeviceParams
+import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM
+import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT
+import android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME
@@ -33,10 +37,7 @@ 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.VirtualDeviceRule
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -66,18 +67,25 @@ class DevicePermissionsTest {
private lateinit var permissionManager: PermissionManager
@get:Rule
- var mVirtualDeviceRule = VirtualDeviceRule.withAdditionalPermissions(
+ var mVirtualDeviceRule =
+ VirtualDeviceRule.withAdditionalPermissions(
Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
- Manifest.permission.GET_RUNTIME_PERMISSIONS
+ Manifest.permission.GET_RUNTIME_PERMISSIONS,
)
@Rule @JvmField val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@Before
fun setup() {
- virtualDevice = mVirtualDeviceRule.createManagedVirtualDevice()
+ virtualDevice =
+ mVirtualDeviceRule.createManagedVirtualDevice(
+ // Without custom audio policy, the RECORD_AUDIO permission won't be device aware.
+ VirtualDeviceParams.Builder()
+ .setDevicePolicy(POLICY_TYPE_AUDIO, DEVICE_POLICY_CUSTOM)
+ .build()
+ )
virtualDeviceContext = defaultDeviceContext.createDeviceContext(virtualDevice.deviceId)
permissionManager = virtualDeviceContext.getSystemService(PermissionManager::class.java)!!
persistentDeviceId = virtualDevice.persistentDeviceId!!
@@ -89,43 +97,47 @@ class DevicePermissionsTest {
runShellCommandOrThrow("pm uninstall $TEST_PACKAGE_NAME")
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
- )
@Test
- fun testDeviceAwareRuntimePermissionIsGranted() {
- grantPermissionAndAssertGranted(Manifest.permission.CAMERA, virtualDeviceContext)
+ fun virtualDeviceDefaultPolicy_deviceAwarePermissionFallsBackToDefaultDevice() {
+ virtualDevice =
+ mVirtualDeviceRule.createManagedVirtualDevice(
+ // With default audio policy, the RECORD_AUDIO permission won't be device aware.
+ VirtualDeviceParams.Builder()
+ .setDevicePolicy(POLICY_TYPE_AUDIO, DEVICE_POLICY_DEFAULT)
+ .build()
+ )
+ virtualDeviceContext = defaultDeviceContext.createDeviceContext(virtualDevice.deviceId)
+
+ grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, defaultDeviceContext)
+ assertPermission(DEVICE_AWARE_PERMISSION, PERMISSION_GRANTED, virtualDeviceContext)
}
- @RequiresFlagsDisabled(Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED)
@Test
- fun testDeviceAwareRuntimePermissionGrantIsInherited() {
- grantPermissionAndAssertGranted(Manifest.permission.CAMERA, defaultDeviceContext)
+ fun virtualDeviceCustomPolicy_deviceAwarePermissionGrantedOnVirtualDevice() {
+ // When a device aware permission is granted on the default device, it's not automatically
+ // granted on the virtual device.
+ grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, defaultDeviceContext)
+ assertPermission(DEVICE_AWARE_PERMISSION, PERMISSION_DENIED, virtualDeviceContext)
- assertPermission(Manifest.permission.CAMERA, PERMISSION_GRANTED, virtualDeviceContext)
+ grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
}
@Test
- fun testNonDeviceAwareRuntimePermissionGrantIsInherited() {
+ fun normalPermissionGrantedOnDefaultDevice_isGrantedOnVirtualDevice() {
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() {
+ fun virtualDeviceCustomPolicy_deviceAwarePermissionIsRevoked() {
grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
revokePermissionAndAssertDenied(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
}
@Test
- fun testNonDeviceAwareRuntimePermissionIsRevokedForDefaultDevice() {
+ fun normalPermissionRevokedFromVirtualDevice_isAlsoRevokedOnDefaultDevice() {
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.
@@ -134,24 +146,24 @@ class DevicePermissionsTest {
}
@Test
- fun testNormalPermissionGrantIsInherited() {
+ fun normalPermission_isInheritedOnVirtualDevice() {
assertPermission(Manifest.permission.INTERNET, PERMISSION_GRANTED, virtualDeviceContext)
}
@Test
- fun testSignaturePermissionGrantIsInherited() {
+ fun signaturePermission_isInheritedOnVirtualDevice() {
assertPermission(CUSTOM_SIGNATURE_PERMISSION, PERMISSION_GRANTED, virtualDeviceContext)
}
@Test
- fun testOneTimePermissionIsRevoked() {
+ fun virtualDeviceCustomPolicy_oneTimePermissionIsRevoked() {
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)
+ UserHandle.of(virtualDeviceContext.userId),
)
permissionManager.startOneTimePermissionSession(
@@ -159,19 +171,15 @@ class DevicePermissionsTest {
0,
0,
IMPORTANCE_FOREGROUND,
- IMPORTANCE_FOREGROUND_SERVICE
+ 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() {
+ fun virtualDeviceCustomPolicy_revokeSelfPermissionOnKill_permissionIsRevoked() {
grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
revokeSelfPermission(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
@@ -180,105 +188,90 @@ class DevicePermissionsTest {
}
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
- )
@Test
- fun testGrantAndRevokeDeviceAwarePermissionByPersistentDeviceId() {
- val deviceAwarePermission = DEVICE_AWARE_PERMISSION
-
+ fun usePersistentDeviceIdToRevokeDeviceAwarePermission_permissionIsRevoked() {
permissionManager.grantRuntimePermission(
TEST_PACKAGE_NAME,
- deviceAwarePermission,
- persistentDeviceId
+ DEVICE_AWARE_PERMISSION,
+ persistentDeviceId,
)
assertThat(
permissionManager.checkPermission(
- deviceAwarePermission,
+ DEVICE_AWARE_PERMISSION,
TEST_PACKAGE_NAME,
- virtualDevice.persistentDeviceId!!
+ virtualDevice.persistentDeviceId!!,
)
)
.isEqualTo(PERMISSION_GRANTED)
assertThat(
permissionManager.checkPermission(
- deviceAwarePermission,
+ DEVICE_AWARE_PERMISSION,
TEST_PACKAGE_NAME,
- PERSISTENT_DEVICE_ID_DEFAULT
+ PERSISTENT_DEVICE_ID_DEFAULT,
)
)
.isEqualTo(PERMISSION_DENIED)
permissionManager.revokeRuntimePermission(
TEST_PACKAGE_NAME,
- deviceAwarePermission,
+ DEVICE_AWARE_PERMISSION,
persistentDeviceId,
- "test"
+ "test",
)
assertThat(
permissionManager.checkPermission(
- deviceAwarePermission,
+ DEVICE_AWARE_PERMISSION,
TEST_PACKAGE_NAME,
- virtualDevice.persistentDeviceId!!
+ 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
+ fun updateAndGetPermissionFlagsByPersistentDeviceId() {
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
+ DEVICE_AWARE_PERMISSION,
+ persistentDeviceId,
)
)
.isEqualTo(0)
permissionManager.updatePermissionFlags(
TEST_PACKAGE_NAME,
- deviceAwarePermission,
+ DEVICE_AWARE_PERMISSION,
persistentDeviceId,
flagMask,
- flag
+ flag,
)
assertThat(
permissionManager.getPermissionFlags(
TEST_PACKAGE_NAME,
- deviceAwarePermission,
- persistentDeviceId
+ DEVICE_AWARE_PERMISSION,
+ persistentDeviceId,
)
)
.isEqualTo(FLAG_PERMISSION_USER_SET)
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
- )
@Test
- fun testAllPermissionStatesApiGrantForVirtualDevice() {
+ fun permissionGrantedOnVirtualDevice_reflectedInGetAllPermissionStatesApi() {
// Setting a flag explicitly so that the permission consistently stays in the state
permissionManager.updatePermissionFlags(
TEST_PACKAGE_NAME,
DEVICE_AWARE_PERMISSION,
PERSISTENT_DEVICE_ID_DEFAULT,
FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED,
- FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
+ FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED,
)
assertThat(
@@ -291,7 +284,7 @@ class DevicePermissionsTest {
permissionManager.grantRuntimePermission(
TEST_PACKAGE_NAME,
DEVICE_AWARE_PERMISSION,
- persistentDeviceId
+ persistentDeviceId,
)
val permissionStateMap =
@@ -312,7 +305,7 @@ class DevicePermissionsTest {
TEST_PACKAGE_NAME,
DEVICE_AWARE_PERMISSION,
persistentDeviceId,
- "test"
+ "test",
)
assertThat(
@@ -323,12 +316,8 @@ class DevicePermissionsTest {
.isFalse()
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
- )
@Test
- fun testAllPermissionStatesApiFlagsForVirtualDevice() {
+ fun setPermissionFlagOnVirtualDevice_reflectedInGetAllPermissionStatesApi() {
val flagMask = FLAG_PERMISSION_USER_SET or FLAG_PERMISSION_USER_FIXED
val flag = FLAG_PERMISSION_USER_SET
@@ -340,7 +329,7 @@ class DevicePermissionsTest {
DEVICE_AWARE_PERMISSION,
persistentDeviceId,
flagMask,
- flag
+ flag,
)
assertThat(
@@ -349,7 +338,7 @@ class DevicePermissionsTest {
.getAllPermissionStates(TEST_PACKAGE_NAME, persistentDeviceId)[
DEVICE_AWARE_PERMISSION]!!
.flags,
- flag
+ flag,
)
)
.isTrue()
@@ -360,15 +349,14 @@ class DevicePermissionsTest {
.getAllPermissionStates(TEST_PACKAGE_NAME, persistentDeviceId)[
DEVICE_AWARE_PERMISSION]!!
.flags,
- FLAG_PERMISSION_USER_FIXED
+ FLAG_PERMISSION_USER_FIXED,
)
)
.isFalse()
}
- @RequiresFlagsEnabled(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
@Test
- fun testAllPermissionStatesApiGrantForDefaultDevice() {
+ fun permissionGrantedOnDefaultDevice_reflectedInGetAllPermissionStatesApi() {
// Setting a flag explicitly so that the permission consistently stays in the state upon
// revoke
permissionManager.updatePermissionFlags(
@@ -376,13 +364,13 @@ class DevicePermissionsTest {
DEVICE_AWARE_PERMISSION,
PERSISTENT_DEVICE_ID_DEFAULT,
FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED,
- FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
+ FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED,
)
permissionManager.grantRuntimePermission(
TEST_PACKAGE_NAME,
DEVICE_AWARE_PERMISSION,
- PERSISTENT_DEVICE_ID_DEFAULT
+ PERSISTENT_DEVICE_ID_DEFAULT,
)
assertThat(
@@ -404,7 +392,7 @@ class DevicePermissionsTest {
TEST_PACKAGE_NAME,
DEVICE_AWARE_PERMISSION,
PERSISTENT_DEVICE_ID_DEFAULT,
- "test"
+ "test",
)
assertThat(
@@ -416,9 +404,8 @@ class DevicePermissionsTest {
.isFalse()
}
- @RequiresFlagsEnabled(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
@Test
- fun testAllPermissionStatesApiFlagsForDefaultDevice() {
+ fun setPermissionFlagOnDefaultDevice_reflectedInGetAllPermissionStatesApi() {
val flagMask = FLAG_PERMISSION_USER_SET or FLAG_PERMISSION_USER_FIXED
val flag = FLAG_PERMISSION_USER_SET
@@ -434,7 +421,7 @@ class DevicePermissionsTest {
DEVICE_AWARE_PERMISSION,
PERSISTENT_DEVICE_ID_DEFAULT,
flagMask,
- flag
+ flag,
)
assertThat(
@@ -443,7 +430,7 @@ class DevicePermissionsTest {
.getAllPermissionStates(TEST_PACKAGE_NAME, PERSISTENT_DEVICE_ID_DEFAULT)[
DEVICE_AWARE_PERMISSION]!!
.flags,
- flag
+ flag,
)
)
.isTrue()
@@ -454,19 +441,18 @@ class DevicePermissionsTest {
.getAllPermissionStates(TEST_PACKAGE_NAME, PERSISTENT_DEVICE_ID_DEFAULT)[
DEVICE_AWARE_PERMISSION]!!
.flags,
- FLAG_PERMISSION_USER_FIXED
+ FLAG_PERMISSION_USER_FIXED,
)
)
.isFalse()
}
- @RequiresFlagsEnabled(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
@Test
- fun testAllPermissionStatesApiThatNonDeviceAwareRuntimePermissionGrantIsNotInherited() {
+ fun getAllPermissionStates_normalPermissionIsNotInherited() {
permissionManager.grantRuntimePermission(
TEST_PACKAGE_NAME,
NON_DEVICE_AWARE_PERMISSION,
- PERSISTENT_DEVICE_ID_DEFAULT
+ PERSISTENT_DEVICE_ID_DEFAULT,
)
assertThat(
@@ -501,7 +487,7 @@ class DevicePermissionsTest {
context.packageManager.grantRuntimePermission(
TEST_PACKAGE_NAME,
permissionName,
- UserHandle.of(context.userId)
+ UserHandle.of(context.userId),
)
assertPermission(permissionName, PERMISSION_GRANTED, context)
}
@@ -510,18 +496,14 @@ class DevicePermissionsTest {
context.packageManager.revokeRuntimePermission(
TEST_PACKAGE_NAME,
permissionName,
- UserHandle.of(context.userId)
+ 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)
+ private fun assertPermission(permissionName: String, permissionState: Int, context: Context) {
+ val uid = defaultDeviceContext.packageManager.getApplicationInfo(TEST_PACKAGE_NAME, 0).uid
+ assertThat(context.checkPermission(permissionName, -1, uid)).isEqualTo(permissionState)
}
companion object {
diff --git a/tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java
index 437aa19c4..ce724b13e 100644
--- a/tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java
+++ b/tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java
@@ -166,7 +166,8 @@ public class NoSystemFunctionPermissionTest extends AndroidTestCase {
*/
@SmallTest
public void testSendSms() {
- if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ if (!getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_MESSAGING)) {
return;
}
diff --git a/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/AndroidManifest.xml b/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/AndroidManifest.xml
index 211e415bd..c31bfdaf3 100644
--- a/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/AndroidManifest.xml
+++ b/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/AndroidManifest.xml
@@ -21,6 +21,7 @@
package="android.permissionmultidevice.cts.accessremotedevicecamera">
<uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
<application>
<activity android:name=".RequestPermissionActivity" android:exported="true" />
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
index fa1d1f83c..33daa1253 100644
--- a/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/src/android/permissionmultidevice/cts/accessremotedevicecamera/RequestPermissionActivity.kt
+++ b/tests/cts/permissionmultidevice/AccessRemoteDeviceCameraApp/src/android/permissionmultidevice/cts/accessremotedevicecamera/RequestPermissionActivity.kt
@@ -16,7 +16,6 @@
package android.permissionmultidevice.cts.accessremotedevicecamera
-import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
@@ -33,17 +32,24 @@ class RequestPermissionActivity : Activity() {
val deviceId =
intent.getIntExtra(
PackageManager.EXTRA_REQUEST_PERMISSIONS_DEVICE_ID,
- Context.DEVICE_ID_DEFAULT
+ Context.DEVICE_ID_INVALID,
)
- requestPermissions(DEVICE_AWARE_PERMISSIONS, 1001, deviceId)
+ val permissions =
+ intent.getStringArrayExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES)!!
+
+ if (deviceId != Context.DEVICE_ID_INVALID) {
+ requestPermissions(permissions, 1001, deviceId)
+ } else {
+ requestPermissions(permissions, 1001)
+ }
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray,
- deviceId: Int
+ deviceId: Int,
) {
val resultReceiver =
intent.getParcelableExtra(Intent.EXTRA_RESULT_RECEIVER, RemoteCallback::class.java)
@@ -57,8 +63,4 @@ class RequestPermissionActivity : Activity() {
resultReceiver?.sendResult(result)
finish()
}
-
- companion object {
- private val DEVICE_AWARE_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
- }
}
diff --git a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt
index 907917f6f..76c86df76 100644
--- a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt
+++ b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt
@@ -108,7 +108,7 @@ class AppPermissionsTest {
@RequiresFlagsEnabled(
Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
)
@Test
fun externalDevicePermissionGrantTest() {
@@ -123,7 +123,7 @@ class AppPermissionsTest {
verifyRadioButtonStates(
allowForegroundChecked = true,
askChecked = false,
- denyChecked = false
+ denyChecked = false,
)
UiAutomatorUtils2.getUiDevice().pressBack()
@@ -131,14 +131,14 @@ class AppPermissionsTest {
mapOf(
"Allowed" to listOf(externalDeviceCameraText),
"Ask every time" to emptyList(),
- "Not allowed" to listOf("Camera")
+ "Not allowed" to listOf("Camera", "Contacts"),
)
assertEquals(expectedGrantInfoMap, getGrantInfoMap())
}
@RequiresFlagsEnabled(
Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
)
@Test
fun externalDevicePermissionChangeToAskTest() {
@@ -153,7 +153,7 @@ class AppPermissionsTest {
@RequiresFlagsEnabled(
Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
)
@Test
fun externalDevicePermissionChangeToDenyTest() {
@@ -168,7 +168,7 @@ class AppPermissionsTest {
@RequiresFlagsEnabled(
Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
)
@Test
fun externalDevicePermissionChangeToAllowTest() {
@@ -180,7 +180,7 @@ class AppPermissionsTest {
verifyRadioButtonStates(
allowForegroundChecked = false,
askChecked = true,
- denyChecked = false
+ denyChecked = false,
)
clickAllowForegroundButton()
@@ -189,7 +189,7 @@ class AppPermissionsTest {
@RequiresFlagsEnabled(
Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
)
@Test
fun externalDevicePermissionNotDisplayedInitiallyTest() {
@@ -200,14 +200,14 @@ class AppPermissionsTest {
mapOf(
"Allowed" to emptyList(),
"Ask every time" to emptyList(),
- "Not allowed" to listOf("Camera")
+ "Not allowed" to listOf("Camera", "Contacts"),
)
assertEquals(expectedGrantInfoMap, getGrantInfoMap())
}
@RequiresFlagsEnabled(
Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
)
@Test
fun externalDevicePermissionStickyOnGrantTest() {
@@ -219,7 +219,7 @@ class AppPermissionsTest {
verifyRadioButtonStates(
allowForegroundChecked = true,
askChecked = false,
- denyChecked = false
+ denyChecked = false,
)
clickDenyButton()
@@ -232,7 +232,7 @@ class AppPermissionsTest {
mapOf(
"Allowed" to emptyList(),
"Ask every time" to emptyList(),
- "Not allowed" to listOf("Camera", externalDeviceCameraText)
+ "Not allowed" to listOf("Camera", externalDeviceCameraText, "Contacts"),
)
assertEquals(expectedGrantInfoMap, getGrantInfoMap())
}
@@ -243,7 +243,7 @@ class AppPermissionsTest {
verifyRadioButtonStates(
allowForegroundChecked = false,
askChecked = true,
- denyChecked = false
+ denyChecked = false,
)
UiAutomatorUtils2.getUiDevice().pressBack()
@@ -252,7 +252,7 @@ class AppPermissionsTest {
mapOf(
"Allowed" to emptyList(),
"Ask every time" to listOf(externalDeviceCameraText),
- "Not allowed" to listOf("Camera")
+ "Not allowed" to listOf("Camera", "Contacts"),
)
assertEquals(expectedGrantInfoMap, getGrantInfoMap())
@@ -270,7 +270,7 @@ class AppPermissionsTest {
verifyRadioButtonStates(
allowForegroundChecked = false,
askChecked = false,
- denyChecked = true
+ denyChecked = true,
)
UiAutomatorUtils2.getUiDevice().pressBack()
@@ -279,7 +279,7 @@ class AppPermissionsTest {
mapOf(
"Allowed" to emptyList(),
"Ask every time" to emptyList(),
- "Not allowed" to listOf("Camera", externalDeviceCameraText)
+ "Not allowed" to listOf("Camera", externalDeviceCameraText, "Contacts"),
)
assertEquals(expectedGrantInfoMap, getGrantInfoMap())
@@ -297,7 +297,7 @@ class AppPermissionsTest {
verifyRadioButtonStates(
allowForegroundChecked = true,
askChecked = false,
- denyChecked = false
+ denyChecked = false,
)
UiAutomatorUtils2.getUiDevice().pressBack()
@@ -306,7 +306,7 @@ class AppPermissionsTest {
mapOf(
"Allowed" to listOf(externalDeviceCameraText),
"Ask every time" to emptyList(),
- "Not allowed" to listOf("Camera")
+ "Not allowed" to listOf("Camera", "Contacts"),
)
assertEquals(expectedGrantInfoMap, getGrantInfoMap())
@@ -327,7 +327,7 @@ class AppPermissionsTest {
mapOf(
"Allowed" to mutableListOf<String>(),
"Ask every time" to mutableListOf(),
- "Not allowed" to mutableListOf()
+ "Not allowed" to mutableListOf(),
)
val outOfScopeTitles = setOf("Unused app settings", "Manage app if unused")
@@ -360,21 +360,21 @@ class AppPermissionsTest {
private fun verifyRadioButtonStates(
allowForegroundChecked: Boolean,
askChecked: Boolean,
- denyChecked: Boolean
+ denyChecked: Boolean,
) {
eventually {
assertEquals(
allowForegroundChecked,
UiAutomatorUtils2.waitFindObject(By.res(ALLOW_FOREGROUND_ONLY_RADIO_BUTTON))
- .isChecked
+ .isChecked,
)
assertEquals(
askChecked,
- UiAutomatorUtils2.waitFindObject(By.res(ASK_RADIO_BUTTON)).isChecked
+ UiAutomatorUtils2.waitFindObject(By.res(ASK_RADIO_BUTTON)).isChecked,
)
assertEquals(
denyChecked,
- UiAutomatorUtils2.waitFindObject(By.res(DENY_RADIO_BUTTON)).isChecked
+ UiAutomatorUtils2.waitFindObject(By.res(DENY_RADIO_BUTTON)).isChecked,
)
}
}
@@ -392,7 +392,7 @@ class AppPermissionsTest {
)
},
Until.newWindow(),
- NEW_WINDOW_TIMEOUT_MILLIS
+ NEW_WINDOW_TIMEOUT_MILLIS,
)
}
@@ -418,7 +418,7 @@ class AppPermissionsTest {
permissionManager.grantRuntimePermission(
APP_PACKAGE_NAME,
DEVICE_AWARE_PERMISSION,
- persistentDeviceId
+ persistentDeviceId,
)
private fun getPermState(): Map<String, PermissionManager.PermissionState> =
diff --git a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
index 5c7573a0b..0f6504502 100644
--- a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
+++ b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
@@ -24,11 +24,11 @@ import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM
import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT
import android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.content.Intent.EXTRA_RESULT_RECEIVER
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.os.Bundle
@@ -41,7 +41,6 @@ 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.RequiresFlagsDisabled
import android.platform.test.annotations.RequiresFlagsEnabled
import android.provider.Settings
import android.view.Display
@@ -63,15 +62,16 @@ import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName = "VanillaIceCream")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
@AppModeFull(reason = "VirtualDeviceManager cannot be accessed by instant apps")
-class DeviceAwarePermissionGrantTest {
+open class DeviceAwarePermissionGrantTest {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val defaultDeviceContext = instrumentation.targetContext
+ private lateinit var defaultDeviceName: String
private lateinit var virtualDeviceManager: VirtualDeviceManager
private lateinit var virtualDevice: VirtualDeviceManager.VirtualDevice
private lateinit var virtualDisplay: VirtualDisplay
- private lateinit var deviceDisplayName: String
+ private lateinit var virtualDeviceName: String
private val permissionManager =
defaultDeviceContext.getSystemService(PermissionManager::class.java)!!
@@ -88,194 +88,458 @@ class DeviceAwarePermissionGrantTest {
installPackage(APP_APK_PATH_STREAMING)
virtualDeviceManager =
defaultDeviceContext.getSystemService(VirtualDeviceManager::class.java)!!
+
+ defaultDeviceName =
+ Settings.Global.getString(
+ defaultDeviceContext.contentResolver,
+ Settings.Global.DEVICE_NAME,
+ )
+ }
+
+ @After
+ fun cleanup() {
+ uninstallPackage(APP_PACKAGE_NAME, requireSuccess = false)
+ Thread.sleep(2000)
+ }
+
+ private fun createVirtualDevice(cameraPolicy: Int = DEVICE_POLICY_DEFAULT) {
virtualDevice =
virtualDeviceRule.createManagedVirtualDevice(
VirtualDeviceParams.Builder()
- .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_CUSTOM)
+ .setDevicePolicy(POLICY_TYPE_CAMERA, cameraPolicy)
.build()
)
-
- val displayConfigBuilder =
- 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
- )
-
virtualDisplay =
- virtualDeviceRule.createManagedVirtualDisplay(virtualDevice, displayConfigBuilder)!!
- deviceDisplayName =
+ virtualDeviceRule.createManagedVirtualDisplay(
+ virtualDevice,
+ VirtualDeviceRule.createTrustedVirtualDisplayConfigBuilder(),
+ )!!
+ virtualDeviceName =
virtualDeviceManager.getVirtualDevice(virtualDevice.deviceId)!!.displayName.toString()
}
- @After
- fun cleanup() {
- uninstallPackage(APP_PACKAGE_NAME, requireSuccess = false)
- Thread.sleep(2000)
+ @Test
+ fun deviceAwarePermission_onHost_requestHostPermission() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_CUSTOM)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = DEVICE_ID_DEFAULT,
+ requestingForDevice = DEVICE_ID_DEFAULT,
+ dialogDeviceName = DEVICE_ID_INVALID,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = false,
+ )
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
- )
@Test
- fun onHostDevice_requestPermissionForHostDevice_shouldGrantPermission() {
- assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
- assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
+ fun deviceAwarePermission_onHost_withRemotePermission_requestHostPermission() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_CUSTOM)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = true,
+ requestingFromDevice = DEVICE_ID_DEFAULT,
+ requestingForDevice = DEVICE_ID_DEFAULT,
+ dialogDeviceName = DEVICE_ID_INVALID,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
- testGrantPermissionForDevice(
- Display.DEFAULT_DISPLAY,
- DEVICE_ID_DEFAULT,
- false,
- "",
+ @Test
+ fun deviceAwarePermission_onHost_requestPermissionWithoutDeviceId() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_CUSTOM)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = DEVICE_ID_DEFAULT,
+ requestingForDevice = DEVICE_ID_INVALID,
+ dialogDeviceName = DEVICE_ID_INVALID,
expectPermissionGrantedOnDefaultDevice = true,
- expectPermissionGrantedOnRemoteDevice = false,
+ expectPermissionGrantedOnVirtualDevice = false,
)
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
- )
@Test
- fun onHostDevice_requestPermissionForRemoteDevice_shouldGrantPermission() {
- assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
- assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
+ fun deviceAwarePermission_onHost_withRemotePermission_requestPermissionWithoutDeviceId() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_CUSTOM)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = true,
+ requestingFromDevice = DEVICE_ID_DEFAULT,
+ requestingForDevice = DEVICE_ID_INVALID,
+ dialogDeviceName = DEVICE_ID_INVALID,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
- testGrantPermissionForDevice(
- Display.DEFAULT_DISPLAY,
- virtualDevice.deviceId,
- true,
- deviceDisplayName,
+ @Test
+ fun deviceAwarePermission_onHost_requestRemotePermission() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_CUSTOM)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = DEVICE_ID_DEFAULT,
+ requestingForDevice = virtualDevice.deviceId,
+ dialogDeviceName = virtualDevice.deviceId,
expectPermissionGrantedOnDefaultDevice = false,
- expectPermissionGrantedOnRemoteDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
)
}
@Test
- fun onHostDevice_requestPermissionForRemoteDeviceAfterPermissionGrantedToHostDevice() {
- instrumentation.uiAutomation.grantRuntimePermission(APP_PACKAGE_NAME, PERMISSION)
+ fun deviceAwarePermission_onHost_withHostPermission_requestRemotePermission() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_CUSTOM)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = true,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = DEVICE_ID_DEFAULT,
+ requestingForDevice = virtualDevice.deviceId,
+ dialogDeviceName = virtualDevice.deviceId,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
- assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, true)
- assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
+ @Test
+ fun deviceAwarePermission_onRemote_requestRemotePermission() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_CUSTOM)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = virtualDevice.deviceId,
+ requestingForDevice = virtualDevice.deviceId,
+ dialogDeviceName = virtualDevice.deviceId,
+ expectPermissionGrantedOnDefaultDevice = false,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
- testGrantPermissionForDevice(
- Display.DEFAULT_DISPLAY,
- virtualDevice.deviceId,
- true,
- deviceDisplayName,
+ @Test
+ fun deviceAwarePermission_onRemote_withHostPermission_requestRemotePermission() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_CUSTOM)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = true,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = virtualDevice.deviceId,
+ requestingForDevice = virtualDevice.deviceId,
+ dialogDeviceName = virtualDevice.deviceId,
expectPermissionGrantedOnDefaultDevice = true,
- expectPermissionGrantedOnRemoteDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
)
}
@Test
- fun onHostDevice_requestPermissionForHostDeviceAfterPermissionGrantedToRemoteDevice() {
- permissionManager.grantRuntimePermission(
- APP_PACKAGE_NAME,
- PERMISSION,
- virtualDevice.persistentDeviceId!!,
+ fun deviceAwarePermission_onRemote_requestPermissionWithoutDeviceId() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_CUSTOM)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = virtualDevice.deviceId,
+ requestingForDevice = DEVICE_ID_INVALID,
+ dialogDeviceName = virtualDevice.deviceId,
+ expectPermissionGrantedOnDefaultDevice = false,
+ expectPermissionGrantedOnVirtualDevice = true,
)
+ }
- assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
- assertAppHasPermissionForDevice(virtualDevice.deviceId, true)
+ @Test
+ fun deviceAwarePermission_onRemote_withHostPermission_requestPermissionWithoutDeviceId() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_CUSTOM)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = true,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = virtualDevice.deviceId,
+ requestingForDevice = DEVICE_ID_INVALID,
+ dialogDeviceName = virtualDevice.deviceId,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
- testGrantPermissionForDevice(
- Display.DEFAULT_DISPLAY,
- DEVICE_ID_DEFAULT,
- false,
- "",
+ @RequiresFlagsEnabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES)
+ @Test
+ fun deviceAwarePermission_onRemote_requestHostPermission() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_CUSTOM)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = virtualDevice.deviceId,
+ requestingForDevice = DEVICE_ID_DEFAULT,
+ dialogDeviceName = DEVICE_ID_DEFAULT,
expectPermissionGrantedOnDefaultDevice = true,
- expectPermissionGrantedOnRemoteDevice = true,
+ expectPermissionGrantedOnVirtualDevice = false,
)
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
- )
- @RequiresFlagsDisabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES)
+ @RequiresFlagsEnabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES)
@Test
- fun onRemoteDevice_requestPermissionForHostDevice_shouldShowWarningDialog() {
- assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
- assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
+ fun deviceAwarePermission_onRemote_withRemotePermission_requestHostPermission() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_CUSTOM)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = true,
+ requestingFromDevice = virtualDevice.deviceId,
+ requestingForDevice = DEVICE_ID_DEFAULT,
+ dialogDeviceName = DEVICE_ID_DEFAULT,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
- requestPermissionOnDevice(virtualDisplay.display.displayId, DEVICE_ID_DEFAULT)
+ @Test
+ fun deviceAwarePermissionWithoutCapability_onHost_requestHostPermission() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_DEFAULT)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = DEVICE_ID_DEFAULT,
+ requestingForDevice = DEVICE_ID_DEFAULT,
+ dialogDeviceName = DEVICE_ID_INVALID,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
- val displayId = virtualDisplay.display.displayId
- waitFindObject(By.displayId(displayId).textContains("Permission request suppressed"))
+ @Test
+ fun deviceAwarePermissionWithoutCapability_onHost_requestPermissionWithoutDeviceId() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_DEFAULT)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = DEVICE_ID_DEFAULT,
+ requestingForDevice = DEVICE_ID_INVALID,
+ dialogDeviceName = DEVICE_ID_INVALID,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
- Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES,
- )
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @RequiresFlagsEnabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES)
@Test
- fun onRemoteDevice_requestPermissionForHostDevice_shouldGrantPermission() {
- assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
- assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
- // Create a virtual device with default policy, so that camera permission request will
- // correspond to default device camera access.
- virtualDevice =
- virtualDeviceRule.createManagedVirtualDevice(
- VirtualDeviceParams.Builder()
- .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_DEFAULT)
- .build()
- )
- testGrantPermissionForDevice(
- virtualDisplay.display.displayId,
- virtualDevice.deviceId,
- true,
- Settings.Global.getString(
- defaultDeviceContext.contentResolver,
- Settings.Global.DEVICE_NAME,
- ),
+ fun deviceAwarePermissionWithoutCapability_onHost_requestRemotePermission() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_DEFAULT)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = DEVICE_ID_DEFAULT,
+ requestingForDevice = virtualDevice.deviceId,
+ dialogDeviceName = DEVICE_ID_INVALID,
expectPermissionGrantedOnDefaultDevice = true,
- expectPermissionGrantedOnRemoteDevice = false,
+ expectPermissionGrantedOnVirtualDevice = true,
)
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
- )
+ @RequiresFlagsEnabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES)
@Test
- fun onRemoteDevice_requestPermissionForRemoteDevice_shouldGrantPermission() {
- assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
- assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
+ fun deviceAwarePermissionWithoutCapability_onRemote_requestRemotePermission() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_DEFAULT)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = virtualDevice.deviceId,
+ requestingForDevice = virtualDevice.deviceId,
+ dialogDeviceName = DEVICE_ID_DEFAULT,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
- testGrantPermissionForDevice(
- virtualDisplay.display.displayId,
- virtualDevice.deviceId,
- true,
- deviceDisplayName,
- expectPermissionGrantedOnDefaultDevice = false,
- expectPermissionGrantedOnRemoteDevice = true,
+ @RequiresFlagsEnabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES)
+ @Test
+ fun deviceAwarePermissionWithoutCapability_onRemote_requestPermissionWithoutDeviceId() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_DEFAULT)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = virtualDevice.deviceId,
+ requestingForDevice = DEVICE_ID_INVALID,
+ dialogDeviceName = DEVICE_ID_DEFAULT,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
)
}
- private fun testGrantPermissionForDevice(
- displayId: Int,
- targetDeviceId: Int,
- showDeviceName: Boolean,
- expectedDeviceNameInDialog: String,
+ @RequiresFlagsEnabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES)
+ @Test
+ fun deviceAwarePermissionWithoutCapability_onRemote_requestHostPermission() {
+ createVirtualDevice(cameraPolicy = DEVICE_POLICY_DEFAULT)
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.CAMERA,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = virtualDevice.deviceId,
+ requestingForDevice = DEVICE_ID_DEFAULT,
+ dialogDeviceName = DEVICE_ID_DEFAULT,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
+
+ @Test
+ fun nonDeviceAwarePermission_onHost_requestHostPermission() {
+ createVirtualDevice()
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.READ_CONTACTS,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = DEVICE_ID_DEFAULT,
+ requestingForDevice = DEVICE_ID_DEFAULT,
+ dialogDeviceName = DEVICE_ID_INVALID,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
+
+ @Test
+ fun nonDeviceAwarePermission_onHost_requestPermissionWithoutDeviceId() {
+ createVirtualDevice()
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.READ_CONTACTS,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = DEVICE_ID_DEFAULT,
+ requestingForDevice = DEVICE_ID_INVALID,
+ dialogDeviceName = DEVICE_ID_INVALID,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES)
+ @Test
+ fun nonDeviceAwarePermission_onHost_requestRemotePermission() {
+ createVirtualDevice()
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.READ_CONTACTS,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = DEVICE_ID_DEFAULT,
+ requestingForDevice = virtualDevice.deviceId,
+ dialogDeviceName = DEVICE_ID_INVALID,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES)
+ @Test
+ fun nonDeviceAwarePermission_onRemote_requestRemotePermission() {
+ createVirtualDevice()
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.READ_CONTACTS,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = virtualDevice.deviceId,
+ requestingForDevice = virtualDevice.deviceId,
+ dialogDeviceName = DEVICE_ID_DEFAULT,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES)
+ @Test
+ fun nonDeviceAwarePermission_onRemote_requestPermissionWithoutDeviceId() {
+ createVirtualDevice()
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.READ_CONTACTS,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = virtualDevice.deviceId,
+ requestingForDevice = DEVICE_ID_INVALID,
+ dialogDeviceName = DEVICE_ID_DEFAULT,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES)
+ @Test
+ fun nonDeviceAwarePermission_onRemote_requestHostPermission() {
+ createVirtualDevice()
+ testMultiDevicePermissionGrant(
+ permission = Manifest.permission.READ_CONTACTS,
+ initialDefaultDevicePermission = false,
+ initialVirtualDevicePermission = false,
+ requestingFromDevice = virtualDevice.deviceId,
+ requestingForDevice = DEVICE_ID_DEFAULT,
+ dialogDeviceName = DEVICE_ID_DEFAULT,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnVirtualDevice = true,
+ )
+ }
+
+ private fun testMultiDevicePermissionGrant(
+ permission: String,
+ initialDefaultDevicePermission: Boolean,
+ initialVirtualDevicePermission: Boolean,
+ requestingFromDevice: Int,
+ requestingForDevice: Int,
+ dialogDeviceName: Int,
expectPermissionGrantedOnDefaultDevice: Boolean,
- expectPermissionGrantedOnRemoteDevice: Boolean,
+ expectPermissionGrantedOnVirtualDevice: Boolean,
) {
- val future = requestPermissionOnDevice(displayId, targetDeviceId)
+ if (initialDefaultDevicePermission) {
+ instrumentation.uiAutomation.grantRuntimePermission(APP_PACKAGE_NAME, permission)
+ }
+ if (initialVirtualDevicePermission) {
+ grantRuntimePermissionOnVirtualDevice(permission)
+ }
+ assertAppHasPermissionForDevice(
+ DEVICE_ID_DEFAULT,
+ permission,
+ initialDefaultDevicePermission,
+ )
+ assertAppHasPermissionForDevice(
+ virtualDevice.deviceId,
+ permission,
+ initialVirtualDevicePermission,
+ )
- if (showDeviceName) {
+ val displayId =
+ if (requestingFromDevice == DEVICE_ID_DEFAULT) {
+ Display.DEFAULT_DISPLAY
+ } else {
+ virtualDisplay.display.displayId
+ }
+
+ val future = requestPermissionOnDevice(displayId, requestingForDevice, permission)
+
+ if (dialogDeviceName != DEVICE_ID_INVALID) {
+ val expectedDeviceNameInDialog =
+ if (dialogDeviceName == DEVICE_ID_DEFAULT) {
+ defaultDeviceName
+ } else {
+ virtualDeviceName
+ }
assertPermissionMessageContainsDeviceName(displayId, expectedDeviceNameInDialog)
+ } else {
+ assertPermissionMessageDoesNotContainDeviceName(displayId)
}
// Click the allow button in the dialog to grant permission
- SystemUtil.eventually { click(By.displayId(displayId).res(ALLOW_BUTTON)) }
+ val allowButton =
+ if (permission == Manifest.permission.CAMERA) ALLOW_BUTTON_FOREGROUND else ALLOW_BUTTON
+ SystemUtil.eventually { click(By.displayId(displayId).res(allowButton)) }
// Validate permission grant result returned from callback
val grantPermissionResult = future.get(TIMEOUT, TimeUnit.MILLISECONDS)
@@ -284,34 +548,49 @@ class DeviceAwarePermissionGrantTest {
TestConstants.PERMISSION_RESULT_KEY_PERMISSIONS
)
)
- .isEqualTo(arrayOf(PERMISSION))
+ .isEqualTo(arrayOf(permission))
assertThat(
grantPermissionResult.getIntArray(TestConstants.PERMISSION_RESULT_KEY_GRANT_RESULTS)
)
.isEqualTo(arrayOf(PackageManager.PERMISSION_GRANTED).toIntArray())
+
+ val expectedPermissionResultDeviceId =
+ if (requestingForDevice == DEVICE_ID_INVALID) {
+ requestingFromDevice
+ } else {
+ requestingForDevice
+ }
assertThat(grantPermissionResult.getInt(TestConstants.PERMISSION_RESULT_KEY_DEVICE_ID))
- .isEqualTo(targetDeviceId)
+ .isEqualTo(expectedPermissionResultDeviceId)
// Validate whether permission is granted as expected
- assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, expectPermissionGrantedOnDefaultDevice)
+ assertAppHasPermissionForDevice(
+ DEVICE_ID_DEFAULT,
+ permission,
+ expectPermissionGrantedOnDefaultDevice,
+ )
assertAppHasPermissionForDevice(
virtualDevice.deviceId,
- expectPermissionGrantedOnRemoteDevice,
+ permission,
+ expectPermissionGrantedOnVirtualDevice,
)
}
private fun requestPermissionOnDevice(
displayId: Int,
targetDeviceId: Int,
+ permission: String,
): CompletableFuture<Bundle> {
val future = CompletableFuture<Bundle>()
val callback = RemoteCallback { result: Bundle? -> future.complete(result) }
+ val permissions = mutableListOf(permission).toTypedArray()
val intent =
Intent()
.setComponent(
ComponentName(APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.RequestPermissionActivity")
)
.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_DEVICE_ID, targetDeviceId)
+ .putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, permissions)
.putExtra(EXTRA_RESULT_RECEIVER, callback)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
@@ -327,12 +606,23 @@ class DeviceAwarePermissionGrantTest {
assertThat(text).contains(deviceName)
}
- private fun assertAppHasPermissionForDevice(deviceId: Int, expectPermissionGranted: Boolean) {
+ private fun assertPermissionMessageDoesNotContainDeviceName(displayId: Int) {
+ waitFindObject(By.displayId(displayId).res(PERMISSION_MESSAGE_ID))
+ val text = findTextForView(By.displayId(displayId).res(PERMISSION_MESSAGE_ID))
+ assertThat(text).doesNotContain(virtualDeviceName)
+ assertThat(text).doesNotContain(defaultDeviceName)
+ }
+
+ private fun assertAppHasPermissionForDevice(
+ deviceId: Int,
+ permission: String,
+ expectPermissionGranted: Boolean,
+ ) {
val checkPermissionResult =
defaultDeviceContext
.createDeviceContext(deviceId)
.packageManager
- .checkPermission(PERMISSION, APP_PACKAGE_NAME)
+ .checkPermission(permission, APP_PACKAGE_NAME)
if (expectPermissionGranted) {
Assert.assertEquals(PackageManager.PERMISSION_GRANTED, checkPermissionResult)
@@ -347,18 +637,25 @@ class DeviceAwarePermissionGrantTest {
return intent.resolveActivity(defaultDeviceContext.packageManager)
}
+ private fun grantRuntimePermissionOnVirtualDevice(permission: String) {
+ permissionManager.grantRuntimePermission(
+ APP_PACKAGE_NAME,
+ permission,
+ virtualDevice.persistentDeviceId!!,
+ )
+ }
+
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 =
+ const val ALLOW_BUTTON_FOREGROUND =
"com.android.permissioncontroller:id/permission_allow_foreground_only_button"
- const val DEVICE_ID_DEFAULT = 0
+ const val ALLOW_BUTTON = "com.android.permissioncontroller:id/permission_allow_button"
+ const val DEVICE_ID_DEFAULT = Context.DEVICE_ID_DEFAULT
+ const val DEVICE_ID_INVALID = Context.DEVICE_ID_INVALID
const val PERSISTENT_DEVICE_ID_DEFAULT = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
- const val PERMISSION = Manifest.permission.CAMERA
const val TIMEOUT = 5000L
- private const val DISPLAY_HEIGHT = 1920
- private const val DISPLAY_WIDTH = 1080
}
}
diff --git a/tests/cts/permissionpolicy/res/raw/android_manifest.xml b/tests/cts/permissionpolicy/res/raw/android_manifest.xml
index eb6764cae..200e3cf42 100644
--- a/tests/cts/permissionpolicy/res/raw/android_manifest.xml
+++ b/tests/cts/permissionpolicy/res/raw/android_manifest.xml
@@ -5394,13 +5394,13 @@
corresponding permission such as {@link #HEAD_TRACKING} or
{@link #FACE_TRACKING} for the data being accessed.
- <p>Protection level: normal|appop
+ <p>Protection level: signature|privileged
@SystemApi
@FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
@hide -->
<permission android:name="android.permission.XR_TRACKING_IN_BACKGROUND"
- android:protectionLevel="normal|appop"
+ android:protectionLevel="signature|privileged"
android:description="@string/permdesc_xr_tracking_in_background"
android:label="@string/permlab_xr_tracking_in_background"
android:featureFlag="android.xr.xr_manifest_entries" />
@@ -7759,7 +7759,17 @@
@FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
@hide -->
<permission android:name="android.permission.READ_BLOCKED_NUMBERS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature"
+ android:featureFlag="!android.permission.flags.grant_read_blocked_numbers_to_system_ui_intelligence" />
+
+ <!-- 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|role"
+ android:featureFlag="android.permission.flags.grant_read_blocked_numbers_to_system_ui_intelligence" />
<!-- Allows the holder to write blocked numbers. See
{@link android.provider.BlockedNumberContract}.
@@ -8985,13 +8995,13 @@
<!-- @SystemApi
@FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled")
- This permission is required to access the specific text classifier you need from the
+ This permission is required to access the specific text classifier from the
TextClassificationManager.
- <p>Protection level: signature|role
+ <p>Protection level: signature|role|privileged
@hide
-->
<permission android:name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"
- android:protectionLevel="signature|role"
+ android:protectionLevel="signature|role|privileged"
android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/>
<!-- Attribution for Geofencing service. -->
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt
index ccc5a0a5e..432d1efb1 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt
@@ -36,8 +36,10 @@ import android.os.Build
import android.os.Process
import android.provider.DeviceConfig
import android.provider.Settings
+import android.server.wm.WindowManagerStateHelper
import android.text.Spanned
import android.text.style.ClickableSpan
+import android.util.Log
import android.view.View
import android.view.accessibility.AccessibilityNodeInfo
import androidx.test.uiautomator.By
@@ -51,9 +53,11 @@ 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.compatibility.common.util.UiDumpUtils
import com.android.modules.utils.build.SdkLevel
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
import java.util.regex.Pattern
import org.junit.After
import org.junit.Assert
@@ -64,6 +68,7 @@ import org.junit.Before
abstract class BaseUsePermissionTest : BasePermissionTest() {
companion object {
+ const val LOG_TAG = "BaseUsePermissionTest"
const val APP_APK_NAME_31 = "CtsUsePermissionApp31.apk"
const val APP_APK_NAME_31_WITH_ASL = "CtsUsePermissionApp31WithAsl.apk"
const val APP_APK_NAME_LATEST = "CtsUsePermissionAppLatest.apk"
@@ -232,7 +237,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_PRIVACY,
PICKER_ENABLED_SETTING,
- true
+ true,
)
}
}
@@ -241,9 +246,11 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
enum class PermissionState {
ALLOWED,
DENIED,
- DENIED_WITH_PREJUDICE
+ DENIED_WITH_PREJUDICE,
}
+ private val windowManagerStateHelper = WindowManagerStateHelper()
+
private val platformResources = context.createPackageContext("android", 0).resources
private val permissionToLabelResNameMap =
mapOf(
@@ -304,7 +311,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
android.Manifest.permission.READ_MEDIA_IMAGES to
"@android:string/permgrouplab_readMediaVisual",
android.Manifest.permission.READ_MEDIA_VIDEO to
- "@android:string/permgrouplab_readMediaVisual"
+ "@android:string/permgrouplab_readMediaVisual",
)
@Before
@@ -326,7 +333,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
grantRuntimePermissions,
expectSuccess,
installSource,
- false
+ false,
)
}
@@ -343,7 +350,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
reinstall,
grantRuntimePermissions,
expectSuccess,
- installSource
+ installSource,
)
val targetSdk = getTargetSdk()
@@ -390,9 +397,9 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
uiDevice.wait(
Until.hasObject(
By.textStartsWith("This app was built for an older version of Android")
- .displayId(displayId)
+ .displayId(displayId)
),
- timeoutMillis
+ timeoutMillis,
)
if (targetSdkWarningVisible) {
try {
@@ -418,8 +425,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
if (isAutomotive || isWatch) {
clickAndWaitForWindowTransition(
By.text(getPermissionControllerString("review_button_continue"))
- .displayId(displayId),
- TIMEOUT_MILLIS * 2
+ .displayId(displayId),
+ TIMEOUT_MILLIS * 2,
)
} else {
clickAndWaitForWindowTransition(
@@ -445,7 +452,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
installPackageViaSession(
apkName,
AppMetadata.createDefaultAppMetadata(),
- PACKAGE_SOURCE_STORE
+ PACKAGE_SOURCE_STORE,
)
}
@@ -453,7 +460,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
installPackageViaSession(
apkName,
AppMetadata.createDefaultAppMetadata(),
- PACKAGE_SOURCE_LOCAL_FILE
+ PACKAGE_SOURCE_LOCAL_FILE,
)
}
@@ -461,18 +468,18 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
installPackageViaSession(
apkName,
AppMetadata.createDefaultAppMetadata(),
- PACKAGE_SOURCE_DOWNLOADED_FILE
+ PACKAGE_SOURCE_DOWNLOADED_FILE,
)
}
protected fun installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
- apkName: String,
+ apkName: String
) {
installPackageViaSession(
apkName,
AppMetadata.createDefaultAppMetadata(),
PACKAGE_SOURCE_DOWNLOADED_FILE,
- allowlistedRestrictedPermissions = SessionParams.RESTRICTED_PERMISSIONS_ALL
+ allowlistedRestrictedPermissions = SessionParams.RESTRICTED_PERMISSIONS_ALL,
)
}
@@ -480,7 +487,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
installPackageViaSession(
apkName,
AppMetadata.createDefaultAppMetadata(),
- PACKAGE_SOURCE_OTHER
+ PACKAGE_SOURCE_OTHER,
)
}
@@ -509,38 +516,38 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun installPackageWithInstallSourceAndMetadataWithoutTopLevelVersion(
- apkName: String,
+ apkName: String
) {
installPackageViaSession(
apkName,
- AppMetadata.createInvalidAppMetadataWithoutTopLevelVersion()
+ AppMetadata.createInvalidAppMetadataWithoutTopLevelVersion(),
)
}
protected fun installPackageWithInstallSourceAndMetadataWithInvalidTopLevelVersion(
- apkName: String,
+ apkName: String
) {
installPackageViaSession(
apkName,
- AppMetadata.createInvalidAppMetadataWithInvalidTopLevelVersion()
+ AppMetadata.createInvalidAppMetadataWithInvalidTopLevelVersion(),
)
}
protected fun installPackageWithInstallSourceAndMetadataWithoutSafetyLabelVersion(
- apkName: String,
+ apkName: String
) {
installPackageViaSession(
apkName,
- AppMetadata.createInvalidAppMetadataWithoutSafetyLabelVersion()
+ AppMetadata.createInvalidAppMetadataWithoutSafetyLabelVersion(),
)
}
protected fun installPackageWithInstallSourceAndMetadataWithInvalidSafetyLabelVersion(
- apkName: String,
+ apkName: String
) {
installPackageViaSession(
apkName,
- AppMetadata.createInvalidAppMetadataWithInvalidSafetyLabelVersion()
+ AppMetadata.createInvalidAppMetadataWithInvalidSafetyLabelVersion(),
)
}
@@ -550,12 +557,14 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun assertPermissionRationaleActivityTitleIsVisible(expected: Boolean) {
- findView(By.res(PERMISSION_RATIONALE_ACTIVITY_TITLE_VIEW).displayId(displayId),
- expected = expected)
+ findView(
+ By.res(PERMISSION_RATIONALE_ACTIVITY_TITLE_VIEW).displayId(displayId),
+ expected = expected,
+ )
}
protected fun assertPermissionRationaleActivityDataSharingSourceSectionVisible(
- expected: Boolean,
+ expected: Boolean
) {
findView(By.res(DATA_SHARING_SOURCE_TITLE_ID).displayId(displayId), expected = expected)
findView(By.res(DATA_SHARING_SOURCE_MESSAGE_ID).displayId(displayId), expected = expected)
@@ -572,8 +581,10 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun assertPermissionRationaleActivitySettingsSectionVisible(expected: Boolean) {
- findView(By.res(PERMISSION_RATIONALE_SETTINGS_SECTION).displayId(displayId),
- expected = expected)
+ findView(
+ By.res(PERMISSION_RATIONALE_SETTINGS_SECTION).displayId(displayId),
+ expected = expected,
+ )
findView(By.res(SETTINGS_TITLE_ID).displayId(displayId), expected = expected)
findView(By.res(SETTINGS_MESSAGE_ID).displayId(displayId), expected = expected)
}
@@ -592,8 +603,10 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun assertPermissionRationaleContainerOnGrantDialogIsVisible(expected: Boolean) {
- findView(By.res(GRANT_DIALOG_PERMISSION_RATIONALE_CONTAINER_VIEW).displayId(displayId),
- expected = expected)
+ findView(
+ By.res(GRANT_DIALOG_PERMISSION_RATIONALE_CONTAINER_VIEW).displayId(displayId),
+ expected = expected,
+ )
}
protected fun clickPermissionReviewCancel() {
@@ -638,7 +651,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
block()
assertEquals(
expectedResultCode,
- future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS).resultCode
+ future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS).resultCode,
)
}
@@ -653,7 +666,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
component =
ComponentName(
APP_PACKAGE_NAME,
- "$APP_PACKAGE_NAME.RequestPermissionsActivity"
+ "$APP_PACKAGE_NAME.RequestPermissionsActivity",
)
putExtra("$APP_PACKAGE_NAME.PERMISSIONS", permissions)
addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
@@ -672,19 +685,14 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
): 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)
- }
- )
+ // The WindowManagerStateHelper#waitForValidState only supports S+
+ if (SdkLevel.isAtLeastS()) {
+ future = startActivityForFuture(*permissions, askTwice = askTwice)
+ waitForPermissionRequestActivity()
+ } else {
+ doAndWaitForWindowTransition {
+ future = startActivityForFuture(*permissions, askTwice = askTwice)
+ }
}
// Notification permission prompt is shown first, so get it out of the way
@@ -695,7 +703,46 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
} else {
block()
}
- return future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ try {
+ return future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ } catch (e: TimeoutException) {
+ val uiDump = StringBuilder()
+ UiDumpUtils.dumpNodes(uiDump)
+ Log.e(LOG_TAG, "Timed out waiting for activity result, UI dump: $uiDump")
+ throw e
+ }
+ }
+
+ fun startActivityForFuture(
+ vararg permissions: String?,
+ askTwice: Boolean,
+ ): CompletableFuture<Instrumentation.ActivityResult> =
+ 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)
+ }
+ )
+
+ /**
+ * This method waits for permission controller activity to be in a valid state, the timeout is 5
+ * seconds.
+ */
+ fun waitForPermissionRequestActivity() {
+ val requestPermissionIntent = Intent(PackageManager.ACTION_REQUEST_PERMISSIONS)
+ val componentName =
+ requestPermissionIntent.resolveActivity(context.packageManager)
+ ?: throw RuntimeException("Permission request is not handled by any activity.")
+ try {
+ windowManagerStateHelper.waitForValidState(componentName)
+ } catch (ex: Exception) {
+ // It doesn't mean a test would fail, it just meant that the test would proceed before
+ // waiting for permission request dialog. Permission request dialog should eventually
+ // come on the screen when ui-automator is trying to search for ui element.
+ Log.w(LOG_TAG, "Couldn't wait for permission request activity.", ex)
+ }
}
protected inline fun requestAppPermissionsAndAssertResult(
@@ -725,12 +772,12 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
*permissions,
askTwice = askTwice,
waitForWindowTransition = shouldWaitForWindowTransition,
- block = block
+ block = block,
)
assertEquals(
"Permission request result had unexpected resultCode:",
Activity.RESULT_OK,
- result.resultCode
+ result.resultCode,
)
val responseSize: Int =
@@ -738,14 +785,14 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
assertEquals(
"Permission request result had unexpected number of grant results:",
responseSize,
- result.resultData!!.getIntArrayExtra("$APP_PACKAGE_NAME.GRANT_RESULTS")!!.size
+ 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
+ permissions.size >= responseSize,
)
assertEquals(
"Permission request result had unexpected grant results:",
@@ -757,7 +804,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
result.resultData!!.getIntArrayExtra("$APP_PACKAGE_NAME.GRANT_RESULTS")!!.map {
it == PackageManager.PERMISSION_GRANTED
}
- )
+ ),
)
permissionAndExpectedGrantResults.forEach {
@@ -776,7 +823,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
permissionAndExpectedGrantResults,
askTwice,
waitForWindowTransition,
- block
+ block,
)
}
@@ -787,18 +834,23 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
!uiDevice.performActionAndWait(
{ block() },
Until.newWindow(),
- NEW_WINDOW_TIMEOUT_MILLIS
+ NEW_WINDOW_TIMEOUT_MILLIS,
)
if (timeoutOccurred) {
+ val uiDump = StringBuilder()
+ UiDumpUtils.dumpNodes(uiDump)
+ Log.w(LOG_TAG, "Timed out waiting for window transition, UI dump: $uiDump")
throw RuntimeException("Timed out waiting for window transition.")
}
}
protected fun findPermissionRequestAllowButton(timeoutMillis: Long = 20000) {
if (isAutomotive || isWatch) {
- waitFindObject(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT))
- .displayId(displayId), timeoutMillis)
+ waitFindObject(
+ By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)).displayId(displayId),
+ timeoutMillis,
+ )
} else {
waitFindObject(By.res(ALLOW_BUTTON).displayId(displayId), timeoutMillis)
}
@@ -809,8 +861,10 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
isHealthPermission: Boolean = false,
) {
if (isAutomotive || isWatch || isHealthPermission) {
- click(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)).displayId(displayId),
- timeoutMillis)
+ click(
+ By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)).displayId(displayId),
+ timeoutMillis,
+ )
} else {
click(By.res(ALLOW_BUTTON).displayId(displayId), timeoutMillis)
}
@@ -829,14 +883,16 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
uiDevice.wait(
Until.hasObject(
By.text(getPermissionControllerString(NOTIF_TEXT, APP_PACKAGE_NAME))
- .displayId(displayId)
+ .displayId(displayId)
),
- 1000
+ 1000,
)
if (notificationPermissionRequestVisible) {
if (isAutomotive) {
- click(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT))
- .displayId(displayId))
+ click(
+ By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT))
+ .displayId(displayId)
+ )
} else {
click(By.res(ALLOW_BUTTON).displayId(displayId))
}
@@ -852,11 +908,15 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected fun clickAllowAlwaysInSettings() {
if (isAutomotive || isTv || isWatch) {
- click(By.text(getPermissionControllerString("app_permission_button_allow_always"))
- .displayId(displayId))
+ click(
+ By.text(getPermissionControllerString("app_permission_button_allow_always"))
+ .displayId(displayId)
+ )
} else {
- click(By.res("com.android.permissioncontroller:id/allow_always_radio_button")
- .displayId(displayId))
+ click(
+ By.res("com.android.permissioncontroller:id/allow_always_radio_button")
+ .displayId(displayId)
+ )
}
}
@@ -866,11 +926,14 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected fun clicksDenyInSettings() {
if (isAutomotive || isWatch) {
- click(By.text(getPermissionControllerString("app_permission_button_deny"))
- .displayId(displayId))
+ click(
+ By.text(getPermissionControllerString("app_permission_button_deny"))
+ .displayId(displayId)
+ )
} else {
- click(By.res("com.android.permissioncontroller:id/deny_radio_button")
- .displayId(displayId))
+ click(
+ By.res("com.android.permissioncontroller:id/deny_radio_button").displayId(displayId)
+ )
}
}
@@ -878,8 +941,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
if (isAutomotive || isWatch) {
waitFindObject(
By.text(getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT))
- .displayId(displayId),
- timeoutMillis
+ .displayId(displayId),
+ timeoutMillis,
)
} else {
waitFindObject(By.res(ALLOW_FOREGROUND_BUTTON).displayId(displayId), timeoutMillis)
@@ -890,8 +953,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
if (isAutomotive || isWatch) {
click(
By.text(getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT))
- .displayId(displayId),
- timeoutMillis
+ .displayId(displayId),
+ timeoutMillis,
)
} else {
click(By.res(ALLOW_FOREGROUND_BUTTON).displayId(displayId), timeoutMillis)
@@ -969,7 +1032,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
val nextScreenNode: AccessibilityNodeInfo? =
findAccessibilityNodeInfosByTextForSurfaceView(
uiAutomation.rootInActiveWindow,
- "All the time"
+ "All the time",
)
if (nextScreenNode != null) {
clickedOnLink = true
@@ -987,9 +1050,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
if (!isWatch) {
// Check "Allow all" button is visible.
val allowAllNode =
- uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByText(
- HEALTH_PERMISSION_ALLOW_ALL_PLAIN_TEXT
- )[0]
+ uiAutomation.rootInActiveWindow
+ .findAccessibilityNodeInfosByText(HEALTH_PERMISSION_ALLOW_ALL_PLAIN_TEXT)[0]
assertTrue(allowAllNode.isVisibleToUser)
// Select "Heart rate" toggle and click "Allow" button.
@@ -1015,7 +1077,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
scrollToBottom()
clickAndWaitForWindowTransition(
By.text(getPermissionControllerString(DENY_AND_DONT_ASK_AGAIN_BUTTON_TEXT))
- .displayId(displayId)
+ .displayId(displayId)
)
} else if (isWatch) {
click(By.text(getPermissionControllerString(DENY_BUTTON_TEXT)).displayId(displayId))
@@ -1031,15 +1093,17 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
} else {
click(
By.res("com.android.permissioncontroller:id/permission_deny_dont_ask_again_button")
- .displayId(displayId)
+ .displayId(displayId)
)
}
}
protected fun clickPermissionRequestNoUpgradeAndDontAskAgainButton() {
if (isAutomotive || isWatch) {
- click(By.text(getPermissionControllerString(NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON_TEXT))
- .displayId(displayId))
+ click(
+ By.text(getPermissionControllerString(NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON_TEXT))
+ .displayId(displayId)
+ )
} else {
click(By.res(NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON).displayId(displayId))
}
@@ -1048,13 +1112,14 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected fun clickPermissionRationaleContentInAppPermission() {
clickAndWaitForWindowTransition(
By.text(getPermissionControllerString(APP_PERMISSION_RATIONALE_SUBTITLE_TEXT))
- .displayId(displayId)
+ .displayId(displayId)
)
}
protected fun clickPermissionRationaleViewInGrantDialog() {
clickAndWaitForWindowTransition(
- By.res(GRANT_DIALOG_PERMISSION_RATIONALE_CONTAINER_VIEW).displayId(displayId))
+ By.res(GRANT_DIALOG_PERMISSION_RATIONALE_CONTAINER_VIEW).displayId(displayId)
+ )
}
protected fun grantAppPermissionsByUi(vararg permissions: String) {
@@ -1074,7 +1139,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
setAppPermissionState(
*permissions,
state = PermissionState.DENIED,
- isLegacyApp = isLegacyApp
+ isLegacyApp = isLegacyApp,
)
}
@@ -1116,7 +1181,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
throw e
}
},
- TIMEOUT_MILLIS
+ TIMEOUT_MILLIS,
)
}
@@ -1129,6 +1194,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
}
}
+
protected fun navigateToIndividualPermissionSetting(
permission: String,
manuallyNavigate: Boolean = false,
@@ -1146,8 +1212,10 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
navigateToAppPermissionSettings()
val permissionLabel = getPermissionLabel(permission)
if (isWatch) {
- clickAndWaitForWindowTransition(By.text(permissionLabel).displayId(displayId),
- 40_000)
+ clickAndWaitForWindowTransition(
+ By.text(permissionLabel).displayId(displayId),
+ 40_000,
+ )
} else {
clickPermissionControllerUi(By.text(permissionLabel).displayId(displayId))
}
@@ -1245,24 +1313,28 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
// won't show an "Ask every time" message
!waitFindObject(
By.text(getPermissionControllerString("app_permission_button_deny"))
- .displayId(displayId)
+ .displayId(displayId)
)
.isChecked
} else if (isTv || isWatch) {
- !(waitFindObject(By.text(getPermissionControllerString(DENY_BUTTON_TEXT))
- .displayId(displayId))
+ !(waitFindObject(
+ By.text(getPermissionControllerString(DENY_BUTTON_TEXT))
+ .displayId(displayId)
+ )
.isChecked ||
(!isLegacyApp &&
hasAskButton(permission) &&
- waitFindObject(By.text(getPermissionControllerString(ASK_BUTTON_TEXT))
- .displayId(displayId))
+ waitFindObject(
+ By.text(getPermissionControllerString(ASK_BUTTON_TEXT))
+ .displayId(displayId)
+ )
.isChecked))
} else {
!(waitFindObject(By.res(DENY_RADIO_BUTTON).displayId(displayId)).isChecked ||
(!isLegacyApp &&
hasAskButton(permission) &&
waitFindObject(By.res(ASK_RADIO_BUTTON).displayId(displayId))
- .isChecked))
+ .isChecked))
}
var alreadyChecked = false
val button =
@@ -1274,50 +1346,55 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
PermissionState.ALLOWED ->
if (showsForegroundOnlyButton(permission)) {
By.text(
- getPermissionControllerString(
- "app_permission_button_allow_foreground"
+ getPermissionControllerString(
+ "app_permission_button_allow_foreground"
+ )
)
- ).displayId(displayId)
+ .displayId(displayId)
} else {
By.text(
- getPermissionControllerString("app_permission_button_allow")
- ).displayId(displayId)
+ getPermissionControllerString(
+ "app_permission_button_allow"
+ )
+ )
+ .displayId(displayId)
}
PermissionState.DENIED ->
By.text(getPermissionControllerString("app_permission_button_deny"))
- .displayId(displayId)
+ .displayId(displayId)
PermissionState.DENIED_WITH_PREJUDICE ->
By.text(getPermissionControllerString("app_permission_button_deny"))
- .displayId(displayId)
+ .displayId(displayId)
}
} else if (isTv || isWatch) {
when (state) {
PermissionState.ALLOWED ->
if (showsForegroundOnlyButton(permission)) {
By.text(
- getPermissionControllerString(
- ALLOW_FOREGROUND_PREFERENCE_TEXT
+ getPermissionControllerString(
+ ALLOW_FOREGROUND_PREFERENCE_TEXT
+ )
)
- ).displayId(displayId)
+ .displayId(displayId)
} else {
byAnyText(
getPermissionControllerResString(ALLOW_BUTTON_TEXT),
getPermissionControllerResString(
ALLOW_ALL_FILES_BUTTON_TEXT
- )
+ ),
)
}
PermissionState.DENIED ->
if (!isLegacyApp && hasAskButton(permission)) {
By.text(getPermissionControllerString(ASK_BUTTON_TEXT))
- .displayId(displayId)
+ .displayId(displayId)
} else {
By.text(getPermissionControllerString(DENY_BUTTON_TEXT))
- .displayId(displayId)
+ .displayId(displayId)
}
PermissionState.DENIED_WITH_PREJUDICE ->
By.text(getPermissionControllerString(DENY_BUTTON_TEXT))
- .displayId(displayId)
+ .displayId(displayId)
}
} else {
when (state) {
@@ -1335,8 +1412,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
} else {
By.res(DENY_RADIO_BUTTON).displayId(displayId)
}
- PermissionState.DENIED_WITH_PREJUDICE -> By.res(DENY_RADIO_BUTTON)
- .displayId(displayId)
+ PermissionState.DENIED_WITH_PREJUDICE ->
+ By.res(DENY_RADIO_BUTTON).displayId(displayId)
}
}
)
@@ -1353,8 +1430,11 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
if (isWatch) {
click(
By.desc(
- getPermissionControllerString("media_confirm_dialog_positive_button")
- ).displayId(displayId)
+ getPermissionControllerString(
+ "media_confirm_dialog_positive_button"
+ )
+ )
+ .displayId(displayId)
)
} else {
click(By.res(ALERT_DIALOG_OK_BUTTON).displayId(displayId))
@@ -1365,7 +1445,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
if (isWatch) {
waitFindObject(
By.text(getPermissionControllerString("old_sdk_deny_warning"))
- .displayId(displayId)
+ .displayId(displayId)
)
} else {
waitFindObject(By.res(ALERT_DIALOG_MESSAGE).displayId(displayId))
@@ -1386,7 +1466,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
resources.getIdentifier(
"com.android.permissioncontroller:string/grant_dialog_button_deny_anyway",
null,
- null
+ null,
)
val confirmText = resources.getString(confirmTextRes)
@@ -1412,8 +1492,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
android.Manifest.permission.RECORD_AUDIO,
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
- android.Manifest.permission.ACCESS_BACKGROUND_LOCATION,
- -> true
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION -> true
else -> false
}
@@ -1424,8 +1503,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
return when (permission) {
Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED,
Manifest.permission.READ_MEDIA_IMAGES,
- Manifest.permission.READ_MEDIA_VIDEO,
- -> true
+ Manifest.permission.READ_MEDIA_VIDEO -> true
else -> false
}
}
@@ -1433,8 +1511,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
private fun showsForegroundOnlyButton(permission: String): Boolean =
when (permission) {
android.Manifest.permission.CAMERA,
- android.Manifest.permission.RECORD_AUDIO,
- -> true
+ android.Manifest.permission.RECORD_AUDIO -> true
else -> false
}
@@ -1480,7 +1557,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
private fun byTextRes(textRes: Int): BySelector =
- By.text(context.getString(textRes)).displayId(displayId)
+ By.text(context.getString(textRes)).displayId(displayId)
private fun byTextStartsWithCaseInsensitive(prefix: String): BySelector =
By.text(Pattern.compile("(?i)^${Pattern.quote(prefix)}.*$")).displayId(displayId)
@@ -1490,7 +1567,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
assertTrue(
"Invalid permission check result: $checkPermissionResult",
checkPermissionResult == PackageManager.PERMISSION_GRANTED ||
- checkPermissionResult == PackageManager.PERMISSION_DENIED
+ checkPermissionResult == PackageManager.PERMISSION_DENIED,
)
if (!expectPermission && checkPermissionResult == PackageManager.PERMISSION_GRANTED) {
Assert.fail(
@@ -1513,7 +1590,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
component =
ComponentName(
APP_PACKAGE_NAME,
- "$APP_PACKAGE_NAME.CheckCalendarAccessActivity"
+ "$APP_PACKAGE_NAME.CheckCalendarAccessActivity",
)
}
)
@@ -1523,7 +1600,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
assertTrue(result.resultData!!.hasExtra("$APP_PACKAGE_NAME.HAS_ACCESS"))
assertEquals(
expectAccess,
- result.resultData!!.getBooleanExtra("$APP_PACKAGE_NAME.HAS_ACCESS", false)
+ result.resultData!!.getBooleanExtra("$APP_PACKAGE_NAME.HAS_ACCESS", false),
)
}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt
index 798b1942f..e1cb76171 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt
@@ -123,7 +123,6 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
.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
@@ -181,7 +180,6 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
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(
@@ -193,23 +191,6 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
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() {
// Skip the tests as Camera and Mic are not supported for visible background users.
@@ -225,9 +206,6 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
{ assertIndicatorsShown(false, false, false) },
AUTO_MIC_INDICATOR_DISMISSAL_TIMEOUT_MS,
)
- if (!wasEnabled) {
- setIndicatorsEnabledStateIfNeeded(false)
- }
runWithShellPermissionIdentity {
Settings.System.putLong(
context.contentResolver,
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionDecisionsTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionDecisionsTest.kt
index ae6e33b2a..e4ee52186 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/PermissionDecisionsTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionDecisionsTest.kt
@@ -53,7 +53,8 @@ class PermissionDecisionsTest : BaseUsePermissionTest() {
openPermissionDecisions()
waitFindObject(
By.hasChild(
- By.text("You gave $APP_PACKAGE_NAME access to location").displayId(displayId))
+ By.text("You gave $APP_PACKAGE_NAME access to location").displayId(displayId)
+ )
.hasChild(By.text("Today").displayId(displayId))
.displayId(displayId)
)
@@ -69,7 +70,8 @@ class PermissionDecisionsTest : BaseUsePermissionTest() {
openPermissionDecisions()
waitFindObject(
By.hasChild(
- By.text("You denied $APP_PACKAGE_NAME access to location").displayId(displayId))
+ By.text("You denied $APP_PACKAGE_NAME access to location").displayId(displayId)
+ )
.hasChild(By.text("Today").displayId(displayId))
.displayId(displayId)
)
@@ -86,11 +88,13 @@ class PermissionDecisionsTest : BaseUsePermissionTest() {
openPermissionDecisions()
assertNull(
waitFindObjectOrNull(
- By.hasChild(By.text("You denied $APP_PACKAGE_NAME access to location")
- .displayId(displayId))
+ By.hasChild(
+ By.text("You denied $APP_PACKAGE_NAME access to location")
+ .displayId(displayId)
+ )
.hasChild(By.text("Today").displayId(displayId))
.displayId(displayId),
- ASSERT_ABSENT_SELECTOR_TIMEOUT_MS
+ ASSERT_ABSENT_SELECTOR_TIMEOUT_MS,
)
)
}
@@ -105,8 +109,10 @@ class PermissionDecisionsTest : BaseUsePermissionTest() {
openPermissionDecisions()
waitFindObject(
- By.hasChild(By.text("You gave $APP_PACKAGE_NAME access to location")
- .displayId(displayId))
+ By.hasChild(
+ By.text("You gave $APP_PACKAGE_NAME access to location")
+ .displayId(displayId)
+ )
.hasChild(By.text("Today").displayId(displayId))
.displayId(displayId)
)
@@ -121,7 +127,8 @@ class PermissionDecisionsTest : BaseUsePermissionTest() {
pressBack()
waitFindObject(
By.hasChild(
- By.text("You denied $APP_PACKAGE_NAME access to location").displayId(displayId))
+ By.text("You denied $APP_PACKAGE_NAME access to location").displayId(displayId)
+ )
.hasChild(By.text("Today").displayId(displayId))
.displayId(displayId)
)
@@ -132,7 +139,7 @@ class PermissionDecisionsTest : BaseUsePermissionTest() {
SystemUtil.runWithShellPermissionIdentity {
context.startActivity(
Intent(PermissionManager.ACTION_REVIEW_PERMISSION_DECISIONS).apply {
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
)
}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt
index 751c56b3c..9a12765c0 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt
@@ -41,8 +41,7 @@ import org.junit.Test
@FlakyTest
class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
- @get:Rule
- val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+ @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@get:Rule
val deviceConfigPermissionRationaleEnabled =
@@ -50,7 +49,7 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
context,
DeviceConfig.NAMESPACE_PRIVACY,
PERMISSION_RATIONALE_ENABLED,
- true.toString()
+ true.toString(),
)
@Before
@@ -248,8 +247,10 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
}
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
- "VanillaIceCream")
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ codeName = "VanillaIceCream",
+ )
@RequiresFlagsEnabled(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
@Test
fun requestCoarseLocationPerm_hasAslInApk_packageSourceUnspecified() {
@@ -262,8 +263,10 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
}
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
- "VanillaIceCream")
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ codeName = "VanillaIceCream",
+ )
@RequiresFlagsEnabled(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
@Test
fun requestCoarseLocationPerm_hasAslInApk_packageSourceStore() {
@@ -276,8 +279,10 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
}
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
- "VanillaIceCream")
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ codeName = "VanillaIceCream",
+ )
@RequiresFlagsEnabled(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
@Test
fun requestCoarseLocationPerm_hasAslInApk_packageSourceLocalFile() {
@@ -290,8 +295,10 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
}
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
- "VanillaIceCream")
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ codeName = "VanillaIceCream",
+ )
@RequiresFlagsEnabled(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
@Test
fun requestCoarseLocationPerm_hasAslInApk_packageSourceDownloadedFile() {
@@ -304,8 +311,10 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
}
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
- "VanillaIceCream")
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ codeName = "VanillaIceCream",
+ )
@RequiresFlagsEnabled(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
@Test
fun requestCoarseLocationPerm_hasAslInApk_packageSourceOther() {
@@ -338,7 +347,6 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
requestAppPermissionsForNoResult(ACCESS_FINE_LOCATION) {
clickPermissionRationaleViewInGrantDialog()
assertPermissionRationaleDialogIsVisible(true)
- assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
}
}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
index c2b5447dd..17cef0e31 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
@@ -16,6 +16,7 @@
package android.permissionui.cts
+import android.content.pm.PackageManager
import android.health.connect.HealthPermissions
import android.os.Build
import android.permission.flags.Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED
@@ -25,6 +26,7 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.filters.FlakyTest
import androidx.test.filters.SdkSuppress
import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -33,6 +35,12 @@ import org.junit.Test
@FlakyTest
class PermissionSplitTest : BaseUsePermissionTest() {
+ companion object {
+ @JvmStatic
+ private val supportHeartrate =
+ packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_HEART_RATE)
+ }
+
@Rule @JvmField val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@Before
@@ -146,6 +154,7 @@ class PermissionSplitTest : BaseUsePermissionTest() {
@RequiresFlagsEnabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
@Test
fun testBodySensorSplitOnBaklava_splitToReadHeartRate() {
+ assumeTrue(supportHeartrate)
installPackage(APP_APK_PATH_30_WITH_BACKGROUND)
assertAppHasPermission(android.Manifest.permission.BODY_SENSORS, false)
assertAppHasPermission(HealthPermissions.READ_HEART_RATE, false)
@@ -195,6 +204,7 @@ class PermissionSplitTest : BaseUsePermissionTest() {
}
private fun testBodySensorPermissionSplitToBodySensorsBackground(expectSplit: Boolean) {
+ assumeTrue(supportHeartrate)
assertAppHasPermission(android.Manifest.permission.BODY_SENSORS, false)
assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, false)
diff --git a/tests/cts/role/src/android/app/role/cts/RoleManagerSecurityTest.kt b/tests/cts/role/src/android/app/role/cts/RoleManagerSecurityTest.kt
new file mode 100644
index 000000000..59a8c21b2
--- /dev/null
+++ b/tests/cts/role/src/android/app/role/cts/RoleManagerSecurityTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.content.Context
+import android.os.Build
+import android.os.Process
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests {@link RoleManager} security fixes. */
+@RunWith(AndroidJUnit4::class)
+class RoleManagerSecurityTest {
+ private var browserRoleHolder: String? = null
+
+ @Before
+ fun setUp() {
+ saveBrowserRoleHolder()
+ }
+
+ @After
+ fun tearDown() {
+ restoreBrowserRoleHolder()
+ }
+
+ private fun saveBrowserRoleHolder() {
+ val roleHolders: List<String> = getRoleHolders(RoleManager.ROLE_BROWSER, roleManager)
+ browserRoleHolder = if (roleHolders.isNotEmpty()) roleHolders[0] else null
+ }
+
+ private fun restoreBrowserRoleHolder() {
+ browserRoleHolder?.let { packageName ->
+ addRoleHolderAsUser(
+ RoleManager.ROLE_BROWSER,
+ packageName,
+ Process.myUserHandle(),
+ true,
+ roleManager,
+ context.mainExecutor,
+ )
+ }
+ }
+
+ private fun getRoleHolders(roleName: String, roleManager: RoleManager): List<String> {
+ return SystemUtil.callWithShellPermissionIdentity { roleManager.getRoleHolders(roleName) }
+ }
+
+ private fun addRoleHolderAsUser(
+ roleName: String,
+ packageName: String,
+ userHandle: UserHandle,
+ expectSuccess: Boolean,
+ roleManager: RoleManager,
+ executor: Executor,
+ ) {
+ val future = CallbackFuture()
+ SystemUtil.runWithShellPermissionIdentity {
+ roleManager.addRoleHolderAsUser(roleName, packageName, 0, userHandle, executor, future)
+ }
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isEqualTo(expectSuccess)
+ }
+
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.S,
+ maxSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ )
+ @Test
+ fun cannotGetDefaultApplicationOnOlderSdk() {
+ assumeTrue(roleManager.isRoleAvailable(RoleManager.ROLE_BROWSER))
+ try {
+ roleManager.getDefaultApplication(RoleManager.ROLE_BROWSER)
+ } catch (e: NoSuchMethodError) {
+ // Expected when permission module hasn't been updated
+ } catch (e: IllegalStateException) {
+ // Expected when permission module has been updated, and SDK 33 or below
+ } catch (e: Throwable) {
+ fail("Missing patch for cveBugId = [379362792]")
+ }
+ }
+
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.S,
+ maxSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ )
+ @Test
+ fun cannotSetDefaultApplicationOnOlderSdk() {
+ assumeTrue(roleManager.isRoleAvailable(RoleManager.ROLE_BROWSER))
+ val future = CallbackFuture()
+ try {
+ roleManager.setDefaultApplication(
+ RoleManager.ROLE_BROWSER,
+ APP_PACKAGE_NAME,
+ 0,
+ context.mainExecutor,
+ future,
+ )
+ future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ } catch (e: NoSuchMethodError) {
+ // Expected when permission module hasn't been updated
+ } catch (e: IllegalStateException) {
+ // Expected when permission module has been updated, and SDK 33 or below
+ } catch (e: Throwable) {
+ fail("Missing patch for cveBugId = [379362792]")
+ }
+ }
+
+ private class CallbackFuture : CompletableFuture<Boolean?>(), Consumer<Boolean?> {
+ override fun accept(successful: Boolean?) {
+ complete(successful)
+ }
+ }
+
+ companion object {
+ private const val TIMEOUT_MILLIS: Long = (15 * 1000).toLong()
+ private const val APP_PACKAGE_NAME: String = "android.app.role.cts.app"
+
+ private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
+ private val roleManager: RoleManager = context.getSystemService(RoleManager::class.java)
+ }
+}
diff --git a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
index 5e61c66be..e2310e7fb 100644
--- a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
+++ b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
@@ -105,7 +105,6 @@ public class RoleManagerTest {
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 PROFILE_GROUP_EXCLUSIVE_ROLE_NAME =
RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY;
private static final String ROLE_SHORT_LABEL = "Browser app";
@@ -288,8 +287,11 @@ public class RoleManagerTest {
@RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
@FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
public void requestRoleThenBlockRequestRoleDialogByRestrictedSettingDialog() throws Exception {
- assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
assumeFalse(sIsWatch || sIsAutomotive || sIsTelevision);
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+ assumeFalse(callWithShellPermissionIdentity(
+ () -> getRoleHolders(RoleManager.ROLE_SMS)).contains(APP_PACKAGE_NAME));
+
// TODO: b/388960315 - Remove wait after addressing race condition
runWithShellPermissionIdentity(
() -> waitForEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME,
@@ -298,7 +300,7 @@ public class RoleManagerTest {
() -> setEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME,
AppOpsManager.MODE_ERRORED));
- requestRole(ROLE_SMS_NAME);
+ requestRole(RoleManager.ROLE_SMS);
waitFindObject(ENHANCED_CONFIRMATION_DIALOG_SELECTOR, TIMEOUT_MILLIS);
pressBack();
@@ -829,6 +831,13 @@ public class RoleManagerTest {
@FlakyTest
@Test
public void openDefaultAppListThenIsNotDefaultAppInList() throws Exception {
+ assumeFalse(callWithShellPermissionIdentity(
+ () -> getRoleHolders(RoleManager.ROLE_BROWSER)).contains(APP_PACKAGE_NAME));
+ assumeFalse(callWithShellPermissionIdentity(
+ () -> getRoleHolders(RoleManager.ROLE_DIALER)).contains(APP_PACKAGE_NAME));
+ assumeFalse(callWithShellPermissionIdentity(
+ () -> getRoleHolders(RoleManager.ROLE_SMS)).contains(APP_PACKAGE_NAME));
+
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));
@@ -869,7 +878,6 @@ public class RoleManagerTest {
}
@Test
- @RequiresFlagsEnabled(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE)
@EnsureHasPrivateProfile(installInstrumentedApp = OptionalBoolean.TRUE)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
codeName = "VanillaIceCream")
@@ -885,6 +893,9 @@ public class RoleManagerTest {
assertThat(privateProfile).isNotNull();
installPackage(APP_APK_PATH, privateProfile);
installPackage(APP_CLONE_APK_PATH, privateProfile);
+
+ UiAutomatorUtils.getUiDevice().waitForIdle(30 * 1000);
+
addRoleHolderAsUser(ROLE_NAME, APP_CLONE_PACKAGE_NAME, privateProfile);
sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
@@ -1193,6 +1204,8 @@ public class RoleManagerTest {
@Test
public void removeSmsRoleHolderThenPermissionIsRevoked() throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+ assumeFalse(callWithShellPermissionIdentity(
+ () -> getRoleHolders(RoleManager.ROLE_SMS)).contains(APP_PACKAGE_NAME));
String smsRoleHolder = getRoleHolders(RoleManager.ROLE_SMS).get(0);
addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
diff --git a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt
index 98aa5fbf1..3e24d5025 100644
--- a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt
+++ b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt
@@ -27,10 +27,12 @@ import android.os.Process
import android.os.UserHandle
import android.os.UserManager.DISALLOW_CONFIG_DEFAULT_APPS
import android.provider.Settings
+import android.util.Log
import android.util.Pair
import androidx.test.filters.SdkSuppress
import androidx.test.rule.ActivityTestRule
import androidx.test.uiautomator.By
+import com.android.bedstead.enterprise.annotations.EnsureDoesNotHaveUserRestriction
import com.android.bedstead.enterprise.annotations.EnsureHasNoWorkProfile
import com.android.bedstead.enterprise.annotations.EnsureHasUserRestriction
import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile
@@ -53,6 +55,7 @@ import com.android.bedstead.nene.TestApis.context
import com.android.bedstead.nene.TestApis.permissions
import com.android.bedstead.nene.TestApis.users
import com.android.bedstead.nene.types.OptionalBoolean
+import com.android.bedstead.nene.userrestrictions.CommonUserRestrictions.DISALLOW_ADD_MANAGED_PROFILE
import com.android.bedstead.nene.users.UserReference
import com.android.bedstead.nene.users.UserType
import com.android.bedstead.permissions.CommonPermissions.INTERACT_ACROSS_USERS_FULL
@@ -69,7 +72,6 @@ import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject
import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObjectOrNull
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
-import java.util.Objects
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
import java.util.function.Consumer
@@ -94,16 +96,24 @@ class RoleManagerMultiUserTest {
ActivityTestRule(WaitForResultActivity::class.java)
@Before
- @Throws(java.lang.Exception::class)
fun setUp() {
assumeTrue(RoleManagerUtil.isCddCompliantScreenSize())
installAppForAllUsers()
+
+ // If "none" selected in test, ensure we re-enable fallback for other test runs
+ permissions().withPermission(MANAGE_ROLE_HOLDERS, INTERACT_ACROSS_USERS_FULL).use {
+ setRoleFallbackEnabledForAllUsers()
+ }
}
@After
- @Throws(java.lang.Exception::class)
fun tearDown() {
uninstallAppForAllUsers()
+
+ // If "none" selected in test, ensure we re-enable fallback for other test runs
+ permissions().withPermission(MANAGE_ROLE_HOLDERS, INTERACT_ACROSS_USERS_FULL).use {
+ setRoleFallbackEnabledForAllUsers()
+ }
}
@RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
@@ -315,7 +325,7 @@ class RoleManagerMultiUserTest {
// initialUser needs to be not the targetUser
val targetActiveUser = users().current().userHandle()
val initialUser =
- if (Objects.equals(targetActiveUser, deviceState.initialUser().userHandle())) {
+ if (targetActiveUser == deviceState.initialUser().userHandle()) {
deviceState.workProfile().userHandle()
} else {
deviceState.initialUser().userHandle()
@@ -378,7 +388,7 @@ class RoleManagerMultiUserTest {
val initialUser = deviceState.workProfile().userHandle()
// setActiveUserForRole and getActiveUserForRole is used to ensure initial active users
// state and requires INTERACT_ACROSS_USERS_FULL
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use {
roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
.isEqualTo(initialUser)
@@ -406,7 +416,7 @@ class RoleManagerMultiUserTest {
// getActiveUserForRole is used to ensure addRoleHolderAsUser didn't set active user, and
// requires INTERACT_ACROSS_USERS_FULL
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use {
assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
.isEqualTo(initialUser)
}
@@ -471,6 +481,47 @@ class RoleManagerMultiUserTest {
assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
}
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun addRoleHolderAsUserReenablesFallbackOnProfileParent() {
+ // Set other user as active
+ val initialUserReference = deviceState.initialUser()
+ val initialUser = initialUserReference.userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val profileParentRoleManager = getRoleManagerForUser(initialUserReference)
+ profileParentRoleManager.setRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, false)
+ assertThat(
+ profileParentRoleManager.isRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ )
+ .isFalse()
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(
+ profileParentRoleManager.isRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ )
+ .isTrue()
+ }
+
@RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
@EnsureHasPermission(MANAGE_DEFAULT_APPLICATIONS)
@EnsureDoesNotHavePermission(INTERACT_ACROSS_USERS_FULL)
@@ -483,7 +534,7 @@ class RoleManagerMultiUserTest {
val initialUser = deviceState.workProfile().userHandle()
// setActiveUserForRole and getActiveUserForRole is used to ensure initial active users
// state and requires INTERACT_ACROSS_USERS_FULL
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use {
roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
.isEqualTo(initialUser)
@@ -503,7 +554,7 @@ class RoleManagerMultiUserTest {
// getActiveUserForRole is used to ensure setDefaultApplication didn't set active user,
// and requires INTERACT_ACROSS_USERS_FULL
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use {
assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
.isEqualTo(initialUser)
}
@@ -565,11 +616,56 @@ class RoleManagerMultiUserTest {
eventually { assertExpectedProfileHasRoleUsingGetDefaultApplication(targetActiveUser) }
}
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasPermission(
+ INTERACT_ACROSS_USERS_FULL,
+ MANAGE_DEFAULT_APPLICATIONS,
+ MANAGE_ROLE_HOLDERS,
+ )
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun setDefaultApplicationReenablesFallbackOnProfileParent() {
+ // Set other user as active
+ val initialUserReference = deviceState.initialUser()
+ val initialUser = initialUserReference.userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val profileParentRoleManager = getRoleManagerForUser(initialUserReference)
+ profileParentRoleManager.setRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, false)
+ assertThat(
+ profileParentRoleManager.isRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ )
+ .isFalse()
+
+ val future = CallbackFuture()
+ getRoleManagerForUser(deviceState.workProfile())
+ .setDefaultApplication(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(
+ profileParentRoleManager.isRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ )
+ .isTrue()
+ }
+
@RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
@EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
@EnsureCanAddUser
@EnsureHasNoWorkProfile
@RequireRunOnPrimaryUser
+ @EnsureDoesNotHaveUserRestriction(DISALLOW_ADD_MANAGED_PROFILE)
@Test
@Throws(Exception::class)
fun ensureActiveUserSetToParentOnUserRemoved() {
@@ -1279,7 +1375,7 @@ class RoleManagerMultiUserTest {
// setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
// Set test default role holder. Ensures fallbacks to a default holder
setDefaultHoldersForTestForAllUsers()
setRoleVisibleForTestForAllUsers()
@@ -1306,7 +1402,7 @@ class RoleManagerMultiUserTest {
// getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
// MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
// holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
.isEqualTo(targetActiveUser)
assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
@@ -1315,7 +1411,7 @@ class RoleManagerMultiUserTest {
// clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
clearDefaultHoldersForTestForAllUsers()
clearRoleVisibleForTestForAllUsers()
}
@@ -1332,7 +1428,7 @@ class RoleManagerMultiUserTest {
// setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
// Set test default role holder. Ensures fallbacks to a default holder
setDefaultHoldersForTestForAllUsers()
setRoleVisibleForTestForAllUsers()
@@ -1359,7 +1455,7 @@ class RoleManagerMultiUserTest {
// getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
// MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
// holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
.isEqualTo(targetActiveUser)
assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
@@ -1368,7 +1464,7 @@ class RoleManagerMultiUserTest {
// clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
clearDefaultHoldersForTestForAllUsers()
clearRoleVisibleForTestForAllUsers()
}
@@ -1385,7 +1481,7 @@ class RoleManagerMultiUserTest {
// setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
// Set test default role holder. Ensures fallbacks to a default holder
setDefaultHoldersForTestForAllUsers()
setRoleVisibleForTestForAllUsers()
@@ -1410,7 +1506,7 @@ class RoleManagerMultiUserTest {
// getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
// MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
// holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
.isEqualTo(deviceState.initialUser().userHandle())
assertNoRoleHoldersUsingGetRoleHoldersAsUser()
@@ -1419,7 +1515,7 @@ class RoleManagerMultiUserTest {
// clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
clearDefaultHoldersForTestForAllUsers()
clearRoleVisibleForTestForAllUsers()
}
@@ -1436,7 +1532,7 @@ class RoleManagerMultiUserTest {
// setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
// Set test default role holder. Ensures fallbacks to a default holder
setDefaultHoldersForTestForAllUsers()
setRoleVisibleForTestForAllUsers()
@@ -1463,7 +1559,7 @@ class RoleManagerMultiUserTest {
// getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
// MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
// holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
.isEqualTo(targetActiveUser)
assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
@@ -1472,7 +1568,7 @@ class RoleManagerMultiUserTest {
// clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
clearDefaultHoldersForTestForAllUsers()
clearRoleVisibleForTestForAllUsers()
}
@@ -1489,7 +1585,7 @@ class RoleManagerMultiUserTest {
// setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
// Set test default role holder. Ensures fallbacks to a default holder
setDefaultHoldersForTestForAllUsers()
setRoleVisibleForTestForAllUsers()
@@ -1516,7 +1612,7 @@ class RoleManagerMultiUserTest {
// getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
// MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
// holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
.isEqualTo(targetActiveUser)
assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
@@ -1525,7 +1621,7 @@ class RoleManagerMultiUserTest {
// clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
clearDefaultHoldersForTestForAllUsers()
clearRoleVisibleForTestForAllUsers()
}
@@ -1542,7 +1638,7 @@ class RoleManagerMultiUserTest {
// setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
// Set test default role holder. Ensures fallbacks to a default holder
setDefaultHoldersForTestForAllUsers()
setRoleVisibleForTestForAllUsers()
@@ -1567,7 +1663,7 @@ class RoleManagerMultiUserTest {
// getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
// MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
// holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
.isEqualTo(deviceState.initialUser().userHandle())
assertNoRoleHoldersUsingGetRoleHoldersAsUser()
@@ -1576,7 +1672,7 @@ class RoleManagerMultiUserTest {
// clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
clearDefaultHoldersForTestForAllUsers()
clearRoleVisibleForTestForAllUsers()
}
@@ -1645,7 +1741,12 @@ class RoleManagerMultiUserTest {
getUiDevice().waitForIdle()
// CollapsingToolbar title can't be found by text, so using description instead.
- assertNull(waitFindObjectOrNull(By.desc(PROFILE_GROUP_EXCLUSIVITY_ROLE_LABEL)))
+ assertNull(
+ waitFindObjectOrNull(
+ By.desc(PROFILE_GROUP_EXCLUSIVITY_ROLE_LABEL),
+ IDLE_TIMEOUT_MILLIS,
+ )
+ )
pressBack()
pressBack()
@@ -1681,7 +1782,12 @@ class RoleManagerMultiUserTest {
getUiDevice().waitForIdle()
// CollapsingToolbar title can't be found by text, so using description instead.
- assertNull(waitFindObjectOrNull(By.desc(PROFILE_GROUP_EXCLUSIVITY_ROLE_LABEL)))
+ assertNull(
+ waitFindObjectOrNull(
+ By.desc(PROFILE_GROUP_EXCLUSIVITY_ROLE_LABEL),
+ IDLE_TIMEOUT_MILLIS,
+ )
+ )
pressBack()
pressBack()
@@ -1816,7 +1922,8 @@ class RoleManagerMultiUserTest {
if (isWatch) {
assertNull(
waitFindObjectOrNull(
- By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel))
+ By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel)),
+ IDLE_TIMEOUT_MILLIS,
)
)
} else {
@@ -1824,7 +1931,8 @@ class RoleManagerMultiUserTest {
waitFindObjectOrNull(
By.clickable(true)
.hasDescendant(By.checkable(true).checked(true))
- .hasDescendant(By.text(targetAppLabel))
+ .hasDescendant(By.text(targetAppLabel)),
+ IDLE_TIMEOUT_MILLIS,
)
)
}
@@ -1895,7 +2003,8 @@ class RoleManagerMultiUserTest {
if (isWatch) {
assertNull(
waitFindObjectOrNull(
- By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel))
+ By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel)),
+ IDLE_TIMEOUT_MILLIS,
)
)
} else {
@@ -1903,7 +2012,8 @@ class RoleManagerMultiUserTest {
waitFindObjectOrNull(
By.clickable(true)
.hasDescendant(By.checkable(true).checked(true))
- .hasDescendant(By.text(targetAppLabel))
+ .hasDescendant(By.text(targetAppLabel)),
+ IDLE_TIMEOUT_MILLIS,
)
)
}
@@ -1934,7 +2044,7 @@ class RoleManagerMultiUserTest {
// setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
// Set test default role holder. Ensures fallbacks to a default holder
setDefaultHoldersForTestForAllUsers()
setRoleVisibleForTestForAllUsers()
@@ -1961,7 +2071,7 @@ class RoleManagerMultiUserTest {
// getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
// MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
// holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
.isEqualTo(targetActiveUser)
assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
@@ -1970,7 +2080,7 @@ class RoleManagerMultiUserTest {
// clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
clearDefaultHoldersForTestForAllUsers()
clearRoleVisibleForTestForAllUsers()
}
@@ -1991,7 +2101,7 @@ class RoleManagerMultiUserTest {
// setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
// Set test default role holder. Ensures fallbacks to a default holder
setDefaultHoldersForTestForAllUsers()
setRoleVisibleForTestForAllUsers()
@@ -2016,7 +2126,7 @@ class RoleManagerMultiUserTest {
// clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
clearDefaultHoldersForTestForAllUsers()
clearRoleVisibleForTestForAllUsers()
}
@@ -2037,7 +2147,7 @@ class RoleManagerMultiUserTest {
// setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
// Set test default role holder. Ensures fallbacks to a default holder
setDefaultHoldersForTestForAllUsers()
setRoleVisibleForTestForAllUsers()
@@ -2062,17 +2172,15 @@ class RoleManagerMultiUserTest {
// clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
// INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
// role active user and role holder states
- permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use { _ ->
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
clearDefaultHoldersForTestForAllUsers()
clearRoleVisibleForTestForAllUsers()
}
}
}
- @Throws(java.lang.Exception::class)
private fun installAppForAllUsers() {
SystemUtil.runShellCommandOrThrow("pm install -r --user all $APP_APK_PATH")
- SystemUtil.waitForBroadcasts()
}
private fun uninstallAppForAllUsers() {
@@ -2099,8 +2207,7 @@ class RoleManagerMultiUserTest {
}
val result: Pair<Int, Intent?> = clickButtonAndWaitForResult(allow)
val expectedResult =
- if (allow && Objects.equals(targetActiveUser, users().instrumented().userHandle()))
- Activity.RESULT_OK
+ if (allow && targetActiveUser == users().instrumented().userHandle()) Activity.RESULT_OK
else Activity.RESULT_CANCELED
assertThat(result.first).isEqualTo(expectedResult)
@@ -2118,7 +2225,8 @@ class RoleManagerMultiUserTest {
}
private fun roleRequestNotShown() {
- val requestRoleItem = waitFindObjectOrNull(By.textStartsWith(APP_LABEL))
+ val requestRoleItem =
+ waitFindObjectOrNull(By.textStartsWith(APP_LABEL), IDLE_TIMEOUT_MILLIS)
assertNull(requestRoleItem)
val result: Pair<Int, Intent?> = waitForResult()
@@ -2148,9 +2256,9 @@ class RoleManagerMultiUserTest {
) {
for (userReference in users().profileGroup(deviceState.initialUser())) {
val user = userReference.userHandle()
- if (Objects.equals(user, expectedActiveUser)) {
- val roleHolders =
- roleManager.getRoleHoldersAsUser(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, user)
+ val roleHolders =
+ roleManager.getRoleHoldersAsUser(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, user)
+ if (user == expectedActiveUser) {
assertWithMessage(
"Expected user ${user.identifier} to have a role holder for " +
" $PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME"
@@ -2169,9 +2277,7 @@ class RoleManagerMultiUserTest {
"Expected user ${user.identifier} to not have a role holder for" +
" $PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME"
)
- .that(
- roleManager.getRoleHoldersAsUser(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, user)
- )
+ .that(roleHolders)
.isEmpty()
}
}
@@ -2183,7 +2289,7 @@ class RoleManagerMultiUserTest {
for (userReference in users().profileGroup(deviceState.initialUser())) {
val userRoleManager = getRoleManagerForUser(userReference)
val user = userReference.userHandle()
- if (Objects.equals(user, expectedActiveUser)) {
+ if (user == expectedActiveUser) {
assertWithMessage("Expected default application for user ${user.identifier}")
.that(
userRoleManager.getDefaultApplication(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
@@ -2203,7 +2309,7 @@ class RoleManagerMultiUserTest {
private fun setDefaultHoldersForTestForAllUsers() {
// Set test default role holder. Ensures fallbacks to a default holder
for (userRoleManager in
- users().profileGroup(deviceState.initialUser()).map { getRoleManagerForUser(it) }) {
+ users().profileGroup(users().current()).map { getRoleManagerForUser(it) }) {
userRoleManager.setDefaultHoldersForTest(
PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
listOf(APP_PACKAGE_NAME),
@@ -2214,7 +2320,7 @@ class RoleManagerMultiUserTest {
private fun clearDefaultHoldersForTestForAllUsers() {
// Set test default role holder. Ensures fallbacks to a default holder
for (userRoleManager in
- users().profileGroup(deviceState.initialUser()).map { getRoleManagerForUser(it) }) {
+ users().profileGroup(users().current()).map { getRoleManagerForUser(it) }) {
userRoleManager.setDefaultHoldersForTest(
PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
emptyList(),
@@ -2225,7 +2331,7 @@ class RoleManagerMultiUserTest {
private fun setRoleVisibleForTestForAllUsers() {
// Set test default role holder. Ensures fallbacks to a default holder
for (userRoleManager in
- users().profileGroup(deviceState.initialUser()).map { getRoleManagerForUser(it) }) {
+ users().profileGroup(users().current()).map { getRoleManagerForUser(it) }) {
userRoleManager.setRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, true)
}
}
@@ -2233,11 +2339,28 @@ class RoleManagerMultiUserTest {
private fun clearRoleVisibleForTestForAllUsers() {
// Set test default role holder. Ensures fallbacks to a default holder
for (userRoleManager in
- users().profileGroup(deviceState.initialUser()).map { getRoleManagerForUser(it) }) {
+ users().profileGroup(users().current()).map { getRoleManagerForUser(it) }) {
userRoleManager.setRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, false)
}
}
+ private fun setRoleFallbackEnabledForAllUsers() {
+ for (userReference in users().profileGroup(users().current())) {
+ try {
+ val userRoleManager = getRoleManagerForUser(userReference)
+ userRoleManager.setRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, true)
+ } catch (e: Exception) {
+ Log.w(
+ LOG_TAG,
+ "Encountered error setting fallback enabled for" +
+ " $PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME@" +
+ "${userReference.userHandle().identifier}",
+ e,
+ )
+ }
+ }
+ }
+
private fun getRoleManagerForUser(user: UserReference): RoleManager {
val userContext = context().androidContextAsUser(user)
return userContext.getSystemService(RoleManager::class.java)
@@ -2250,7 +2373,10 @@ class RoleManagerMultiUserTest {
}
companion object {
+ private val LOG_TAG = RoleManagerMultiUserTest::class.java.simpleName
+
private const val TIMEOUT_MILLIS: Long = (15 * 1000).toLong()
+ private const val IDLE_TIMEOUT_MILLIS: Long = (2 * 1000).toLong()
private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME =
RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY
private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_LABEL =
diff --git a/tests/cts/safetycenter/AndroidTest.xml b/tests/cts/safetycenter/AndroidTest.xml
index 6d8c3069c..ed161f0b6 100644
--- a/tests/cts/safetycenter/AndroidTest.xml
+++ b/tests/cts/safetycenter/AndroidTest.xml
@@ -47,6 +47,10 @@
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Belt-and-braces attempt to dismiss keyguard. Tradefed should have already done this
+ for us, but this is a precaution in an attempt to mitigate b/379620557. -->
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
<!-- 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>
diff --git a/tests/functional/safetycenter/multiusers/AndroidTest.xml b/tests/functional/safetycenter/multiusers/AndroidTest.xml
index 20032357a..bfb7fdbf2 100644
--- a/tests/functional/safetycenter/multiusers/AndroidTest.xml
+++ b/tests/functional/safetycenter/multiusers/AndroidTest.xml
@@ -47,6 +47,10 @@
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Belt-and-braces attempt to dismiss keyguard. Tradefed should have already done this
+ for us, but this is a precaution in an attempt to mitigate b/379620557. -->
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
<!-- 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>
diff --git a/tests/functional/safetycenter/safetycenteractivity/Android.bp b/tests/functional/safetycenter/safetycenteractivity/Android.bp
index ea5f9f286..2346a0d5f 100644
--- a/tests/functional/safetycenter/safetycenteractivity/Android.bp
+++ b/tests/functional/safetycenter/safetycenteractivity/Android.bp
@@ -29,6 +29,7 @@ android_test {
"src/**/*.kt",
],
static_libs: [
+ "aconfig_settingstheme_exported_flags_java_lib",
"androidx.test.rules",
"androidx.test.ext.junit",
"compatibility-device-preconditions",
diff --git a/tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml b/tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml
index a1826653f..ee79dcd2a 100644
--- a/tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml
+++ b/tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml
@@ -47,6 +47,10 @@
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Belt-and-braces attempt to dismiss keyguard. Tradefed should have already done this
+ for us, but this is a precaution in an attempt to mitigate b/379620557. -->
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
<!-- 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>
diff --git a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
index 09a32f058..fb577e8f6 100644
--- a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
+++ b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
@@ -21,6 +21,9 @@ import android.os.Build
import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.Bundle
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ID
import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ISSUE_ID
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
@@ -74,6 +77,7 @@ import com.android.safetycenter.testing.UiTestHelper.waitPageTitleDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceDataDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueNotDisplayed
+import com.android.settingslib.widget.theme.flags.Flags as SettingsThemeFlags
import java.util.regex.Pattern
import org.junit.After
import org.junit.Assume.assumeFalse
@@ -95,6 +99,8 @@ class SafetyCenterActivityTest {
@get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
@get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule()
@get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule()
+ @get:Rule(order = 5)
+ val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@After
fun clearDataAfterTest() {
@@ -567,6 +573,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/398188361 - Update this for expressive theme
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun issueCard_noAttribution_hasProperContentDescriptions() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceNoGroupTitleConfig)
@@ -581,6 +589,8 @@ class SafetyCenterActivityTest {
@Test
@SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
+ // TODO: b/398188361 - Update this for expressive theme
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun issueCard_withAttribution_hasProperContentDescriptions() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -693,7 +703,7 @@ class SafetyCenterActivityTest {
@Test
fun issueCard_resolveIssue_successConfirmationShown() {
- SafetyCenterFlags.hideResolvedIssueUiTransitionDelay = TIMEOUT_LONG
+ SafetyCenterFlags.setHideResolvedIssueUiTransitionDelay(context, TIMEOUT_LONG)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
// Set the initial data for the source
@@ -829,7 +839,7 @@ class SafetyCenterActivityTest {
@Test
fun issueCard_resolveIssue_noSuccessMessage_noResolutionUiShown_issueDismisses() {
- SafetyCenterFlags.hideResolvedIssueUiTransitionDelay = TIMEOUT_LONG
+ SafetyCenterFlags.setHideResolvedIssueUiTransitionDelay(context, TIMEOUT_LONG)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
// Set the initial data for the source
@@ -954,6 +964,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun launchActivity_fromQuickSettings_issuesExpanded() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -978,6 +990,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun launchActivity_fromNotification_targetIssueAlreadyFirstIssue() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1003,6 +1017,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun launchActivity_fromNotification_targetIssueSamePriorityAsFirstIssue_reorderedFirstIssue() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1028,6 +1044,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun launchActivity_fromNotification_targetLowerPriorityAsFirstIssue_reorderedSecondIssue() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1052,6 +1070,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun launchActivity_fromNotification_targetIssueNotFound() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1091,6 +1111,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun moreIssuesCard_moreIssuesCardShown_additionalIssueCardsCollapsed() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1113,6 +1135,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun moreIssuesCard_expandAdditionalIssueCards() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1139,6 +1163,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun moreIssuesCard_rotation_cardsStillExpanded() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1173,6 +1199,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun moreIssuesCard_withThreeIssues_showsTopIssuesAndMoreIssuesCard() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1197,6 +1225,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun moreIssuesCard_twoIssuesAlreadyShown_expandAdditionalIssueCards() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
diff --git a/tests/functional/safetycenter/singleuser/AndroidTest.xml b/tests/functional/safetycenter/singleuser/AndroidTest.xml
index af040eb6f..f778ca93e 100644
--- a/tests/functional/safetycenter/singleuser/AndroidTest.xml
+++ b/tests/functional/safetycenter/singleuser/AndroidTest.xml
@@ -47,8 +47,8 @@
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
- <!-- TODO(b/379928062): Ensure device not on lockscreen. Reassess when keyguard bug is
- closed -->
+ <!-- Belt-and-braces attempt to dismiss keyguard. Tradefed should have already done this
+ for us, but this is a precaution in an attempt to mitigate b/379620557. -->
<option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
<option name="run-command" value="wm dismiss-keyguard" />
<!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
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 64db7d47a..f5d230deb 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
@@ -75,6 +75,7 @@ import com.android.safetycenter.internaldata.SafetyCenterIds
import com.android.safetycenter.resources.SafetyCenterResourcesApk
import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG
import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT
+import com.android.safetycenter.testing.Coroutines.assertWithTimeout
import com.android.safetycenter.testing.Coroutines.waitForWithTimeout
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.dismissSafetyCenterIssueWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetyCenterConfigWithPermission
@@ -2356,18 +2357,14 @@ class SafetyCenterManagerTest {
groupId = MULTIPLE_SOURCES_GROUP_ID_2,
)
)
- waitForWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) {
- val hasResurfaced =
- safetyCenterManager
- .getSafetyCenterDataWithPermission()
- .issues
- .contains(
- safetyCenterTestData.safetyCenterIssueCritical(
- SOURCE_ID_5,
- groupId = MULTIPLE_SOURCES_GROUP_ID_2,
- )
+ assertWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) {
+ assertThat(safetyCenterManager.getSafetyCenterDataWithPermission().issues)
+ .contains(
+ safetyCenterTestData.safetyCenterIssueCritical(
+ SOURCE_ID_5,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2,
)
- hasResurfaced
+ )
}
}
@@ -3986,9 +3983,9 @@ class SafetyCenterManagerTest {
companion object {
private val RESURFACE_DELAY = Duration.ofMillis(500)
- // Wait 3 times the RESURFACE_DELAY before asserting whether an issue has or has not
+ // Wait 5 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)
+ private val RESURFACE_TIMEOUT = RESURFACE_DELAY.multipliedBy(5)
// 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)
diff --git a/tests/functional/safetycenter/subpages/AndroidTest.xml b/tests/functional/safetycenter/subpages/AndroidTest.xml
index c3245e9d7..ac493841f 100644
--- a/tests/functional/safetycenter/subpages/AndroidTest.xml
+++ b/tests/functional/safetycenter/subpages/AndroidTest.xml
@@ -47,6 +47,10 @@
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Belt-and-braces attempt to dismiss keyguard. Tradefed should have already done this
+ for us, but this is a precaution in an attempt to mitigate b/379620557. -->
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
<!-- 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>
diff --git a/tests/hostside/safetycenter/AndroidTest.xml b/tests/hostside/safetycenter/AndroidTest.xml
index a28b70c3c..41f0bcc40 100644
--- a/tests/hostside/safetycenter/AndroidTest.xml
+++ b/tests/hostside/safetycenter/AndroidTest.xml
@@ -32,6 +32,10 @@
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Belt-and-braces attempt to dismiss keyguard. Tradefed should have already done this
+ for us, but this is a precaution in an attempt to mitigate b/379620557. -->
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
<!-- 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>
diff --git a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt
index 60e6e41ec..c56c913b6 100644
--- a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt
+++ b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt
@@ -69,6 +69,12 @@ class SafetyCenterNotificationLoggingHelperTests {
@Test
fun sendNotification() {
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, newTestDataWithNotifiableIssue())
+ TestNotificationListener.waitForSingleNotificationMatching(
+ NotificationCharacteristics(
+ actions = listOf("See issue"),
+ safetySourceId = SINGLE_SOURCE_ID,
+ )
+ )
}
@Test
@@ -104,7 +110,7 @@ class SafetyCenterNotificationLoggingHelperTests {
statusBarNotificationWithChannel.statusBarNotification.notification.contentIntent
SafetyCenterActivityLauncher.executeBlockAndExit(
launchActivity = { PendingIntentSender.send(contentIntent) },
- block = {} // No action required
+ block = {}, // No action required
)
}
}
diff --git a/tests/utils/safetycenter/Android.bp b/tests/utils/safetycenter/Android.bp
index fab8c8dde..11fd3951d 100644
--- a/tests/utils/safetycenter/Android.bp
+++ b/tests/utils/safetycenter/Android.bp
@@ -36,6 +36,7 @@ android_library {
"kotlinx-coroutines-android",
"safety-center-internal-data",
"safety-center-resources-lib",
+ "SettingsLibSettingsTheme",
// TODO(b/326414126): aconfig: support multi-container library
"com.android.permission.flags-aconfig-java",
],
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt
index cc8c53d5e..47f5165e2 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt
@@ -22,6 +22,7 @@ import java.time.Duration
import kotlinx.coroutines.DEBUG_PROPERTY_NAME
import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_AUTO
import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON
+import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
@@ -68,6 +69,20 @@ object Coroutines {
runBlockingWithTimeout(timeout) { waitFor(checkPeriod, condition) }
}
+ /** Check an assertion passes, with a timeout if it does not. */
+ fun assertWithTimeout(
+ timeout: Duration = TIMEOUT_LONG,
+ checkPeriod: Duration = CHECK_PERIOD,
+ assertion: () -> Unit,
+ ) {
+ try {
+ runBlockingWithTimeout(timeout) { assertThatWaiting(checkPeriod, assertion) }
+ } catch (ex: TimeoutCancellationException) {
+ // Rerun the assertion to generate a meaningful error message that isn't just "timeout"
+ assertion()
+ }
+ }
+
/** Retries a [fallibleAction] until no errors are thrown or a timeout occurs. */
fun waitForSuccessWithTimeout(
timeout: Duration = TIMEOUT_LONG,
@@ -105,6 +120,21 @@ object Coroutines {
}
}
+ /** Check an assertion passes using coroutines. */
+ private suspend fun assertThatWaiting(
+ checkPeriod: Duration = CHECK_PERIOD,
+ assertion: () -> Unit,
+ ) {
+ while (true) {
+ try {
+ assertion()
+ break
+ } catch (ex: AssertionError) {
+ delay(checkPeriod.toMillis())
+ }
+ }
+ }
+
private const val TAG: String = "Coroutines"
/** A medium period, to be used for conditions that are expected to change. */
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
index 7efbba7a0..4b4abf360 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
@@ -21,6 +21,7 @@ import android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG
import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.annotation.TargetApi
import android.app.job.JobInfo
+import android.content.Context
import android.content.pm.PackageManager
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.provider.DeviceConfig
@@ -34,16 +35,21 @@ import android.safetycenter.SafetyCenterManager.REFRESH_REASON_PERIODIC
import android.safetycenter.SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK
import android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CENTER_ENABLED
import android.safetycenter.SafetySourceData
+import android.util.Log
import com.android.modules.utils.build.SdkLevel
import com.android.safetycenter.testing.Coroutines.TEST_TIMEOUT
import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG
import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
+import com.android.settingslib.widget.SettingsThemeHelper
import java.time.Duration
import kotlin.reflect.KProperty
/** A class that facilitates working with Safety Center flags. */
object SafetyCenterFlags {
+ /** This is a hidden API constant within [DeviceConfig]. */
+ private const val NAMESPACE_SETTINGS_UI = "settings_ui"
+
/** Flag that determines whether Safety Center is enabled. */
private val isEnabledFlag =
Flag("safety_center_is_enabled", defaultValue = SdkLevel.isAtLeastU(), BooleanParser())
@@ -143,8 +149,7 @@ object SafetyCenterFlags {
)
/**
- * Flag that determines the time for which Safety Center will wait before starting dismissal of
- * resolved issue UI
+ * Flag that determines how long Safety Center will wait before hiding the resolved issue UI.
*/
private val hideResolveUiTransitionDelayFlag =
Flag(
@@ -154,6 +159,20 @@ object SafetyCenterFlags {
)
/**
+ * Flag that determines how long an expressive BannerMessagePreference will wait before hiding
+ * the resolved UI.
+ */
+ private val bannerMessagePrefHideResolvedContentTransitionDelayFlag =
+ Flag(
+ "banner_message_pref_hide_resolved_content_delay_millis",
+ defaultValue = Duration.ofMillis(400),
+ DurationParser(),
+ namespace = NAMESPACE_SETTINGS_UI,
+ // This flag is only writeable on BP2A builds built after 3 March 2025
+ writeMustSucceed = false,
+ )
+
+ /**
* Flag containing a comma delimited lists of source IDs that we won't track when deciding if a
* broadcast is completed. We still send broadcasts to (and handle API calls from) these sources
* as normal.
@@ -312,6 +331,7 @@ object SafetyCenterFlags {
resolveActionTimeoutFlag,
tempHiddenIssueResurfaceDelayFlag,
hideResolveUiTransitionDelayFlag,
+ bannerMessagePrefHideResolvedContentTransitionDelayFlag,
untrackedSourcesFlag,
resurfaceIssueMaxCountsFlag,
resurfaceIssueDelaysFlag,
@@ -327,6 +347,16 @@ object SafetyCenterFlags {
periodicBackgroundRefreshIntervalFlag,
)
+ /** All the Safety Center flags that should be written to during setup and reset. */
+ private val SETUP_FLAGS =
+ FLAGS.filter { it.name != isEnabledFlag.name }
+ .filter {
+ // This flag is only writeable on BP2A builds built after 3 March 2025
+ // Don't set it up on versions we know will trigger a write error.
+ it.name != bannerMessagePrefHideResolvedContentTransitionDelayFlag.name ||
+ SdkLevel.isAtLeastB()
+ }
+
/** A property that allows getting and setting the [isEnabledFlag]. */
var isEnabled: Boolean by isEnabledFlag
@@ -357,9 +387,28 @@ object SafetyCenterFlags {
/** A property that allows getting and setting the [tempHiddenIssueResurfaceDelayFlag]. */
var tempHiddenIssueResurfaceDelay: Duration by tempHiddenIssueResurfaceDelayFlag
+ // TODO: b/379849464 - replace remaining usages and make this private
/** A property that allows getting and setting the [hideResolveUiTransitionDelayFlag]. */
var hideResolvedIssueUiTransitionDelay: Duration by hideResolveUiTransitionDelayFlag
+ /**
+ * A property that allows getting and setting the
+ * [bannerMessagePrefHideResolvedContentTransitionDelayFlag]
+ */
+ private var bannerMessagePrefHideResolvedContentTransitionDelay: Duration by
+ bannerMessagePrefHideResolvedContentTransitionDelayFlag
+
+ /**
+ * Sets the proper hide_resolved_issue_ui_transition_delay flag based on expressive design
+ * state.
+ */
+ fun setHideResolvedIssueUiTransitionDelay(context: Context, value: Duration) =
+ if (SettingsThemeHelper.isExpressiveTheme(context)) {
+ bannerMessagePrefHideResolvedContentTransitionDelay = value
+ } else {
+ hideResolvedIssueUiTransitionDelay = value
+ }
+
/** A property that allows getting and setting the [untrackedSourcesFlag]. */
var untrackedSources: Set<String> by untrackedSourcesFlag
@@ -396,14 +445,23 @@ object SafetyCenterFlags {
* This snapshot is only taken once and cached afterwards. [setup] must be called at least once
* prior to modifying any flag for the snapshot to be taken with the right values.
*/
- @Volatile lateinit var snapshot: Properties
+ @Volatile lateinit var snapshot: Map<String, Properties>
- private val lazySnapshot: Properties by lazy {
+ private val lazySnapshot: Map<String, Properties> by lazy {
callWithShellPermissionIdentity(READ_DEVICE_CONFIG) {
- DeviceConfig.getProperties(NAMESPACE_PRIVACY, *FLAGS.map { it.name }.toTypedArray())
+ mapOf(
+ NAMESPACE_PRIVACY to fetchPropertiesForNamespace(NAMESPACE_PRIVACY),
+ NAMESPACE_SETTINGS_UI to fetchPropertiesForNamespace(NAMESPACE_SETTINGS_UI),
+ )
}
}
+ private fun fetchPropertiesForNamespace(namespace: String) =
+ DeviceConfig.getProperties(
+ namespace,
+ *FLAGS.filter { it.namespace == namespace }.map { it.name }.toTypedArray(),
+ )
+
/**
* Takes a snapshot of all Safety Center flags and sets them up to their default values.
*
@@ -413,8 +471,7 @@ object SafetyCenterFlags {
*/
fun setup() {
snapshot = lazySnapshot
- FLAGS.filter { it.name != isEnabledFlag.name }
- .forEach { writeDeviceConfigProperty(it.name, it.defaultStringValue) }
+ SETUP_FLAGS.forEach { it.writeToDeviceConfig(it.defaultStringValue) }
}
/**
@@ -428,12 +485,11 @@ object SafetyCenterFlags {
// Write flags one by one instead of using `DeviceConfig#setProperties` as the latter does
// not work when DeviceConfig sync is disabled and does not take uninitialized values into
// account.
- FLAGS.filter { it.name != isEnabledFlag.name }
- .forEach {
- val key = it.name
- val value = snapshot.getString(key, /* defaultValue */ null)
- writeDeviceConfigProperty(key, value)
- }
+ SETUP_FLAGS.forEach {
+ val key = it.name
+ val value = snapshot[it.namespace]?.getString(key, /* defaultValue */ null)
+ it.writeToDeviceConfig(value)
+ }
}
/** Sets the [refreshTimeouts] for all refresh reasons to the given [refreshTimeout]. */
@@ -442,8 +498,8 @@ object SafetyCenterFlags {
}
/** Returns the [isEnabledFlag] value of the Safety Center flags snapshot. */
- fun Properties.isSafetyCenterEnabled() =
- getBoolean(isEnabledFlag.name, isEnabledFlag.defaultValue)
+ fun Map<String, Properties>.isSafetyCenterEnabled(): Boolean =
+ this[NAMESPACE_PRIVACY]!!.getBoolean(isEnabledFlag.name, isEnabledFlag.defaultValue)
@TargetApi(UPSIDE_DOWN_CAKE)
private fun getAllRefreshTimeoutsMap(refreshTimeout: Duration): Map<Int, Duration> =
@@ -516,32 +572,48 @@ object SafetyCenterFlags {
.joinToString(entriesDelimiter)
}
- private class Flag<T>(val name: String, val defaultValue: T, private val parser: Parser<T>) {
+ private class Flag<T>(
+ val name: String,
+ val defaultValue: T,
+ private val parser: Parser<T>,
+ val namespace: String = NAMESPACE_PRIVACY,
+ val writeMustSucceed: Boolean = true,
+ ) {
val defaultStringValue = parser.toString(defaultValue)
operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
- readDeviceConfigProperty(name)?.let(parser::parseFromString) ?: defaultValue
+ readFromDeviceConfig(name)?.let(parser::parseFromString) ?: defaultValue
- operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
- writeDeviceConfigProperty(name, parser.toString(value))
- }
- }
+ private fun readFromDeviceConfig(name: String): String? =
+ callWithShellPermissionIdentity(READ_DEVICE_CONFIG) {
+ DeviceConfig.getProperty(namespace, name)
+ }
- private fun readDeviceConfigProperty(name: String): String? =
- callWithShellPermissionIdentity(READ_DEVICE_CONFIG) {
- DeviceConfig.getProperty(NAMESPACE_PRIVACY, name)
+ operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
+ writeToDeviceConfig(parser.toString(value))
}
- private fun writeDeviceConfigProperty(name: String, stringValue: String?) {
- callWithShellPermissionIdentity(WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG) {
- val valueWasSet =
- DeviceConfig.setProperty(
- NAMESPACE_PRIVACY,
- name,
- stringValue, /* makeDefault */
- false,
- )
- require(valueWasSet) { "Could not set $name to: $stringValue" }
+ fun writeToDeviceConfig(stringValue: String?) {
+ callWithShellPermissionIdentity(WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG) {
+ val valueWasSet =
+ try {
+ DeviceConfig.setProperty(
+ namespace,
+ name,
+ stringValue,
+ /* makeDefault */ false,
+ )
+ } catch (e: Exception) {
+ Log.w(TAG, "Error while setting $name to: $stringValue", e)
+ false
+ }
+
+ if (writeMustSucceed) {
+ require(valueWasSet) { "Could not set $name to: $stringValue" }
+ }
+ }
}
}
+
+ private const val TAG = "SafetyCenterFlags"
}