xref: /aosp_15_r20/cts/tests/framework/base/windowmanager/util/src/android/server/wm/MultiDisplayTestBase.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.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.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
22 import static android.content.pm.PackageManager.FEATURE_INPUT_METHODS;
23 import static android.server.wm.ShellCommandHelper.executeShellCommand;
24 import static android.server.wm.UiDeviceUtils.pressSleepButton;
25 import static android.view.Display.INVALID_DISPLAY;
26 
27 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
28 
29 import static com.android.cts.mockime.ImeEventStreamTestUtils.clearAllEvents;
30 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
31 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
32 import static com.android.cts.mockime.ImeEventStreamTestUtils.withDescription;
33 
34 import static org.junit.Assert.assertEquals;
35 import static org.junit.Assert.assertNotEquals;
36 
37 import android.app.WallpaperManager;
38 import android.content.ComponentName;
39 import android.content.Context;
40 import android.content.pm.PackageManager;
41 import android.content.res.Configuration;
42 import android.inputmethodservice.InputMethodService;
43 import android.os.Bundle;
44 import android.server.wm.CommandSession.ActivitySession;
45 import android.server.wm.CommandSession.ActivitySessionClient;
46 import android.server.wm.WindowManagerState.DisplayContent;
47 import android.util.Pair;
48 
49 import androidx.annotation.Nullable;
50 
51 import com.android.cts.mockime.ImeEvent;
52 import com.android.cts.mockime.ImeEventStream;
53 import com.android.cts.mockime.ImeEventStreamTestUtils;
54 
55 import org.junit.Before;
56 import org.junit.ClassRule;
57 
58 import java.util.List;
59 import java.util.concurrent.TimeUnit;
60 import java.util.function.Consumer;
61 import java.util.function.Predicate;
62 
63 /**
64  * Base class for ActivityManager display tests.
65  *
66  * @see android.server.wm.display.DisplayTests
67  * @see android.server.wm.display.AppConfigurationTests
68  * @see android.server.wm.multidisplay.MultiDisplayKeyguardTests
69  * @see android.server.wm.multidisplay.MultiDisplayLockedKeyguardTests
70  */
71 public class MultiDisplayTestBase extends ActivityManagerTestBase {
72 
73     public static final int CUSTOM_DENSITY_DPI = 222;
74     protected Context mTargetContext;
75 
76     @ClassRule
77     public static DisableImmersiveModeConfirmationRule mDisableImmersiveModeConfirmationRule =
78             new DisableImmersiveModeConfirmationRule();
79 
80     @Before
81     @Override
setUp()82     public void setUp() throws Exception {
83         super.setUp();
84         mTargetContext = getInstrumentation().getTargetContext();
85     }
86 
isAutomotive()87     protected boolean isAutomotive() {
88         return mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE);
89     }
90 
supportsInstallableIme()91     protected boolean supportsInstallableIme() {
92         return mContext.getPackageManager().hasSystemFeature(FEATURE_INPUT_METHODS);
93     }
94 
95     public static class LetterboxAspectRatioSession extends IgnoreOrientationRequestSession {
96         private static final String WM_SET_LETTERBOX_STYLE_ASPECT_RATIO =
97                 "wm set-letterbox-style --aspectRatio ";
98         private static final String WM_RESET_LETTERBOX_STYLE_ASPECT_RATIO =
99                 "wm reset-letterbox-style aspectRatio";
100 
LetterboxAspectRatioSession(float aspectRatio)101         LetterboxAspectRatioSession(float aspectRatio) {
102             super(true);
103             executeShellCommand(WM_SET_LETTERBOX_STYLE_ASPECT_RATIO + aspectRatio);
104         }
105 
106         @Override
close()107         public void close() {
108             super.close();
109             executeShellCommand(WM_RESET_LETTERBOX_STYLE_ASPECT_RATIO);
110         }
111     }
112 
113     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedLetterboxAspectRatioSession( float aspectRatio)114     protected LetterboxAspectRatioSession createManagedLetterboxAspectRatioSession(
115             float aspectRatio) {
116         return mObjectTracker.manage(new LetterboxAspectRatioSession(aspectRatio));
117     }
118 
119     // TODO(b/112837428): Merge into VirtualDisplaySession when all usages are migrated.
120     public class VirtualDisplayLauncher extends VirtualDisplaySession {
121         private final ActivitySessionClient mActivitySessionClient = createActivitySessionClient();
122 
launchActivityOnDisplay(ComponentName activityName, DisplayContent display)123         public ActivitySession launchActivityOnDisplay(ComponentName activityName,
124                 DisplayContent display) {
125             return launchActivityOnDisplay(activityName, display, null /* extrasConsumer */,
126                     true /* withShellPermission */, true /* waitForLaunch */);
127         }
128 
launchActivityOnDisplay(ComponentName activityName, DisplayContent display, Consumer<Bundle> extrasConsumer, boolean withShellPermission, boolean waitForLaunch)129         public ActivitySession launchActivityOnDisplay(ComponentName activityName,
130                 DisplayContent display, Consumer<Bundle> extrasConsumer,
131                 boolean withShellPermission, boolean waitForLaunch) {
132             return launchActivity(builder -> builder
133                     // VirtualDisplayActivity is in different package. If the display is not public,
134                     // it requires shell permission to launch activity ({@see com.android.server.wm.
135                     // ActivityStackSupervisor#isCallerAllowedToLaunchOnDisplay}).
136                     .setWithShellPermission(withShellPermission)
137                     .setWaitForLaunched(waitForLaunch)
138                     .setIntentExtra(extrasConsumer)
139                     .setTargetActivity(activityName)
140                     .setDisplayId(display.mId));
141         }
142 
launchActivity(Consumer<LaunchActivityBuilder> setupBuilder)143         public ActivitySession launchActivity(Consumer<LaunchActivityBuilder> setupBuilder) {
144             final LaunchActivityBuilder builder = getLaunchActivityBuilder()
145                     .setUseInstrumentation();
146             setupBuilder.accept(builder);
147             return mActivitySessionClient.startActivity(builder);
148         }
149 
150         @Override
close()151         public void close() {
152             super.close();
153             mActivitySessionClient.close();
154         }
155     }
156 
157     /** A clearer alias of {@link Pair#create(Object, Object)}. */
pair(K k, V v)158     protected <K, V> Pair<K, V> pair(K k, V v) {
159         return new Pair<>(k, v);
160     }
161 
assertBothDisplaysHaveResumedActivities( Pair<Integer, ComponentName> firstPair, Pair<Integer, ComponentName> secondPair)162     protected void assertBothDisplaysHaveResumedActivities(
163             Pair<Integer, ComponentName> firstPair, Pair<Integer, ComponentName> secondPair) {
164         assertNotEquals("Displays must be different.  First display id: "
165                         + firstPair.first, firstPair.first, secondPair.first);
166         mWmState.assertResumedActivities("Both displays must have resumed activities",
167                 mapping -> {
168                     mapping.put(firstPair.first, firstPair.second);
169                     mapping.put(secondPair.first, secondPair.second);
170                 });
171     }
172 
173     /** Checks if the device supports multi-display. */
supportsMultiDisplay()174     protected boolean supportsMultiDisplay() {
175         return hasDeviceFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
176     }
177 
178     /** Checks if the device supports live wallpaper for multi-display. */
supportsLiveWallpaper()179     protected boolean supportsLiveWallpaper() {
180         return hasDeviceFeature(PackageManager.FEATURE_LIVE_WALLPAPER);
181     }
182 
183     /** Checks if the device supports wallpaper. */
supportsWallpaper()184     protected boolean supportsWallpaper() {
185         return WallpaperManager.getInstance(mContext).isWallpaperSupported();
186     }
187 
188     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedExternalDisplaySession()189     protected ExternalDisplaySession createManagedExternalDisplaySession() {
190         return mObjectTracker.manage(new ExternalDisplaySession());
191     }
192 
193     @SafeVarargs
waitOrderedImeEventsThenAssertImeShown(ImeEventStream stream, int displayId, Predicate<ImeEvent>... conditions)194     protected final void waitOrderedImeEventsThenAssertImeShown(ImeEventStream stream,
195             int displayId,
196             Predicate<ImeEvent>... conditions) throws Exception {
197         for (var condition : conditions) {
198             expectEvent(stream, condition, TimeUnit.SECONDS.toMillis(5) /* eventTimeout */);
199         }
200         // Assert the IME is shown on the expected display.
201         mWmState.waitAndAssertImeWindowShownOnDisplay(displayId);
202     }
203 
waitAndAssertImeNoScreenSizeChanged(ImeEventStream stream)204     protected void waitAndAssertImeNoScreenSizeChanged(ImeEventStream stream) {
205         notExpectEvent(stream, withDescription("onConfigurationChanged(SCREEN_SIZE | ..)",
206                 event -> "onConfigurationChanged".equals(event.getEventName())
207                         && (event.getArguments().getInt("ConfigUpdates") & CONFIG_SCREEN_SIZE)
208                         != 0), TimeUnit.SECONDS.toMillis(1) /* eventTimeout */);
209     }
210 
211     /**
212      * Clears all {@link InputMethodService#onConfigurationChanged(Configuration)} events from the
213      * given {@code stream} and returns a forked {@link ImeEventStream}.
214      *
215      * @see ImeEventStreamTestUtils#clearAllEvents(ImeEventStream, String)
216      */
clearOnConfigurationChangedFromStream(ImeEventStream stream)217     protected ImeEventStream clearOnConfigurationChangedFromStream(ImeEventStream stream) {
218         return clearAllEvents(stream, "onConfigurationChanged");
219     }
220 
221     /**
222      * This class is used when you need to test virtual display created by a privileged app.
223      *
224      * If you need to test virtual display created by a non-privileged app or when you need to test
225      * on simulated display, please use {@link VirtualDisplaySession} instead.
226      */
227     public class ExternalDisplaySession implements AutoCloseable {
228 
229         private boolean mCanShowWithInsecureKeyguard = false;
230         private boolean mPublicDisplay = false;
231         private boolean mShowSystemDecorations = false;
232 
233         private int mDisplayId = INVALID_DISPLAY;
234 
235         @Nullable
236         private VirtualDisplayHelper mExternalDisplayHelper;
237 
setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard)238         public ExternalDisplaySession setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard) {
239             mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard;
240             return this;
241         }
242 
setPublicDisplay(boolean publicDisplay)243         public ExternalDisplaySession setPublicDisplay(boolean publicDisplay) {
244             mPublicDisplay = publicDisplay;
245             return this;
246         }
247 
248         /**
249          * @deprecated untrusted virtual display won't have system decorations even it has the flag.
250          * Only use this method to verify that. To test secondary display with system decorations,
251          * please use simulated display.
252          */
253         @Deprecated
setShowSystemDecorations(boolean showSystemDecorations)254         public ExternalDisplaySession setShowSystemDecorations(boolean showSystemDecorations) {
255             mShowSystemDecorations = showSystemDecorations;
256             return this;
257         }
258 
259         /**
260          * Creates a private virtual display with insecure keyguard flags set.
261          */
createVirtualDisplay()262         public DisplayContent createVirtualDisplay() {
263             final List<DisplayContent> originalDS = getDisplaysStates();
264             final int originalDisplayCount = originalDS.size();
265 
266             mExternalDisplayHelper = new VirtualDisplayHelper();
267             mExternalDisplayHelper
268                     .setPublicDisplay(mPublicDisplay)
269                     .setCanShowWithInsecureKeyguard(mCanShowWithInsecureKeyguard)
270                     .setShowSystemDecorations(mShowSystemDecorations)
271                     .createAndWaitForDisplay();
272 
273             // Wait for the virtual display to be created and get configurations.
274             final List<DisplayContent> ds = getDisplayStateAfterChange(originalDisplayCount + 1);
275             assertEquals("New virtual display must be created", originalDisplayCount + 1,
276                     ds.size());
277 
278             // Find the newly added display.
279             final DisplayContent newDisplay = findNewDisplayStates(originalDS, ds).get(0);
280             mDisplayId = newDisplay.mId;
281             return newDisplay;
282         }
283 
turnDisplayOff()284         public void turnDisplayOff() {
285             if (mExternalDisplayHelper == null) {
286                 throw new RuntimeException("No external display created");
287             }
288             mExternalDisplayHelper.turnDisplayOff();
289         }
290 
turnDisplayOn()291         public void turnDisplayOn() {
292             if (mExternalDisplayHelper == null) {
293                 throw new RuntimeException("No external display created");
294             }
295             mExternalDisplayHelper.turnDisplayOn();
296         }
297 
298         @Override
close()299         public void close() {
300             if (mExternalDisplayHelper != null) {
301                 mExternalDisplayHelper.releaseDisplay();
302                 mExternalDisplayHelper = null;
303 
304                 waitForDisplayGone(d -> d.mId == mDisplayId);
305                 mDisplayId = INVALID_DISPLAY;
306             }
307         }
308     }
309 
310     public class PrimaryDisplayStateSession implements AutoCloseable {
311 
turnScreenOff()312         public void turnScreenOff() {
313             setPrimaryDisplayState(false);
314         }
315 
316         @Override
close()317         public void close() {
318             setPrimaryDisplayState(true);
319         }
320 
321         /** Turns the primary display on/off by pressing the power key */
setPrimaryDisplayState(boolean wantOn)322         private void setPrimaryDisplayState(boolean wantOn) {
323             if (wantOn) {
324                 UiDeviceUtils.wakeUpAndUnlock(mContext);
325             } else {
326                 pressSleepButton();
327             }
328             VirtualDisplayHelper.waitForDefaultDisplayState(wantOn);
329         }
330     }
331 }
332