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.server.wm;
18 
19 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
20 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
21 import static android.server.wm.ShellCommandHelper.executeShellCommand;
22 import static android.server.wm.UiDeviceUtils.pressSleepButton;
23 import static android.server.wm.app.Components.VIRTUAL_DISPLAY_ACTIVITY;
24 import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_CREATE_DISPLAY;
25 import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_DESTROY_DISPLAY;
26 import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_RESIZE_DISPLAY;
27 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD;
28 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_COMMAND;
29 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_COUNT;
30 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_DENSITY_DPI;
31 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_PRESENTATION_DISPLAY;
32 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_PUBLIC_DISPLAY;
33 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_RESIZE_DISPLAY;
34 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_SHOW_SYSTEM_DECORATIONS;
35 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_SUPPORTS_TOUCH;
36 import static android.server.wm.app.Components.VirtualDisplayActivity.VIRTUAL_DISPLAY_PREFIX;
37 import static android.view.Display.DEFAULT_DISPLAY;
38 import static android.view.Display.INVALID_DISPLAY;
39 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
40 
41 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
42 
43 import static com.android.cts.mockime.ImeEventStreamTestUtils.clearAllEvents;
44 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
45 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
46 import static com.android.cts.mockime.ImeEventStreamTestUtils.withDescription;
47 
48 import static org.hamcrest.MatcherAssert.assertThat;
49 import static org.hamcrest.Matchers.hasSize;
50 import static org.junit.Assert.assertEquals;
51 import static org.junit.Assert.assertNotEquals;
52 
53 import android.app.WallpaperManager;
54 import android.content.ComponentName;
55 import android.content.Context;
56 import android.content.pm.PackageManager;
57 import android.content.res.Configuration;
58 import android.inputmethodservice.InputMethodService;
59 import android.os.Bundle;
60 import android.provider.Settings;
61 import android.server.wm.CommandSession.ActivitySession;
62 import android.server.wm.CommandSession.ActivitySessionClient;
63 import android.server.wm.WindowManagerState.DisplayContent;
64 import android.server.wm.settings.SettingsSession;
65 import android.util.Pair;
66 import android.util.Size;
67 import android.view.WindowManager;
68 
69 import androidx.annotation.NonNull;
70 import androidx.annotation.Nullable;
71 
72 import com.android.compatibility.common.util.SystemUtil;
73 import com.android.cts.mockime.ImeEvent;
74 import com.android.cts.mockime.ImeEventStream;
75 import com.android.cts.mockime.ImeEventStreamTestUtils;
76 
77 import org.junit.Before;
78 import org.junit.ClassRule;
79 
80 import java.util.ArrayList;
81 import java.util.Collections;
82 import java.util.List;
83 import java.util.Objects;
84 import java.util.concurrent.TimeUnit;
85 import java.util.function.Consumer;
86 import java.util.function.Predicate;
87 
88 /**
89  * Base class for ActivityManager display tests.
90  *
91  * @see android.server.wm.display.DisplayTests
92  * @see android.server.wm.display.MultiDisplayKeyguardTests
93  * @see android.server.wm.display.MultiDisplayLockedKeyguardTests
94  * @see android.server.wm.display.AppConfigurationTests
95  */
96 public class MultiDisplayTestBase extends ActivityManagerTestBase {
97 
98     public static final int CUSTOM_DENSITY_DPI = 222;
99     private static final int INVALID_DENSITY_DPI = -1;
100     protected Context mTargetContext;
101 
102     @ClassRule
103     public static DisableImmersiveModeConfirmationRule mDisableImmersiveModeConfirmationRule =
104             new DisableImmersiveModeConfirmationRule();
105 
106     @Before
107     @Override
setUp()108     public void setUp() throws Exception {
109         super.setUp();
110         mTargetContext = getInstrumentation().getTargetContext();
111     }
112 
getDisplayState(int displayId)113     protected DisplayContent getDisplayState(int displayId) {
114         return getDisplayState(getDisplaysStates(), displayId);
115     }
116 
getDisplayState(List<DisplayContent> displays, int displayId)117     protected DisplayContent getDisplayState(List<DisplayContent> displays, int displayId) {
118         for (DisplayContent display : displays) {
119             if (display.mId == displayId) {
120                 return display;
121             }
122         }
123         return null;
124     }
125 
126     /** Return the display state with width, height, dpi. Always not default display. */
getDisplayState(List<DisplayContent> displays, int width, int height, int dpi)127     protected DisplayContent getDisplayState(List<DisplayContent> displays, int width, int height,
128             int dpi) {
129         for (DisplayContent display : displays) {
130             if (display.mId == DEFAULT_DISPLAY) {
131                 continue;
132             }
133             final Configuration config = display.getFullConfiguration();
134             if (config.densityDpi == dpi && config.screenWidthDp == width
135                     && config.screenHeightDp == height) {
136                 return display;
137             }
138         }
139         return null;
140     }
141 
getDisplaysStates()142     protected List<DisplayContent> getDisplaysStates() {
143         mWmState.computeState();
144         return mWmState.getDisplays();
145     }
146 
147     /** Find the display that was not originally reported in oldDisplays and added in newDisplays */
findNewDisplayStates(List<DisplayContent> oldDisplays, List<DisplayContent> newDisplays)148     List<DisplayContent> findNewDisplayStates(List<DisplayContent> oldDisplays,
149             List<DisplayContent> newDisplays) {
150         final ArrayList<DisplayContent> result = new ArrayList<>();
151 
152         for (DisplayContent newDisplay : newDisplays) {
153             if (oldDisplays.stream().noneMatch(d -> d.mId == newDisplay.mId)) {
154                 result.add(newDisplay);
155             }
156         }
157 
158         return result;
159     }
160 
161     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedDisplayMetricsSession(int displayId)162     protected DisplayMetricsSession createManagedDisplayMetricsSession(int displayId) {
163         return mObjectTracker.manage(new DisplayMetricsSession(displayId));
164     }
165 
166     public static class LetterboxAspectRatioSession extends IgnoreOrientationRequestSession {
167         private static final String WM_SET_LETTERBOX_STYLE_ASPECT_RATIO =
168                 "wm set-letterbox-style --aspectRatio ";
169         private static final String WM_RESET_LETTERBOX_STYLE_ASPECT_RATIO =
170                 "wm reset-letterbox-style aspectRatio";
171 
LetterboxAspectRatioSession(float aspectRatio)172         LetterboxAspectRatioSession(float aspectRatio) {
173             super(true);
174             executeShellCommand(WM_SET_LETTERBOX_STYLE_ASPECT_RATIO + aspectRatio);
175         }
176 
177         @Override
close()178         public void close() {
179             super.close();
180             executeShellCommand(WM_RESET_LETTERBOX_STYLE_ASPECT_RATIO);
181         }
182     }
183 
184     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedLetterboxAspectRatioSession( float aspectRatio)185     protected LetterboxAspectRatioSession createManagedLetterboxAspectRatioSession(
186             float aspectRatio) {
187         return mObjectTracker.manage(new LetterboxAspectRatioSession(aspectRatio));
188     }
189 
waitForDisplayGone(Predicate<DisplayContent> displayPredicate)190     void waitForDisplayGone(Predicate<DisplayContent> displayPredicate) {
191         waitForOrFail("displays to be removed", () -> {
192             mWmState.computeState();
193             return mWmState.getDisplays().stream().noneMatch(displayPredicate);
194         });
195     }
196 
197     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedVirtualDisplaySession()198     protected VirtualDisplaySession createManagedVirtualDisplaySession() {
199         return mObjectTracker.manage(new VirtualDisplaySession());
200     }
201 
202     /**
203      * This class should only be used when you need to test virtual display created by a
204      * non-privileged app.
205      * Or when you need to test on simulated display.
206      *
207      * If you need to test virtual display created by a privileged app, please use
208      * {@link ExternalDisplaySession} instead.
209      */
210     public class VirtualDisplaySession implements AutoCloseable {
211         private int mDensityDpi = CUSTOM_DENSITY_DPI;
212         private boolean mLaunchInSplitScreen = false;
213         private boolean mCanShowWithInsecureKeyguard = false;
214         private boolean mPublicDisplay = false;
215         private boolean mResizeDisplay = true;
216         private boolean mShowSystemDecorations = false;
217         private boolean mOwnContentOnly = false;
218         private int mDisplayImePolicy = DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
219         private boolean mPresentationDisplay = false;
220         private boolean mSimulateDisplay = false;
221         private boolean mSupportsTouch = false;
222         private boolean mMustBeCreated = true;
223         private Size mSimulationDisplaySize = new Size(1024 /* width */, 768 /* height */);
224 
225         private boolean mVirtualDisplayCreated = false;
226         private OverlayDisplayDevicesSession mOverlayDisplayDeviceSession;
227 
setDensityDpi(int densityDpi)228         VirtualDisplaySession setDensityDpi(int densityDpi) {
229             mDensityDpi = densityDpi;
230             return this;
231         }
232 
setLaunchInSplitScreen(boolean launchInSplitScreen)233         public VirtualDisplaySession setLaunchInSplitScreen(boolean launchInSplitScreen) {
234             mLaunchInSplitScreen = launchInSplitScreen;
235             return this;
236         }
237 
setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard)238         public VirtualDisplaySession setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard) {
239             mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard;
240             return this;
241         }
242 
setPublicDisplay(boolean publicDisplay)243         public VirtualDisplaySession setPublicDisplay(boolean publicDisplay) {
244             mPublicDisplay = publicDisplay;
245             return this;
246         }
247 
setResizeDisplay(boolean resizeDisplay)248         public VirtualDisplaySession setResizeDisplay(boolean resizeDisplay) {
249             mResizeDisplay = resizeDisplay;
250             return this;
251         }
252 
setShowSystemDecorations(boolean showSystemDecorations)253         public VirtualDisplaySession setShowSystemDecorations(boolean showSystemDecorations) {
254             mShowSystemDecorations = showSystemDecorations;
255             return this;
256         }
257 
setOwnContentOnly(boolean ownContentOnly)258         public VirtualDisplaySession setOwnContentOnly(boolean ownContentOnly) {
259             mOwnContentOnly = ownContentOnly;
260             return this;
261         }
262 
setSupportsTouch(boolean supportsTouch)263         public VirtualDisplaySession setSupportsTouch(boolean supportsTouch) {
264             mSupportsTouch = supportsTouch;
265             return this;
266         }
267 
268 
269         /**
270          * Sets the policy for how the display should show the ime.
271          *
272          * Set to one of:
273          *   <ul>
274          *     <li>{@link WindowManager#DISPLAY_IME_POLICY_LOCAL}
275          *     <li>{@link WindowManager#DISPLAY_IME_POLICY_FALLBACK_DISPLAY}
276          *     <li>{@link WindowManager#DISPLAY_IME_POLICY_HIDE}
277          *   </ul>
278          */
setDisplayImePolicy(int displayImePolicy)279         public VirtualDisplaySession setDisplayImePolicy(int displayImePolicy) {
280             mDisplayImePolicy = displayImePolicy;
281             return this;
282         }
283 
setPresentationDisplay(boolean presentationDisplay)284         public VirtualDisplaySession setPresentationDisplay(boolean presentationDisplay) {
285             mPresentationDisplay = presentationDisplay;
286             return this;
287         }
288 
289         // TODO(b/154565343) move simulate display out of VirtualDisplaySession
setSimulateDisplay(boolean simulateDisplay)290         public VirtualDisplaySession setSimulateDisplay(boolean simulateDisplay) {
291             mSimulateDisplay = simulateDisplay;
292             return this;
293         }
294 
setSimulationDisplaySize(int width, int height)295         public VirtualDisplaySession setSimulationDisplaySize(int width, int height) {
296             mSimulationDisplaySize = new Size(width, height);
297             return this;
298         }
299 
300         @Nullable
createDisplay(boolean mustBeCreated)301         public DisplayContent createDisplay(boolean mustBeCreated) {
302             mMustBeCreated = mustBeCreated;
303             final DisplayContent display = createDisplays(1).stream().findFirst().orElse(null);
304             if (mustBeCreated && display == null) {
305                 throw new IllegalStateException("No display is created");
306             }
307             return display;
308         }
309 
310         @NonNull
createDisplay()311         public DisplayContent createDisplay() {
312             return Objects.requireNonNull(createDisplay(true /* mustBeCreated */));
313         }
314 
315         @NonNull
createDisplays(int count)316         public List<DisplayContent> createDisplays(int count) {
317             if (mSimulateDisplay) {
318                 return simulateDisplays(count);
319             } else {
320                 return createVirtualDisplays(count);
321             }
322         }
323 
resizeDisplay()324         public void resizeDisplay() {
325             if (mSimulateDisplay) {
326                 throw new IllegalStateException(
327                         "Please use ReportedDisplayMetrics#setDisplayMetrics to resize"
328                                 + " simulate display");
329             }
330             executeShellCommand(getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
331                     + " -f 0x20000000" + " --es " + KEY_COMMAND + " " + COMMAND_RESIZE_DISPLAY);
332         }
333 
334         @Override
close()335         public void close()  {
336             if (mOverlayDisplayDeviceSession != null) {
337                 mOverlayDisplayDeviceSession.close();
338             }
339             if (mVirtualDisplayCreated) {
340                 destroyVirtualDisplays();
341                 mVirtualDisplayCreated = false;
342             }
343         }
344 
345         /**
346          * Simulate new display.
347          * <pre>
348          * <code>mDensityDpi</code> provide custom density for the display.
349          * </pre>
350          * @return {@link DisplayContent} of newly created display.
351          */
simulateDisplays(int count)352         private List<DisplayContent> simulateDisplays(int count) {
353             mOverlayDisplayDeviceSession = new OverlayDisplayDevicesSession(mContext);
354             mOverlayDisplayDeviceSession.createDisplays(mSimulationDisplaySize, mDensityDpi,
355                     mOwnContentOnly, mShowSystemDecorations, count);
356             mOverlayDisplayDeviceSession.configureDisplays(mDisplayImePolicy /* imePolicy */);
357             return mOverlayDisplayDeviceSession.getCreatedDisplays();
358         }
359 
360         /**
361          * Create new virtual display.
362          * <pre>
363          * <code>mDensityDpi</code> provide custom density for the display.
364          * <code>mLaunchInSplitScreen</code> start
365          *     {@link android.server.wm.app.VirtualDisplayActivity} to side from
366          *     {@link android.server.wm.app.LaunchingActivity} on primary display.
367          * <code>mCanShowWithInsecureKeyguard</code>  allow showing content when device is
368          *     showing an insecure keyguard.
369          * <code>mMustBeCreated</code> should assert if the display was or wasn't created.
370          * <code>mPublicDisplay</code> make display public.
371          * <code>mResizeDisplay</code> should resize display when surface size changes.
372          * <code>LaunchActivity</code> should launch test activity immediately after display
373          *     creation.
374          * </pre>
375          * @param displayCount number of displays to be created.
376          * @return A list of {@link DisplayContent} that represent newly created displays.
377          * @throws Exception
378          */
createVirtualDisplays(int displayCount)379         private List<DisplayContent> createVirtualDisplays(int displayCount) {
380             // Start an activity that is able to create virtual displays.
381             if (mLaunchInSplitScreen) {
382                 getLaunchActivityBuilder()
383                         .setToSide(true)
384                         .setTargetActivity(VIRTUAL_DISPLAY_ACTIVITY)
385                         .execute();
386                 final int secondaryTaskId =
387                         mWmState.getTaskByActivity(VIRTUAL_DISPLAY_ACTIVITY).getTaskId();
388                 mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
389             } else {
390                 launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
391             }
392             mWmState.computeState(
393                     new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
394             mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
395             mWmState.assertFocusedActivity("Focus must be on virtual display host activity",
396                     VIRTUAL_DISPLAY_ACTIVITY);
397             final List<DisplayContent> originalDS = getDisplaysStates();
398 
399             // Create virtual display with custom density dpi.
400             final StringBuilder createVirtualDisplayCommand = new StringBuilder(
401                     getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY))
402                     .append(" -f 0x20000000")
403                     .append(" --es " + KEY_COMMAND + " " + COMMAND_CREATE_DISPLAY);
404             if (mDensityDpi != INVALID_DENSITY_DPI) {
405                 createVirtualDisplayCommand
406                         .append(" --ei " + KEY_DENSITY_DPI + " ")
407                         .append(mDensityDpi);
408             }
409             createVirtualDisplayCommand.append(" --ei " + KEY_COUNT + " ").append(displayCount)
410                     .append(" --ez " + KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD + " ")
411                     .append(mCanShowWithInsecureKeyguard)
412                     .append(" --ez " + KEY_PUBLIC_DISPLAY + " ").append(mPublicDisplay)
413                     .append(" --ez " + KEY_RESIZE_DISPLAY + " ").append(mResizeDisplay)
414                     .append(" --ez " + KEY_SHOW_SYSTEM_DECORATIONS + " ")
415                     .append(mShowSystemDecorations)
416                     .append(" --ez " + KEY_PRESENTATION_DISPLAY + " ").append(mPresentationDisplay)
417                     .append(" --ez " + KEY_SUPPORTS_TOUCH + " ").append(mSupportsTouch);
418             executeShellCommand(createVirtualDisplayCommand.toString());
419             mVirtualDisplayCreated = true;
420 
421             return assertAndGetNewDisplays(mMustBeCreated ? displayCount : -1, originalDS);
422         }
423 
424         /**
425          * Destroy existing virtual display.
426          */
destroyVirtualDisplays()427         void destroyVirtualDisplays() {
428             final String destroyVirtualDisplayCommand = getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
429                     + " -f 0x20000000"
430                     + " --es " + KEY_COMMAND + " " + COMMAND_DESTROY_DISPLAY;
431             executeShellCommand(destroyVirtualDisplayCommand);
432             waitForDisplayGone(
433                     d -> d.getName() != null && d.getName().contains(VIRTUAL_DISPLAY_PREFIX));
434         }
435     }
436 
437     // TODO(b/112837428): Merge into VirtualDisplaySession when all usages are migrated.
438     public class VirtualDisplayLauncher extends VirtualDisplaySession {
439         private final ActivitySessionClient mActivitySessionClient = createActivitySessionClient();
440 
launchActivityOnDisplay(ComponentName activityName, DisplayContent display)441         public ActivitySession launchActivityOnDisplay(ComponentName activityName,
442                 DisplayContent display) {
443             return launchActivityOnDisplay(activityName, display, null /* extrasConsumer */,
444                     true /* withShellPermission */, true /* waitForLaunch */);
445         }
446 
launchActivityOnDisplay(ComponentName activityName, DisplayContent display, Consumer<Bundle> extrasConsumer, boolean withShellPermission, boolean waitForLaunch)447         public ActivitySession launchActivityOnDisplay(ComponentName activityName,
448                 DisplayContent display, Consumer<Bundle> extrasConsumer,
449                 boolean withShellPermission, boolean waitForLaunch) {
450             return launchActivity(builder -> builder
451                     // VirtualDisplayActivity is in different package. If the display is not public,
452                     // it requires shell permission to launch activity ({@see com.android.server.wm.
453                     // ActivityStackSupervisor#isCallerAllowedToLaunchOnDisplay}).
454                     .setWithShellPermission(withShellPermission)
455                     .setWaitForLaunched(waitForLaunch)
456                     .setIntentExtra(extrasConsumer)
457                     .setTargetActivity(activityName)
458                     .setDisplayId(display.mId));
459         }
460 
launchActivity(Consumer<LaunchActivityBuilder> setupBuilder)461         public ActivitySession launchActivity(Consumer<LaunchActivityBuilder> setupBuilder) {
462             final LaunchActivityBuilder builder = getLaunchActivityBuilder()
463                     .setUseInstrumentation();
464             setupBuilder.accept(builder);
465             return mActivitySessionClient.startActivity(builder);
466         }
467 
468         @Override
close()469         public void close() {
470             super.close();
471             mActivitySessionClient.close();
472         }
473     }
474 
475     /** Helper class to save, set, and restore overlay_display_devices preference. */
476     private class OverlayDisplayDevicesSession extends SettingsSession<String> {
477         /** See display_manager_overlay_display_name. */
478         private static final String OVERLAY_DISPLAY_NAME_PREFIX = "Overlay #";
479 
480         /** See {@link OverlayDisplayAdapter#OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY}. */
481         private static final String OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY = ",own_content_only";
482 
483         /**
484          * See {@link OverlayDisplayAdapter#OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}.
485          */
486         private static final String OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS =
487                 ",should_show_system_decorations";
488 
489         /** The displays which are created by this session. */
490         private final List<DisplayContent> mDisplays = new ArrayList<>();
491         /** The configured displays that need to be restored when this session is closed. */
492         private final List<OverlayDisplayState> mDisplayStates = new ArrayList<>();
493         private final WindowManager mWm;
494 
OverlayDisplayDevicesSession(Context context)495         OverlayDisplayDevicesSession(Context context) {
496             super(Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
497                     Settings.Global::getString,
498                     Settings.Global::putString);
499             // Remove existing overlay display to avoid display count problem.
500             removeExisting();
501             mWm = context.getSystemService(WindowManager.class);
502         }
503 
getCreatedDisplays()504         List<DisplayContent> getCreatedDisplays() {
505             return new ArrayList<>(mDisplays);
506         }
507 
508         @Override
set(String value)509         public void set(String value) {
510             final List<DisplayContent> originalDisplays = getDisplaysStates();
511             super.set(value);
512             final int newDisplayCount = 1 + (int) value.chars().filter(ch -> ch == ';').count();
513             mDisplays.addAll(assertAndGetNewDisplays(newDisplayCount, originalDisplays));
514         }
515 
516         /** Creates overlay display with custom density dpi, specified size, and test flags. */
createDisplays(Size displaySize, int densityDpi, boolean ownContentOnly, boolean shouldShowSystemDecorations, int count)517         void createDisplays(Size displaySize, int densityDpi, boolean ownContentOnly,
518                 boolean shouldShowSystemDecorations, int count) {
519             final StringBuilder builder = new StringBuilder();
520             for (int i = 0; i < count; i++) {
521                 String displaySettingsEntry = displaySize + "/" + densityDpi;
522                 if (ownContentOnly) {
523                     displaySettingsEntry += OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY;
524                 }
525                 if (shouldShowSystemDecorations) {
526                     displaySettingsEntry += OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
527                 }
528                 builder.append(displaySettingsEntry);
529                 // Creating n displays needs (n - 1) ';'.
530                 if (i < count - 1) {
531                     builder.append(';');
532                 }
533             }
534             set(builder.toString());
535         }
536 
configureDisplays(int imePolicy)537         void configureDisplays(int imePolicy) {
538             SystemUtil.runWithShellPermissionIdentity(() -> {
539                 for (DisplayContent display : mDisplays) {
540                     final int oldImePolicy = mWm.getDisplayImePolicy(display.mId);
541                     mDisplayStates.add(new OverlayDisplayState(display.mId, oldImePolicy));
542                     if (imePolicy != oldImePolicy) {
543                         mWm.setDisplayImePolicy(display.mId, imePolicy);
544                         waitForOrFail("display config show-IME to be set",
545                                 () -> (mWm.getDisplayImePolicy(display.mId) == imePolicy));
546                     }
547                 }
548             });
549         }
550 
restoreDisplayStates()551         private void restoreDisplayStates() {
552             mDisplayStates.forEach(state -> SystemUtil.runWithShellPermissionIdentity(() -> {
553                 mWm.setDisplayImePolicy(state.mId, state.mImePolicy);
554 
555                 // Only need to wait the last flag to be set.
556                 waitForOrFail("display config show-IME to be restored",
557                         () -> (mWm.getDisplayImePolicy(state.mId) == state.mImePolicy));
558             }));
559         }
560 
561         @Override
close()562         public void close() {
563             // Need to restore display state before display is destroyed.
564             restoreDisplayStates();
565             super.close();
566             // Waiting for restoring to the state before this session was created.
567             waitForDisplayGone(display -> mDisplays.stream()
568                     .anyMatch(createdDisplay -> createdDisplay.mId == display.mId));
569         }
570 
removeExisting()571         private void removeExisting() {
572             if (!mHasInitialValue || mInitialValue == null) {
573                 // No existing overlay displays.
574                 return;
575             }
576             delete(mUri);
577             // Make sure all overlay displays are completely removed.
578             waitForDisplayGone(
579                     display -> display.getName().startsWith(OVERLAY_DISPLAY_NAME_PREFIX));
580         }
581 
582         private class OverlayDisplayState {
583             int mId;
584             int mImePolicy;
585 
OverlayDisplayState(int displayId, int imePolicy)586             OverlayDisplayState(int displayId, int imePolicy) {
587                 mId = displayId;
588                 mImePolicy = imePolicy;
589             }
590         }
591     }
592 
593     /** Wait for provided number of displays and report their configurations. */
getDisplayStateAfterChange(int expectedDisplayCount)594     protected List<DisplayContent> getDisplayStateAfterChange(int expectedDisplayCount) {
595         return Condition.waitForResult("the correct number of displays=" + expectedDisplayCount,
596                 condition -> condition
597                         .setReturnLastResult(true)
598                         .setResultSupplier(this::getDisplaysStates)
599                         .setResultValidator(
600                                 displays -> areDisplaysValid(displays, expectedDisplayCount)));
601     }
602 
areDisplaysValid(List<DisplayContent> displays, int expectedDisplayCount)603     private boolean areDisplaysValid(List<DisplayContent> displays, int expectedDisplayCount) {
604         if (displays.size() != expectedDisplayCount) {
605             return false;
606         }
607         for (DisplayContent display : displays) {
608             if (display.getOverrideConfiguration().densityDpi == 0) {
609                 return false;
610             }
611         }
612         return true;
613     }
614 
615     /**
616      * Wait for desired number of displays to be created and get their properties.
617      *
618      * @param newDisplayCount expected display count, -1 if display should not be created.
619      * @param originalDisplays display states before creation of new display(s).
620      * @return list of new displays, empty list if no new display is created.
621      */
assertAndGetNewDisplays(int newDisplayCount, List<DisplayContent> originalDisplays)622     private List<DisplayContent> assertAndGetNewDisplays(int newDisplayCount,
623             List<DisplayContent> originalDisplays) {
624         final int originalDisplayCount = originalDisplays.size();
625 
626         // Wait for the display(s) to be created and get configurations.
627         final List<DisplayContent> ds = getDisplayStateAfterChange(
628                 originalDisplayCount + newDisplayCount);
629         if (newDisplayCount != -1) {
630             assertEquals("New virtual display(s) must be created",
631                     originalDisplayCount + newDisplayCount, ds.size());
632         } else {
633             assertEquals("New virtual display must not be created",
634                     originalDisplayCount, ds.size());
635             return Collections.emptyList();
636         }
637 
638         // Find the newly added display(s).
639         final List<DisplayContent> newDisplays = findNewDisplayStates(originalDisplays, ds);
640         assertThat("New virtual display must be created", newDisplays, hasSize(newDisplayCount));
641 
642         return newDisplays;
643     }
644 
645     /** A clearer alias of {@link Pair#create(Object, Object)}. */
pair(K k, V v)646     protected <K, V> Pair<K, V> pair(K k, V v) {
647         return new Pair<>(k, v);
648     }
649 
assertBothDisplaysHaveResumedActivities( Pair<Integer, ComponentName> firstPair, Pair<Integer, ComponentName> secondPair)650     protected void assertBothDisplaysHaveResumedActivities(
651             Pair<Integer, ComponentName> firstPair, Pair<Integer, ComponentName> secondPair) {
652         assertNotEquals("Displays must be different.  First display id: "
653                         + firstPair.first, firstPair.first, secondPair.first);
654         mWmState.assertResumedActivities("Both displays must have resumed activities",
655                 mapping -> {
656                     mapping.put(firstPair.first, firstPair.second);
657                     mapping.put(secondPair.first, secondPair.second);
658                 });
659     }
660 
661     /** Checks if the device supports multi-display. */
supportsMultiDisplay()662     protected boolean supportsMultiDisplay() {
663         return hasDeviceFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
664     }
665 
666     /** Checks if the device supports live wallpaper for multi-display. */
supportsLiveWallpaper()667     protected boolean supportsLiveWallpaper() {
668         return hasDeviceFeature(PackageManager.FEATURE_LIVE_WALLPAPER);
669     }
670 
671     /** Checks if the device supports wallpaper. */
supportsWallpaper()672     protected boolean supportsWallpaper() {
673         return WallpaperManager.getInstance(mContext).isWallpaperSupported();
674     }
675 
676     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedExternalDisplaySession()677     protected ExternalDisplaySession createManagedExternalDisplaySession() {
678         return mObjectTracker.manage(new ExternalDisplaySession());
679     }
680 
681     @SafeVarargs
waitOrderedImeEventsThenAssertImeShown(ImeEventStream stream, int displayId, Predicate<ImeEvent>... conditions)682     protected final void waitOrderedImeEventsThenAssertImeShown(ImeEventStream stream,
683             int displayId,
684             Predicate<ImeEvent>... conditions) throws Exception {
685         for (var condition : conditions) {
686             expectEvent(stream, condition, TimeUnit.SECONDS.toMillis(5) /* eventTimeout */);
687         }
688         // Assert the IME is shown on the expected display.
689         mWmState.waitAndAssertImeWindowShownOnDisplay(displayId);
690     }
691 
waitAndAssertImeNoScreenSizeChanged(ImeEventStream stream)692     protected void waitAndAssertImeNoScreenSizeChanged(ImeEventStream stream) {
693         notExpectEvent(stream, withDescription("onConfigurationChanged(SCREEN_SIZE | ..)",
694                 event -> "onConfigurationChanged".equals(event.getEventName())
695                         && (event.getArguments().getInt("ConfigUpdates") & CONFIG_SCREEN_SIZE)
696                         != 0), TimeUnit.SECONDS.toMillis(1) /* eventTimeout */);
697     }
698 
699     /**
700      * Clears all {@link InputMethodService#onConfigurationChanged(Configuration)} events from the
701      * given {@code stream} and returns a forked {@link ImeEventStream}.
702      *
703      * @see ImeEventStreamTestUtils#clearAllEvents(ImeEventStream, String)
704      */
clearOnConfigurationChangedFromStream(ImeEventStream stream)705     protected ImeEventStream clearOnConfigurationChangedFromStream(ImeEventStream stream) {
706         return clearAllEvents(stream, "onConfigurationChanged");
707     }
708 
709     /**
710      * This class is used when you need to test virtual display created by a privileged app.
711      *
712      * If you need to test virtual display created by a non-privileged app or when you need to test
713      * on simulated display, please use {@link VirtualDisplaySession} instead.
714      */
715     public class ExternalDisplaySession implements AutoCloseable {
716 
717         private boolean mCanShowWithInsecureKeyguard = false;
718         private boolean mPublicDisplay = false;
719         private boolean mShowSystemDecorations = false;
720 
721         private int mDisplayId = INVALID_DISPLAY;
722 
723         @Nullable
724         private VirtualDisplayHelper mExternalDisplayHelper;
725 
setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard)726         public ExternalDisplaySession setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard) {
727             mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard;
728             return this;
729         }
730 
setPublicDisplay(boolean publicDisplay)731         public ExternalDisplaySession setPublicDisplay(boolean publicDisplay) {
732             mPublicDisplay = publicDisplay;
733             return this;
734         }
735 
736         /**
737          * @deprecated untrusted virtual display won't have system decorations even it has the flag.
738          * Only use this method to verify that. To test secondary display with system decorations,
739          * please use simulated display.
740          */
741         @Deprecated
setShowSystemDecorations(boolean showSystemDecorations)742         public ExternalDisplaySession setShowSystemDecorations(boolean showSystemDecorations) {
743             mShowSystemDecorations = showSystemDecorations;
744             return this;
745         }
746 
747         /**
748          * Creates a private virtual display with insecure keyguard flags set.
749          */
createVirtualDisplay()750         public DisplayContent createVirtualDisplay() {
751             final List<DisplayContent> originalDS = getDisplaysStates();
752             final int originalDisplayCount = originalDS.size();
753 
754             mExternalDisplayHelper = new VirtualDisplayHelper();
755             mExternalDisplayHelper
756                     .setPublicDisplay(mPublicDisplay)
757                     .setCanShowWithInsecureKeyguard(mCanShowWithInsecureKeyguard)
758                     .setShowSystemDecorations(mShowSystemDecorations)
759                     .createAndWaitForDisplay();
760 
761             // Wait for the virtual display to be created and get configurations.
762             final List<DisplayContent> ds = getDisplayStateAfterChange(originalDisplayCount + 1);
763             assertEquals("New virtual display must be created", originalDisplayCount + 1,
764                     ds.size());
765 
766             // Find the newly added display.
767             final DisplayContent newDisplay = findNewDisplayStates(originalDS, ds).get(0);
768             mDisplayId = newDisplay.mId;
769             return newDisplay;
770         }
771 
turnDisplayOff()772         public void turnDisplayOff() {
773             if (mExternalDisplayHelper == null) {
774                 throw new RuntimeException("No external display created");
775             }
776             mExternalDisplayHelper.turnDisplayOff();
777         }
778 
turnDisplayOn()779         public void turnDisplayOn() {
780             if (mExternalDisplayHelper == null) {
781                 throw new RuntimeException("No external display created");
782             }
783             mExternalDisplayHelper.turnDisplayOn();
784         }
785 
786         @Override
close()787         public void close() {
788             if (mExternalDisplayHelper != null) {
789                 mExternalDisplayHelper.releaseDisplay();
790                 mExternalDisplayHelper = null;
791 
792                 waitForDisplayGone(d -> d.mId == mDisplayId);
793                 mDisplayId = INVALID_DISPLAY;
794             }
795         }
796     }
797 
798     public class PrimaryDisplayStateSession implements AutoCloseable {
799 
turnScreenOff()800         public void turnScreenOff() {
801             setPrimaryDisplayState(false);
802         }
803 
804         @Override
close()805         public void close() {
806             setPrimaryDisplayState(true);
807         }
808 
809         /** Turns the primary display on/off by pressing the power key */
setPrimaryDisplayState(boolean wantOn)810         private void setPrimaryDisplayState(boolean wantOn) {
811             if (wantOn) {
812                 UiDeviceUtils.wakeUpAndUnlock(mContext);
813             } else {
814                 pressSleepButton();
815             }
816             VirtualDisplayHelper.waitForDefaultDisplayState(wantOn);
817         }
818     }
819 }
820