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