1 /* 2 * 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.virtualdevice.cts.common; 18 19 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS; 20 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; 21 22 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 23 24 import static com.google.common.truth.Truth.assertThat; 25 26 import static org.junit.Assume.assumeFalse; 27 import static org.junit.Assume.assumeNotNull; 28 import static org.junit.Assume.assumeTrue; 29 30 import android.annotation.TargetApi; 31 import android.app.Activity; 32 import android.app.ActivityOptions; 33 import android.app.UiAutomation; 34 import android.companion.AssociationInfo; 35 import android.companion.AssociationRequest; 36 import android.companion.virtual.VirtualDeviceManager; 37 import android.companion.virtual.VirtualDeviceManager.VirtualDevice; 38 import android.companion.virtual.VirtualDeviceParams; 39 import android.content.ComponentName; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.hardware.display.DisplayManager; 43 import android.hardware.display.VirtualDisplay; 44 import android.hardware.display.VirtualDisplayConfig; 45 import android.media.ImageReader; 46 import android.os.Bundle; 47 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 48 import android.server.wm.Condition; 49 import android.server.wm.WindowManagerState; 50 import android.server.wm.WindowManagerStateHelper; 51 import android.view.Display; 52 import android.view.Surface; 53 54 import androidx.annotation.NonNull; 55 import androidx.annotation.Nullable; 56 import androidx.core.os.BuildCompat; 57 58 import com.android.compatibility.common.util.AdoptShellPermissionsRule; 59 import com.android.compatibility.common.util.FeatureUtil; 60 61 import org.junit.rules.ExternalResource; 62 import org.junit.rules.RuleChain; 63 import org.junit.rules.TestRule; 64 import org.junit.runner.Description; 65 import org.junit.runners.model.Statement; 66 67 import java.util.ArrayList; 68 import java.util.Set; 69 import java.util.function.BooleanSupplier; 70 import java.util.function.Supplier; 71 72 /** 73 * A test rule that allows for testing VDM and virtual device features. 74 */ 75 @TargetApi(34) 76 public class VirtualDeviceRule implements TestRule { 77 78 public static final VirtualDeviceParams DEFAULT_VIRTUAL_DEVICE_PARAMS = 79 new VirtualDeviceParams.Builder().build(); 80 81 public static final String DEFAULT_VIRTUAL_DISPLAY_NAME = "testVirtualDisplay"; 82 public static final int DEFAULT_VIRTUAL_DISPLAY_WIDTH = 640; 83 public static final int DEFAULT_VIRTUAL_DISPLAY_HEIGHT = 480; 84 public static final int DEFAULT_VIRTUAL_DISPLAY_DPI = 240; 85 86 public static final ComponentName BLOCKED_ACTIVITY_COMPONENT = 87 new ComponentName("android", "com.android.internal.app.BlockedAppStreamingActivity"); 88 89 private RuleChain mRuleChain; 90 private final FakeAssociationRule mFakeAssociationRule; 91 private final VirtualDeviceTrackerRule mTrackerRule = new VirtualDeviceTrackerRule(); 92 93 private final Context mContext = getInstrumentation().getTargetContext(); 94 private final VirtualDeviceManager mVirtualDeviceManager = 95 mContext.getSystemService(VirtualDeviceManager.class); 96 private final WindowManagerStateHelper mWmState = new WindowManagerStateHelper(); 97 98 /** A default virtual device for tests that only use the rule to access VDM functionality. */ 99 private VirtualDevice mDefaultVirtualDevice = null; 100 101 /** Creates a rule with the required permissions for creating virtual devices and displays. */ createDefault()102 public static VirtualDeviceRule createDefault() { 103 return new VirtualDeviceRule(AssociationRequest.DEVICE_PROFILE_APP_STREAMING); 104 } 105 106 /** Creates a rule with an explicit device profile. */ withDeviceProfile(String deviceProfile)107 public static VirtualDeviceRule withDeviceProfile(String deviceProfile) { 108 return new VirtualDeviceRule(deviceProfile); 109 } 110 111 /** Creates a rule with any additional permission needed for the specific test. */ withAdditionalPermissions(String... additionalPermissions)112 public static VirtualDeviceRule withAdditionalPermissions(String... additionalPermissions) { 113 return new VirtualDeviceRule(AssociationRequest.DEVICE_PROFILE_APP_STREAMING, 114 additionalPermissions); 115 } 116 VirtualDeviceRule(String deviceProfile, String... permissions)117 private VirtualDeviceRule(String deviceProfile, String... permissions) { 118 mFakeAssociationRule = new FakeAssociationRule(deviceProfile); 119 mRuleChain = RuleChain 120 .outerRule(mFakeAssociationRule) 121 .around(DeviceFlagsValueProvider.createCheckFlagsRule()) 122 .around(new AdoptShellPermissionsRule( 123 getInstrumentation().getUiAutomation(), permissions)) 124 .around(mTrackerRule); 125 } 126 127 /** Creates a rule with virtual camera support check before test execution. */ withVirtualCameraSupportCheck()128 public VirtualDeviceRule withVirtualCameraSupportCheck() { 129 mRuleChain = mRuleChain.around(new VirtualCameraSupportRule(this)); 130 return this; 131 } 132 133 @Override apply(final Statement base, final Description description)134 public Statement apply(final Statement base, final Description description) { 135 assumeNotNull(mVirtualDeviceManager); 136 return mRuleChain.apply(base, description); 137 } 138 139 /** 140 * Returns a default virtual device. 141 */ getDefaultVirtualDevice()142 public VirtualDevice getDefaultVirtualDevice() { 143 if (mDefaultVirtualDevice == null) { 144 mDefaultVirtualDevice = createManagedVirtualDevice(); 145 } 146 return mDefaultVirtualDevice; 147 } 148 149 /** 150 * Returns the VirtualDevice object for the given deviceId 151 */ getVirtualDevice(int deviceId)152 public android.companion.virtual.VirtualDevice getVirtualDevice(int deviceId) { 153 if (BuildCompat.isAtLeastV()) { 154 return mVirtualDeviceManager.getVirtualDevice(deviceId); 155 } else { 156 return mVirtualDeviceManager.getVirtualDevices().stream() 157 .filter(device -> device.getDeviceId() == deviceId).findFirst().orElse(null); 158 } 159 } 160 161 /** 162 * Creates a virtual device with default params that will be automatically closed when the 163 * test is torn down. 164 */ 165 @NonNull createManagedVirtualDevice()166 public VirtualDevice createManagedVirtualDevice() { 167 return createManagedVirtualDevice(DEFAULT_VIRTUAL_DEVICE_PARAMS); 168 } 169 170 /** 171 * Creates a virtual device with the given params that will be automatically closed when the 172 * test is torn down. 173 */ 174 @NonNull createManagedVirtualDevice(@onNull VirtualDeviceParams params)175 public VirtualDevice createManagedVirtualDevice(@NonNull VirtualDeviceParams params) { 176 final VirtualDevice virtualDevice = mVirtualDeviceManager.createVirtualDevice( 177 mFakeAssociationRule.getAssociationInfo().getId(), params); 178 mTrackerRule.mVirtualDevices.add(virtualDevice); 179 return virtualDevice; 180 } 181 182 /** 183 * Creates a virtual display associated with the given device that will be automatically 184 * released when the test is torn down. 185 */ 186 @Nullable createManagedVirtualDisplay(@onNull VirtualDevice virtualDevice)187 public VirtualDisplay createManagedVirtualDisplay(@NonNull VirtualDevice virtualDevice) { 188 return createManagedVirtualDisplay(virtualDevice, 189 createDefaultVirtualDisplayConfigBuilder()); 190 } 191 192 /** 193 * Creates a virtual display associated with the given device and flags that will be 194 * automatically released when the test is torn down. 195 */ 196 @Nullable createManagedVirtualDisplayWithFlags( @onNull VirtualDevice virtualDevice, int flags)197 public VirtualDisplay createManagedVirtualDisplayWithFlags( 198 @NonNull VirtualDevice virtualDevice, int flags) { 199 return createManagedVirtualDisplay(virtualDevice, 200 createDefaultVirtualDisplayConfigBuilder().setFlags(flags)); 201 } 202 203 /** 204 * Creates a virtual display associated with the given device and config that will be 205 * automatically released when the test is torn down. 206 */ 207 @Nullable createManagedVirtualDisplay(@onNull VirtualDevice virtualDevice, @NonNull VirtualDisplayConfig.Builder builder)208 public VirtualDisplay createManagedVirtualDisplay(@NonNull VirtualDevice virtualDevice, 209 @NonNull VirtualDisplayConfig.Builder builder) { 210 return createManagedVirtualDisplay(virtualDevice, builder, /* callback= */ null); 211 } 212 213 /** 214 * Creates a virtual display associated with the given device and config that will be 215 * automatically released when the test is torn down. 216 */ 217 @Nullable createManagedVirtualDisplay(@onNull VirtualDevice virtualDevice, @NonNull VirtualDisplayConfig.Builder builder, @Nullable VirtualDisplay.Callback callback)218 public VirtualDisplay createManagedVirtualDisplay(@NonNull VirtualDevice virtualDevice, 219 @NonNull VirtualDisplayConfig.Builder builder, 220 @Nullable VirtualDisplay.Callback callback) { 221 VirtualDisplayConfig config = builder.build(); 222 final Surface surface = prepareSurface(config.getWidth(), config.getHeight()); 223 final VirtualDisplay virtualDisplay = virtualDevice.createVirtualDisplay( 224 builder.setSurface(surface).build(), Runnable::run, callback); 225 if (virtualDisplay != null) { 226 assertDisplayExists(virtualDisplay.getDisplay().getDisplayId()); 227 // There's no need to track managed virtual displays to have them released on tear-down 228 // because they will be released automatically when the VirtualDevice is closed. 229 } 230 return virtualDisplay; 231 } 232 233 /** 234 * Creates a virtual display not associated with the any virtual device that will be 235 * automatically released when the test is torn down. 236 */ 237 @Nullable createManagedUnownedVirtualDisplay()238 public VirtualDisplay createManagedUnownedVirtualDisplay() { 239 return createManagedUnownedVirtualDisplay(createTrustedVirtualDisplayConfigBuilder()); 240 } 241 242 /** 243 * Creates a virtual display not associated with the any virtual device with the given flags 244 * that will be automatically released when the test is torn down. 245 */ 246 @Nullable createManagedUnownedVirtualDisplayWithFlags(int flags)247 public VirtualDisplay createManagedUnownedVirtualDisplayWithFlags(int flags) { 248 return createManagedUnownedVirtualDisplay( 249 createDefaultVirtualDisplayConfigBuilder().setFlags(flags)); 250 } 251 252 /** 253 * Creates a virtual display not associated with the any virtual device with the given config 254 * that will be automatically released when the test is torn down. 255 */ 256 @Nullable createManagedUnownedVirtualDisplay( @onNull VirtualDisplayConfig.Builder builder)257 public VirtualDisplay createManagedUnownedVirtualDisplay( 258 @NonNull VirtualDisplayConfig.Builder builder) { 259 VirtualDisplayConfig config = builder.build(); 260 final Surface surface = prepareSurface(config.getWidth(), config.getHeight()); 261 final VirtualDisplay virtualDisplay = 262 mContext.getSystemService(DisplayManager.class).createVirtualDisplay( 263 builder.setSurface(surface).build()); 264 if (virtualDisplay != null) { 265 assertDisplayExists(virtualDisplay.getDisplay().getDisplayId()); 266 mTrackerRule.mVirtualDisplays.add(virtualDisplay); 267 } 268 return virtualDisplay; 269 } 270 271 /** 272 * Default config for virtual display creation, with a predefined name, dimensions and an empty 273 * surface. 274 */ 275 @NonNull createDefaultVirtualDisplayConfigBuilder()276 public static VirtualDisplayConfig.Builder createDefaultVirtualDisplayConfigBuilder() { 277 return createDefaultVirtualDisplayConfigBuilder( 278 DEFAULT_VIRTUAL_DISPLAY_WIDTH, DEFAULT_VIRTUAL_DISPLAY_HEIGHT); 279 } 280 281 /** 282 * Config for trusted virtual display creation, which applies the flags 283 * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED}, 284 * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC} and 285 * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY}. A predefined name and 286 * dimensions and an empty surface are used. 287 */ 288 @NonNull createTrustedVirtualDisplayConfigBuilder()289 public static VirtualDisplayConfig.Builder createTrustedVirtualDisplayConfigBuilder() { 290 return createDefaultVirtualDisplayConfigBuilder().setFlags( 291 DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC 292 | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED 293 | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); 294 } 295 296 /** 297 * Default config for virtual display creation with custom dimensions. 298 */ 299 @NonNull createDefaultVirtualDisplayConfigBuilder( int width, int height)300 public static VirtualDisplayConfig.Builder createDefaultVirtualDisplayConfigBuilder( 301 int width, int height) { 302 return new VirtualDisplayConfig.Builder( 303 DEFAULT_VIRTUAL_DISPLAY_NAME, width, height, DEFAULT_VIRTUAL_DISPLAY_DPI); 304 } 305 306 /** 307 * Blocks until the display with the given ID is available. 308 */ assertDisplayExists(int displayId)309 public void assertDisplayExists(int displayId) { 310 waitAndAssertWindowManagerState("Waiting for display to be available", 311 () -> mWmState.getDisplay(displayId) != null); 312 } 313 314 /** 315 * Blocks until the display with the given ID is removed. 316 */ assertDisplayDoesNotExist(int displayId)317 public void assertDisplayDoesNotExist(int displayId) { 318 waitAndAssertWindowManagerState("Waiting for display to be removed", 319 () -> mWmState.getDisplay(displayId) == null); 320 } 321 322 /** Returns the WM state helper. */ getWmState()323 public WindowManagerStateHelper getWmState() { 324 return mWmState; 325 } 326 327 /** Creates a new CDM association. */ createManagedAssociation()328 public AssociationInfo createManagedAssociation() { 329 return mFakeAssociationRule.createManagedAssociation(); 330 } 331 332 /** Drops the current CDM association. */ dropCompanionDeviceAssociation()333 public void dropCompanionDeviceAssociation() { 334 mFakeAssociationRule.disassociate(); 335 } 336 337 /** 338 * Temporarily assumes the given permissions and executes the given supplier. Reverts any 339 * permissions currently held after the execution. 340 */ runWithTemporaryPermission(Supplier<T> supplier, String... permissions)341 public <T> T runWithTemporaryPermission(Supplier<T> supplier, String... permissions) { 342 UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); 343 final Set<String> currentPermissions = uiAutomation.getAdoptedShellPermissions(); 344 uiAutomation.adoptShellPermissionIdentity(permissions); 345 try { 346 return supplier.get(); 347 } finally { 348 // Revert the permissions needed for the test again. 349 uiAutomation.adoptShellPermissionIdentity( 350 currentPermissions.toArray(new String[0])); 351 } 352 } 353 354 /** 355 * Temporarily drops any permissions and executes the given supplier. Reverts any permissions 356 * currently held after the execution. 357 */ runWithoutPermissions(Supplier<T> supplier)358 public <T> T runWithoutPermissions(Supplier<T> supplier) { 359 UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); 360 final Set<String> currentPermissions = uiAutomation.getAdoptedShellPermissions(); 361 uiAutomation.dropShellPermissionIdentity(); 362 try { 363 return supplier.get(); 364 } finally { 365 // Revert the permissions needed for the test again. 366 uiAutomation.adoptShellPermissionIdentity( 367 currentPermissions.toArray(new String[0])); 368 } 369 } 370 371 /** 372 * Sends the given intent to the given virtual display. 373 */ sendIntentToDisplay(Intent intent, VirtualDisplay virtualDisplay)374 public void sendIntentToDisplay(Intent intent, VirtualDisplay virtualDisplay) { 375 sendIntentToDisplay(intent, virtualDisplay.getDisplay().getDisplayId()); 376 } 377 378 /** 379 * Sends the given intent to the given display. 380 */ sendIntentToDisplay(Intent intent, int displayId)381 public void sendIntentToDisplay(Intent intent, int displayId) { 382 assumeActivityLaunchSupported(displayId); 383 mContext.startActivity(intent, createActivityOptions(displayId)); 384 } 385 386 /** 387 * Starts the activity for the given class on the given virtual display and blocks until it is 388 * successfully launched there. The activity will be finished after the test run. 389 */ startActivityOnDisplaySync( VirtualDisplay virtualDisplay, Class<T> clazz)390 public <T extends Activity> T startActivityOnDisplaySync( 391 VirtualDisplay virtualDisplay, Class<T> clazz) { 392 final int displayId = virtualDisplay.getDisplay().getDisplayId(); 393 return startActivityOnDisplaySync(displayId, clazz); 394 } 395 396 /** 397 * Starts the activity for the given class on the given display and blocks until it is 398 * successfully launched there. The activity will be finished after the test run. 399 */ startActivityOnDisplaySync(int displayId, Class<T> clazz)400 public <T extends Activity> T startActivityOnDisplaySync(int displayId, Class<T> clazz) { 401 return startActivityOnDisplaySync(displayId, new Intent(mContext, clazz) 402 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)); 403 } 404 405 /** 406 * Starts the activity for the given intent on the given virtual display and blocks until it is 407 * successfully launched there. The activity will be finished after the test run. 408 */ startActivityOnDisplaySync( VirtualDisplay virtualDisplay, Intent intent)409 public <T extends Activity> T startActivityOnDisplaySync( 410 VirtualDisplay virtualDisplay, Intent intent) { 411 return startActivityOnDisplaySync(virtualDisplay.getDisplay().getDisplayId(), intent); 412 } 413 414 /** 415 * Starts the activity for the given intent on the given display and blocks until it is 416 * successfully launched there. The activity will be finished after the test run. 417 */ startActivityOnDisplaySync(int displayId, Intent intent)418 public <T extends Activity> T startActivityOnDisplaySync(int displayId, Intent intent) { 419 assumeActivityLaunchSupported(displayId); 420 T activity = (T) getInstrumentation() 421 .startActivitySync(intent, createActivityOptions(displayId)); 422 mTrackerRule.mActivities.add(activity); 423 return activity; 424 } 425 426 /** 427 * Creates activity options for launching activities on the given virtual display. 428 */ createActivityOptions(VirtualDisplay virtualDisplay)429 public static Bundle createActivityOptions(VirtualDisplay virtualDisplay) { 430 return createActivityOptions(virtualDisplay.getDisplay().getDisplayId()); 431 } 432 433 /** 434 * Creates activity options for launching activities on the given display. 435 */ createActivityOptions(int displayId)436 public static Bundle createActivityOptions(int displayId) { 437 return ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(); 438 } 439 440 /** 441 * Skips the test if the device doesn't support virtual displays that can host activities. 442 */ assumeActivityLaunchSupported(int displayId)443 public void assumeActivityLaunchSupported(int displayId) { 444 if (displayId != Display.DEFAULT_DISPLAY) { 445 assumeTrue(FeatureUtil.hasSystemFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS)); 446 // TODO(b/261155110): Re-enable once freeform mode is supported on virtual displays. 447 assumeFalse(FeatureUtil.hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)); 448 } 449 } 450 451 /** 452 * Blocks until the given activity is in resumed state. 453 */ waitAndAssertActivityResumed(ComponentName componentName)454 public void waitAndAssertActivityResumed(ComponentName componentName) { 455 waitAndAssertWindowManagerState("Waiting for activity to be resumed", 456 () -> mWmState.hasActivityState(componentName, WindowManagerState.STATE_RESUMED)); 457 } 458 459 /** 460 * Blocks until the given activity is in resumed state on the given display. 461 */ waitAndAssertActivityResumed(ComponentName componentName, int displayId)462 public void waitAndAssertActivityResumed(ComponentName componentName, int displayId) { 463 waitAndAssertActivityResumed(componentName); 464 mWmState.assertResumedActivities("Activity must be on display " + displayId, 465 mapping -> mapping.put(displayId, componentName)); 466 } 467 468 /** 469 * Blocks until the given activity is gone. 470 */ waitAndAssertActivityRemoved(ComponentName componentName)471 public void waitAndAssertActivityRemoved(ComponentName componentName) { 472 waitAndAssertWindowManagerState("Waiting for activity to be removed", 473 () -> !mWmState.containsActivity(componentName)); 474 } 475 476 /** 477 * Override the default retry limit of WindowManagerStateHelper. 478 * Destroying activities on virtual displays and destroying the virtual displays themselves 479 * takes longer than the default timeout of 5s. 480 */ waitAndAssertWindowManagerState( String message, BooleanSupplier waitCondition)481 private void waitAndAssertWindowManagerState( 482 String message, BooleanSupplier waitCondition) { 483 final Condition<String> condition = 484 new Condition<>(message, () -> { 485 mWmState.computeState(); 486 return waitCondition.getAsBoolean(); 487 }); 488 condition.setRetryLimit(10); 489 assertThat(Condition.waitFor(condition)).isTrue(); 490 } 491 prepareSurface(int width, int height)492 private Surface prepareSurface(int width, int height) { 493 ImageReader imageReader = new ImageReader.Builder(width, height).build(); 494 mTrackerRule.mImageReaders.add(imageReader); 495 return imageReader.getSurface(); 496 } 497 498 /** 499 * Internal rule that tracks all created virtual devices and displays and ensures they are 500 * properly closed and released after the test. 501 */ 502 private static final class VirtualDeviceTrackerRule extends ExternalResource { 503 504 final ArrayList<VirtualDevice> mVirtualDevices = new ArrayList<>(); 505 final ArrayList<VirtualDisplay> mVirtualDisplays = new ArrayList<>(); 506 final ArrayList<Activity> mActivities = new ArrayList<>(); 507 final ArrayList<ImageReader> mImageReaders = new ArrayList<>(); 508 509 @Override after()510 protected void after() { 511 for (Activity activity : mActivities) { 512 activity.finish(); 513 } 514 mActivities.clear(); 515 516 for (VirtualDevice virtualDevice : mVirtualDevices) { 517 virtualDevice.close(); 518 } 519 mVirtualDevices.clear(); 520 for (VirtualDisplay virtualDisplay : mVirtualDisplays) { 521 virtualDisplay.release(); 522 } 523 mVirtualDisplays.clear(); 524 for (ImageReader imageReader : mImageReaders) { 525 imageReader.close(); 526 } 527 mImageReaders.clear(); 528 super.after(); 529 } 530 } 531 532 } 533