diff options
author | 2025-03-11 00:21:47 -0700 | |
---|---|---|
committer | 2025-03-11 00:21:47 -0700 | |
commit | db9f5e553fc8fc1bd683aac053be70c33eb7d273 (patch) | |
tree | 08ab4fe9fd3ec1ba7734e83bd1568d9e4a58a1ae | |
parent | c85d32e09875ec2e849c60ea881913915fa7a6c0 (diff) |
Exhaustive multi-device permission CTS
- Fix GrantPermissionsActivity for non-device-aware permissions.
Such requests should not be ignored, but should fallback to the
default device.
- Fix the permission check for passing grant results. When requesting
default device aware permission from virtual device, the call to
checkPermission uses the wrong context. It can simply use
mPackageManager, which is always associated with the target device.
Fix: 401540899
Test: atest
Relnote: n/a
Flag: EXEMPT bugfix
Change-Id: Ib5de5ce0cf09f6ad1e445b01ca2443bf102ab598
5 files changed, 476 insertions, 205 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java index 7573b571d..0dd07ffd0 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java @@ -311,10 +311,8 @@ public class GrantPermissionsActivity extends SettingsActivity PackageManager.EXTRA_REQUEST_PERMISSIONS_DEVICE_ID, ContextCompat.DEVICE_ID_DEFAULT); - if (mTargetDeviceId != ContextCompat.DEVICE_ID_DEFAULT) { - mPackageManager = ContextCompat.createDeviceContext(this, mTargetDeviceId) - .getPackageManager(); - } + mPackageManager = ContextCompat.createDeviceContext(this, mTargetDeviceId) + .getPackageManager(); // When the permission grant dialog is streamed to a virtual device, and when requested // permissions include both device-aware permissions and non-device aware permissions, @@ -337,21 +335,6 @@ public class GrantPermissionsActivity extends SettingsActivity new Intent(this, PermissionDialogStreamingBlockedActivity.class)); return; } - } else if (mTargetDeviceId != ContextCompat.DEVICE_ID_DEFAULT) { - // On the default device, when requested permissions are for a remote device, - // filter out non-device aware permissions. - for (int i = mRequestedPermissions.size() - 1; i >= 0; i--) { - if (!MultiDeviceUtils.isPermissionDeviceAware( - getApplicationContext(), - mTargetDeviceId, - mRequestedPermissions.get(i))) { - Log.e( - LOG_TAG, - "non-device aware permission is requested for a remote device: " - + mRequestedPermissions.get(i)); - mRequestedPermissions.remove(i); - } - } } } @@ -740,7 +723,7 @@ public class GrantPermissionsActivity extends SettingsActivity int dialogDisplayDeviceId = ContextCompat.getDeviceId(this); boolean isMessageDeviceAware = dialogDisplayDeviceId != ContextCompat.DEVICE_ID_DEFAULT - || dialogDisplayDeviceId != mTargetDeviceId; + || dialogDisplayDeviceId != info.getDeviceId(); int messageId = getMessageId(info.getGroupName(), info.getPrompt(), isMessageDeviceAware); CharSequence message = @@ -1132,17 +1115,9 @@ public class GrantPermissionsActivity extends SettingsActivity if ((mDelegated || (mViewModel != null && mViewModel.shouldReturnPermissionState())) && mTargetPackage != null) { - PackageManager defaultDevicePackageManager = SdkLevel.isAtLeastV() - && mTargetDeviceId != ContextCompat.DEVICE_ID_DEFAULT - ? createDeviceContext(ContextCompat.DEVICE_ID_DEFAULT).getPackageManager() - : mPackageManager; - PackageManager targetDevicePackageManager = mPackageManager; for (int i = 0; i < resultPermissions.length; i++) { String permission = resultPermissions[i]; - PackageManager pm = MultiDeviceUtils.isPermissionDeviceAware( - getApplicationContext(), mTargetDeviceId, permission) - ? targetDevicePackageManager : defaultDevicePackageManager; - grantResults[i] = pm.checkPermission(resultPermissions[i], mTargetPackage); + grantResults[i] = mPackageManager.checkPermission(permission, mTargetPackage); } } else { grantResults = new int[0]; 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..e1068e19a 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 @@ -65,13 +64,19 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName = "VanillaIceCream") @AppModeFull(reason = "VirtualDeviceManager cannot be accessed by instant apps") -class DeviceAwarePermissionGrantTest { +@RequiresFlagsEnabled( + Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED, + Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED, + Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES, +) +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 +93,449 @@ 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, - "", + // TODO: Receives PERMISSION_DENIED but it's fine if remote permission is held?? + @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) @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") @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, - ) @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, + @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, + @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, + ) + } + + @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, + ) + } + + @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, + ) + } + + @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, + ) + } + + @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 +544,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 +602,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 +633,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 } } |