xref: /aosp_15_r20/cts/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2016 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.app.AppOpsManager.MODE_ALLOWED;
20 import static android.app.AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW;
21 import static android.app.Instrumentation.ActivityMonitor;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
23 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
24 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
25 import static android.content.Intent.ACTION_MAIN;
26 import static android.content.Intent.CATEGORY_HOME;
27 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
28 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
29 import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
30 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
31 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
32 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
33 import static android.content.pm.PackageManager.DONT_KILL_APP;
34 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
35 import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
36 import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
37 import static android.content.pm.PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE;
38 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
39 import static android.content.pm.PackageManager.FEATURE_INPUT_METHODS;
40 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
41 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
42 import static android.content.pm.PackageManager.FEATURE_SCREEN_LANDSCAPE;
43 import static android.content.pm.PackageManager.FEATURE_SCREEN_PORTRAIT;
44 import static android.content.pm.PackageManager.FEATURE_SECURE_LOCK_SCREEN;
45 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
46 import static android.content.pm.PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE;
47 import static android.content.pm.PackageManager.FEATURE_WATCH;
48 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
49 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
50 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
51 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
52 import static android.os.UserHandle.USER_ALL;
53 import static android.provider.Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS;
54 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
55 import static android.server.wm.ActivityLauncher.KEY_NEW_TASK;
56 import static android.server.wm.ActivityLauncher.KEY_TARGET_COMPONENT;
57 import static android.server.wm.ComponentNameUtils.getActivityName;
58 import static android.server.wm.ComponentNameUtils.getLogTag;
59 import static android.server.wm.ShellCommandHelper.executeShellCommand;
60 import static android.server.wm.ShellCommandHelper.executeShellCommandAndGetStdout;
61 import static android.server.wm.StateLogger.log;
62 import static android.server.wm.StateLogger.logE;
63 import static android.server.wm.UiDeviceUtils.pressSleepButton;
64 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
65 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
66 import static android.server.wm.WindowManagerState.STATE_PAUSED;
67 import static android.server.wm.WindowManagerState.STATE_RESUMED;
68 import static android.server.wm.WindowManagerState.STATE_STOPPED;
69 import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY;
70 import static android.server.wm.app.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST;
71 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_BROADCAST_ORIENTATION;
72 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_CUTOUT_EXISTS;
73 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD;
74 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD_METHOD;
75 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST;
76 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_MOVE_BROADCAST_TO_BACK;
77 import static android.server.wm.app.Components.PipActivity.ACTION_CHANGE_ASPECT_RATIO;
78 import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP;
79 import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP_AND_WAIT_FOR_UI_STATE;
80 import static android.server.wm.app.Components.PipActivity.ACTION_EXPAND_PIP;
81 import static android.server.wm.app.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
82 import static android.server.wm.app.Components.PipActivity.ACTION_UPDATE_PIP_STATE;
83 import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION;
84 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_DENOMINATOR;
85 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_NUMERATOR;
86 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR;
87 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR;
88 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_PIP_CALLBACK;
89 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_PIP_STASHED;
90 import static android.server.wm.app.Components.TEST_ACTIVITY;
91 import static android.server.wm.app.Components.VIRTUAL_DISPLAY_ACTIVITY;
92 import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_CREATE_DISPLAY;
93 import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_DESTROY_DISPLAY;
94 import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_RESIZE_DISPLAY;
95 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD;
96 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_COMMAND;
97 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_COUNT;
98 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_DENSITY_DPI;
99 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_PRESENTATION_DISPLAY;
100 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_PUBLIC_DISPLAY;
101 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_RESIZE_DISPLAY;
102 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_SHOW_SYSTEM_DECORATIONS;
103 import static android.server.wm.app.Components.VirtualDisplayActivity.KEY_SUPPORTS_TOUCH;
104 import static android.server.wm.app.Components.VirtualDisplayActivity.VIRTUAL_DISPLAY_PREFIX;
105 import static android.server.wm.second.Components.SECOND_ACTIVITY;
106 import static android.server.wm.third.Components.THIRD_ACTIVITY;
107 import static android.view.Display.DEFAULT_DISPLAY;
108 import static android.view.Surface.ROTATION_0;
109 import static android.view.Surface.ROTATION_90;
110 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
111 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
112 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
113 import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
114 
115 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
116 
117 import static org.hamcrest.MatcherAssert.assertThat;
118 import static org.hamcrest.Matchers.hasSize;
119 import static org.junit.Assert.assertEquals;
120 import static org.junit.Assert.assertNotEquals;
121 import static org.junit.Assert.assertNotNull;
122 import static org.junit.Assert.assertTrue;
123 import static org.junit.Assert.fail;
124 import static org.junit.Assume.assumeFalse;
125 import static org.junit.Assume.assumeTrue;
126 
127 import static java.lang.Integer.toHexString;
128 
129 import android.app.Activity;
130 import android.app.ActivityManager;
131 import android.app.ActivityOptions;
132 import android.app.ActivityTaskManager;
133 import android.app.Instrumentation;
134 import android.app.KeyguardManager;
135 import android.app.WallpaperManager;
136 import android.app.WindowConfiguration;
137 import android.content.ComponentName;
138 import android.content.Context;
139 import android.content.Intent;
140 import android.content.pm.PackageManager;
141 import android.content.pm.ResolveInfo;
142 import android.content.res.Configuration;
143 import android.content.res.Resources;
144 import android.graphics.Bitmap;
145 import android.graphics.Canvas;
146 import android.graphics.Color;
147 import android.graphics.Rect;
148 import android.hardware.display.AmbientDisplayConfiguration;
149 import android.hardware.display.DisplayManager;
150 import android.os.Bundle;
151 import android.os.Process;
152 import android.os.RemoteCallback;
153 import android.os.SystemClock;
154 import android.os.SystemProperties;
155 import android.provider.Settings;
156 import android.server.wm.CommandSession.ActivityCallback;
157 import android.server.wm.CommandSession.ActivitySession;
158 import android.server.wm.CommandSession.ActivitySessionClient;
159 import android.server.wm.CommandSession.ConfigInfo;
160 import android.server.wm.CommandSession.SizeInfo;
161 import android.server.wm.TestJournalProvider.TestJournalContainer;
162 import android.server.wm.WindowManagerState.DisplayContent;
163 import android.server.wm.WindowManagerState.Task;
164 import android.server.wm.WindowManagerState.WindowState;
165 import android.server.wm.settings.SettingsSession;
166 import android.util.DisplayMetrics;
167 import android.util.EventLog;
168 import android.util.EventLog.Event;
169 import android.util.Log;
170 import android.util.Pair;
171 import android.util.Size;
172 import android.view.Display;
173 import android.view.View;
174 import android.view.WindowManager;
175 
176 import androidx.annotation.NonNull;
177 import androidx.annotation.Nullable;
178 import androidx.test.core.app.ApplicationProvider;
179 import androidx.test.ext.junit.rules.ActivityScenarioRule;
180 
181 import com.android.compatibility.common.util.AppOpsUtils;
182 import com.android.compatibility.common.util.FeatureUtil;
183 import com.android.compatibility.common.util.GestureNavSwitchHelper;
184 import com.android.compatibility.common.util.SystemUtil;
185 import com.android.compatibility.common.util.UserHelper;
186 
187 import org.junit.After;
188 import org.junit.Before;
189 import org.junit.Rule;
190 import org.junit.rules.ErrorCollector;
191 import org.junit.rules.RuleChain;
192 import org.junit.rules.TestRule;
193 import org.junit.runner.Description;
194 import org.junit.runners.model.Statement;
195 
196 import java.io.IOException;
197 import java.util.ArrayList;
198 import java.util.Arrays;
199 import java.util.Collections;
200 import java.util.Iterator;
201 import java.util.List;
202 import java.util.Objects;
203 import java.util.Optional;
204 import java.util.UUID;
205 import java.util.concurrent.CompletableFuture;
206 import java.util.concurrent.TimeUnit;
207 import java.util.concurrent.atomic.AtomicBoolean;
208 import java.util.function.BooleanSupplier;
209 import java.util.function.Predicate;
210 import java.util.function.Supplier;
211 import java.util.regex.Matcher;
212 import java.util.regex.Pattern;
213 
214 public abstract class ActivityManagerTestBase {
215     private static final String TAG = ActivityManagerTestBase.class.getSimpleName();
216     private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false;
217     private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false;
218     private static final String LOG_SEPARATOR = "LOG_SEPARATOR";
219     // Use one of the test tags as a separator
220     private static final int EVENT_LOG_SEPARATOR_TAG = 42;
221 
222     private static final String TEST_PACKAGE = TEST_ACTIVITY.getPackageName();
223     private static final String SECOND_TEST_PACKAGE = SECOND_ACTIVITY.getPackageName();
224     private static final String THIRD_TEST_PACKAGE = THIRD_ACTIVITY.getPackageName();
225     private static final List<String> TEST_PACKAGES = List.of(
226             TEST_PACKAGE,
227             SECOND_TEST_PACKAGE,
228             THIRD_TEST_PACKAGE,
229             "android.server.wm.cts",
230             "android.server.wm.jetpack",
231             "android.server.wm.jetpack.second"
232     );
233 
234     protected static final String AM_START_HOME_ACTIVITY_COMMAND =
235             "am start -a android.intent.action.MAIN -c android.intent.category.HOME --user "
236                     + Process.myUserHandle().getIdentifier();
237 
238     protected static final String MSG_NO_MOCK_IME =
239             "MockIme cannot be used for devices that do not support installable IMEs";
240 
241     private static final String AM_BROADCAST_CLOSE_SYSTEM_DIALOGS =
242             "am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --user " + USER_ALL;
243 
244     private static final String ASSIGN_USER_TO_EXTRA_DISPLAY =
245             "cmd car_service assign-extra-display ";
246 
247     private static final String UNASSIGN_USER_TO_EXTRA_DISPLAY =
248             "cmd car_service unassign-extra-display ";
249 
250     protected static final String LOCK_CREDENTIAL = "1234";
251 
252     private static final int UI_MODE_TYPE_MASK = 0x0f;
253     private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
254 
255     public static final boolean ENABLE_SHELL_TRANSITIONS =
256             SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
257 
258     private static Boolean sHasHomeScreen = null;
259     private static Boolean sSupportsSystemDecorsOnSecondaryDisplays = null;
260     private static Boolean sIsAssistantOnTop = null;
261     private static Boolean sIsTablet = null;
262     private static Boolean sDismissDreamOnActivityStart = null;
263     private static GestureNavSwitchHelper sGestureNavSwitchHelper = null;
264     private static boolean sIllegalTaskStateFound;
265 
266     protected static final int INVALID_DEVICE_ROTATION = -1;
267 
268     protected final Instrumentation mInstrumentation = getInstrumentation();
269     protected final Context mContext = getInstrumentation().getContext();
270     protected final ActivityManager mAm = mContext.getSystemService(ActivityManager.class);
271     protected final ActivityTaskManager mAtm = mContext.getSystemService(ActivityTaskManager.class);
272     protected final DisplayManager mDm = mContext.getSystemService(DisplayManager.class);
273     protected final WindowManager mWm = mContext.getSystemService(WindowManager.class);
274     protected final KeyguardManager mKm = mContext.getSystemService(KeyguardManager.class);
275     private final UserHelper mUserHelper = new UserHelper(mContext);
276 
277     /** The tracker to manage objects (especially {@link AutoCloseable}) in a test method. */
278     protected final ObjectTracker mObjectTracker = new ObjectTracker();
279 
280     /** The last rule to handle all errors. */
281     private final ErrorCollector mPostAssertionRule = new PostAssertionRule();
282 
283     /** The necessary procedures of set up and tear down. */
284     @Rule
285     public final TestRule mBaseRule = RuleChain.outerRule(mPostAssertionRule)
286             .around(new WrapperRule(null /* before */, this::tearDownBase));
287 
288     /**
289      * Whether to wait for the rotation to be stable state after testing. It can be set if the
290      * display rotation may be changed by test.
291      */
292     protected boolean mWaitForRotationOnTearDown;
293 
294     /** Indicate to wait for all non-home activities to be destroyed when test finished. */
295     protected boolean mShouldWaitForAllNonHomeActivitiesToDestroyed = false;
296     protected int mUserId;
297 
298     @NonNull
299     private SplitScreenActivityUtils mSplitScreenActivityUtils;
300 
301     /**
302      * @return the am command to start the given activity with the following extra key/value pairs.
303      * {@param extras} a list of {@link CliIntentExtra} representing a generic intent extra
304      */
305     // TODO: Make this more generic, for instance accepting flags or extras of other types.
getAmStartCmd(final ComponentName activityName, final CliIntentExtra... extras)306     protected static String getAmStartCmd(final ComponentName activityName,
307             final CliIntentExtra... extras) {
308         return getAmStartCmdInternal(getActivityName(activityName), extras);
309     }
310 
getAmStartCmdInternal(final String activityName, final CliIntentExtra... extras)311     private static String getAmStartCmdInternal(final String activityName,
312             final CliIntentExtra... extras) {
313         return appendKeyValuePairs(
314                 new StringBuilder("am start --user ").append(Process.myUserHandle().getIdentifier())
315                         .append(" -n ").append(activityName), extras);
316     }
317 
appendKeyValuePairs( final StringBuilder cmd, final CliIntentExtra... extras)318     private static String appendKeyValuePairs(
319             final StringBuilder cmd, final CliIntentExtra... extras) {
320         for (int i = 0; i < extras.length; i++) {
321             extras[i].appendTo(cmd);
322         }
323         return cmd.toString();
324     }
325 
getAmStartCmd(final ComponentName activityName, final int displayId, final CliIntentExtra... extras)326     protected static String getAmStartCmd(final ComponentName activityName, final int displayId,
327             final CliIntentExtra... extras) {
328         return getAmStartCmdInternal(getActivityName(activityName), displayId, extras);
329     }
330 
getAmStartCmdInternal(final String activityName, final int displayId, final CliIntentExtra... extras)331     private static String getAmStartCmdInternal(final String activityName, final int displayId,
332             final CliIntentExtra... extras) {
333         return appendKeyValuePairs(
334                 new StringBuilder("am start -n ")
335                         .append(activityName)
336                         .append(" -f 0x")
337                         .append(toHexString(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK))
338                         .append(" --display ")
339                         .append(displayId)
340                         .append(" --user ")
341                         .append(Process.myUserHandle().getIdentifier()),
342                 extras);
343     }
344 
getAmStartCmdInNewTask(final ComponentName activityName)345     protected static String getAmStartCmdInNewTask(final ComponentName activityName) {
346         return "am start -n " + getActivityName(activityName) + " -f 0x18000000 --user "
347                 + Process.myUserHandle().getIdentifier();
348     }
349 
getAmStartCmdWithData(final ComponentName activityName, String data)350     protected static String getAmStartCmdWithData(final ComponentName activityName, String data) {
351         return "am start -n " + getActivityName(activityName) + " -d " + data + " --user "
352                 + Process.myUserHandle().getIdentifier();
353     }
354 
getAmStartCmdWithNoAnimation(final ComponentName activityName, final CliIntentExtra... extras)355     protected static String getAmStartCmdWithNoAnimation(final ComponentName activityName,
356             final CliIntentExtra... extras) {
357         return appendKeyValuePairs(
358                 new StringBuilder("am start -n ")
359                         .append(getActivityName(activityName))
360                         .append(" -f 0x")
361                         .append(toHexString(FLAG_ACTIVITY_NO_ANIMATION)),
362                 extras);
363     }
364 
getAmStartCmdWithDismissKeyguardIfInsecure( final ComponentName activityName)365     protected static String getAmStartCmdWithDismissKeyguardIfInsecure(
366             final ComponentName activityName) {
367         return "am start --dismiss-keyguard-if-insecure -n " + getActivityName(activityName);
368     }
369 
getAmStartCmdWithNoUserAction(final ComponentName activityName, final CliIntentExtra... extras)370     protected static String getAmStartCmdWithNoUserAction(final ComponentName activityName,
371             final CliIntentExtra... extras) {
372         return appendKeyValuePairs(
373                 new StringBuilder("am start -n ")
374                         .append(getActivityName(activityName))
375                         .append(" -f 0x")
376                         .append(toHexString(FLAG_ACTIVITY_NO_USER_ACTION)),
377                 extras);
378     }
379 
getAmStartCmdWithWindowingMode( final ComponentName activityName, int windowingMode)380     protected static String getAmStartCmdWithWindowingMode(
381             final ComponentName activityName, int windowingMode) {
382         return getAmStartCmdInNewTask(activityName) + " --windowingMode " + windowingMode;
383     }
384 
385     protected WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
386     protected TouchHelper mTouchHelper = new TouchHelper(mInstrumentation, mWmState);
387     // Initialized in setUp to execute with proper permission, such as MANAGE_ACTIVITY_TASKS
388     public TestTaskOrganizer mTaskOrganizer;
389 
getWmState()390     public WindowManagerStateHelper getWmState() {
391         return mWmState;
392     }
393 
394     protected BroadcastActionTrigger mBroadcastActionTrigger = new BroadcastActionTrigger();
395 
396     /** Runs a runnable with shell permissions. These can be nested. */
runWithShellPermission(Runnable runnable)397     protected void runWithShellPermission(Runnable runnable) {
398         NestedShellPermission.run(runnable);
399     }
400 
401     /**
402      * Returns true if the activity is shown before timeout.
403      */
waitForActivityFocused(int timeoutMs, ComponentName componentName)404     protected boolean waitForActivityFocused(int timeoutMs, ComponentName componentName) {
405         waitForActivityResumed(timeoutMs, componentName);
406         return getActivityName(componentName).equals(mWmState.getFocusedActivity());
407     }
408 
waitForActivityResumed(int timeoutMs, ComponentName componentName)409     protected void waitForActivityResumed(int timeoutMs, ComponentName componentName) {
410         waitForActivityState(timeoutMs, componentName, STATE_RESUMED);
411     }
412 
waitForActivityState(int timeoutMs, ComponentName componentName, String expectedState)413     protected void waitForActivityState(int timeoutMs, ComponentName componentName,
414             String expectedState) {
415         long endTime = System.currentTimeMillis() + timeoutMs;
416         while (endTime > System.currentTimeMillis()) {
417             mWmState.computeState();
418             if (mWmState.hasActivityState(componentName, expectedState)) {
419                 SystemClock.sleep(200);
420                 mWmState.computeState();
421                 break;
422             }
423             SystemClock.sleep(200);
424             mWmState.computeState();
425         }
426     }
427 
428     /**
429      * Helper class to process test actions by broadcast.
430      */
431     protected class BroadcastActionTrigger {
432 
createIntentWithAction(String broadcastAction)433         private Intent createIntentWithAction(String broadcastAction) {
434             return new Intent(broadcastAction)
435                     .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
436         }
437 
doAction(String broadcastAction)438         public void doAction(String broadcastAction) {
439             mContext.sendBroadcast(createIntentWithAction(broadcastAction));
440         }
441 
doActionWithRemoteCallback( String broadcastAction, String callbackName, RemoteCallback callback)442         public void doActionWithRemoteCallback(
443                 String broadcastAction, String callbackName, RemoteCallback callback) {
444             try {
445                 // We need also a RemoteCallback to ensure the callback passed in is properly set
446                 // in the Activity before moving forward.
447                 final CompletableFuture<Boolean> future = new CompletableFuture<>();
448                 final RemoteCallback setCallback = new RemoteCallback(
449                         (Bundle result) -> future.complete(true));
450                 mContext.sendBroadcast(createIntentWithAction(broadcastAction)
451                         .putExtra(callbackName, callback)
452                         .putExtra(EXTRA_SET_PIP_CALLBACK, setCallback));
453                 assertTrue(future.get(5000, TimeUnit.MILLISECONDS));
454             } catch (Exception e) {
455                 logE("doActionWithRemoteCallback failed", e);
456             }
457         }
458 
finishBroadcastReceiverActivity()459         public void finishBroadcastReceiverActivity() {
460             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
461                     .putExtra(EXTRA_FINISH_BROADCAST, true));
462         }
463 
launchActivityNewTask(String launchComponent)464         public void launchActivityNewTask(String launchComponent) {
465             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
466                     .putExtra(KEY_LAUNCH_ACTIVITY, true)
467                     .putExtra(KEY_NEW_TASK, true)
468                     .putExtra(KEY_TARGET_COMPONENT, launchComponent));
469         }
470 
moveTopTaskToBack()471         public void moveTopTaskToBack() {
472             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
473                     .putExtra(EXTRA_MOVE_BROADCAST_TO_BACK, true));
474         }
475 
requestOrientation(int orientation)476         public void requestOrientation(int orientation) {
477             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
478                     .putExtra(EXTRA_BROADCAST_ORIENTATION, orientation));
479         }
480 
dismissKeyguardByFlag()481         public void dismissKeyguardByFlag() {
482             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
483                     .putExtra(EXTRA_DISMISS_KEYGUARD, true));
484         }
485 
dismissKeyguardByMethod()486         public void dismissKeyguardByMethod() {
487             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
488                     .putExtra(EXTRA_DISMISS_KEYGUARD_METHOD, true));
489         }
490 
enterPipAndWait()491         public void enterPipAndWait() {
492             try {
493                 final CompletableFuture<Boolean> future = new CompletableFuture<>();
494                 final RemoteCallback remoteCallback = new RemoteCallback(
495                         (Bundle result) -> future.complete(true));
496                 mContext.sendBroadcast(createIntentWithAction(ACTION_ENTER_PIP)
497                         .putExtra(EXTRA_SET_PIP_CALLBACK, remoteCallback));
498                 assertTrue(future.get(5000, TimeUnit.MILLISECONDS));
499             } catch (Exception e) {
500                 logE("enterPipAndWait failed", e);
501             }
502         }
503 
expandPip()504         public void expandPip() {
505             mContext.sendBroadcast(createIntentWithAction(ACTION_EXPAND_PIP));
506         }
507 
expandPipWithAspectRatio(String extraNum, String extraDenom)508         public void expandPipWithAspectRatio(String extraNum, String extraDenom) {
509             mContext.sendBroadcast(createIntentWithAction(ACTION_EXPAND_PIP)
510                     .putExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR, extraNum)
511                     .putExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR, extraDenom));
512         }
513 
sendPipStateUpdate(RemoteCallback callback, boolean stashed)514         public void sendPipStateUpdate(RemoteCallback callback, boolean stashed) {
515             mContext.sendBroadcast(createIntentWithAction(ACTION_UPDATE_PIP_STATE)
516                     .putExtra(EXTRA_SET_PIP_CALLBACK, callback)
517                     .putExtra(EXTRA_SET_PIP_STASHED, stashed));
518         }
519 
enterPipAndWaitForPipUiStateChange(RemoteCallback callback)520         public void enterPipAndWaitForPipUiStateChange(RemoteCallback callback) {
521             mContext.sendBroadcast(createIntentWithAction(ACTION_ENTER_PIP_AND_WAIT_FOR_UI_STATE)
522                     .putExtra(EXTRA_SET_PIP_CALLBACK, callback));
523         }
524 
requestOrientationForPip(int orientation)525         public void requestOrientationForPip(int orientation) {
526             mContext.sendBroadcast(createIntentWithAction(ACTION_SET_REQUESTED_ORIENTATION)
527                     .putExtra(EXTRA_PIP_ORIENTATION, String.valueOf(orientation)));
528         }
529 
changeAspectRatio(int numerator, int denominator)530         public void changeAspectRatio(int numerator, int denominator) {
531             mContext.sendBroadcast(createIntentWithAction(ACTION_CHANGE_ASPECT_RATIO)
532                     .putExtra(EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(numerator))
533                     .putExtra(EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denominator)));
534         }
535     }
536 
537     /**
538      * Helper class to launch / close test activity by instrumentation way.
539      */
540     protected class TestActivitySession<T extends Activity> implements AutoCloseable {
541         private T mTestActivity;
542         boolean mFinishAfterClose;
543         private static final int ACTIVITY_LAUNCH_TIMEOUT = 10000;
544         private static final int WAIT_SLICE = 50;
545 
setFinishAfterClose(boolean value)546         public void setFinishAfterClose(boolean value) {
547             mFinishAfterClose = value;
548         }
549 
550         /**
551          * Launches an {@link Activity} on a target display synchronously.
552          * @param activityClass The {@link Activity} class to be launched
553          * @param displayId ID of the target display
554          */
launchTestActivityOnDisplaySync(Class<T> activityClass, int displayId)555         public void launchTestActivityOnDisplaySync(Class<T> activityClass, int displayId) {
556             launchTestActivityOnDisplaySync(activityClass, displayId, WINDOWING_MODE_UNDEFINED);
557         }
558 
559         /**
560          * Launches an {@link Activity} on a target display synchronously.
561          *
562          * @param activityClass The {@link Activity} class to be launched
563          * @param displayId ID of the target display
564          * @param windowingMode Windowing mode at launch
565          */
launchTestActivityOnDisplaySync( Class<T> activityClass, int displayId, int windowingMode)566         public void launchTestActivityOnDisplaySync(
567                 Class<T> activityClass, int displayId, int windowingMode) {
568             final Intent intent = new Intent(mContext, activityClass)
569                     .addFlags(FLAG_ACTIVITY_NEW_TASK);
570             final String className = intent.getComponent().getClassName();
571             launchTestActivityOnDisplaySync(className, intent, displayId, windowingMode);
572         }
573 
574         /**
575          * Launches an {@link Activity} synchronously on a target display. The class name needs to
576          * be provided either implicitly through the {@link Intent} or explicitly as a parameter
577          *
578          * @param className Optional class name of expected activity
579          * @param intent Intent to launch an activity
580          * @param displayId ID for the target display
581          */
launchTestActivityOnDisplaySync( @ullable String className, Intent intent, int displayId)582         public void launchTestActivityOnDisplaySync(
583                 @Nullable String className, Intent intent, int displayId) {
584             launchTestActivityOnDisplaySync(className, intent, displayId, WINDOWING_MODE_UNDEFINED);
585         }
586 
587         /**
588          * Launches an {@link Activity} synchronously on a target display. The class name needs to
589          * be provided either implicitly through the {@link Intent} or explicitly as a parameter
590          *
591          * @param className Optional class name of expected activity
592          * @param intent Intent to launch an activity
593          * @param displayId ID for the target display
594          * @param windowingMode Windowing mode at launch
595          */
launchTestActivityOnDisplaySync( @ullable String className, Intent intent, int displayId, int windowingMode)596         public void launchTestActivityOnDisplaySync(
597                 @Nullable String className, Intent intent, int displayId, int windowingMode) {
598             runWithShellPermission(
599                     () -> {
600                         mTestActivity =
601                                 launchActivityOnDisplay(
602                                         className, intent, displayId, windowingMode);
603                         // Check activity is launched and resumed.
604                         final ComponentName testActivityName = mTestActivity.getComponentName();
605                         waitAndAssertResumedAndFocusedActivityOnDisplay(
606                                 testActivityName, displayId, "Activity must be resumed");
607                     });
608         }
609 
610         /**
611          * Launches an {@link Activity} on a target display asynchronously.
612          * @param activityClass The {@link Activity} class to be launched
613          * @param displayId ID of the target display
614          */
launchTestActivityOnDisplay(Class<T> activityClass, int displayId)615         public void launchTestActivityOnDisplay(Class<T> activityClass, int displayId) {
616             final Intent intent = new Intent(mContext, activityClass)
617                     .addFlags(FLAG_ACTIVITY_NEW_TASK);
618             final String className = intent.getComponent().getClassName();
619             runWithShellPermission(
620                     () -> {
621                         mTestActivity =
622                                 launchActivityOnDisplay(
623                                         className, intent, displayId, WINDOWING_MODE_UNDEFINED);
624                         assertNotNull(mTestActivity);
625                     });
626         }
627 
628         /**
629          * Launches an {@link Activity} on a target display. In order to return the correct activity
630          * the class name or an explicit {@link Intent} must be provided.
631          *
632          * @param className Optional class name of expected activity
633          * @param intent {@link Intent} to launch an activity
634          * @param displayId ID for the target display
635          * @param windowingMode Windowing mode at launch
636          * @return The {@link Activity} that was launched
637          */
launchActivityOnDisplay( @ullable String className, Intent intent, int displayId, int windowingMode)638         private T launchActivityOnDisplay(
639                 @Nullable String className, Intent intent, int displayId, int windowingMode) {
640             final String localClassName = className != null ? className :
641               (intent.getComponent() != null ? intent.getComponent().getClassName() : null);
642             if (localClassName == null || localClassName.isEmpty()) {
643                 fail("Must provide either a class name or an intent with a component");
644             }
645             final ActivityOptions launchOptions = ActivityOptions.makeBasic();
646             launchOptions.setLaunchDisplayId(displayId);
647             launchOptions.setLaunchWindowingMode(windowingMode);
648             final Bundle bundle = launchOptions.toBundle();
649             final ActivityMonitor monitor = mInstrumentation.addMonitor(localClassName, null,
650                     false);
651             mContext.startActivity(intent.addFlags(FLAG_ACTIVITY_NEW_TASK), bundle);
652             // Wait for activity launch with timeout.
653             mTestActivity = (T) mInstrumentation.waitForMonitorWithTimeout(monitor,
654                     ACTIVITY_LAUNCH_TIMEOUT);
655             assertNotNull(mTestActivity);
656             return mTestActivity;
657         }
658 
finishCurrentActivityNoWait()659         public void finishCurrentActivityNoWait() {
660             if (mTestActivity != null) {
661                 mTestActivity.finishAndRemoveTask();
662                 mTestActivity = null;
663             }
664         }
665 
runOnMainSyncAndWait(Runnable runnable)666         public void runOnMainSyncAndWait(Runnable runnable) {
667             mInstrumentation.runOnMainSync(runnable);
668             mInstrumentation.waitForIdleSync();
669         }
670 
runOnMainAndAssertWithTimeout( @onNull BooleanSupplier condition, long timeoutMs, String message)671         public void runOnMainAndAssertWithTimeout(
672                 @NonNull BooleanSupplier condition, long timeoutMs, String message) {
673             final AtomicBoolean result = new AtomicBoolean();
674             final long expiredTime = System.currentTimeMillis() + timeoutMs;
675             while (!result.get()) {
676                 if (System.currentTimeMillis() >= expiredTime) {
677                     fail(message);
678                 }
679                 runOnMainSyncAndWait(() -> {
680                     if (condition.getAsBoolean()) {
681                         result.set(true);
682                     }
683                 });
684                 SystemClock.sleep(WAIT_SLICE);
685             }
686         }
687 
getActivity()688         public T getActivity() {
689             return mTestActivity;
690         }
691 
692         @Override
close()693         public void close() {
694             if (mTestActivity != null && mFinishAfterClose) {
695                 mTestActivity.finishAndRemoveTask();
696             }
697         }
698     }
699 
700     @Before
setUp()701     public void setUp() throws Exception {
702         UiDeviceUtils.wakeUpAndUnlock(mContext);
703         if (isKeyguardLocked()) {
704             unlockUnexpectedLockedKeyguard();
705         }
706 
707         launchHomeActivityNoWait();
708 
709         finishAndRemoveCurrentTestActivityTasks();
710         // Stop any residual tasks from the test package.
711         forceStopAllTestPackages();
712 
713         runWithShellPermission(() -> {
714             // TaskOrganizer ctor requires MANAGE_ACTIVITY_TASKS permission
715             mTaskOrganizer = new TestTaskOrganizer();
716             // Clear launch params for all test packages to make sure each test is run in a clean
717             // state.
718             mAtm.clearLaunchParamsForPackages(TEST_PACKAGES);
719         });
720         mSplitScreenActivityUtils = new SplitScreenActivityUtils(mWmState, mTaskOrganizer);
721 
722         mUserId = mContext.getUserId();
723     }
724 
725     /** It always executes after {@link org.junit.After}. */
tearDownBase()726     private void tearDownBase() {
727         mObjectTracker.tearDown(mPostAssertionRule::addError);
728 
729         if (mTaskOrganizer != null) {
730             mTaskOrganizer.unregisterOrganizerIfNeeded();
731         }
732 
733         UiDeviceUtils.wakeUpAndUnlock(mContext);
734         launchHomeActivityNoWait();
735 
736         // Synchronous execution of finishAndRemoveCurrentTestActivityTasks() ensures that activity
737         // tasks associated with this test package are cleaned up at the end of each test. Am force
738         // stop shell commands might be asynchronous and could interrupt the task cleanup process
739         // if executed first.
740         finishAndRemoveCurrentTestActivityTasks();
741         forceStopAllTestPackages();
742 
743         if (mShouldWaitForAllNonHomeActivitiesToDestroyed) {
744             mWmState.waitForAllNonHomeActivitiesToDestroyed();
745         }
746 
747         if (mWaitForRotationOnTearDown) {
748             mWmState.waitForDisplayUnfrozen();
749         }
750 
751         if (ENABLE_SHELL_TRANSITIONS
752                 && !mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY)) {
753             mPostAssertionRule.addError(
754                     new IllegalStateException("Shell transition left unfinished!"));
755         }
756     }
757 
forceStopAllTestPackages()758     private void forceStopAllTestPackages() {
759         stopTestPackage(TEST_PACKAGE);
760         stopTestPackage(SECOND_TEST_PACKAGE);
761         stopTestPackage(THIRD_TEST_PACKAGE);
762     }
763 
764     /** This should only be called if keyguard is still locked unexpectedly. */
unlockUnexpectedLockedKeyguard()765     private void unlockUnexpectedLockedKeyguard() {
766         logE("Try to recover unexpected locked keyguard");
767         // To clear the credential immediately, the screen need to be turned on.
768         pressWakeupButton();
769         if (supportsSecureLock()) {
770             removeLockCredential();
771         }
772         // Off/on to refresh the keyguard state.
773         pressSleepButton();
774         pressWakeupButton();
775         pressUnlockButton();
776     }
777 
778     /**
779      * After home key is pressed ({@link #pressHomeButton} is called), the later launch may be
780      * deferred if the calling uid doesn't have android.permission.STOP_APP_SWITCHES. This method
781      * will resume the temporary stopped state, so the launch won't be affected.
782      */
resumeAppSwitches()783     protected void resumeAppSwitches() {
784         SystemUtil.runWithShellPermissionIdentity(ActivityManager::resumeAppSwitches);
785     }
786 
startActivityOnDisplay(int displayId, ComponentName component)787     protected void startActivityOnDisplay(int displayId, ComponentName component) {
788         final ActivityOptions options = ActivityOptions.makeBasic();
789         options.setLaunchDisplayId(displayId);
790 
791         mContext.startActivity(new Intent().addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
792                 .setComponent(component), options.toBundle());
793     }
794 
noHomeScreen()795     protected boolean noHomeScreen() {
796         try {
797             return mContext.getResources().getBoolean(
798                     Resources.getSystem().getIdentifier("config_noHomeScreen", "bool",
799                             "android"));
800         } catch (Resources.NotFoundException e) {
801             // Assume there's a home screen.
802             return false;
803         }
804     }
805 
getSupportsSystemDecorsOnSecondaryDisplays()806     private boolean getSupportsSystemDecorsOnSecondaryDisplays() {
807         try {
808             return mContext.getResources().getBoolean(
809                     Resources.getSystem().getIdentifier(
810                             "config_supportsSystemDecorsOnSecondaryDisplays", "bool", "android"));
811         } catch (Resources.NotFoundException e) {
812             // Assume this device support system decorations.
813             return true;
814         }
815     }
816 
createHomeIntent(String category)817     protected Intent createHomeIntent(String category) {
818         final Intent intent = new Intent(Intent.ACTION_MAIN);
819         intent.addCategory(category);
820         return intent;
821     }
822 
getDefaultSecondaryHomeComponent()823     protected ComponentName getDefaultSecondaryHomeComponent() {
824         assumeTrue(supportsMultiDisplay());
825         final Intent intent = createHomeIntent(Intent.CATEGORY_SECONDARY_HOME);
826         int resId = Resources.getSystem().getIdentifier(
827                 "config_secondaryHomePackage", "string", "android");
828         intent.setPackage(mContext.getResources().getString(resId));
829         final ResolveInfo resolveInfo =
830                 mContext.getPackageManager().resolveActivity(intent, MATCH_DEFAULT_ONLY);
831         assertNotNull("Should have default secondary home activity", resolveInfo);
832 
833         return new ComponentName(resolveInfo.activityInfo.packageName,
834                 resolveInfo.activityInfo.name);
835     }
836 
837     /**
838      * Insert an input event (ACTION_DOWN -> ACTION_CANCEL) to ensures the display to be focused
839      * without triggering potential clicked to impact the test environment.
840      * (e.g: Keyguard credential activated unexpectedly.)
841      *
842      * @param displayId the display ID to gain focused by inject swipe action
843      */
touchAndCancelOnDisplayCenterSync(int displayId)844     protected void touchAndCancelOnDisplayCenterSync(int displayId) {
845         mTouchHelper.touchAndCancelOnDisplayCenterSync(displayId);
846     }
847 
tapOnDisplaySync(int x, int y, int displayId)848     protected void tapOnDisplaySync(int x, int y, int displayId) {
849         mTouchHelper.tapOnDisplaySync(x, y, displayId);
850     }
851 
tapOnDisplay(int x, int y, int displayId, boolean sync)852     private void tapOnDisplay(int x, int y, int displayId, boolean sync) {
853         mTouchHelper.tapOnDisplay(x, y, displayId, sync);
854     }
855 
tapOnCenter(Rect bounds, int displayId)856     protected void tapOnCenter(Rect bounds, int displayId) {
857         mTouchHelper.tapOnCenter(bounds, displayId);
858     }
859 
tapOnViewCenter(View view)860     protected void tapOnViewCenter(View view) {
861         mTouchHelper.tapOnViewCenter(view);
862     }
863 
tapOnTaskCenter(Task task)864     protected void tapOnTaskCenter(Task task) {
865         mTouchHelper.tapOnTaskCenter(task);
866     }
867 
tapOnDisplayCenter(int displayId)868     protected void tapOnDisplayCenter(int displayId) {
869         mTouchHelper.tapOnDisplayCenter(displayId);
870     }
871 
injectKey(int keyCode, boolean longPress, boolean sync)872     public static void injectKey(int keyCode, boolean longPress, boolean sync) {
873         TouchHelper.injectKey(keyCode, longPress, sync);
874     }
875 
876     /**
877      * Finishes and removes the activity tasks associated with this test package.
878      * <p>
879      * This method is intended for self-instrumenting tests bundled with activities.
880      * It finishes all activities in each task and removes them from the recent tasks list,
881      * ensuring a clean state for test execution.
882      * <p>
883      * For test app packages, consider using {@link #stopTestPackage} instead.
884      *
885      * @see ActivityManager#getAppTasks()
886      * @see ActivityManager.AppTask#finishAndRemoveTask()
887      */
finishAndRemoveCurrentTestActivityTasks()888     protected void finishAndRemoveCurrentTestActivityTasks() {
889         mAm.getAppTasks().forEach(ActivityManager.AppTask::finishAndRemoveTask);
890         waitForIdle();
891     }
892 
removeRootTask(int taskId)893     protected void removeRootTask(int taskId) {
894         runWithShellPermission(() -> mAtm.removeTask(taskId));
895         waitForIdle();
896     }
897 
takeScreenshot()898     protected Bitmap takeScreenshot() {
899         return mInstrumentation.getUiAutomation().takeScreenshot();
900     }
901 
902     /**
903      * Do a back gesture and trigger a back event from it.
904      * Attempt to simulate human behavior, so don't wait for animations.
905      */
triggerBackEventByGesture(int displayId)906     protected void triggerBackEventByGesture(int displayId) {
907         mTouchHelper.triggerBackEventByGesture(
908                 displayId, true /* sync */, false /* waitForAnimations */);
909     }
910 
launchActivity(final ComponentName activityName, final CliIntentExtra... extras)911     protected void launchActivity(final ComponentName activityName,
912             final CliIntentExtra... extras) {
913         launchActivityNoWait(activityName, extras);
914         mWmState.waitForValidState(activityName);
915     }
916 
launchActivityNoWait(final ComponentName activityName, final CliIntentExtra... extras)917     protected void launchActivityNoWait(final ComponentName activityName,
918             final CliIntentExtra... extras) {
919         executeShellCommand(getAmStartCmd(activityName, extras));
920     }
921 
assignUserToExtraDisplay(int userId, int displayId)922     protected void assignUserToExtraDisplay(int userId, int displayId) {
923         executeShellCommand(ASSIGN_USER_TO_EXTRA_DISPLAY + userId + " " + displayId);
924     }
925 
unassignUserToExtraDisplay(int userId, int displayId)926     protected void unassignUserToExtraDisplay(int userId, int displayId) {
927         executeShellCommand(UNASSIGN_USER_TO_EXTRA_DISPLAY + userId + " " + displayId);
928     }
929 
launchActivityInNewTask(final ComponentName activityName)930     protected void launchActivityInNewTask(final ComponentName activityName) {
931         executeShellCommand(getAmStartCmdInNewTask(activityName));
932         mWmState.waitForValidState(activityName);
933     }
934 
launchActivityWithData(final ComponentName activityName, String data)935     protected void launchActivityWithData(final ComponentName activityName, String data) {
936         executeShellCommand(getAmStartCmdWithData(activityName, data));
937         mWmState.waitForValidState(activityName);
938     }
939 
launchActivityWithNoAnimation(final ComponentName activityName, final CliIntentExtra... extras)940     protected void launchActivityWithNoAnimation(final ComponentName activityName,
941             final CliIntentExtra... extras) {
942         executeShellCommand(getAmStartCmdWithNoAnimation(activityName, extras));
943         mWmState.waitForValidState(activityName);
944     }
945 
launchActivityWithDismissKeyguardIfInsecure( final ComponentName activityName)946     protected void launchActivityWithDismissKeyguardIfInsecure(
947             final ComponentName activityName) {
948         executeShellCommand(getAmStartCmdWithDismissKeyguardIfInsecure(activityName));
949         mWmState.waitForValidState(activityName);
950     }
951 
launchActivityWithNoUserAction(final ComponentName activityName, final CliIntentExtra... extras)952     protected void launchActivityWithNoUserAction(final ComponentName activityName,
953             final CliIntentExtra... extras) {
954         executeShellCommand(getAmStartCmdWithNoUserAction(activityName, extras));
955         mWmState.waitForValidState(activityName);
956     }
957 
launchActivityInFullscreen(final ComponentName activityName)958     protected void launchActivityInFullscreen(final ComponentName activityName) {
959         executeShellCommand(
960                 getAmStartCmdWithWindowingMode(activityName, WINDOWING_MODE_FULLSCREEN));
961         mWmState.waitForValidState(activityName);
962     }
963 
waitForIdle()964     protected static void waitForIdle() {
965         getInstrumentation().waitForIdleSync();
966     }
967 
waitForOrFail(String message, BooleanSupplier condition)968     public static void waitForOrFail(String message, BooleanSupplier condition) {
969         Condition.waitFor(new Condition<>(message, condition)
970                 .setRetryIntervalMs(500)
971                 .setRetryLimit(20)
972                 .setOnFailure(unusedResult -> fail("FAILED because unsatisfied: " + message)));
973     }
974 
975     /** Returns the root task that contains the provided leaf task id. */
getRootTaskForLeafTaskId(int taskId)976     protected Task getRootTaskForLeafTaskId(int taskId) {
977         mWmState.computeState();
978         final List<Task> rootTasks = mWmState.getRootTasks();
979         for (Task rootTask : rootTasks) {
980             if (rootTask.getTask(taskId) != null) {
981                 return rootTask;
982             }
983         }
984         return null;
985     }
986 
getRootTask(int taskId)987     protected Task getRootTask(int taskId) {
988         mWmState.computeState();
989         final List<Task> rootTasks = mWmState.getRootTasks();
990         for (Task rootTask : rootTasks) {
991             if (rootTask.getRootTaskId() == taskId) {
992                 return rootTask;
993             }
994         }
995         return null;
996     }
997 
getDefaultWindowingModeByActivity(ComponentName activity)998     protected int getDefaultWindowingModeByActivity(ComponentName activity) {
999         return mWmState.getTaskDisplayArea(activity).getWindowingMode();
1000     }
1001 
closeSystemDialogs()1002     public static void closeSystemDialogs() {
1003         executeShellCommand(AM_BROADCAST_CLOSE_SYSTEM_DIALOGS);
1004     }
1005 
1006     /**
1007      * Launches the home activity directly. If there is no specific reason to simulate a home key
1008      * (which will trigger stop-app-switches), it is the recommended method to go home.
1009      */
launchHomeActivityNoWait()1010     public static void launchHomeActivityNoWait() {
1011         // dismiss all system dialogs before launch home.
1012         closeSystemDialogs();
1013         executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
1014     }
1015 
launchHomeActivityNoWaitExpectFailure()1016     protected static void launchHomeActivityNoWaitExpectFailure() {
1017         closeSystemDialogs();
1018         try {
1019             executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
1020         } catch (AssertionError e) {
1021             if (e.getMessage().contains("Error: Activity not started")) {
1022                 // expected
1023                 return;
1024             }
1025             throw new AssertionError("Expected activity start to fail, but got", e);
1026         }
1027         fail("Expected home activity launch to fail but didn't.");
1028     }
1029 
1030     /** Launches the home activity directly with waiting for it to be visible. */
launchHomeActivity()1031     protected void launchHomeActivity() {
1032         launchHomeActivityNoWait();
1033         mWmState.waitForHomeActivityVisible();
1034     }
1035 
launchActivityNoWait(ComponentName activityName, int windowingMode, final CliIntentExtra... extras)1036     protected void launchActivityNoWait(ComponentName activityName, int windowingMode,
1037             final CliIntentExtra... extras) {
1038         executeShellCommand(getAmStartCmd(activityName, extras)
1039                 + " --windowingMode " + windowingMode);
1040     }
1041 
launchActivity(ComponentName activityName, int windowingMode, final CliIntentExtra... keyValuePairs)1042     protected void launchActivity(ComponentName activityName, int windowingMode,
1043             final CliIntentExtra... keyValuePairs) {
1044         launchActivityNoWait(activityName, windowingMode, keyValuePairs);
1045         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
1046                 .setWindowingMode(windowingMode)
1047                 .build());
1048     }
1049 
launchActivityOnDisplay(ComponentName activityName, int windowingMode, int displayId, final CliIntentExtra... extras)1050     protected void launchActivityOnDisplay(ComponentName activityName, int windowingMode,
1051             int displayId, final CliIntentExtra... extras) {
1052         executeShellCommand(getAmStartCmd(activityName, displayId, extras)
1053                 + " --windowingMode " + windowingMode);
1054         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
1055                 .setWindowingMode(windowingMode)
1056                 .build());
1057     }
1058 
launchActivityOnTaskDisplayArea(ComponentName activityName, int windowingMode, int launchTaskDisplayAreaFeatureId, final CliIntentExtra... extras)1059     protected void launchActivityOnTaskDisplayArea(ComponentName activityName, int windowingMode,
1060             int launchTaskDisplayAreaFeatureId, final CliIntentExtra... extras) {
1061         executeShellCommand(getAmStartCmd(activityName, extras)
1062                 + " --task-display-area-feature-id " + launchTaskDisplayAreaFeatureId
1063                 + " --windowingMode " + windowingMode);
1064         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
1065                 .setWindowingMode(windowingMode)
1066                 .build());
1067     }
1068 
launchActivityOnTaskDisplayArea(ComponentName activityName, int windowingMode, int launchTaskDisplayAreaFeatureId, int displayId, final CliIntentExtra... extras)1069     protected void launchActivityOnTaskDisplayArea(ComponentName activityName, int windowingMode,
1070             int launchTaskDisplayAreaFeatureId, int displayId, final CliIntentExtra... extras) {
1071         executeShellCommand(getAmStartCmd(activityName, displayId, extras)
1072                 + " --task-display-area-feature-id " + launchTaskDisplayAreaFeatureId
1073                 + " --windowingMode " + windowingMode);
1074         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
1075                 .setWindowingMode(windowingMode)
1076                 .build());
1077     }
1078 
launchActivityOnDisplay(ComponentName activityName, int displayId, CliIntentExtra... extras)1079     protected void launchActivityOnDisplay(ComponentName activityName, int displayId,
1080             CliIntentExtra... extras) {
1081         launchActivityOnDisplayNoWait(activityName, displayId, extras);
1082         mWmState.waitForValidState(activityName);
1083     }
1084 
launchActivityOnDisplayNoWait(ComponentName activityName, int displayId, CliIntentExtra... extras)1085     protected void launchActivityOnDisplayNoWait(ComponentName activityName, int displayId,
1086             CliIntentExtra... extras) {
1087         executeShellCommand(getAmStartCmd(activityName, displayId, extras));
1088     }
1089 
launchActivityInPrimarySplit(ComponentName activityName)1090     protected void launchActivityInPrimarySplit(ComponentName activityName) {
1091         runWithShellPermission(() -> {
1092             launchActivity(activityName);
1093             mSplitScreenActivityUtils.putActivityInPrimarySplit(activityName);
1094         });
1095     }
1096 
launchActivityInSecondarySplit(ComponentName activityName)1097     protected void launchActivityInSecondarySplit(ComponentName activityName) {
1098         runWithShellPermission(() -> {
1099             launchActivity(activityName);
1100             mSplitScreenActivityUtils.putActivityInSecondarySplit(activityName);
1101         });
1102     }
1103 
1104     /** @see SplitScreenActivityUtils#putActivityInPrimarySplit(ComponentName) */
putActivityInPrimarySplit(ComponentName activityName)1105     protected void putActivityInPrimarySplit(ComponentName activityName) {
1106         mSplitScreenActivityUtils.putActivityInPrimarySplit(activityName);
1107     }
1108 
1109     /** @see SplitScreenActivityUtils#putActivityInSecondarySplit(ComponentName) */
putActivityInSecondarySplit(ComponentName activityName)1110     protected void putActivityInSecondarySplit(ComponentName activityName) {
1111         mSplitScreenActivityUtils.putActivityInSecondarySplit(activityName);
1112     }
1113 
1114     /**
1115      * @see SplitScreenActivityUtils#launchActivitiesInSplitScreen(LaunchActivityBuilder,
1116      * LaunchActivityBuilder)
1117      */
launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity, LaunchActivityBuilder secondaryActivity)1118     protected void launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity,
1119             LaunchActivityBuilder secondaryActivity) {
1120         mSplitScreenActivityUtils.launchActivitiesInSplitScreen(primaryActivity, secondaryActivity);
1121     }
1122 
1123     /** @see SplitScreenActivityUtils#moveActivitiesToSplitScreen(ComponentName, ComponentName) */
moveActivitiesToSplitScreen(ComponentName primaryActivity, ComponentName secondaryActivity)1124     protected void moveActivitiesToSplitScreen(ComponentName primaryActivity,
1125             ComponentName secondaryActivity) {
1126         mSplitScreenActivityUtils.moveActivitiesToSplitScreen(primaryActivity, secondaryActivity);
1127     }
1128 
1129     /** @see SplitScreenActivityUtils#dismissSplitScreen(boolean) */
dismissSplitScreen(boolean primaryOnTop)1130     protected void dismissSplitScreen(boolean primaryOnTop) {
1131         mSplitScreenActivityUtils.dismissSplitScreen(primaryOnTop);
1132     }
1133 
1134     /**
1135      * Move activity to root task or on top of the given root task when the root task is also a leaf
1136      * task.
1137      */
moveActivityToRootTaskOrOnTop(ComponentName activityName, int rootTaskId)1138     protected void moveActivityToRootTaskOrOnTop(ComponentName activityName, int rootTaskId) {
1139         moveActivityToRootTaskOrOnTop(activityName, rootTaskId, FEATURE_UNDEFINED);
1140     }
1141 
moveActivityToRootTaskOrOnTop(ComponentName activityName, int rootTaskId, int taskDisplayAreaFeatureId)1142     protected void moveActivityToRootTaskOrOnTop(ComponentName activityName, int rootTaskId,
1143                                                  int taskDisplayAreaFeatureId) {
1144         mWmState.computeState(activityName);
1145         Task rootTask = getRootTask(rootTaskId);
1146         if (rootTask.getActivities().size() != 0) {
1147             // If the root task is a 1-level task, start the activity on top of given task.
1148             getLaunchActivityBuilder()
1149                     .setDisplayId(rootTask.mDisplayId)
1150                     .setWindowingMode(rootTask.getWindowingMode())
1151                     .setActivityType(rootTask.getActivityType())
1152                     .setLaunchTaskDisplayAreaFeatureId(taskDisplayAreaFeatureId)
1153                     .setTargetActivity(activityName)
1154                     .allowMultipleInstances(false)
1155                     .setUseInstrumentation()
1156                     .execute();
1157         } else {
1158             final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
1159             runWithShellPermission(() -> mAtm.moveTaskToRootTask(taskId, rootTaskId, true));
1160         }
1161         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
1162                 .setRootTaskId(rootTaskId)
1163                 .build());
1164     }
1165 
resizeActivityTask( ComponentName activityName, int left, int top, int right, int bottom)1166     protected void resizeActivityTask(
1167             ComponentName activityName, int left, int top, int right, int bottom) {
1168         mWmState.computeState(activityName);
1169         final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
1170         runWithShellPermission(() -> mAtm.resizeTask(taskId, new Rect(left, top, right, bottom)));
1171     }
1172 
supportsVrMode()1173     protected boolean supportsVrMode() {
1174         return hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE);
1175     }
1176 
supportsPip()1177     protected boolean supportsPip() {
1178         return hasDeviceFeature(FEATURE_PICTURE_IN_PICTURE)
1179                 || PRETEND_DEVICE_SUPPORTS_PIP;
1180     }
1181 
supportsExpandedPip()1182     protected boolean supportsExpandedPip() {
1183         return hasDeviceFeature(FEATURE_EXPANDED_PICTURE_IN_PICTURE);
1184     }
1185 
supportsFreeform()1186     protected boolean supportsFreeform() {
1187         return hasDeviceFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
1188                 || PRETEND_DEVICE_SUPPORTS_FREEFORM;
1189     }
1190 
1191     /** Whether or not the device supports lock screen. */
supportsLockScreen()1192     protected boolean supportsLockScreen() {
1193         return supportsInsecureLock() || supportsSecureLock();
1194     }
1195 
1196     /** Whether or not the device supports pin/pattern/password lock. */
supportsSecureLock()1197     protected boolean supportsSecureLock() {
1198         return FeatureUtil.hasSystemFeature(FEATURE_SECURE_LOCK_SCREEN);
1199     }
1200 
1201     /** Whether or not the device supports "swipe" lock. */
supportsInsecureLock()1202     protected boolean supportsInsecureLock() {
1203         return !FeatureUtil.hasAnySystemFeature(
1204                 FEATURE_LEANBACK, FEATURE_WATCH, FEATURE_EMBEDDED, FEATURE_AUTOMOTIVE)
1205                 && getSupportsInsecureLockScreen();
1206     }
1207 
1208     /** Try to enable gesture navigation mode */
enableAndAssumeGestureNavigationMode()1209     protected void enableAndAssumeGestureNavigationMode() {
1210         if (sGestureNavSwitchHelper == null) {
1211             sGestureNavSwitchHelper = new GestureNavSwitchHelper();
1212         }
1213         assumeTrue(sGestureNavSwitchHelper.enableGestureNavigationMode());
1214     }
1215 
supportsBlur()1216     protected boolean supportsBlur() {
1217         return SystemProperties.get("ro.surface_flinger.supports_background_blur", "default")
1218                 .equals("1");
1219     }
1220 
isWatch()1221     protected boolean isWatch() {
1222         return hasDeviceFeature(FEATURE_WATCH);
1223     }
1224 
isCar()1225     protected boolean isCar() {
1226         return hasDeviceFeature(FEATURE_AUTOMOTIVE);
1227     }
1228 
isLeanBack()1229     protected boolean isLeanBack() {
1230         return hasDeviceFeature(FEATURE_TELEVISION);
1231     }
1232 
isTablet()1233     public static boolean isTablet() {
1234         if (sIsTablet == null) {
1235             // Use WindowContext with type application overlay to prevent the metrics overridden by
1236             // activity bounds. Note that process configuration may still be overridden by
1237             // foreground Activity.
1238             final Context appContext = ApplicationProvider.getApplicationContext();
1239             final Display defaultDisplay = appContext.getSystemService(DisplayManager.class)
1240                     .getDisplay(DEFAULT_DISPLAY);
1241             final Context windowContext = appContext.createWindowContext(defaultDisplay,
1242                     TYPE_APPLICATION_OVERLAY, null /* options */);
1243             sIsTablet = windowContext.getResources().getConfiguration().smallestScreenWidthDp
1244                     >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
1245         }
1246         return sIsTablet;
1247     }
1248 
waitAndAssertActivityState(ComponentName activityName, String state, String message)1249     protected void waitAndAssertActivityState(ComponentName activityName,
1250             String state, String message) {
1251         mWmState.waitForActivityState(activityName, state);
1252 
1253         assertTrue(message, mWmState.hasActivityState(activityName, state));
1254     }
1255 
isKeyguardLocked()1256     protected boolean isKeyguardLocked() {
1257         return mKm != null && mKm.isKeyguardLocked();
1258     }
1259 
waitAndAssertActivityStateOnDisplay(ComponentName activityName, String state, int displayId, String message)1260     protected void waitAndAssertActivityStateOnDisplay(ComponentName activityName, String state,
1261             int displayId, String message) {
1262         waitAndAssertActivityState(activityName, state, message);
1263         assertEquals(message,
1264                 /* expected = */ displayId,
1265                 /* actual = */ mWmState.getDisplayByActivity(activityName));
1266     }
1267 
1268     /**
1269      * Waits and asserts that the activity represented by the given activity name, display id
1270      * is focused.
1271      */
waitAndAssertResumedAndFocusedActivityOnDisplay(ComponentName activityName, int displayId, String message)1272     public void waitAndAssertResumedAndFocusedActivityOnDisplay(ComponentName activityName,
1273             int displayId, String message) {
1274         final String activityClassName = getActivityName(activityName);
1275         mWmState.waitForWithAmState(state ->
1276                 activityClassName.equals(state.getFocusedActivityOnDisplay(displayId)),
1277                 "activity to be on top");
1278         waitAndAssertResumedActivity(activityName, "Activity must be resumed");
1279         mWmState.assertFocusedActivityOnDisplay(message, activityName, displayId);
1280 
1281         final int frontRootTaskId = mWmState.getFrontRootTaskId(displayId);
1282         Task frontRootTaskOnDisplay = mWmState.getRootTask(frontRootTaskId);
1283         assertEquals(
1284                 "Resumed activity of front root task of the target display must match. " + message,
1285                 activityClassName,
1286                 frontRootTaskOnDisplay.isLeafTask() ? frontRootTaskOnDisplay.mResumedActivity
1287                         : frontRootTaskOnDisplay.getTopTask().mResumedActivity);
1288         mWmState.assertFocusedRootTaskOnDisplay("Top activity's rootTask must also be on top",
1289                 frontRootTaskId, displayId);
1290     }
1291 
1292     /**
1293      * Waits and asserts that the activity represented by the given activity name is resumed and
1294      * visible, but is not necessarily the top activity.
1295      *
1296      * @param activityName the activity name
1297      */
waitAndAssertResumedActivity(ComponentName activityName)1298     public void waitAndAssertResumedActivity(ComponentName activityName) {
1299         waitAndAssertResumedActivity(
1300                 activityName, activityName.toShortString() + " must be resumed");
1301     }
1302 
1303     /**
1304      * Waits and asserts that the activity represented by the given activity name is resumed and
1305      * visible, but is not necessarily the top activity.
1306      *
1307      * @param activityName the activity name
1308      * @param message the error message
1309      */
waitAndAssertResumedActivity(ComponentName activityName, String message)1310     public void waitAndAssertResumedActivity(ComponentName activityName, String message) {
1311         mWmState.waitForActivityState(activityName, STATE_RESUMED);
1312         mWmState.waitForValidState(activityName);
1313         mWmState.assertValidity();
1314         assertTrue(message, mWmState.hasActivityState(activityName, STATE_RESUMED));
1315         mWmState.assertVisibility(activityName, true /* visible */);
1316     }
1317 
1318     /**
1319      * Waits and asserts that the activity represented by the given activity name is stopped and
1320      * invisible.
1321      *
1322      * @param activityName the activity name
1323      */
waitAndAssertStoppedActivity(ComponentName activityName)1324     public void waitAndAssertStoppedActivity(ComponentName activityName) {
1325         waitAndAssertStoppedActivity(
1326                 activityName, activityName.toShortString() + " must be stopped");
1327     }
1328 
1329     /**
1330      * Waits and asserts that the activity represented by the given activity name is stopped and
1331      * invisible.
1332      *
1333      * @param activityName the activity name
1334      * @param message the error message
1335      */
waitAndAssertStoppedActivity(ComponentName activityName, String message)1336     public void waitAndAssertStoppedActivity(ComponentName activityName, String message) {
1337         mWmState.waitForValidState(activityName);
1338         mWmState.waitForActivityState(activityName, STATE_STOPPED);
1339         mWmState.assertValidity();
1340         assertTrue(message, mWmState.hasActivityState(activityName, STATE_STOPPED));
1341         mWmState.assertVisibility(activityName, false /* visible */);
1342     }
1343 
1344     // TODO: Switch to using a feature flag, when available.
isUiModeLockedToVrHeadset()1345     protected static boolean isUiModeLockedToVrHeadset() {
1346         final String output = runCommandAndPrintOutput("dumpsys uimode");
1347 
1348         Integer curUiMode = null;
1349         Boolean uiModeLocked = null;
1350         for (String line : output.split("\\n")) {
1351             line = line.trim();
1352             Matcher matcher = sCurrentUiModePattern.matcher(line);
1353             if (matcher.find()) {
1354                 curUiMode = Integer.parseInt(matcher.group(1), 16);
1355             }
1356             matcher = sUiModeLockedPattern.matcher(line);
1357             if (matcher.find()) {
1358                 uiModeLocked = matcher.group(1).equals("true");
1359             }
1360         }
1361 
1362         boolean uiModeLockedToVrHeadset = (curUiMode != null) && (uiModeLocked != null)
1363                 && ((curUiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET) && uiModeLocked;
1364 
1365         if (uiModeLockedToVrHeadset) {
1366             log("UI mode is locked to VR headset");
1367         }
1368 
1369         return uiModeLockedToVrHeadset;
1370     }
1371 
supportsMultiWindow()1372     protected boolean supportsMultiWindow() {
1373         Display defaultDisplay = mDm.getDisplay(DEFAULT_DISPLAY);
1374         return supportsMultiWindow(mContext.createDisplayContext(defaultDisplay));
1375     }
1376 
1377     /**
1378      * Returns true if the Context supports multi-window-mode
1379      */
supportsMultiWindow(Context context)1380     protected final boolean supportsMultiWindow(Context context) {
1381         return ActivityTaskManager.supportsSplitScreenMultiWindow(context);
1382     }
1383 
1384     /** @see SplitScreenActivityUtils#supportsSplitScreenMultiWindow(Context) */
supportsSplitScreenMultiWindow()1385     protected boolean supportsSplitScreenMultiWindow() {
1386         return SplitScreenActivityUtils.supportsSplitScreenMultiWindow(mContext);
1387     }
1388 
hasHomeScreen()1389     protected boolean hasHomeScreen() {
1390         if (sHasHomeScreen == null) {
1391             sHasHomeScreen = !noHomeScreen();
1392         }
1393         return sHasHomeScreen;
1394     }
1395 
supportsSystemDecorsOnSecondaryDisplays()1396     protected boolean supportsSystemDecorsOnSecondaryDisplays() {
1397         if (sSupportsSystemDecorsOnSecondaryDisplays == null) {
1398             sSupportsSystemDecorsOnSecondaryDisplays = getSupportsSystemDecorsOnSecondaryDisplays();
1399         }
1400         return sSupportsSystemDecorsOnSecondaryDisplays;
1401     }
1402 
getSupportsInsecureLockScreen()1403     protected boolean getSupportsInsecureLockScreen() {
1404         boolean insecure;
1405         try {
1406             insecure = mContext.getResources().getBoolean(
1407                     Resources.getSystem().getIdentifier(
1408                             "config_supportsInsecureLockScreen", "bool", "android"));
1409         } catch (Resources.NotFoundException e) {
1410             insecure = true;
1411         }
1412         return insecure;
1413     }
1414 
isAssistantOnTopOfDream()1415     protected boolean isAssistantOnTopOfDream() {
1416         if (sIsAssistantOnTop == null) {
1417             sIsAssistantOnTop = mContext.getResources().getBoolean(
1418                     android.R.bool.config_assistantOnTopOfDream);
1419         }
1420         return sIsAssistantOnTop;
1421     }
1422 
dismissDreamOnActivityStart()1423     protected boolean dismissDreamOnActivityStart() {
1424         if (sDismissDreamOnActivityStart == null) {
1425             try {
1426                 sDismissDreamOnActivityStart = mContext.getResources().getBoolean(
1427                         Resources.getSystem().getIdentifier(
1428                                 "config_dismissDreamOnActivityStart", "bool", "android"));
1429             } catch (Resources.NotFoundException e) {
1430                 sDismissDreamOnActivityStart = true;
1431             }
1432         }
1433         return sDismissDreamOnActivityStart;
1434     }
1435 
1436     /**
1437      * Rotation support is indicated by explicitly having both landscape and portrait
1438      * features or not listing either at all.
1439      */
supportsRotation()1440     protected boolean supportsRotation() {
1441         final boolean supportsLandscape = hasDeviceFeature(FEATURE_SCREEN_LANDSCAPE);
1442         final boolean supportsPortrait = hasDeviceFeature(FEATURE_SCREEN_PORTRAIT);
1443         return (supportsLandscape && supportsPortrait)
1444                 || (!supportsLandscape && !supportsPortrait);
1445     }
1446 
1447     /**
1448      * The device should support orientation request from apps if it supports rotation and the
1449      * display is not close to square.
1450      */
supportsOrientationRequest()1451     protected boolean supportsOrientationRequest() {
1452         return supportsRotation() && !isCloseToSquareDisplay();
1453     }
1454 
1455     /** Checks whether the display dimension is close to square. */
isCloseToSquareDisplay()1456     protected boolean isCloseToSquareDisplay() {
1457         return isCloseToSquareDisplay(mContext);
1458     }
1459 
1460     /** Checks whether the display dimension is close to square. */
isCloseToSquareDisplay(Context context)1461     public static boolean isCloseToSquareDisplay(Context context) {
1462         final Resources resources = context.getResources();
1463         final float closeToSquareMaxAspectRatio;
1464         try {
1465             closeToSquareMaxAspectRatio = resources.getFloat(resources.getIdentifier(
1466                     "config_closeToSquareDisplayMaxAspectRatio", "dimen", "android"));
1467         } catch (Resources.NotFoundException e) {
1468             // Assume device is not close to square.
1469             return false;
1470         }
1471         final DisplayMetrics displayMetrics = new DisplayMetrics();
1472         context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY)
1473                 .getRealMetrics(displayMetrics);
1474         final int w = displayMetrics.widthPixels;
1475         final int h = displayMetrics.heightPixels;
1476         final float aspectRatio = Math.max(w, h) / (float) Math.min(w, h);
1477         return aspectRatio <= closeToSquareMaxAspectRatio;
1478     }
1479 
hasDeviceFeature(final String requiredFeature)1480     protected boolean hasDeviceFeature(final String requiredFeature) {
1481         return mContext.getPackageManager()
1482                 .hasSystemFeature(requiredFeature);
1483     }
1484 
isDisplayPortrait()1485     protected static boolean isDisplayPortrait() {
1486         final DisplayManager displayManager = getInstrumentation()
1487                 .getContext().getSystemService(DisplayManager.class);
1488         final Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
1489         final DisplayMetrics displayMetrics = new DisplayMetrics();
1490         display.getRealMetrics(displayMetrics);
1491         return displayMetrics.widthPixels < displayMetrics.heightPixels;
1492     }
1493 
isDisplayOn(int displayId)1494     protected static boolean isDisplayOn(int displayId) {
1495         final DisplayManager displayManager = getInstrumentation()
1496                 .getContext().getSystemService(DisplayManager.class);
1497         final Display display = displayManager.getDisplay(displayId);
1498         return display != null && display.getState() == Display.STATE_ON;
1499     }
1500 
perDisplayFocusEnabled()1501     protected static boolean perDisplayFocusEnabled() {
1502         return getInstrumentation().getTargetContext().getResources()
1503                 .getBoolean(android.R.bool.config_perDisplayFocusEnabled);
1504     }
1505 
removeLockCredential()1506     protected static void removeLockCredential() {
1507         runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
1508     }
1509 
remoteInsetsControllerControlsSystemBars()1510     protected static boolean remoteInsetsControllerControlsSystemBars() {
1511         return getInstrumentation().getTargetContext().getResources()
1512                 .getBoolean(android.R.bool.config_remoteInsetsControllerControlsSystemBars);
1513     }
1514 
1515     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedHomeActivitySession(ComponentName homeActivity)1516     protected HomeActivitySession createManagedHomeActivitySession(ComponentName homeActivity) {
1517         return mObjectTracker.manage(new HomeActivitySession(homeActivity));
1518     }
1519 
1520     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedActivityClientSession()1521     protected ActivitySessionClient createManagedActivityClientSession() {
1522         return mObjectTracker.manage(new ActivitySessionClient(mContext));
1523     }
1524 
1525     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedLockScreenSession()1526     protected LockScreenSession createManagedLockScreenSession() {
1527         return mObjectTracker.manage(new LockScreenSession(mInstrumentation, mWmState));
1528     }
1529 
1530     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedLockScreenSessionAndClearActivitiesOnClose()1531     protected LockScreenSession createManagedLockScreenSessionAndClearActivitiesOnClose() {
1532         return mObjectTracker.manage(new LockScreenSession(mInstrumentation, mWmState) {
1533             @Override
1534             public void close() {
1535                 finishAndRemoveCurrentTestActivityTasks();
1536                 super.close();
1537             }
1538         });
1539     }
1540 
1541     /** @see ObjectTracker#manage(AutoCloseable) */
1542     protected RotationSession createManagedRotationSession() {
1543         mWaitForRotationOnTearDown = true;
1544         return mObjectTracker.manage(new RotationSession(mWmState));
1545     }
1546 
1547     /** @see ObjectTracker#manage(AutoCloseable) */
1548     protected AodSession createManagedAodSession() {
1549         return mObjectTracker.manage(new AodSession());
1550     }
1551 
1552     /** @see ObjectTracker#manage(AutoCloseable) */
1553     protected DevEnableNonResizableMultiWindowSession
1554     createManagedDevEnableNonResizableMultiWindowSession() {
1555         return mObjectTracker.manage(new DevEnableNonResizableMultiWindowSession());
1556     }
1557 
1558     /** @see ObjectTracker#manage(AutoCloseable) */
1559     protected <T extends Activity> TestActivitySession<T> createManagedTestActivitySession() {
1560         return new TestActivitySession<T>();
1561     }
1562 
1563     /** @see ObjectTracker#manage(AutoCloseable) */
1564     protected SystemAlertWindowAppOpSession createAllowSystemAlertWindowAppOpSession() {
1565         return mObjectTracker.manage(
1566                 new SystemAlertWindowAppOpSession(mContext.getOpPackageName(), MODE_ALLOWED));
1567     }
1568 
1569     /** @see ObjectTracker#manage(AutoCloseable) */
1570     protected FontScaleSession createManagedFontScaleSession() {
1571         return mObjectTracker.manage(new FontScaleSession());
1572     }
1573 
1574     /** @see ObjectTracker#manage(AutoCloseable) */
1575     protected DisplayMetricsSession createManagedDisplayMetricsSession(int displayId) {
1576         return mObjectTracker.manage(new DisplayMetricsSession(displayId));
1577     }
1578 
1579     /** @see ObjectTracker#manage(AutoCloseable) */
1580     protected VirtualDisplaySession createManagedVirtualDisplaySession() {
1581         return mObjectTracker.manage(new VirtualDisplaySession());
1582     }
1583 
1584     /** Allows requesting orientation in case ignore_orientation_request is set to true. */
1585     protected void disableIgnoreOrientationRequest() {
1586         mObjectTracker.manage(new IgnoreOrientationRequestSession(false /* enable */));
1587     }
1588 
1589     /**
1590      * Test @Rule class that disables Immersive mode confirmation dialog.
1591      */
1592     public static class DisableImmersiveModeConfirmationRule implements TestRule {
1593         @Override
1594         public Statement apply(Statement base, Description description) {
1595             return new Statement() {
1596                 @Override
1597                 public void evaluate() throws Throwable {
1598                     try (SettingsSession<String> immersiveModeConfirmationSetting =
1599                                  new SettingsSession<>(
1600                             Settings.Secure.getUriFor(IMMERSIVE_MODE_CONFIRMATIONS),
1601                             Settings.Secure::getString, Settings.Secure::putString)) {
1602                         immersiveModeConfirmationSetting.set("confirmed");
1603                         base.evaluate();
1604                     }
1605                 }
1606             };
1607         }
1608     }
1609 
1610     /**
1611      * Test @Rule class that disables screen doze settings before each test method running and
1612      * restoring to initial values after test method finished.
1613      */
1614     protected class DisableScreenDozeRule implements TestRule {
1615         AmbientDisplayConfiguration mConfig;
1616 
1617         public DisableScreenDozeRule() {
1618             mConfig = new AmbientDisplayConfiguration(mContext);
1619         }
1620 
1621         @Override
1622         public Statement apply(final Statement base, final Description description) {
1623             return new Statement() {
1624                 @Override
1625                 public void evaluate() throws Throwable {
1626                     try {
1627                         SystemUtil.runWithShellPermissionIdentity(() -> {
1628                             // disable current doze settings
1629                             mConfig.disableDozeSettings(true /* shouldDisableNonUserConfigurable */,
1630                                     android.os.Process.myUserHandle().getIdentifier());
1631                         });
1632                         base.evaluate();
1633                     } finally {
1634                         SystemUtil.runWithShellPermissionIdentity(() -> {
1635                             // restore doze settings
1636                             mConfig.restoreDozeSettings(
1637                                     android.os.Process.myUserHandle().getIdentifier());
1638                         });
1639                     }
1640                 }
1641             };
1642         }
1643     }
1644 
1645     public ComponentName getDefaultHomeComponent() {
1646         final Intent intent = new Intent(ACTION_MAIN);
1647         intent.addCategory(CATEGORY_HOME);
1648         intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
1649         final ResolveInfo resolveInfo =
1650                 mContext.getPackageManager().resolveActivity(intent, MATCH_DEFAULT_ONLY);
1651         if (resolveInfo == null) {
1652             throw new AssertionError("Home activity not found");
1653         }
1654         return new ComponentName(resolveInfo.activityInfo.packageName,
1655                 resolveInfo.activityInfo.name);
1656     }
1657 
1658     /**
1659      * HomeActivitySession is used to replace the default home component, so that you can use
1660      * your preferred home for testing within the session. The original default home will be
1661      * restored automatically afterward.
1662      */
1663     protected class HomeActivitySession implements AutoCloseable {
1664         private PackageManager mPackageManager;
1665         private ComponentName mOrigHome;
1666         private ComponentName mSessionHome;
1667 
1668         HomeActivitySession(ComponentName sessionHome) {
1669             mSessionHome = sessionHome;
1670             mPackageManager = mContext.getPackageManager();
1671             mOrigHome = getDefaultHomeComponent();
1672 
1673             runWithShellPermission(
1674                     () -> mPackageManager.setComponentEnabledSetting(mSessionHome,
1675                             COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP));
1676             setDefaultHome(mSessionHome);
1677         }
1678 
1679         @Override
1680         public void close() {
1681             runWithShellPermission(
1682                     () -> mPackageManager.setComponentEnabledSetting(mSessionHome,
1683                             COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP));
1684             if (mOrigHome != null) {
1685                 setDefaultHome(mOrigHome);
1686             }
1687         }
1688 
1689         private void setDefaultHome(ComponentName componentName) {
1690             executeShellCommand("cmd package set-home-activity --user "
1691                     + android.os.Process.myUserHandle().getIdentifier() + " "
1692                     + componentName.flattenToString());
1693         }
1694     }
1695 
1696     /** Helper class to set and restore appop mode "android:system_alert_window". */
1697     protected static class SystemAlertWindowAppOpSession implements AutoCloseable {
1698         private final String mPackageName;
1699         private final int mPreviousOpMode;
1700 
1701         SystemAlertWindowAppOpSession(String packageName, int mode) {
1702             mPackageName = packageName;
1703             try {
1704                 mPreviousOpMode = AppOpsUtils.getOpMode(mPackageName, OPSTR_SYSTEM_ALERT_WINDOW);
1705             } catch (IOException e) {
1706                 throw new RuntimeException(e);
1707             }
1708             setOpMode(mode);
1709         }
1710 
1711         @Override
1712         public void close() {
1713             setOpMode(mPreviousOpMode);
1714         }
1715 
1716         void setOpMode(int mode) {
1717             try {
1718                 AppOpsUtils.setOpMode(mPackageName, OPSTR_SYSTEM_ALERT_WINDOW, mode);
1719             } catch (IOException e) {
1720                 throw new RuntimeException(e);
1721             }
1722         }
1723     }
1724 
1725     protected class AodSession extends SettingsSession<Integer> {
1726         private AmbientDisplayConfiguration mConfig;
1727 
1728         AodSession() {
1729             super(Settings.Secure.getUriFor(Settings.Secure.DOZE_ALWAYS_ON),
1730                     Settings.Secure::getInt,
1731                     Settings.Secure::putInt);
1732             mConfig = new AmbientDisplayConfiguration(mContext);
1733         }
1734 
1735         public boolean isAodAvailable() {
1736             return mConfig.alwaysOnAvailable();
1737         }
1738 
1739         public void setAodEnabled(boolean enabled) {
1740             set(enabled ? 1 : 0);
1741         }
1742     }
1743 
1744     protected class DevEnableNonResizableMultiWindowSession extends SettingsSession<Integer> {
1745         DevEnableNonResizableMultiWindowSession() {
1746             super(Settings.Global.getUriFor(
1747                     Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW),
1748                     (cr, name) -> Settings.Global.getInt(cr, name, 0 /* def */),
1749                     Settings.Global::putInt);
1750         }
1751     }
1752 
1753     /** Helper class to save, set, and restore font_scale preferences. */
1754     protected static class FontScaleSession extends SettingsSession<Float> {
1755         FontScaleSession() {
1756             super(Settings.System.getUriFor(Settings.System.FONT_SCALE),
1757                     Settings.System::getFloat,
1758                     Settings.System::putFloat);
1759         }
1760 
1761         @Override
1762         public Float get() {
1763             Float value = super.get();
1764             return value == null ? 1f : value;
1765         }
1766     }
1767 
1768     protected ChangeWallpaperSession createManagedChangeWallpaperSession() {
1769         return mObjectTracker.manage(new ChangeWallpaperSession());
1770     }
1771 
1772     protected class ChangeWallpaperSession implements AutoCloseable {
1773         private final WallpaperManager mWallpaperManager;
1774         private Bitmap mTestBitmap;
1775 
1776         public ChangeWallpaperSession() {
1777             mWallpaperManager = WallpaperManager.getInstance(mContext);
1778         }
1779 
1780         public Bitmap getTestBitmap() {
1781             if (mTestBitmap == null) {
1782                 mTestBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
1783                 final Canvas canvas = new Canvas(mTestBitmap);
1784                 canvas.drawColor(Color.BLUE);
1785             }
1786             return mTestBitmap;
1787         }
1788 
1789         public void setImageWallpaper(Bitmap bitmap) {
1790             SystemUtil.runWithShellPermissionIdentity(() ->
1791                     mWallpaperManager.setBitmap(bitmap));
1792         }
1793 
1794         public void setWallpaperComponent(ComponentName componentName) {
1795             SystemUtil.runWithShellPermissionIdentity(() ->
1796                     mWallpaperManager.setWallpaperComponent(componentName));
1797         }
1798 
1799         @Override
1800         public void close() {
1801             SystemUtil.runWithShellPermissionIdentity(() -> mWallpaperManager.clearWallpaper());
1802             if (mTestBitmap != null) {
1803                 mTestBitmap.recycle();
1804             }
1805             // Turning screen off/on to flush deferred color events due to wallpaper changed.
1806             pressSleepButton();
1807             pressWakeupButton();
1808             pressUnlockButton();
1809         }
1810     }
1811     /**
1812      * Returns whether the test device respects settings of locked user rotation mode.
1813      *
1814      * The method sets the locked user rotation settings to the rotation that rotates the display by
1815      * 180 degrees and checks if the actual display rotation changes after that.
1816      *
1817      * This is a necessary assumption check before leveraging user rotation mode to force display
1818      * rotation, because there is no requirement that an Android device that supports both
1819      * orientations needs to support user rotation mode.
1820      *
1821      * @param session   the rotation session used to set user rotation
1822      * @param displayId the display ID to check rotation against
1823      * @return {@code true} if test device respects settings of locked user rotation mode;
1824      * {@code false} if not.
1825      */
1826     protected boolean supportsLockedUserRotation(RotationSession session, int displayId) {
1827         final int origRotation = getDeviceRotation(displayId);
1828         // Use the same orientation as target rotation to avoid affect of app-requested orientation.
1829         final int targetRotation = (origRotation + 2) % 4;
1830         session.set(targetRotation);
1831         final boolean result = (getDeviceRotation(displayId) == targetRotation);
1832         session.set(origRotation);
1833         return result;
1834     }
1835 
1836     protected int getDeviceRotation(int displayId) {
1837         final String displays = runCommandAndPrintOutput("dumpsys display displays").trim();
1838         Pattern pattern = Pattern.compile(
1839                 "(mDisplayId=" + displayId + ")([\\s\\S]*?)(mOverrideDisplayInfo)(.*)"
1840                         + "(rotation)(\\s+)(\\d+)");
1841         Matcher matcher = pattern.matcher(displays);
1842         if (matcher.find()) {
1843             final String match = matcher.group(7);
1844             return Integer.parseInt(match);
1845         }
1846 
1847         return INVALID_DEVICE_ROTATION;
1848     }
1849 
1850     /**
1851      * Creates a {#link ActivitySessionClient} instance with instrumentation context. It is used
1852      * when the caller doen't need try-with-resource.
1853      */
1854     public static ActivitySessionClient createActivitySessionClient() {
1855         return new ActivitySessionClient(getInstrumentation().getContext());
1856     }
1857 
1858     /** Empties the test journal so the following events won't be mixed-up with previous records. */
1859     protected void separateTestJournal() {
1860         TestJournalContainer.start();
1861     }
1862 
1863     protected static String runCommandAndPrintOutput(String command) {
1864         final String output = executeShellCommandAndGetStdout(command);
1865         log(output);
1866         return output;
1867     }
1868 
1869     protected static class LogSeparator {
1870         private final String mUniqueString;
1871 
1872         private LogSeparator() {
1873             mUniqueString = UUID.randomUUID().toString();
1874         }
1875 
1876         @Override
1877         public String toString() {
1878             return mUniqueString;
1879         }
1880     }
1881 
1882     /**
1883      * Inserts a log separator so we can always find the starting point from where to evaluate
1884      * following logs.
1885      *
1886      * @return Unique log separator.
1887      */
1888     protected LogSeparator separateLogs() {
1889         final LogSeparator logSeparator = new LogSeparator();
1890         executeShellCommand("log -t " + LOG_SEPARATOR + " " + logSeparator);
1891         EventLog.writeEvent(EVENT_LOG_SEPARATOR_TAG, logSeparator.mUniqueString);
1892         return logSeparator;
1893     }
1894 
1895     protected static String[] getDeviceLogsForComponents(
1896             LogSeparator logSeparator, String... logTags) {
1897         String filters = LOG_SEPARATOR + ":I ";
1898         for (String component : logTags) {
1899             filters += component + ":I ";
1900         }
1901         final String[] result = executeShellCommandAndGetStdout(
1902                 "logcat -v brief -d " + filters + " *:S").split("\\n");
1903         if (logSeparator == null) {
1904             return result;
1905         }
1906 
1907         // Make sure that we only check logs after the separator.
1908         int i = 0;
1909         boolean lookingForSeparator = true;
1910         while (i < result.length && lookingForSeparator) {
1911             if (result[i].contains(logSeparator.toString())) {
1912                 lookingForSeparator = false;
1913             }
1914             i++;
1915         }
1916         final String[] filteredResult = new String[result.length - i];
1917         for (int curPos = 0; i < result.length; curPos++, i++) {
1918             filteredResult[curPos] = result[i];
1919         }
1920         return filteredResult;
1921     }
1922 
1923     protected static List<Event> getEventLogsForComponents(LogSeparator logSeparator, int... tags) {
1924         List<Event> events = new ArrayList<>();
1925 
1926         int[] searchTags = Arrays.copyOf(tags, tags.length + 1);
1927         searchTags[searchTags.length - 1] = EVENT_LOG_SEPARATOR_TAG;
1928 
1929         try {
1930             EventLog.readEvents(searchTags, events);
1931         } catch (IOException e) {
1932             fail("Could not read from event log." + e);
1933         }
1934 
1935         for (Iterator<Event> itr = events.iterator(); itr.hasNext(); ) {
1936             Event event = itr.next();
1937             itr.remove();
1938             if (event.getTag() == EVENT_LOG_SEPARATOR_TAG &&
1939                     logSeparator.mUniqueString.equals(event.getData())) {
1940                 break;
1941             }
1942         }
1943         return events;
1944     }
1945 
1946     protected boolean supportsMultiDisplay() {
1947         return mContext.getPackageManager().hasSystemFeature(
1948                 FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
1949     }
1950 
1951     protected boolean supportsInstallableIme() {
1952         return mContext.getPackageManager().hasSystemFeature(FEATURE_INPUT_METHODS);
1953     }
1954 
1955     /**
1956      * Waits until the given activity has entered picture-in-picture mode (allowing for the
1957      * subsequent animation to start).
1958      */
1959     protected void waitForEnterPip(@NonNull ComponentName activityName) {
1960         mWmState.waitForWithAmState(wmState -> {
1961             Task task = wmState.getTaskByActivity(activityName);
1962             return task != null
1963                     && task.getActivity(activityName).getWindowingMode() == WINDOWING_MODE_PINNED
1964                     && task.isVisible();
1965         }, "checking task windowing mode");
1966     }
1967 
1968     /**
1969      * Waits until the picture-in-picture animation has finished.
1970      */
1971     protected void waitForEnterPipAnimationComplete(@NonNull ComponentName activityName) {
1972         waitForEnterPip(activityName);
1973         mWmState.waitForWithAmState(wmState -> {
1974             Task task = wmState.getTaskByActivity(activityName);
1975             if (task == null) {
1976                 return false;
1977             }
1978             WindowManagerState.Activity activity = task.getActivity(activityName);
1979             return activity.getWindowingMode() == WINDOWING_MODE_PINNED
1980                     && activity.getState().equals(STATE_PAUSED);
1981         }, "checking activity windowing mode");
1982         if (ENABLE_SHELL_TRANSITIONS) {
1983             mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
1984         }
1985     }
1986 
1987     public static class CountSpec<T> {
1988         static final int DONT_CARE = Integer.MIN_VALUE;
1989         public static final int EQUALS = 1;
1990         public static final int GREATER_THAN = 2;
1991         static final int LESS_THAN = 3;
1992         public static final int GREATER_THAN_OR_EQUALS = 4;
1993 
1994         final T mEvent;
1995         final int mRule;
1996         final int mCount;
1997         final String mMessage;
1998 
1999         CountSpec(T event, int rule, int count, String message) {
2000             mEvent = event;
2001             mRule = count == DONT_CARE ? DONT_CARE : rule;
2002             mCount = count;
2003             if (message != null) {
2004                 mMessage = message;
2005             } else {
2006                 switch (rule) {
2007                     case EQUALS:
2008                         mMessage = event + " must equal to " + count;
2009                         break;
2010                     case GREATER_THAN:
2011                         mMessage = event + " must be greater than " + count;
2012                         break;
2013                     case LESS_THAN:
2014                         mMessage = event + " must be less than " + count;
2015                         break;
2016                     case GREATER_THAN_OR_EQUALS:
2017                         mMessage = event + " must be greater than (or equals to) " + count;
2018                         break;
2019                     default:
2020                         mMessage = "Don't care";
2021                 }
2022             }
2023         }
2024 
2025         /** @return {@code true} if the given value is satisfied the condition. */
2026         boolean validate(int value) {
2027             switch (mRule) {
2028                 case DONT_CARE:
2029                     return true;
2030                 case EQUALS:
2031                     return value == mCount;
2032                 case GREATER_THAN:
2033                     return value > mCount;
2034                 case LESS_THAN:
2035                     return value < mCount;
2036                 case GREATER_THAN_OR_EQUALS:
2037                     return value >= mCount;
2038                 default:
2039             }
2040             throw new RuntimeException("Unknown CountSpec rule");
2041         }
2042     }
2043 
2044     static <T> CountSpec<T> countSpec(T event, int rule, int count, String message) {
2045         return new CountSpec<>(event, rule, count, message);
2046     }
2047 
2048     public static <T> CountSpec<T> countSpec(T event, int rule, int count) {
2049         return new CountSpec<>(event, rule, count, null /* message */);
2050     }
2051 
2052     static void assertLifecycleCounts(ComponentName activityName, String message,
2053             int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
2054             int destroyCount, int configChangeCount) {
2055         new ActivityLifecycleCounts(activityName).assertCountWithRetry(
2056                 message,
2057                 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, createCount),
2058                 countSpec(ActivityCallback.ON_START, CountSpec.EQUALS, startCount),
2059                 countSpec(ActivityCallback.ON_RESUME, CountSpec.EQUALS, resumeCount),
2060                 countSpec(ActivityCallback.ON_PAUSE, CountSpec.EQUALS, pauseCount),
2061                 countSpec(ActivityCallback.ON_STOP, CountSpec.EQUALS, stopCount),
2062                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, destroyCount),
2063                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS,
2064                         configChangeCount));
2065     }
2066 
2067     public static void assertLifecycleCounts(
2068             ComponentName activityName,
2069             int createCount,
2070             int startCount,
2071             int resumeCount,
2072             int pauseCount,
2073             int stopCount,
2074             int destroyCount,
2075             int configChangeCount) {
2076         assertLifecycleCounts(activityName, "Assert lifecycle of " + getLogTag(activityName),
2077                 createCount, startCount, resumeCount, pauseCount, stopCount,
2078                 destroyCount, configChangeCount);
2079     }
2080 
2081     public static void assertSingleLaunch(ComponentName activityName) {
2082         assertLifecycleCounts(activityName,
2083                 "activity create, start, and resume",
2084                 1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
2085                 0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */,
2086                 CountSpec.DONT_CARE /* configChangeCount */);
2087     }
2088 
2089     public static void assertSingleLaunchAndStop(ComponentName activityName) {
2090         assertLifecycleCounts(activityName,
2091                 "activity create, start, resume, pause, and stop",
2092                 1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
2093                 1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */,
2094                 CountSpec.DONT_CARE /* configChangeCount */);
2095     }
2096 
2097     public static void assertSingleStartAndStop(ComponentName activityName) {
2098         assertLifecycleCounts(activityName,
2099                 "activity start, resume, pause, and stop",
2100                 0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
2101                 1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */,
2102                 CountSpec.DONT_CARE /* configChangeCount */);
2103     }
2104 
2105     protected static void assertSingleStart(ComponentName activityName) {
2106         assertLifecycleCounts(activityName,
2107                 "activity start and resume",
2108                 0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
2109                 0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */,
2110                 CountSpec.DONT_CARE /* configChangeCount */);
2111     }
2112 
2113     /** Assert the activity is either relaunched or received configuration changed. */
2114     protected static void assertActivityLifecycle(ComponentName activityName, boolean relaunched) {
2115         Condition.<String>waitForResult(
2116                 activityName + (relaunched ? " relaunched" : " config changed"),
2117                 condition -> condition
2118                 .setResultSupplier(() -> checkActivityIsRelaunchedOrConfigurationChanged(
2119                         getActivityName(activityName),
2120                         TestJournalContainer.get(activityName).callbacks, relaunched))
2121                 .setResultValidator(failedReasons -> failedReasons == null)
2122                 .setOnFailure(failedReasons -> fail(failedReasons)));
2123     }
2124 
2125     /** Assert the activity is either relaunched or received configuration changed. */
2126     public static List<ActivityCallback> assertActivityLifecycle(
2127             ActivitySession activitySession, boolean relaunched) {
2128         final String name = activitySession.getName().flattenToShortString();
2129         final List<ActivityCallback> callbackHistory = activitySession.takeCallbackHistory();
2130         String failedReason = checkActivityIsRelaunchedOrConfigurationChanged(
2131                 name, callbackHistory, relaunched);
2132         if (failedReason != null) {
2133             fail(failedReason);
2134         }
2135         return callbackHistory;
2136     }
2137 
2138     private static String checkActivityIsRelaunchedOrConfigurationChanged(String name,
2139             List<ActivityCallback> callbackHistory, boolean relaunched) {
2140         final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(callbackHistory);
2141         if (relaunched) {
2142             return lifecycles.validateCount(
2143                     countSpec(ActivityCallback.ON_DESTROY, CountSpec.GREATER_THAN, 0,
2144                             name + " must have been destroyed."),
2145                     countSpec(ActivityCallback.ON_CREATE, CountSpec.GREATER_THAN, 0,
2146                             name + " must have been (re)created."));
2147         }
2148         return lifecycles.validateCount(
2149                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.LESS_THAN, 1,
2150                         name + " must *NOT* have been destroyed."),
2151                 countSpec(ActivityCallback.ON_CREATE, CountSpec.LESS_THAN, 1,
2152                         name + " must *NOT* have been (re)created."),
2153                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.GREATER_THAN, 0,
2154                                 name + " must have received configuration changed."));
2155     }
2156 
2157     public static void assertRelaunchOrConfigChanged(
2158             ComponentName activityName, int numRelaunch, int numConfigChange) {
2159         new ActivityLifecycleCounts(activityName).assertCountWithRetry("relaunch or config changed",
2160                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, numRelaunch),
2161                 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, numRelaunch),
2162                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS,
2163                         numConfigChange));
2164     }
2165 
2166     public static void assertActivityDestroyed(ComponentName activityName) {
2167         new ActivityLifecycleCounts(activityName).assertCountWithRetry("activity destroyed",
2168                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, 1),
2169                 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, 0),
2170                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 0));
2171     }
2172 
2173     public static void assertSecurityExceptionFromActivityLauncher() {
2174         waitForOrFail("SecurityException from " + ActivityLauncher.TAG,
2175                 ActivityLauncher::hasCaughtSecurityException);
2176     }
2177 
2178     private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)");
2179     private static final Pattern sUiModeLockedPattern =
2180             Pattern.compile("mUiModeLocked=(true|false)");
2181 
2182     @NonNull
2183     public SizeInfo getLastReportedSizesForActivity(ComponentName activityName) {
2184         return Condition.waitForResult("sizes of " + activityName + " to be reported",
2185                 condition -> condition.setResultSupplier(() -> {
2186                     final ConfigInfo info = TestJournalContainer.get(activityName).lastConfigInfo;
2187                     return info != null ? info.sizeInfo : null;
2188                 }).setResultValidator(Objects::nonNull).setOnFailure(unusedResult ->
2189                         fail("No config reported from " + activityName)));
2190     }
2191 
2192     /** Check if a device has display cutout. */
2193     public boolean hasDisplayCutout() {
2194         // Launch an activity to report cutout state
2195         separateTestJournal();
2196         launchActivity(BROADCAST_RECEIVER_ACTIVITY);
2197 
2198         // Read the logs to check if cutout is present
2199         final Boolean displayCutoutPresent = getCutoutStateForActivity(BROADCAST_RECEIVER_ACTIVITY);
2200         assertNotNull("The activity should report cutout state", displayCutoutPresent);
2201 
2202         // Finish activity
2203         mBroadcastActionTrigger.finishBroadcastReceiverActivity();
2204         mWmState.waitForWithAmState(
2205                 (state) -> !state.containsActivity(BROADCAST_RECEIVER_ACTIVITY),
2206                 "activity to be removed");
2207 
2208         return displayCutoutPresent;
2209     }
2210 
2211     /**
2212      * Wait for activity to report cutout state in logs and return it. Will return {@code null}
2213      * after timeout.
2214      */
2215     @Nullable
2216     private Boolean getCutoutStateForActivity(ComponentName activityName) {
2217         return Condition.waitForResult("cutout state to be reported", condition -> condition
2218                 .setResultSupplier(() -> {
2219                     final Bundle extras = TestJournalContainer.get(activityName).extras;
2220                     return extras.containsKey(EXTRA_CUTOUT_EXISTS)
2221                             ? extras.getBoolean(EXTRA_CUTOUT_EXISTS)
2222                             : null;
2223                 }).setResultValidator(cutoutExists -> cutoutExists != null));
2224     }
2225 
2226     /** Waits for at least one onMultiWindowModeChanged event. */
2227     public ActivityLifecycleCounts waitForOnMultiWindowModeChanged(ComponentName activityName) {
2228         final ActivityLifecycleCounts counts = new ActivityLifecycleCounts(activityName);
2229         Condition.waitFor(counts.countWithRetry("waitForOnMultiWindowModeChanged", countSpec(
2230                 ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED, CountSpec.GREATER_THAN, 0)));
2231         return counts;
2232     }
2233 
2234     protected WindowState getPackageWindowState(String packageName) {
2235         final WindowManagerState.WindowState window =
2236                 mWmState.getWindowByPackageName(packageName, TYPE_BASE_APPLICATION);
2237         assertNotNull(window);
2238         return window;
2239     }
2240 
2241     public static class ActivityLifecycleCounts {
2242         private final int[] mCounts = new int[ActivityCallback.SIZE];
2243         private final int[] mFirstIndexes = new int[ActivityCallback.SIZE];
2244         private final int[] mLastIndexes = new int[ActivityCallback.SIZE];
2245         private ComponentName mActivityName;
2246 
2247         public ActivityLifecycleCounts(ComponentName componentName) {
2248             mActivityName = componentName;
2249             updateCount(TestJournalContainer.get(componentName).callbacks);
2250         }
2251 
2252         public ActivityLifecycleCounts(List<ActivityCallback> callbacks) {
2253             updateCount(callbacks);
2254         }
2255 
2256         private void updateCount(List<ActivityCallback> callbacks) {
2257             // The callback list could be from the reference of TestJournal. If we are counting for
2258             // retrying, there may be new data added to the list from other threads.
2259             TestJournalContainer.withThreadSafeAccess(() -> {
2260                 Arrays.fill(mFirstIndexes, -1);
2261                 for (int i = 0; i < callbacks.size(); i++) {
2262                     final ActivityCallback callback = callbacks.get(i);
2263                     final int ordinal = callback.ordinal();
2264                     mCounts[ordinal]++;
2265                     mLastIndexes[ordinal] = i;
2266                     if (mFirstIndexes[ordinal] == -1) {
2267                         mFirstIndexes[ordinal] = i;
2268                     }
2269                 }
2270             });
2271         }
2272 
2273         public int getCount(ActivityCallback callback) {
2274             return mCounts[callback.ordinal()];
2275         }
2276 
2277         public int getFirstIndex(ActivityCallback callback) {
2278             return mFirstIndexes[callback.ordinal()];
2279         }
2280 
2281         public int getLastIndex(ActivityCallback callback) {
2282             return mLastIndexes[callback.ordinal()];
2283         }
2284 
2285         @SafeVarargs
2286         public final Condition<String> countWithRetry(
2287                 String message, CountSpec<ActivityCallback>... countSpecs) {
2288             if (mActivityName == null) {
2289                 throw new IllegalStateException(
2290                         "It is meaningless to retry without specified activity");
2291             }
2292             return new Condition<String>(message)
2293                     .setOnRetry(() -> {
2294                         Arrays.fill(mCounts, 0);
2295                         Arrays.fill(mLastIndexes, 0);
2296                         updateCount(TestJournalContainer.get(mActivityName).callbacks);
2297                     })
2298                     .setResultSupplier(() -> validateCount(countSpecs))
2299                     .setResultValidator(failedReasons -> failedReasons == null);
2300         }
2301 
2302         @SafeVarargs
2303         public final void assertCountWithRetry(
2304                 String message, CountSpec<ActivityCallback>... countSpecs) {
2305             if (mActivityName == null) {
2306                 throw new IllegalStateException(
2307                         "It is meaningless to retry without specified activity");
2308             }
2309             Condition.<String>waitForResult(countWithRetry(message, countSpecs)
2310                     .setOnFailure(failedReasons -> fail(message + ": " + failedReasons)));
2311         }
2312 
2313         @SafeVarargs
2314         final String validateCount(CountSpec<ActivityCallback>... countSpecs) {
2315             ArrayList<String> failedReasons = null;
2316             for (CountSpec<ActivityCallback> spec : countSpecs) {
2317                 final int realCount = mCounts[spec.mEvent.ordinal()];
2318                 if (!spec.validate(realCount)) {
2319                     if (failedReasons == null) {
2320                         failedReasons = new ArrayList<>();
2321                     }
2322                     failedReasons.add(spec.mMessage + " (got " + realCount + ")");
2323                 }
2324             }
2325             return failedReasons == null ? null : String.join("\n", failedReasons);
2326         }
2327     }
2328 
2329     protected void stopTestPackage(final String packageName) {
2330         runWithShellPermission(() -> mAm.forceStopPackage(packageName));
2331     }
2332 
2333     protected LaunchActivityBuilder getLaunchActivityBuilder() {
2334         return new LaunchActivityBuilder(mWmState);
2335     }
2336 
2337     public static <T extends Activity>
2338     ActivityScenarioRule<T> createFullscreenActivityScenarioRule(Class<T> clazz) {
2339         final ActivityOptions options = ActivityOptions.makeBasic();
2340         options.setLaunchWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
2341         return new ActivityScenarioRule<>(clazz, options.toBundle());
2342     }
2343 
2344     /**
2345      * The actions which wraps a test method. It is used to set necessary rules that cannot be
2346      * overridden by subclasses. It executes in the outer scope of {@link Before} and {@link After}.
2347      */
2348     protected class WrapperRule implements TestRule {
2349         private final Runnable mBefore;
2350         private final Runnable mAfter;
2351 
2352         protected WrapperRule(Runnable before, Runnable after) {
2353             mBefore = before;
2354             mAfter = after;
2355         }
2356 
2357         @Override
2358         public Statement apply(final Statement base, final Description description) {
2359             return new Statement() {
2360                 @Override
2361                 public void evaluate()  {
2362                     if (mBefore != null) {
2363                         mBefore.run();
2364                     }
2365                     try {
2366                         base.evaluate();
2367                     } catch (Throwable e) {
2368                         mPostAssertionRule.addError(e);
2369                     } finally {
2370                         if (mAfter != null) {
2371                             mAfter.run();
2372                         }
2373                     }
2374                 }
2375             };
2376         }
2377     }
2378 
2379     /**
2380      * The post assertion to ensure all test methods don't violate the generic rule. It is also used
2381      * to collect multiple errors.
2382      */
2383     private class PostAssertionRule extends ErrorCollector {
2384         private Throwable mLastError;
2385 
2386         @Override
2387         protected void verify() throws Throwable {
2388             if (mLastError != null) {
2389                 // Try to recover the bad state of device to avoid subsequent test failures.
2390                 if (isKeyguardLocked()) {
2391                     mLastError.addSuppressed(new IllegalStateException("Keyguard is locked"));
2392                     unlockUnexpectedLockedKeyguard();
2393                 }
2394                 final String overlayDisplaySettings = Settings.Global.getString(
2395                         mContext.getContentResolver(), Settings.Global.OVERLAY_DISPLAY_DEVICES);
2396                 if (overlayDisplaySettings != null && overlayDisplaySettings.length() > 0) {
2397                     mLastError.addSuppressed(new IllegalStateException(
2398                             "Overlay display is found: " + overlayDisplaySettings));
2399                     // Remove the overlay display because it may obscure the screen and causes the
2400                     // next tests to fail.
2401                     SettingsSession.delete(Settings.Global.getUriFor(
2402                             Settings.Global.OVERLAY_DISPLAY_DEVICES));
2403                 }
2404             }
2405             if (!sIllegalTaskStateFound) {
2406                 // Skip if a illegal task state was already found in previous test, or all tests
2407                 // afterward could also fail and fire unnecessary false alarms.
2408                 try {
2409                     mWmState.assertIllegalTaskState();
2410                 } catch (Throwable t) {
2411                     sIllegalTaskStateFound = true;
2412                     addError(t);
2413                 }
2414             }
2415             super.verify();
2416         }
2417 
2418         @Override
2419         public void addError(Throwable error) {
2420             super.addError(error);
2421             logE("addError: " + error);
2422             mLastError = error;
2423         }
2424     }
2425 
2426     /** Activity that can handle all config changes. */
2427     public static class ConfigChangeHandlingActivity extends CommandSession.BasicTestActivity {
2428     }
2429 
2430     public static class ReportedDisplayMetrics {
2431         private static final String WM_SIZE = "wm size";
2432         private static final String WM_DENSITY = "wm density";
2433         private static final Pattern PHYSICAL_SIZE =
2434                 Pattern.compile("Physical size: (\\d+)x(\\d+)");
2435         private static final Pattern OVERRIDE_SIZE =
2436                 Pattern.compile("Override size: (\\d+)x(\\d+)");
2437         private static final Pattern PHYSICAL_DENSITY =
2438                 Pattern.compile("Physical density: (\\d+)");
2439         private static final Pattern OVERRIDE_DENSITY =
2440                 Pattern.compile("Override density: (\\d+)");
2441 
2442         /** The size of the physical display. */
2443         @NonNull
2444         final Size physicalSize;
2445         /** The density of the physical display. */
2446         final int physicalDensity;
2447 
2448         /** The pre-existing size override applied to a logical display. */
2449         @Nullable
2450         final Size overrideSize;
2451         /** The pre-existing density override applied to a logical display. */
2452         @Nullable
2453         final Integer overrideDensity;
2454 
2455         final int mDisplayId;
2456 
2457         @NonNull
2458         public Size getPhysicalSize() {
2459             return physicalSize;
2460         }
2461 
2462         public int getPhysicalDensity() {
2463             return physicalDensity;
2464         }
2465 
2466         @Nullable
2467         public Size getOverrideSize() {
2468             return overrideSize;
2469         }
2470 
2471         public Integer getOverrideDensity() {
2472             return overrideDensity;
2473         }
2474 
2475         /** Get physical and override display metrics from WM for specified display. */
2476         public static ReportedDisplayMetrics getDisplayMetrics(int displayId) {
2477             return new ReportedDisplayMetrics(
2478                     executeShellCommandAndGetStdout(WM_SIZE + " -d " + displayId)
2479                     + executeShellCommandAndGetStdout(WM_DENSITY + " -d " + displayId), displayId);
2480         }
2481 
2482         public void setDisplayMetrics(final Size size, final int density) {
2483             setSize(size);
2484             setDensity(density);
2485         }
2486 
2487         public void restoreDisplayMetrics() {
2488             if (overrideSize != null) {
2489                 setSize(overrideSize);
2490             } else {
2491                 executeShellCommand(WM_SIZE + " reset -d " + mDisplayId);
2492             }
2493             if (overrideDensity != null) {
2494                 setDensity(overrideDensity);
2495             } else {
2496                 executeShellCommand(WM_DENSITY + " reset -d " + mDisplayId);
2497             }
2498         }
2499 
2500         public void setSize(final Size size) {
2501             executeShellCommand(
2502                     WM_SIZE + " " + size.getWidth() + "x" + size.getHeight() + " -d " + mDisplayId);
2503         }
2504 
2505         public void setDensity(final int density) {
2506             executeShellCommand(WM_DENSITY + " " + density + " -d " + mDisplayId);
2507         }
2508 
2509         /** Get display size that WM operates with. */
2510         public Size getSize() {
2511             return overrideSize != null ? overrideSize : physicalSize;
2512         }
2513 
2514         /** Get density that WM operates with. */
2515         public int getDensity() {
2516             return overrideDensity != null ? overrideDensity : physicalDensity;
2517         }
2518 
2519         private ReportedDisplayMetrics(final String lines, int displayId) {
2520             mDisplayId = displayId;
2521             Matcher matcher = PHYSICAL_SIZE.matcher(lines);
2522             assertTrue("Physical display size must be reported", matcher.find());
2523             log(matcher.group());
2524             physicalSize = new Size(
2525                     Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
2526 
2527             matcher = PHYSICAL_DENSITY.matcher(lines);
2528             assertTrue("Physical display density must be reported", matcher.find());
2529             log(matcher.group());
2530             physicalDensity = Integer.parseInt(matcher.group(1));
2531 
2532             matcher = OVERRIDE_SIZE.matcher(lines);
2533             if (matcher.find()) {
2534                 log(matcher.group());
2535                 overrideSize = new Size(
2536                         Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
2537             } else {
2538                 overrideSize = null;
2539             }
2540 
2541             matcher = OVERRIDE_DENSITY.matcher(lines);
2542             if (matcher.find()) {
2543                 log(matcher.group());
2544                 overrideDensity = Integer.parseInt(matcher.group(1));
2545             } else {
2546                 overrideDensity = null;
2547             }
2548         }
2549     }
2550 
2551     /**
2552      * Either launches activity via {@link CommandSession.ActivitySessionClient} in case it is
2553      * a subclass of {@link CommandSession.BasicTestActivity} (then activity can be destroyed
2554      * by means of sending the finish command). Otherwise, launches activity via ADB commands
2555      * ({@link #launchActivityOnDisplay}), in this case the activity can be destroyed only as part
2556      * of the app package with ADB command `am stop-app`. In this case the activity can be destroyed
2557      * only if it is defined in another apk, so the test suit is not destroyed, this is detected
2558      * when catching {@link ClassNotFoundException} exception.
2559      */
2560     public class ActivitySessionCloseable implements AutoCloseable {
2561         private final ComponentName mActivityName;
2562         @Nullable
2563         protected CommandSession.ActivitySession mActivity;
2564         @Nullable
2565         private CommandSession.ActivitySessionClient mSession;
2566 
2567         public ActivitySessionCloseable(final ComponentName activityName) {
2568             this(activityName, WINDOWING_MODE_FULLSCREEN);
2569         }
2570 
2571         public ActivitySessionCloseable(final ComponentName activityName, final int windowingMode) {
2572             this(activityName, windowingMode, /* forceCommandActivity */ false);
2573         }
2574 
2575         /**
2576          * @param activityName can be created with
2577          *              {@link android.server.wm.component.ComponentsBase#component}.
2578          * @param windowingMode {@link WindowConfiguration.WindowingMode}
2579          * @param forceCommandActivity sometimes Activity implements
2580          *              {@link CommandSession.BasicTestActivity} but is defined in a different apk,
2581          *              so can not be verified if it is a subclass of
2582          *              {@link CommandSession.BasicTestActivity}. In this case forceCommandActivity
2583          *              argument can be used to ensure that this activity is managed as
2584          *              {@link CommandSession.BasicTestActivity}.
2585          */
2586         public ActivitySessionCloseable(
2587                 final ComponentName activityName,
2588                 final int windowingMode,
2589                 final boolean forceCommandActivity) {
2590             mActivityName = activityName;
2591 
2592             if (forceCommandActivity || isCommandActivity()) {
2593                 mSession = new CommandSession.ActivitySessionClient(mContext);
2594                 mActivity = mSession.startActivity(getLaunchActivityBuilder()
2595                                 .setUseInstrumentation()
2596                                 .setWaitForLaunched(true)
2597                                 .setNewTask(true)
2598                                 .setMultipleTask(true)
2599                                 .setWindowingMode(windowingMode)
2600                                 .setTargetActivity(activityName));
2601             } else {
2602                 launchActivityOnDisplay(activityName, windowingMode, DEFAULT_DISPLAY);
2603                 mWmState.computeState(new WaitForValidActivityState(activityName));
2604             }
2605         }
2606 
2607         private boolean isAnotherApp() {
2608             try {
2609                 Class.forName(mActivityName.getClassName());
2610                 return false;
2611             } catch (ClassNotFoundException e) {
2612                 return true;
2613             }
2614         }
2615 
2616         private boolean isCommandActivity() {
2617             try {
2618                 var c = Class.forName(mActivityName.getClassName());
2619                 return CommandSession.BasicTestActivity.class.isAssignableFrom(c);
2620             } catch (ClassNotFoundException e) {
2621                 Log.w(TAG, "Class " + mActivityName.getClassName() + " is not found", e);
2622                 return false;
2623             }
2624         }
2625 
2626         @Override
2627         public void close() {
2628             if (mSession != null && mActivity != null) {
2629                 mSession.close();
2630                 mWmState.waitForActivityRemoved(mActivityName);
2631             } else if (isAnotherApp()) {
2632                 executeShellCommand("am stop-app " + mActivityName.getPackageName());
2633                 mWmState.waitForActivityRemoved(mActivityName);
2634             } else {
2635                 Log.w(TAG, "No explicit cleanup possible for " + mActivityName);
2636             }
2637         }
2638 
2639         public WindowManagerState.Activity getActivityState() {
2640             return getActivityWaitState(mActivityName);
2641         }
2642 
2643         /**
2644          * Not null only for {@link CommandSession.BasicTestActivity} activities.
2645          */
2646         @Nullable
2647         public CommandSession.ActivitySession getActivitySession() {
2648             return mActivity;
2649         }
2650     }
2651 
2652     /**
2653      * Same as ActivitySessionCloseable, but with forceCommandActivity = true
2654      */
2655     public class BaseActivitySessionCloseable extends ActivitySessionCloseable {
2656         public BaseActivitySessionCloseable(ComponentName activityName) {
2657             this(activityName, WINDOWING_MODE_FULLSCREEN);
2658         }
2659 
2660         public BaseActivitySessionCloseable(
2661                 final ComponentName activityName, final int windowingMode) {
2662             super(activityName, windowingMode, /* forceCommandActivity */ true);
2663         }
2664 
2665         @Override
2666         @NonNull
2667         public CommandSession.ActivitySession getActivitySession() {
2668             assertNotNull(mActivity);
2669             return mActivity;
2670         }
2671     }
2672 
2673     /**
2674      * Launches primary and secondary activities in split-screen.
2675      */
2676     public class SplitScreenActivitiesCloseable implements AutoCloseable {
2677         private final ActivitySessionCloseable mPrimarySession;
2678         private final ActivitySessionCloseable mSecondarySession;
2679 
2680         public SplitScreenActivitiesCloseable(
2681                 final ComponentName primaryActivityName,
2682                 final ComponentName secondaryActivityName) {
2683             this(primaryActivityName, WINDOWING_MODE_FULLSCREEN,
2684                     /* forcePrimaryCommandActivity */ false,
2685                     secondaryActivityName, WINDOWING_MODE_FULLSCREEN,
2686                     /* forceSecondaryCommandActivity */ false);
2687         }
2688 
2689         public SplitScreenActivitiesCloseable(
2690                 final ComponentName primaryActivityName,
2691                 final int primaryWindowingMode,
2692                 final boolean forcePrimaryCommandActivity,
2693                 final ComponentName secondaryActivityName,
2694                 final int secondaryWindowingMode,
2695                 final boolean forceSecondaryCommandActivity) {
2696             mPrimarySession = new ActivitySessionCloseable(primaryActivityName,
2697                     primaryWindowingMode, forcePrimaryCommandActivity);
2698             mTaskOrganizer.putTaskInSplitPrimary(
2699                     mWmState.getTaskByActivity(primaryActivityName).mTaskId);
2700             mSecondarySession = new ActivitySessionCloseable(secondaryActivityName,
2701                     secondaryWindowingMode, forceSecondaryCommandActivity);
2702             mTaskOrganizer.putTaskInSplitSecondary(
2703                     mWmState.getTaskByActivity(secondaryActivityName).mTaskId);
2704             mWmState.computeState(new WaitForValidActivityState(primaryActivityName),
2705                     new WaitForValidActivityState(secondaryActivityName));
2706         }
2707 
2708         @Override
2709         public void close() {
2710             mPrimarySession.close();
2711             mSecondarySession.close();
2712         }
2713 
2714         public ActivitySessionCloseable getPrimaryActivity() {
2715             return mPrimarySession;
2716         }
2717 
2718         public ActivitySessionCloseable getSecondaryActivity() {
2719             return mSecondarySession;
2720         }
2721     }
2722 
2723     /**
2724      * Ensures the device is rotated to portrait orientation.
2725      */
2726     public class DeviceOrientationCloseable implements AutoCloseable {
2727         @Nullable
2728         private final RotationSession mRotationSession;
2729 
2730         /** Needed to restore the previous orientation in {@link #close} */
2731         private final Integer mPreviousRotation;
2732 
2733         /**
2734          * @param requestedOrientation values are Configuration#Orientation
2735          *          either {@link ORIENTATION_PORTRAIT} or {@link ORIENTATION_LANDSCAPE}
2736          */
2737         public DeviceOrientationCloseable(int requestedOrientation) {
2738             // Need to use window to get the size of the screen taking orientation into account.
2739             // mWmState.getDisplay(DEFAULT_DISPLAY).getFullConfiguration().orientation
2740             // can not be used because returned orientation can be {@link ORIENTATION_UNDEFINED}
2741             final Size windowSize = asSize(mWm.getMaximumWindowMetrics().getBounds());
2742 
2743             boolean isRotationRequired = false;
2744             if (ORIENTATION_PORTRAIT == requestedOrientation) {
2745                 isRotationRequired = windowSize.getHeight() < windowSize.getWidth();
2746             } else if (ORIENTATION_LANDSCAPE == requestedOrientation) {
2747                 isRotationRequired = windowSize.getHeight() > windowSize.getWidth();
2748             }
2749 
2750             if (isRotationRequired) {
2751                 mPreviousRotation = mWmState.getRotation();
2752                 mRotationSession = new RotationSession(mWmState);
2753                 mRotationSession.set(ROTATION_90);
2754                 assertTrue("display rotation must be ROTATION_90 now",
2755                         mWmState.waitForRotation(ROTATION_90));
2756             } else {
2757                 mRotationSession = null;
2758                 mPreviousRotation = ROTATION_0;
2759             }
2760         }
2761 
2762         @Override
2763         public void close() {
2764             if (mRotationSession != null) {
2765                 mRotationSession.close();
2766                 mWmState.waitForRotation(mPreviousRotation);
2767             }
2768         }
2769 
2770         public boolean isRotationApplied() {
2771             return mRotationSession != null;
2772         }
2773     }
2774 
2775     /**
2776      * Makes sure {@link DisplayMetricsSession} is closed with waitFor original display content
2777      * is restored.
2778      */
2779     public class DisplayMetricsWaitCloseable extends DisplayMetricsSession {
2780         private final int mDisplayId;
2781         private final WindowManagerState.DisplayContent mOriginalDC;
2782 
2783         public DisplayMetricsWaitCloseable() {
2784             this(DEFAULT_DISPLAY);
2785         }
2786 
2787         public DisplayMetricsWaitCloseable(int displayId) {
2788             super(displayId);
2789             mDisplayId = displayId;
2790             mOriginalDC = mWmState.getDisplay(displayId);
2791         }
2792 
2793         @Override
2794         public void restoreDisplayMetrics() {
2795             mWmState.waitForWithAmState(wmState -> {
2796                 super.restoreDisplayMetrics();
2797                 return mWmState.getDisplay(mDisplayId).equals(mOriginalDC);
2798             }, "waiting for display to be restored");
2799         }
2800     }
2801 
2802     /**
2803      * AutoClosable class used for try-with-resources compat change tests, which require a separate
2804      * application task to be started.
2805      */
2806     public static class CompatChangeCloseable implements AutoCloseable {
2807         private final String mChangeName;
2808         private final String mPackageName;
2809 
2810         public CompatChangeCloseable(final Long changeId, String packageName) {
2811             this(changeId.toString(), packageName);
2812         }
2813 
2814         public CompatChangeCloseable(final String changeName, String packageName) {
2815             this.mChangeName = changeName;
2816             this.mPackageName = packageName;
2817 
2818             // Enable change
2819             executeShellCommand("am compat enable " + changeName + " " + packageName);
2820         }
2821 
2822         @Override
2823         public void close() {
2824             executeShellCommand("am compat disable " + mChangeName + " " + mPackageName);
2825         }
2826     }
2827 
2828     /**
2829      * Scales the display size
2830      */
2831     public class DisplaySizeScaleCloseable extends DisplaySizeCloseable {
2832         /**
2833          * @param sizeScaleFactor display size scaling factor.
2834          * @param activity can be null, the activity which is currently on the screen.
2835          */
2836         public DisplaySizeScaleCloseable(double sizeScaleFactor, @Nullable ComponentName activity) {
2837             super(sizeScaleFactor, /* densityScaleFactor */ 1, ORIENTATION_UNDEFINED,
2838                     /* aspectRatio */ -1, asList(activity));
2839         }
2840     }
2841 
2842     /**
2843      * Changes aspectRatio of the display.
2844      */
2845     public class DisplayAspectRatioCloseable extends DisplaySizeCloseable {
2846         /**
2847          * @param requestedOrientation orientation.
2848          * @param aspectRatio aspect ratio of the screen.
2849          */
2850         public DisplayAspectRatioCloseable(int requestedOrientation, double aspectRatio) {
2851             super(/* sizeScaleFactor */ 1, /* densityScaleFactor */ 1, requestedOrientation,
2852                     aspectRatio, /* activities */ List.of());
2853         }
2854 
2855         /**
2856          * @param requestedOrientation orientation.
2857          * @param aspectRatio aspect ratio of the screen.
2858          * @param activity the current activity.
2859          */
2860         public DisplayAspectRatioCloseable(int requestedOrientation, double aspectRatio,
2861                 @Nullable ComponentName activity) {
2862             super(/* sizeScaleFactor */ 1, /* densityScaleFactor */ 1, requestedOrientation,
2863                     aspectRatio, asList(activity));
2864         }
2865     }
2866 
2867     public class DisplaySizeCloseable extends DisplayMetricsWaitCloseable {
2868 
2869         private List<Pair<ComponentName, Rect>> mNewBounds = List.of();
2870 
2871         private static boolean isLandscape(Size s) {
2872             return s.getWidth() > s.getHeight();
2873         }
2874 
2875         protected static <T> List<T> asList(@Nullable T v) {
2876             return (v != null) ? List.of(v) : List.of();
2877         }
2878 
2879         /**
2880          * @param sizeScaleFactor display size scaling factor.
2881          * @param densityScaleFactor density scaling factor.
2882          * @param activities can be empty, the activities which are currently on the screen.
2883          */
2884         public DisplaySizeCloseable(double sizeScaleFactor, double densityScaleFactor,
2885                 final int requestedOrientation, final double aspectRatio,
2886                 @NonNull List<ComponentName> activities) {
2887             if (sizeScaleFactor != 1 || densityScaleFactor != 1) {
2888                 var originalBounds = activities.stream()
2889                         .map(a -> new Pair<>(a, getActivityWaitState(a).getBounds()))
2890                         .toList();
2891 
2892                 final var origDisplaySize = getDisplayMetrics().getSize();
2893 
2894                 changeDisplayMetrics(sizeScaleFactor, densityScaleFactor);
2895                 waitForDisplaySizeChanged(origDisplaySize, sizeScaleFactor);
2896 
2897                 originalBounds.forEach(activityAndBounds -> {
2898                     waitForActivityBoundsChanged(activityAndBounds.first, activityAndBounds.second);
2899                     mWmState.computeState(new WaitForValidActivityState(activityAndBounds.first));
2900                 });
2901 
2902                 mNewBounds = activities.stream()
2903                         .map(a -> new Pair<>(a, getActivityWaitState(a).getBounds()))
2904                         .toList();
2905             }
2906 
2907             if (ORIENTATION_UNDEFINED != requestedOrientation && aspectRatio > 0) {
2908                 final Size maxWindowSize = asSize(mWm.getMaximumWindowMetrics().getBounds());
2909                 final var origDisplaySize = getDisplayMetrics().getSize();
2910 
2911                 var isMatchingOrientation =
2912                         isLandscape(origDisplaySize) == isLandscape(maxWindowSize);
2913                 if (ORIENTATION_LANDSCAPE == requestedOrientation) {
2914                     changeAspectRatio(aspectRatio,
2915                             isMatchingOrientation ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT);
2916                     waitForDisplaySizeChanged(origDisplaySize, aspectRatio);
2917                 } else if (ORIENTATION_PORTRAIT == requestedOrientation) {
2918                     changeAspectRatio(aspectRatio,
2919                             isMatchingOrientation ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE);
2920                     waitForDisplaySizeChanged(origDisplaySize, aspectRatio);
2921                 }
2922             }
2923         }
2924 
2925         @Override
2926         public void close() {
2927             super.close();
2928             mNewBounds.forEach(activityAndBounds -> {
2929                 if (mWmState.isActivityVisible(activityAndBounds.first)) {
2930                     waitForActivityBoundsChanged(activityAndBounds.first, activityAndBounds.second);
2931                     mWmState.computeState(new WaitForValidActivityState(activityAndBounds.first));
2932                 }
2933             });
2934         }
2935 
2936 
2937         /**
2938          * Waits until the given activity has updated task bounds.
2939          */
2940         private void waitForActivityBoundsChanged(ComponentName activityName,
2941                 Rect priorActivityBounds) {
2942             mWmState.waitForWithAmState(wmState -> {
2943                 mWmState.computeState(new WaitForValidActivityState(activityName));
2944                 WindowManagerState.Activity activity = wmState.getActivity(activityName);
2945                 return activity != null && !activity.getBounds().equals(priorActivityBounds);
2946             }, "checking activity bounds updated");
2947         }
2948 
2949         /**
2950          * Waits until the display bounds changed.
2951          */
2952         private void waitForDisplaySizeChanged(final Size originalDisplaySize, final double ratio) {
2953             if (!mWmState.waitForWithAmState(wmState ->
2954                     !originalDisplaySize.equals(getDisplayMetrics().getSize()),
2955                     "waiting for display changing aspect ratio")) {
2956 
2957                 final Size currentDisplaySize = getDisplayMetrics().getSize();
2958                 // Sometimes display size can be capped, making it impossible to scale the size up
2959                 // b/192406238.
2960                 if (ratio >= 1f) {
2961                     assumeFalse("If a display size is capped, resizing may be a no-op",
2962                             originalDisplaySize.equals(currentDisplaySize));
2963                 } else {
2964                     assertNotEquals("Display size must change if sizeRatio < 1f",
2965                             originalDisplaySize, currentDisplaySize);
2966                 }
2967             }
2968         }
2969 
2970         public float getInitialDisplayAspectRatio() {
2971             Size size = getInitialDisplayMetrics().getSize();
2972             return Math.max(size.getHeight(), size.getWidth())
2973                     / (float) (Math.min(size.getHeight(), size.getWidth()));
2974         }
2975     }
2976 
2977     public static Size asSize(Rect r) {
2978         return new Size(r.width(), r.height());
2979     }
2980 
2981     public <T> void waitAssertEquals(final String message, final T expected, Supplier<T> actual) {
2982         assertTrue(message, mWmState.waitFor(state -> expected.equals(actual.get()),
2983                 "wait for correct result"));
2984     }
2985 
2986     public WindowManagerState.Activity getActivityWaitState(ComponentName activityName) {
2987         mWmState.computeState(new WaitForValidActivityState(activityName));
2988         return mWmState.getActivity(activityName);
2989     }
2990 
2991     /**
2992      * Inset given frame if the insets source exist.
2993      *
2994      * @param windowState The window which have the insets source.
2995      * @param predicate Inset source predicate.
2996      * @param inOutBounds In/out the given frame from the inset source.
2997      */
2998     public static void insetGivenFrame(WindowManagerState.WindowState windowState,
2999             Predicate<WindowManagerState.InsetsSource> predicate, Rect inOutBounds) {
3000         Optional<WindowManagerState.InsetsSource> insetsOptional =
3001                 windowState.getMergedLocalInsetsSources().stream().filter(
3002                         predicate).findFirst();
3003         insetsOptional.ifPresent(insets -> insets.insetGivenFrame(inOutBounds));
3004     }
3005 
3006     /**
3007      * Checks whether the test is enabled on visible background users.
3008      */
3009     protected void assumeRunNotOnVisibleBackgroundNonProfileUser(String message) {
3010         assumeFalse(message, mUserHelper.isVisibleBackgroundUser());
3011     }
3012 
3013     /**
3014      * Checks whether the device has automotive split-screen multitasking feature enabled
3015      */
3016     protected boolean hasAutomotiveSplitscreenMultitaskingFeature() {
3017         return mContext.getPackageManager()
3018                 .hasSystemFeature(/* PackageManager.FEATURE_CAR_SPLITSCREEN_MULTITASKING */
3019                         "android.software.car.splitscreen_multitasking") && isCar();
3020     }
3021 
3022     /**
3023      * Checks whether the device supports the visible background user.
3024      *
3025      * <p>The visible background user feature allows full users to be started in background
3026      * visible on their assigned displays.
3027      *
3028      * <p>Note that this feature is typically only supported on automotive system with passenger
3029      * displays. For most devices, this method returns {@code false}.
3030      */
3031     protected boolean isVisibleBackgroundUserSupported() {
3032         return mUserHelper.isVisibleBackgroundUserSupported();
3033     }
3034 
3035     /**
3036      * Returns the main display assigned to the user.
3037      * Note that this returns the DEFAULT_DISPLAY for the current user, and returns the display
3038      * assigned to the user if it is a visible background user.
3039      */
3040     protected int getMainDisplayId() {
3041         return mUserHelper.getMainDisplayId();
3042     }
3043 
3044     /**
3045      * Checks whether the device has non-overlapping multitasking feature enabled.
3046      *
3047      * When this is true, we expect the Task to not occlude other Task below it,
3048      * which means both Tasks can be resumed and visible.
3049      */
3050     protected boolean isNonOverlappingMultiWindowMode(Activity activity) {
3051         if (!activity.isInMultiWindowMode()) {
3052             return false;
3053         }
3054         if (hasAutomotiveSplitscreenMultitaskingFeature()) {
3055             // Automotive SplitScreen Multitasking devices overlap the windows.
3056             return false;
3057         }
3058         return true;
3059     }
3060 
3061     /**
3062      * Retrieves {@link DisplayContent} with {@code displayId} from {@code displays}, or
3063      * {@code null} if there's no such {@link DisplayContent} in the list.
3064      */
3065     @Nullable
3066     protected DisplayContent getDisplayState(@NonNull List<DisplayContent> displays,
3067             int displayId) {
3068         for (DisplayContent display : displays) {
3069             if (display.mId == displayId) {
3070                 return display;
3071             }
3072         }
3073         return null;
3074     }
3075 
3076     /** Return the display state with width, height, dpi. Always not default display. */
3077     @Nullable
3078     protected DisplayContent getDisplayState(@NonNull List<DisplayContent> displays, int width,
3079             int height, int dpi) {
3080         for (DisplayContent display : displays) {
3081             if (display.mId == DEFAULT_DISPLAY) {
3082                 continue;
3083             }
3084             final Configuration config = display.getFullConfiguration();
3085             if (config.densityDpi == dpi && config.screenWidthDp == width
3086                     && config.screenHeightDp == height) {
3087                 return display;
3088             }
3089         }
3090         return null;
3091     }
3092 
3093     /**
3094      * Gets all {@link DisplayContent} instances from {@code mWmState}.
3095      */
3096     @NonNull
3097     protected List<DisplayContent> getDisplaysStates() {
3098         mWmState.computeState();
3099         return mWmState.getDisplays();
3100     }
3101 
3102     /**
3103      * Waits for display specific with {@code displayPredicate} gone, or fails with timeout.
3104      */
3105     void waitForDisplayGone(@NonNull Predicate<DisplayContent> displayPredicate) {
3106         waitForOrFail("displays to be removed", () -> {
3107             mWmState.computeState();
3108             return mWmState.getDisplays().stream().noneMatch(displayPredicate);
3109         });
3110     }
3111 
3112     /** Wait for provided number of displays and report their configurations. */
3113     @NonNull
3114     public List<DisplayContent> getDisplayStateAfterChange(int expectedDisplayCount) {
3115         return Condition.waitForResult("the correct number of displays=" + expectedDisplayCount,
3116                 condition -> condition
3117                         .setReturnLastResult(true)
3118                         .setResultSupplier(this::getDisplaysStates)
3119                         .setResultValidator(
3120                                 displays -> areDisplaysValid(displays, expectedDisplayCount)));
3121     }
3122 
3123     /**
3124      * Finds the display that was not originally reported in {@code oldDisplays} and added in
3125      * {@code newDisplays}.
3126      */
3127     @NonNull
3128     public static List<DisplayContent> findNewDisplayStates(
3129             @NonNull List<DisplayContent> oldDisplays,
3130             @NonNull List<DisplayContent> newDisplays) {
3131         final ArrayList<DisplayContent> result = new ArrayList<>();
3132 
3133         for (DisplayContent newDisplay : newDisplays) {
3134             if (oldDisplays.stream().noneMatch(d -> d.mId == newDisplay.mId)) {
3135                 result.add(newDisplay);
3136             }
3137         }
3138 
3139         return result;
3140     }
3141 
3142     private static boolean areDisplaysValid(@NonNull List<DisplayContent> displays,
3143             int expectedDisplayCount) {
3144         if (displays.size() != expectedDisplayCount) {
3145             return false;
3146         }
3147         for (DisplayContent display : displays) {
3148             if (display.getOverrideConfiguration().densityDpi == 0) {
3149                 return false;
3150             }
3151         }
3152         return true;
3153     }
3154 
3155     /**
3156      * This class should only be used when you need to test virtual display created by a
3157      * non-privileged app.
3158      * Or when you need to test on simulated display.
3159      * <p>
3160      * If you need to test virtual display created by a privileged app, please use
3161      * {@link MultiDisplayTestBase.ExternalDisplaySession} instead.
3162      */
3163     public class VirtualDisplaySession implements AutoCloseable {
3164         private int mDensityDpi = 222;
3165         private boolean mLaunchInSplitScreen = false;
3166         private boolean mCanShowWithInsecureKeyguard = false;
3167         private boolean mPublicDisplay = false;
3168         private boolean mResizeDisplay = true;
3169         private boolean mShowSystemDecorations = false;
3170         private boolean mOwnContentOnly = false;
3171         private int mDisplayImePolicy = DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
3172         private boolean mPresentationDisplay = false;
3173         private boolean mSimulateDisplay = false;
3174         private boolean mSupportsTouch = false;
3175         private boolean mMustBeCreated = true;
3176         @NonNull
3177         private Size mSimulationDisplaySize = new Size(1024 /* width */, 768 /* height */);
3178 
3179         private boolean mVirtualDisplayCreated = false;
3180         @Nullable
3181         private OverlayDisplayDevicesSession mOverlayDisplayDeviceSession;
3182 
3183         private static final int INVALID_DENSITY_DPI = -1;
3184 
3185         /**
3186          * Sets {@code densityDpi} to the virtual display.
3187          */
3188         @NonNull
3189         public VirtualDisplaySession setDensityDpi(int densityDpi) {
3190             mDensityDpi = densityDpi;
3191             return this;
3192         }
3193 
3194         /**
3195          * Sets {@code launchInSplitScreen} to the virtual display.
3196          */
3197         @NonNull
3198         public VirtualDisplaySession setLaunchInSplitScreen(boolean launchInSplitScreen) {
3199             mLaunchInSplitScreen = launchInSplitScreen;
3200             return this;
3201         }
3202 
3203         /**
3204          * Sets {@code canShowWithInsecureKeyguard} to the virtual display.
3205          */
3206         @NonNull
3207         public VirtualDisplaySession setCanShowWithInsecureKeyguard(
3208                 boolean canShowWithInsecureKeyguard) {
3209             mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard;
3210             return this;
3211         }
3212 
3213         /**
3214          * Sets {@code publicDisplay} to the virtual display.
3215          */
3216         @NonNull
3217         public VirtualDisplaySession setPublicDisplay(boolean publicDisplay) {
3218             mPublicDisplay = publicDisplay;
3219             return this;
3220         }
3221 
3222         /**
3223          * Sets {@code resizeDisplay} to the virtual display.
3224          */
3225         @NonNull
3226         public VirtualDisplaySession setResizeDisplay(boolean resizeDisplay) {
3227             mResizeDisplay = resizeDisplay;
3228             return this;
3229         }
3230 
3231         /**
3232          * Sets {@code showSystemDecorations} to the virtual display.
3233          */
3234         @NonNull
3235         public VirtualDisplaySession setShowSystemDecorations(boolean showSystemDecorations) {
3236             mShowSystemDecorations = showSystemDecorations;
3237             return this;
3238         }
3239 
3240         /**
3241          * Sets {@code ownContentOnly} to the virtual display.
3242          */
3243         @NonNull
3244         public VirtualDisplaySession setOwnContentOnly(boolean ownContentOnly) {
3245             mOwnContentOnly = ownContentOnly;
3246             return this;
3247         }
3248 
3249         /**
3250          * Sets {@code supportsTouch} to the virtual display.
3251          */
3252         @NonNull
3253         public VirtualDisplaySession setSupportsTouch(boolean supportsTouch) {
3254             mSupportsTouch = supportsTouch;
3255             return this;
3256         }
3257 
3258 
3259         /**
3260          * Sets the policy for how the display should show the ime.
3261          * <p>
3262          * Set to one of:
3263          * <ul>
3264          *   <li>{@link WindowManager#DISPLAY_IME_POLICY_LOCAL}
3265          *   <li>{@link WindowManager#DISPLAY_IME_POLICY_FALLBACK_DISPLAY}
3266          *   <li>{@link WindowManager#DISPLAY_IME_POLICY_HIDE}
3267          * </ul>
3268          */
3269         @NonNull
3270         public VirtualDisplaySession setDisplayImePolicy(int displayImePolicy) {
3271             mDisplayImePolicy = displayImePolicy;
3272             return this;
3273         }
3274 
3275         /**
3276          * Sets {@code presentationDisplay} to the virtual display.
3277          */
3278         @NonNull
3279         public VirtualDisplaySession setPresentationDisplay(boolean presentationDisplay) {
3280             mPresentationDisplay = presentationDisplay;
3281             return this;
3282         }
3283 
3284         // TODO(b/154565343) move simulate display out of VirtualDisplaySession
3285         /**
3286          * Sets {@code simulateDisplay} to the virtual display.
3287          */
3288         @NonNull
3289         public VirtualDisplaySession setSimulateDisplay(boolean simulateDisplay) {
3290             mSimulateDisplay = simulateDisplay;
3291             return this;
3292         }
3293 
3294         /**
3295          * Sets {@code width} and {@code height} to the virtual display.
3296          */
3297         @NonNull
3298         public VirtualDisplaySession setSimulationDisplaySize(int width, int height) {
3299             mSimulationDisplaySize = new Size(width, height);
3300             return this;
3301         }
3302 
3303         /**
3304          * Creates a virtual display.
3305          *
3306          * @param mustBeCreated {@code true} to throw exception if the display is failed to create.
3307          * @return the created display , or {@code null} if {@code mustBeCreated} is {@code false}
3308          *         and the display is failed to create.
3309          * @throws IllegalStateException if the display is failed to create but
3310          *                               {@code mustBeCreated} is {@code true}
3311          */
3312         @Nullable
3313         public DisplayContent createDisplay(boolean mustBeCreated) {
3314             mMustBeCreated = mustBeCreated;
3315             final WindowManagerState.DisplayContent display = createDisplays(1).stream()
3316                     .findFirst().orElse(null);
3317             if (mustBeCreated && display == null) {
3318                 throw new IllegalStateException("No display is created");
3319             }
3320             return display;
3321         }
3322 
3323         /** @see #createDisplay(boolean)  */
3324         @NonNull
3325         public DisplayContent createDisplay() {
3326             return Objects.requireNonNull(createDisplay(true /* mustBeCreated */));
3327         }
3328 
3329         /**
3330          * Creates displays with {@code count}..
3331          */
3332         @NonNull
3333         public List<DisplayContent> createDisplays(int count) {
3334             if (mSimulateDisplay) {
3335                 return simulateDisplays(count);
3336             } else {
3337                 return createVirtualDisplays(count);
3338             }
3339         }
3340 
3341         /**
3342          * Resizes the virtual display.
3343          *
3344          * @throws IllegalStateException if the created display is a simulated display
3345          */
3346         public void resizeDisplay() {
3347             if (mSimulateDisplay) {
3348                 throw new IllegalStateException(
3349                         "Please use ReportedDisplayMetrics#setDisplayMetrics to resize"
3350                                 + " simulate display");
3351             }
3352             executeShellCommand(getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
3353                     + " -f 0x20000000" + " --es " + KEY_COMMAND + " " + COMMAND_RESIZE_DISPLAY);
3354         }
3355 
3356         @Override
3357         public void close() {
3358             if (mOverlayDisplayDeviceSession != null) {
3359                 mOverlayDisplayDeviceSession.close();
3360             }
3361             if (mVirtualDisplayCreated) {
3362                 destroyVirtualDisplays();
3363                 mVirtualDisplayCreated = false;
3364             }
3365         }
3366 
3367         /**
3368          * Simulate new display.
3369          * <pre>
3370          * <code>mDensityDpi</code> provide custom density for the display.
3371          * </pre>
3372          *
3373          * @return {@link DisplayContent} of newly created display.
3374          */
3375         @NonNull
3376         private List<DisplayContent> simulateDisplays(int count) {
3377             mOverlayDisplayDeviceSession = new OverlayDisplayDevicesSession();
3378             mOverlayDisplayDeviceSession.createDisplays(mSimulationDisplaySize, mDensityDpi,
3379                     mOwnContentOnly, mShowSystemDecorations, count);
3380             mOverlayDisplayDeviceSession.configureDisplays(mDisplayImePolicy /* imePolicy */);
3381             return mOverlayDisplayDeviceSession.getCreatedDisplays();
3382         }
3383 
3384         /**
3385          * Create new virtual display.
3386          * <pre>
3387          * <code>mDensityDpi</code> provide custom density for the display.
3388          * <code>mLaunchInSplitScreen</code> start
3389          *     {@link android.server.wm.app.VirtualDisplayActivity} to side from
3390          *     {@link android.server.wm.app.LaunchingActivity} on primary display.
3391          * <code>mCanShowWithInsecureKeyguard</code>  allow showing content when device is
3392          *     showing an insecure keyguard.
3393          * <code>mMustBeCreated</code> should assert if the display was or wasn't created.
3394          * <code>mPublicDisplay</code> make display public.
3395          * <code>mResizeDisplay</code> should resize display when surface size changes.
3396          * <code>LaunchActivity</code> should launch test activity immediately after display
3397          *     creation.
3398          * </pre>
3399          *
3400          * @param displayCount number of displays to be created.
3401          * @return A list of {@link DisplayContent} that represent newly created displays.
3402          */
3403         @NonNull
3404         private List<DisplayContent> createVirtualDisplays(int displayCount) {
3405             // Start an activity that is able to create virtual displays.
3406             if (mLaunchInSplitScreen) {
3407                 getLaunchActivityBuilder()
3408                         .setToSide(true)
3409                         .setTargetActivity(VIRTUAL_DISPLAY_ACTIVITY)
3410                         .execute();
3411                 final int secondaryTaskId =
3412                         mWmState.getTaskByActivity(VIRTUAL_DISPLAY_ACTIVITY).getTaskId();
3413                 mTaskOrganizer.putTaskInSplitSecondary(secondaryTaskId);
3414             } else {
3415                 launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
3416             }
3417             mWmState.computeState(
3418                     new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
3419             mWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
3420             mWmState.assertFocusedActivity("Focus must be on virtual display host activity",
3421                     VIRTUAL_DISPLAY_ACTIVITY);
3422             final List<DisplayContent> originalDS = getDisplaysStates();
3423 
3424             // Create virtual display with custom density dpi.
3425             final StringBuilder createVirtualDisplayCommand = new StringBuilder(
3426                     getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY))
3427                     .append(" -f 0x20000000")
3428                     .append(" --es " + KEY_COMMAND + " " + COMMAND_CREATE_DISPLAY);
3429             if (mDensityDpi != INVALID_DENSITY_DPI) {
3430                 createVirtualDisplayCommand
3431                         .append(" --ei " + KEY_DENSITY_DPI + " ")
3432                         .append(mDensityDpi);
3433             }
3434             createVirtualDisplayCommand.append(" --ei " + KEY_COUNT + " ").append(displayCount)
3435                     .append(" --ez " + KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD + " ")
3436                     .append(mCanShowWithInsecureKeyguard)
3437                     .append(" --ez " + KEY_PUBLIC_DISPLAY + " ").append(mPublicDisplay)
3438                     .append(" --ez " + KEY_RESIZE_DISPLAY + " ").append(mResizeDisplay)
3439                     .append(" --ez " + KEY_SHOW_SYSTEM_DECORATIONS + " ")
3440                     .append(mShowSystemDecorations)
3441                     .append(" --ez " + KEY_PRESENTATION_DISPLAY + " ").append(mPresentationDisplay)
3442                     .append(" --ez " + KEY_SUPPORTS_TOUCH + " ").append(mSupportsTouch);
3443             executeShellCommand(createVirtualDisplayCommand.toString());
3444             mVirtualDisplayCreated = true;
3445 
3446             return assertAndGetNewDisplays(mMustBeCreated ? displayCount : -1, originalDS);
3447         }
3448 
3449         /**
3450          * Destroy existing virtual display.
3451          */
3452         void destroyVirtualDisplays() {
3453             final String destroyVirtualDisplayCommand = getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
3454                     + " -f 0x20000000"
3455                     + " --es " + KEY_COMMAND + " " + COMMAND_DESTROY_DISPLAY;
3456             executeShellCommand(destroyVirtualDisplayCommand);
3457             waitForDisplayGone(
3458                     d -> d.getName() != null && d.getName().contains(VIRTUAL_DISPLAY_PREFIX));
3459         }
3460 
3461         /**
3462          * Wait for desired number of displays to be created and get their properties.
3463          *
3464          * @param newDisplayCount expected display count, -1 if display should not be created.
3465          * @param originalDisplays display states before creation of new display(s).
3466          * @return list of new displays, empty list if no new display is created.
3467          */
3468         @NonNull
3469         private List<DisplayContent> assertAndGetNewDisplays(int newDisplayCount,
3470                 @NonNull List<DisplayContent> originalDisplays) {
3471             final int originalDisplayCount = originalDisplays.size();
3472 
3473             // Wait for the display(s) to be created and get configurations.
3474             final List<DisplayContent> ds = getDisplayStateAfterChange(
3475                     originalDisplayCount + newDisplayCount);
3476             if (newDisplayCount != -1) {
3477                 assertEquals("New virtual display(s) must be created",
3478                         originalDisplayCount + newDisplayCount, ds.size());
3479             } else {
3480                 assertEquals("New virtual display must not be created",
3481                         originalDisplayCount, ds.size());
3482                 return Collections.emptyList();
3483             }
3484 
3485             // Find the newly added display(s).
3486             final List<WindowManagerState.DisplayContent> newDisplays = findNewDisplayStates(
3487                     originalDisplays, ds);
3488             assertThat("New virtual display must be created", newDisplays,
3489                     hasSize(newDisplayCount));
3490 
3491             return newDisplays;
3492         }
3493 
3494         /**
3495          * Helper class to save, set, and restore overlay_display_devices preference.
3496          */
3497         private class OverlayDisplayDevicesSession extends SettingsSession<String> {
3498             /**
3499              * See display_manager_overlay_display_name.
3500              */
3501             private static final String OVERLAY_DISPLAY_NAME_PREFIX = "Overlay #";
3502 
3503             /**
3504              * See {@code OverlayDisplayAdapter#OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY}.
3505              */
3506             private static final String OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY = ",own_content_only";
3507 
3508             /**
3509              * See {@code OverlayDisplayAdapter
3510              * #OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS}.
3511              */
3512             private static final String OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS =
3513                     ",should_show_system_decorations";
3514 
3515             /**
3516              * The displays which are created by this session.
3517              */
3518             @NonNull
3519             private final List<WindowManagerState.DisplayContent> mDisplays = new ArrayList<>();
3520             /**
3521              * The configured displays that need to be restored when this session is closed.
3522              */
3523             @NonNull
3524             private final List<OverlayDisplayState> mDisplayStates = new ArrayList<>();
3525             @NonNull
3526             private final WindowManager mWm;
3527 
3528             OverlayDisplayDevicesSession() {
3529                 super(Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
3530                         Settings.Global::getString,
3531                         Settings.Global::putString);
3532                 // Remove existing overlay display to avoid display count problem.
3533                 removeExisting();
3534                 mWm = mContext.getSystemService(WindowManager.class);
3535             }
3536 
3537             @NonNull
3538             List<WindowManagerState.DisplayContent> getCreatedDisplays() {
3539                 return new ArrayList<>(mDisplays);
3540             }
3541 
3542             @Override
3543             public void set(String value) {
3544                 final List<DisplayContent> originalDisplays = getDisplaysStates();
3545                 super.set(value);
3546                 final int newDisplayCount = 1 + (int) value.chars().filter(ch -> ch == ';').count();
3547                 mDisplays.addAll(assertAndGetNewDisplays(newDisplayCount, originalDisplays));
3548             }
3549 
3550             /**
3551              * Creates overlay display with custom density dpi, specified size, and test flags.
3552              */
3553             void createDisplays(Size displaySize, int densityDpi, boolean ownContentOnly,
3554                                 boolean shouldShowSystemDecorations, int count) {
3555                 final StringBuilder builder = new StringBuilder();
3556                 for (int i = 0; i < count; i++) {
3557                     String displaySettingsEntry = displaySize + "/" + densityDpi;
3558                     if (ownContentOnly) {
3559                         displaySettingsEntry += OVERLAY_DISPLAY_FLAG_OWN_CONTENT_ONLY;
3560                     }
3561                     if (shouldShowSystemDecorations) {
3562                         displaySettingsEntry += OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
3563                     }
3564                     builder.append(displaySettingsEntry);
3565                     // Creating n displays needs (n - 1) ';'.
3566                     if (i < count - 1) {
3567                         builder.append(';');
3568                     }
3569                 }
3570                 set(builder.toString());
3571             }
3572 
3573             void configureDisplays(int imePolicy) {
3574                 SystemUtil.runWithShellPermissionIdentity(() -> {
3575                     for (WindowManagerState.DisplayContent display : mDisplays) {
3576                         final int oldImePolicy = mWm.getDisplayImePolicy(display.mId);
3577                         mDisplayStates.add(new OverlayDisplayState(display.mId, oldImePolicy));
3578                         if (imePolicy != oldImePolicy) {
3579                             mWm.setDisplayImePolicy(display.mId, imePolicy);
3580                             waitForOrFail("display config show-IME to be set",
3581                                     () -> (mWm.getDisplayImePolicy(display.mId) == imePolicy));
3582                         }
3583                         if (isVisibleBackgroundUserSupported()) {
3584                             // Ensure that the user who is running the test is assigned to the
3585                             // overlay display during its configuration when it is created.
3586                             assignUserToExtraDisplay(mUserId, display.mId);
3587                         }
3588                     }
3589                 });
3590             }
3591 
3592             private void restoreDisplayStates() {
3593                 mDisplayStates.forEach(state -> SystemUtil.runWithShellPermissionIdentity(() -> {
3594                     mWm.setDisplayImePolicy(state.mId, state.mImePolicy);
3595 
3596                     // Only need to wait the last flag to be set.
3597                     waitForOrFail("display config show-IME to be restored",
3598                             () -> (mWm.getDisplayImePolicy(state.mId) == state.mImePolicy));
3599                     if (isVisibleBackgroundUserSupported()) {
3600                         unassignUserToExtraDisplay(mUserId, state.mId);
3601                     }
3602                 }));
3603             }
3604 
3605             @Override
3606             public void close() {
3607                 // Need to restore display state before display is destroyed.
3608                 restoreDisplayStates();
3609                 super.close();
3610                 // Waiting for restoring to the state before this session was created.
3611                 waitForDisplayGone(display -> mDisplays.stream()
3612                         .anyMatch(createdDisplay -> createdDisplay.mId == display.mId));
3613             }
3614 
3615             private void removeExisting() {
3616                 if (!mHasInitialValue || mInitialValue == null) {
3617                     // No existing overlay displays.
3618                     return;
3619                 }
3620                 delete(mUri);
3621                 // Make sure all overlay displays are completely removed.
3622                 waitForDisplayGone(
3623                         display -> display.getName().startsWith(OVERLAY_DISPLAY_NAME_PREFIX));
3624             }
3625 
3626             private static class OverlayDisplayState {
3627                 int mId;
3628                 int mImePolicy;
3629 
3630                 OverlayDisplayState(int displayId, int imePolicy) {
3631                     mId = displayId;
3632                     mImePolicy = imePolicy;
3633                 }
3634             }
3635         }
3636     }
3637 }
3638