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