1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.permissionmultidevice.cts
18 
19 import android.Manifest
20 import android.app.Instrumentation
21 import android.companion.virtual.VirtualDeviceManager
22 import android.companion.virtual.VirtualDeviceParams
23 import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM
24 import android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA
25 import android.content.ComponentName
26 import android.content.Intent
27 import android.content.Intent.EXTRA_RESULT_RECEIVER
28 import android.content.pm.PackageManager
29 import android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS
30 import android.hardware.display.DisplayManager
31 import android.hardware.display.VirtualDisplay
32 import android.os.Build
33 import android.os.Bundle
34 import android.os.RemoteCallback
35 import android.permission.flags.Flags
36 import android.permissionmultidevice.cts.PackageManagementUtils.installPackage
37 import android.permissionmultidevice.cts.PackageManagementUtils.uninstallPackage
38 import android.permissionmultidevice.cts.UiAutomatorUtils.click
39 import android.permissionmultidevice.cts.UiAutomatorUtils.findTextForView
40 import android.permissionmultidevice.cts.UiAutomatorUtils.waitFindObject
41 import android.platform.test.annotations.AppModeFull
42 import android.platform.test.annotations.RequiresFlagsEnabled
43 import android.view.Display
44 import android.virtualdevice.cts.common.VirtualDeviceRule
45 import androidx.test.ext.junit.runners.AndroidJUnit4
46 import androidx.test.filters.SdkSuppress
47 import androidx.test.platform.app.InstrumentationRegistry
48 import androidx.test.uiautomator.By
49 import com.android.compatibility.common.util.SystemUtil
50 import com.google.common.truth.Truth
51 import com.google.common.truth.Truth.assertThat
52 import java.util.concurrent.CompletableFuture
53 import java.util.concurrent.TimeUnit
54 import org.junit.After
55 import org.junit.Assert
56 import org.junit.Assume.assumeFalse
57 import org.junit.Before
58 import org.junit.Rule
59 import org.junit.Test
60 import org.junit.runner.RunWith
61 
62 @RunWith(AndroidJUnit4::class)
63 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName = "VanillaIceCream")
64 @AppModeFull(reason = "VirtualDeviceManager cannot be accessed by instant apps")
65 class DeviceAwarePermissionGrantTest {
66     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
67     private val defaultDeviceContext = instrumentation.targetContext
68     private lateinit var virtualDeviceManager: VirtualDeviceManager
69     private lateinit var virtualDevice: VirtualDeviceManager.VirtualDevice
70     private lateinit var virtualDisplay: VirtualDisplay
71     private lateinit var deviceDisplayName: String
72 
73     @get:Rule var virtualDeviceRule = VirtualDeviceRule.createDefault()
74 
75     @Before
76     fun setup() {
77         assumeFalse(PermissionUtils.isAutomotive(defaultDeviceContext))
78         assumeFalse(PermissionUtils.isTv(defaultDeviceContext))
79         assumeFalse(PermissionUtils.isWatch(defaultDeviceContext))
80 
81         installPackage(APP_APK_PATH_STREAMING)
82         virtualDeviceManager =
83             defaultDeviceContext.getSystemService(VirtualDeviceManager::class.java)!!
84         virtualDevice =
85             virtualDeviceRule.createManagedVirtualDevice(
86                 VirtualDeviceParams.Builder()
87                     .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_CUSTOM)
88                     .build()
89             )
90 
91         val displayConfig =
92             VirtualDeviceRule.createDefaultVirtualDisplayConfigBuilder(
93                     DISPLAY_WIDTH,
94                     DISPLAY_HEIGHT
95                 )
96                 .setFlags(
97                     DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC or
98                         DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED or
99                         DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
100                 )
101                 .build()
102 
103         virtualDisplay =
104             virtualDeviceRule.createManagedVirtualDisplay(virtualDevice, displayConfig)!!
105         deviceDisplayName =
106             virtualDeviceManager.getVirtualDevice(virtualDevice.deviceId)!!.displayName.toString()
107     }
108 
109     @After
110     fun cleanup() {
111         uninstallPackage(APP_PACKAGE_NAME, requireSuccess = false)
112     }
113 
114     @RequiresFlagsEnabled(
115         Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
116         Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
117     )
118     @Test
119     fun onHostDevice_requestPermissionForHostDevice_shouldGrantPermission() {
120         testGrantPermissionForDevice(
121             Display.DEFAULT_DISPLAY,
122             DEVICE_ID_DEFAULT,
123             false,
124             "",
125             expectPermissionGrantedOnDefaultDevice = true,
126             expectPermissionGrantedOnRemoteDevice = false
127         )
128     }
129 
130     @RequiresFlagsEnabled(
131         Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
132         Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
133     )
134     @Test
135     fun onHostDevice_requestPermissionForRemoteDevice_shouldGrantPermission() {
136         testGrantPermissionForDevice(
137             Display.DEFAULT_DISPLAY,
138             virtualDevice.deviceId,
139             true,
140             deviceDisplayName,
141             expectPermissionGrantedOnDefaultDevice = false,
142             expectPermissionGrantedOnRemoteDevice = true
143         )
144     }
145 
146     @RequiresFlagsEnabled(
147         Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
148         Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
149     )
150     @Test
151     fun onRemoteDevice_requestPermissionForHostDevice_shouldShowWarningDialog() {
152         requestPermissionOnDevice(virtualDisplay.display.displayId, DEVICE_ID_DEFAULT)
153 
154         val displayId = virtualDisplay.display.displayId
155         waitFindObject(By.displayId(displayId).textContains("Permission request suppressed"))
156     }
157 
158     @RequiresFlagsEnabled(
159         Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
160         Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
161     )
162     @Test
163     fun onRemoteDevice_requestPermissionForRemoteDevice_shouldGrantPermission() {
164         testGrantPermissionForDevice(
165             virtualDisplay.display.displayId,
166             virtualDevice.deviceId,
167             true,
168             deviceDisplayName,
169             expectPermissionGrantedOnDefaultDevice = false,
170             expectPermissionGrantedOnRemoteDevice = true
171         )
172     }
173 
174     private fun testGrantPermissionForDevice(
175         displayId: Int,
176         targetDeviceId: Int,
177         showDeviceName: Boolean,
178         expectedDeviceNameInDialog: String,
179         expectPermissionGrantedOnDefaultDevice: Boolean,
180         expectPermissionGrantedOnRemoteDevice: Boolean
181     ) {
182         // Assert no permission granted to either default device or virtual device at the beginning
183         assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
184         assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
185 
186         val future = requestPermissionOnDevice(displayId, targetDeviceId)
187         virtualDeviceRule.waitAndAssertActivityResumed(getPermissionDialogComponentName())
188 
189         if (showDeviceName) {
190             assertPermissionMessageContainsDeviceName(displayId, expectedDeviceNameInDialog)
191         }
192 
193         // Click the allow button in the dialog to grant permission
194         SystemUtil.eventually { click(By.displayId(displayId).res(ALLOW_BUTTON)) }
195 
196         // Validate permission grant result returned from callback
197         val grantPermissionResult = future.get(TIMEOUT, TimeUnit.MILLISECONDS)
198         assertThat(
199                 grantPermissionResult.getStringArray(
200                     TestConstants.PERMISSION_RESULT_KEY_PERMISSIONS
201                 )
202             )
203             .isEqualTo(arrayOf(DEVICE_AWARE_PERMISSION))
204         assertThat(
205                 grantPermissionResult.getIntArray(TestConstants.PERMISSION_RESULT_KEY_GRANT_RESULTS)
206             )
207             .isEqualTo(arrayOf(PackageManager.PERMISSION_GRANTED).toIntArray())
208         assertThat(grantPermissionResult.getInt(TestConstants.PERMISSION_RESULT_KEY_DEVICE_ID))
209             .isEqualTo(targetDeviceId)
210 
211         // Validate whether permission is granted as expected
212         assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, expectPermissionGrantedOnDefaultDevice)
213         assertAppHasPermissionForDevice(
214             virtualDevice.deviceId,
215             expectPermissionGrantedOnRemoteDevice
216         )
217     }
218 
219     private fun requestPermissionOnDevice(
220         displayId: Int,
221         targetDeviceId: Int
222     ): CompletableFuture<Bundle> {
223         val future = CompletableFuture<Bundle>()
224         val callback = RemoteCallback { result: Bundle? -> future.complete(result) }
225         val intent =
226             Intent()
227                 .setComponent(
228                     ComponentName(APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.RequestPermissionActivity")
229                 )
230                 .putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_DEVICE_ID, targetDeviceId)
231                 .putExtra(EXTRA_RESULT_RECEIVER, callback)
232                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
233         virtualDeviceRule.sendIntentToDisplay(intent, displayId)
234 
235         return future
236     }
237 
238     private fun assertPermissionMessageContainsDeviceName(displayId: Int, deviceName: String) {
239         waitFindObject(By.displayId(displayId).res(PERMISSION_MESSAGE_ID))
240         val text = findTextForView(By.displayId(displayId).res(PERMISSION_MESSAGE_ID))
241         Truth.assertThat(text).contains(deviceName)
242     }
243 
244     private fun assertAppHasPermissionForDevice(deviceId: Int, expectPermissionGranted: Boolean) {
245         val checkPermissionResult =
246             defaultDeviceContext
247                 .createDeviceContext(deviceId)
248                 .packageManager
249                 .checkPermission(DEVICE_AWARE_PERMISSION, APP_PACKAGE_NAME)
250 
251         if (expectPermissionGranted) {
252             Assert.assertEquals(PackageManager.PERMISSION_GRANTED, checkPermissionResult)
253         } else {
254             Assert.assertEquals(PackageManager.PERMISSION_DENIED, checkPermissionResult)
255         }
256     }
257 
258     private fun getPermissionDialogComponentName(): ComponentName {
259         val intent = Intent(ACTION_REQUEST_PERMISSIONS)
260         intent.setPackage(defaultDeviceContext.packageManager.getPermissionControllerPackageName())
261         return intent.resolveActivity(defaultDeviceContext.packageManager)
262     }
263 
264     companion object {
265         const val APK_DIRECTORY = "/data/local/tmp/cts-permissionmultidevice"
266         const val APP_APK_PATH_STREAMING = "${APK_DIRECTORY}/CtsAccessRemoteDeviceCamera.apk"
267         const val APP_PACKAGE_NAME = "android.permissionmultidevice.cts.accessremotedevicecamera"
268         const val PERMISSION_MESSAGE_ID = "com.android.permissioncontroller:id/permission_message"
269         const val ALLOW_BUTTON =
270             "com.android.permissioncontroller:id/permission_allow_foreground_only_button"
271         const val DEVICE_ID_DEFAULT = 0
272         const val PERSISTENT_DEVICE_ID_DEFAULT = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
273         const val DEVICE_AWARE_PERMISSION = Manifest.permission.CAMERA
274         const val TIMEOUT = 5000L
275         private const val DISPLAY_HEIGHT = 1920
276         private const val DISPLAY_WIDTH = 1080
277     }
278 }
279