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