xref: /aosp_15_r20/cts/tests/tests/virtualdevice/common/src/android/virtualdevice/cts/common/VirtualDeviceRule.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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