• 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.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
20 import static android.app.Instrumentation.ActivityMonitor;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
23 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
24 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
25 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
26 import static android.content.Intent.ACTION_MAIN;
27 import static android.content.Intent.CATEGORY_HOME;
28 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
29 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
30 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
31 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
32 import static android.content.pm.PackageManager.DONT_KILL_APP;
33 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
34 import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
35 import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
36 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
37 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
38 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
39 import static android.content.pm.PackageManager.FEATURE_SCREEN_LANDSCAPE;
40 import static android.content.pm.PackageManager.FEATURE_SCREEN_PORTRAIT;
41 import static android.content.pm.PackageManager.FEATURE_SECURE_LOCK_SCREEN;
42 import static android.content.pm.PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE;
43 import static android.content.pm.PackageManager.FEATURE_WATCH;
44 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
45 import static android.server.wm.ActivityLauncher.KEY_ACTIVITY_TYPE;
46 import static android.server.wm.ActivityLauncher.KEY_DISPLAY_ID;
47 import static android.server.wm.ActivityLauncher.KEY_INTENT_EXTRAS;
48 import static android.server.wm.ActivityLauncher.KEY_INTENT_FLAGS;
49 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
50 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_TO_SIDE;
51 import static android.server.wm.ActivityLauncher.KEY_MULTIPLE_INSTANCES;
52 import static android.server.wm.ActivityLauncher.KEY_MULTIPLE_TASK;
53 import static android.server.wm.ActivityLauncher.KEY_NEW_TASK;
54 import static android.server.wm.ActivityLauncher.KEY_RANDOM_DATA;
55 import static android.server.wm.ActivityLauncher.KEY_REORDER_TO_FRONT;
56 import static android.server.wm.ActivityLauncher.KEY_SUPPRESS_EXCEPTIONS;
57 import static android.server.wm.ActivityLauncher.KEY_TARGET_COMPONENT;
58 import static android.server.wm.ActivityLauncher.KEY_USE_APPLICATION_CONTEXT;
59 import static android.server.wm.ActivityLauncher.KEY_USE_INSTRUMENTATION;
60 import static android.server.wm.ActivityLauncher.launchActivityFromExtras;
61 import static android.server.wm.ActivityManagerState.STATE_RESUMED;
62 import static android.server.wm.ComponentNameUtils.getActivityName;
63 import static android.server.wm.ComponentNameUtils.getLogTag;
64 import static android.server.wm.StateLogger.log;
65 import static android.server.wm.StateLogger.logAlways;
66 import static android.server.wm.StateLogger.logE;
67 import static android.server.wm.UiDeviceUtils.pressAppSwitchButton;
68 import static android.server.wm.UiDeviceUtils.pressBackButton;
69 import static android.server.wm.UiDeviceUtils.pressEnterButton;
70 import static android.server.wm.UiDeviceUtils.pressHomeButton;
71 import static android.server.wm.UiDeviceUtils.pressSleepButton;
72 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
73 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
74 import static android.server.wm.UiDeviceUtils.waitForDeviceIdle;
75 import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY;
76 import static android.server.wm.app.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST;
77 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_BROADCAST_ORIENTATION;
78 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_CUTOUT_EXISTS;
79 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD;
80 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD_METHOD;
81 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST;
82 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_MOVE_BROADCAST_TO_BACK;
83 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
84 import static android.server.wm.app.Components.PipActivity.ACTION_EXPAND_PIP;
85 import static android.server.wm.app.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
86 import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION;
87 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR;
88 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR;
89 import static android.server.wm.app.Components.TEST_ACTIVITY;
90 import static android.server.wm.second.Components.SECOND_ACTIVITY;
91 import static android.server.wm.third.Components.THIRD_ACTIVITY;
92 import static android.view.Display.DEFAULT_DISPLAY;
93 import static android.view.Display.INVALID_DISPLAY;
94 import static android.view.Surface.ROTATION_0;
95 
96 import static androidx.test.InstrumentationRegistry.getInstrumentation;
97 
98 import static org.junit.Assert.assertEquals;
99 import static org.junit.Assert.assertNotNull;
100 import static org.junit.Assert.assertTrue;
101 import static org.junit.Assert.fail;
102 
103 import static java.lang.Integer.toHexString;
104 
105 import android.accessibilityservice.AccessibilityService;
106 import android.app.Activity;
107 import android.app.ActivityManager;
108 import android.app.ActivityOptions;
109 import android.app.ActivityTaskManager;
110 import android.content.ComponentName;
111 import android.content.ContentResolver;
112 import android.content.Context;
113 import android.content.Intent;
114 import android.content.pm.PackageManager;
115 import android.content.pm.ResolveInfo;
116 import android.content.res.Resources;
117 import android.database.ContentObserver;
118 import android.graphics.Bitmap;
119 import android.graphics.Rect;
120 import android.hardware.display.AmbientDisplayConfiguration;
121 import android.hardware.display.DisplayManager;
122 import android.os.Bundle;
123 import android.os.Handler;
124 import android.os.HandlerThread;
125 import android.os.SystemClock;
126 import android.os.UserHandle;
127 import android.provider.Settings;
128 import android.server.wm.CommandSession.ActivityCallback;
129 import android.server.wm.CommandSession.ActivitySession;
130 import android.server.wm.CommandSession.ConfigInfo;
131 import android.server.wm.CommandSession.LaunchInjector;
132 import android.server.wm.CommandSession.LaunchProxy;
133 import android.server.wm.CommandSession.SizeInfo;
134 import android.server.wm.TestJournalProvider.TestJournalContainer;
135 import android.server.wm.settings.SettingsSession;
136 import android.util.EventLog;
137 import android.util.EventLog.Event;
138 import android.view.Display;
139 import android.view.InputDevice;
140 import android.view.MotionEvent;
141 
142 import androidx.annotation.NonNull;
143 import androidx.annotation.Nullable;
144 import androidx.test.rule.ActivityTestRule;
145 
146 import com.android.compatibility.common.util.SystemUtil;
147 
148 import org.junit.After;
149 import org.junit.Before;
150 import org.junit.Rule;
151 import org.junit.rules.TestRule;
152 import org.junit.runner.Description;
153 import org.junit.runners.model.Statement;
154 
155 import java.io.IOException;
156 import java.util.ArrayList;
157 import java.util.Arrays;
158 import java.util.Collections;
159 import java.util.HashMap;
160 import java.util.HashSet;
161 import java.util.Iterator;
162 import java.util.List;
163 import java.util.Map;
164 import java.util.UUID;
165 import java.util.concurrent.Callable;
166 import java.util.concurrent.TimeUnit;
167 import java.util.concurrent.atomic.AtomicBoolean;
168 import java.util.function.BooleanSupplier;
169 import java.util.function.Consumer;
170 import java.util.regex.Matcher;
171 import java.util.regex.Pattern;
172 
173 public abstract class ActivityManagerTestBase {
174     private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false;
175     private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false;
176     private static final String LOG_SEPARATOR = "LOG_SEPARATOR";
177     // Use one of the test tags as a separator
178     private static final int EVENT_LOG_SEPARATOR_TAG = 42;
179 
180     protected static final int[] ALL_ACTIVITY_TYPE_BUT_HOME = {
181             ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
182             ACTIVITY_TYPE_UNDEFINED
183     };
184 
185     private static final String TEST_PACKAGE = TEST_ACTIVITY.getPackageName();
186     private static final String SECOND_TEST_PACKAGE = SECOND_ACTIVITY.getPackageName();
187     private static final String THIRD_TEST_PACKAGE = THIRD_ACTIVITY.getPackageName();
188     private static final List<String> TEST_PACKAGES;
189 
190     static {
191         final List<String> testPackages = new ArrayList<>(3);
192         testPackages.add(TEST_PACKAGE);
193         testPackages.add(SECOND_TEST_PACKAGE);
194         testPackages.add(THIRD_TEST_PACKAGE);
195         testPackages.add("android.server.wm.cts");
196         TEST_PACKAGES = Collections.unmodifiableList(testPackages);
197     }
198 
199     protected static final String AM_START_HOME_ACTIVITY_COMMAND =
200             "am start -a android.intent.action.MAIN -c android.intent.category.HOME";
201 
202     private static final String LOCK_CREDENTIAL = "1234";
203 
204     private static final int UI_MODE_TYPE_MASK = 0x0f;
205     private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
206 
207     private static Boolean sHasHomeScreen = null;
208     private static Boolean sSupportsSystemDecorsOnSecondaryDisplays = null;
209     private static Boolean sSupportsInsecureLockScreen = null;
210 
211     protected static final int INVALID_DEVICE_ROTATION = -1;
212 
213     protected Context mContext;
214     protected ActivityManager mAm;
215     protected ActivityTaskManager mAtm;
216 
217     /**
218      * Callable to clear launch params for all test packages.
219      */
220     private final Callable<Void> mClearLaunchParamsCallable = () -> {
221         mAtm.clearLaunchParamsForPackages(TEST_PACKAGES);
222         return null;
223     };
224 
225     @Rule
226     public final ActivityTestRule<SideActivity> mSideActivityRule =
227             new ActivityTestRule<>(SideActivity.class, true /* initialTouchMode */,
228                     false /* launchActivity */);
229 
230     /**
231      * @return the am command to start the given activity with the following extra key/value pairs.
232      * {@param keyValuePairs} must be a list of arguments defining each key/value extra.
233      */
234     // TODO: Make this more generic, for instance accepting flags or extras of other types.
getAmStartCmd(final ComponentName activityName, final String... keyValuePairs)235     protected static String getAmStartCmd(final ComponentName activityName,
236             final String... keyValuePairs) {
237         return getAmStartCmdInternal(getActivityName(activityName), keyValuePairs);
238     }
239 
getAmStartCmdInternal(final String activityName, final String... keyValuePairs)240     private static String getAmStartCmdInternal(final String activityName,
241             final String... keyValuePairs) {
242         return appendKeyValuePairs(
243                 new StringBuilder("am start -n ").append(activityName),
244                 keyValuePairs);
245     }
246 
appendKeyValuePairs( final StringBuilder cmd, final String... keyValuePairs)247     private static String appendKeyValuePairs(
248             final StringBuilder cmd, final String... keyValuePairs) {
249         if (keyValuePairs.length % 2 != 0) {
250             throw new RuntimeException("keyValuePairs must be pairs of key/value arguments");
251         }
252         for (int i = 0; i < keyValuePairs.length; i += 2) {
253             final String key = keyValuePairs[i];
254             final String value = keyValuePairs[i + 1];
255             cmd.append(" --es ")
256                     .append(key)
257                     .append(" ")
258                     .append(value);
259         }
260         return cmd.toString();
261     }
262 
getAmStartCmd(final ComponentName activityName, final int displayId, final String... keyValuePair)263     protected static String getAmStartCmd(final ComponentName activityName, final int displayId,
264             final String... keyValuePair) {
265         return getAmStartCmdInternal(getActivityName(activityName), displayId, keyValuePair);
266     }
267 
getAmStartCmdInternal(final String activityName, final int displayId, final String... keyValuePairs)268     private static String getAmStartCmdInternal(final String activityName, final int displayId,
269             final String... keyValuePairs) {
270         return appendKeyValuePairs(
271                 new StringBuilder("am start -n ")
272                         .append(activityName)
273                         .append(" -f 0x")
274                         .append(toHexString(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK))
275                         .append(" --display ")
276                         .append(displayId),
277                 keyValuePairs);
278     }
279 
getAmStartCmdInNewTask(final ComponentName activityName)280     protected static String getAmStartCmdInNewTask(final ComponentName activityName) {
281         return "am start -n " + getActivityName(activityName) + " -f 0x18000000";
282     }
283 
getAmStartCmdOverHome(final ComponentName activityName)284     protected static String getAmStartCmdOverHome(final ComponentName activityName) {
285         return "am start --activity-task-on-home -n " + getActivityName(activityName);
286     }
287 
288     protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
289 
getAmWmState()290     public ActivityAndWindowManagersState getAmWmState() {
291         return mAmWmState;
292     }
293 
294     protected BroadcastActionTrigger mBroadcastActionTrigger = new BroadcastActionTrigger();
295 
296     /**
297      * Helper class to process test actions by broadcast.
298      */
299     protected class BroadcastActionTrigger {
300 
createIntentWithAction(String broadcastAction)301         private Intent createIntentWithAction(String broadcastAction) {
302             return new Intent(broadcastAction)
303                     .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
304         }
305 
doAction(String broadcastAction)306         void doAction(String broadcastAction) {
307             mContext.sendBroadcast(createIntentWithAction(broadcastAction));
308         }
309 
finishBroadcastReceiverActivity()310         void finishBroadcastReceiverActivity() {
311             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
312                     .putExtra(EXTRA_FINISH_BROADCAST, true));
313         }
314 
launchActivityNewTask(String launchComponent)315         void launchActivityNewTask(String launchComponent) {
316             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
317                     .putExtra(KEY_LAUNCH_ACTIVITY, true)
318                     .putExtra(KEY_NEW_TASK, true)
319                     .putExtra(KEY_TARGET_COMPONENT, launchComponent));
320         }
321 
moveTopTaskToBack()322         void moveTopTaskToBack() {
323             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
324                     .putExtra(EXTRA_MOVE_BROADCAST_TO_BACK, true));
325         }
326 
requestOrientation(int orientation)327         void requestOrientation(int orientation) {
328             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
329                     .putExtra(EXTRA_BROADCAST_ORIENTATION, orientation));
330         }
331 
dismissKeyguardByFlag()332         void dismissKeyguardByFlag() {
333             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
334                     .putExtra(EXTRA_DISMISS_KEYGUARD, true));
335         }
336 
dismissKeyguardByMethod()337         void dismissKeyguardByMethod() {
338             mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST)
339                     .putExtra(EXTRA_DISMISS_KEYGUARD_METHOD, true));
340         }
341 
expandPipWithAspectRatio(String extraNum, String extraDenom)342         void expandPipWithAspectRatio(String extraNum, String extraDenom) {
343             mContext.sendBroadcast(createIntentWithAction(ACTION_EXPAND_PIP)
344                     .putExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR, extraNum)
345                     .putExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR, extraDenom));
346         }
347 
requestOrientationForPip(int orientation)348         void requestOrientationForPip(int orientation) {
349             mContext.sendBroadcast(createIntentWithAction(ACTION_SET_REQUESTED_ORIENTATION)
350                     .putExtra(EXTRA_PIP_ORIENTATION, String.valueOf(orientation)));
351         }
352     }
353 
354     /**
355      * Helper class to launch / close test activity by instrumentation way.
356      */
357     protected class TestActivitySession<T extends Activity> implements AutoCloseable {
358         private T mTestActivity;
359         boolean mFinishAfterClose;
360         private static final int ACTIVITY_LAUNCH_TIMEOUT = 10000;
361         private static final int WAIT_SLICE = 50;
362 
launchTestActivityOnDisplaySync(Class<T> activityClass, int displayId)363         void launchTestActivityOnDisplaySync(Class<T> activityClass, int displayId) {
364             launchTestActivityOnDisplaySync(new Intent(mContext, activityClass), displayId);
365         }
366 
launchTestActivityOnDisplaySync(Intent intent, int displayId)367         void launchTestActivityOnDisplaySync(Intent intent, int displayId) {
368             SystemUtil.runWithShellPermissionIdentity(() -> {
369                 final Bundle bundle = ActivityOptions.makeBasic()
370                         .setLaunchDisplayId(displayId).toBundle();
371                 final ActivityMonitor monitor = getInstrumentation()
372                         .addMonitor((String) null, null, false);
373                 mContext.startActivity(intent.addFlags(FLAG_ACTIVITY_NEW_TASK), bundle);
374                 // Wait for activity launch with timeout.
375                 mTestActivity = (T) monitor.waitForActivityWithTimeout(ACTIVITY_LAUNCH_TIMEOUT);
376                 assertNotNull(mTestActivity);
377                 // Check activity is launched and resumed.
378                 final ComponentName testActivityName = mTestActivity.getComponentName();
379                 waitAndAssertTopResumedActivity(testActivityName, displayId,
380                         "Activity must be resumed");
381             });
382         }
383 
finishCurrentActivityNoWait()384         void finishCurrentActivityNoWait() {
385             if (mTestActivity != null) {
386                 mTestActivity.finishAndRemoveTask();
387                 mTestActivity = null;
388             }
389         }
390 
runOnMainSyncAndWait(Runnable runnable)391         void runOnMainSyncAndWait(Runnable runnable) {
392             getInstrumentation().runOnMainSync(runnable);
393             getInstrumentation().waitForIdleSync();
394         }
395 
runOnMainAndAssertWithTimeout(@onNull BooleanSupplier condition, long timeoutMs, String message)396         void runOnMainAndAssertWithTimeout(@NonNull BooleanSupplier condition, long timeoutMs,
397                 String message) {
398             final AtomicBoolean result = new AtomicBoolean();
399             final long expiredTime = System.currentTimeMillis() + timeoutMs;
400             while (!result.get()) {
401                 if (System.currentTimeMillis() >= expiredTime) {
402                     fail(message);
403                 }
404                 runOnMainSyncAndWait(() -> {
405                     if (condition.getAsBoolean()) {
406                         result.set(true);
407                     }
408                 });
409                 SystemClock.sleep(WAIT_SLICE);
410             }
411         }
412 
getActivity()413         T getActivity() {
414             return mTestActivity;
415         }
416 
417         @Override
close()418         public void close() throws Exception {
419             if (mTestActivity != null && mFinishAfterClose) {
420                 mTestActivity.finishAndRemoveTask();
421             }
422         }
423     }
424 
425     @Before
setUp()426     public void setUp() throws Exception {
427         mContext = getInstrumentation().getContext();
428         mAm = mContext.getSystemService(ActivityManager.class);
429         mAtm = mContext.getSystemService(ActivityTaskManager.class);
430 
431         pressWakeupButton();
432         pressUnlockButton();
433         pressHomeButton();
434         removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
435 
436         // Clear launch params for all test packages to make sure each test is run in a clean state.
437         SystemUtil.callWithShellPermissionIdentity(mClearLaunchParamsCallable);
438     }
439 
440     @After
tearDown()441     public void tearDown() throws Exception {
442         // Synchronous execution of removeStacksWithActivityTypes() ensures that all activities but
443         // home are cleaned up from the stack at the end of each test. Am force stop shell commands
444         // might be asynchronous and could interrupt the stack cleanup process if executed first.
445         removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
446         stopTestPackage(TEST_PACKAGE);
447         stopTestPackage(SECOND_TEST_PACKAGE);
448         stopTestPackage(THIRD_TEST_PACKAGE);
449         pressHomeButton();
450 
451     }
452 
moveTopActivityToPinnedStack(int stackId)453     protected void moveTopActivityToPinnedStack(int stackId) {
454         SystemUtil.runWithShellPermissionIdentity(
455                 () -> mAtm.moveTopActivityToPinnedStack(stackId, new Rect(0, 0, 500, 500))
456         );
457     }
458 
startActivityOnDisplay(int displayId, ComponentName component)459     protected void startActivityOnDisplay(int displayId, ComponentName component) {
460         final ActivityOptions options = ActivityOptions.makeBasic();
461         options.setLaunchDisplayId(displayId);
462 
463         mContext.startActivity(new Intent().addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
464                 .setComponent(component), options.toBundle());
465     }
466 
noHomeScreen()467     protected boolean noHomeScreen() {
468         try {
469             return mContext.getResources().getBoolean(
470                     Resources.getSystem().getIdentifier("config_noHomeScreen", "bool",
471                             "android"));
472         } catch (Resources.NotFoundException e) {
473             // Assume there's a home screen.
474             return false;
475         }
476     }
477 
getSupportsSystemDecorsOnSecondaryDisplays()478     private boolean getSupportsSystemDecorsOnSecondaryDisplays() {
479         try {
480             return mContext.getResources().getBoolean(
481                     Resources.getSystem().getIdentifier(
482                             "config_supportsSystemDecorsOnSecondaryDisplays", "bool", "android"));
483         } catch (Resources.NotFoundException e) {
484             // Assume this device support system decorations.
485             return true;
486         }
487     }
488 
getDefaultSecondaryHomeComponent()489     protected ComponentName getDefaultSecondaryHomeComponent() {
490         int resId = Resources.getSystem().getIdentifier(
491                 "config_secondaryHomeComponent", "string", "android");
492         return ComponentName.unflattenFromString(mContext.getResources().getString(resId));
493     }
494 
tapOnDisplay(int x, int y, int displayId)495     protected void tapOnDisplay(int x, int y, int displayId) {
496         final long downTime = SystemClock.uptimeMillis();
497         injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId);
498 
499         final long upTime = SystemClock.uptimeMillis();
500         injectMotion(downTime, upTime, MotionEvent.ACTION_UP, x, y, displayId);
501     }
502 
tapOnStackCenter(ActivityManagerState.ActivityStack stack)503     protected void tapOnStackCenter(ActivityManagerState.ActivityStack stack) {
504         final Rect sideStackBounds = stack.getBounds();
505         final int tapX = sideStackBounds.left + sideStackBounds.width() / 2;
506         final int tapY = sideStackBounds.top + sideStackBounds.height() / 2;
507         tapOnDisplay(tapX, tapY, stack.mDisplayId);
508     }
509 
injectMotion(long downTime, long eventTime, int action, int x, int y, int displayId)510     private static void injectMotion(long downTime, long eventTime, int action,
511             int x, int y, int displayId) {
512         final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action,
513                 x, y, 0 /* metaState */);
514         event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
515         event.setDisplayId(displayId);
516         getInstrumentation().getUiAutomation().injectInputEvent(event, true /* sync */);
517     }
518 
removeStacksWithActivityTypes(int... activityTypes)519     protected void removeStacksWithActivityTypes(int... activityTypes) {
520         SystemUtil.runWithShellPermissionIdentity(
521                 () -> mAtm.removeStacksWithActivityTypes(activityTypes));
522         waitForIdle();
523     }
524 
removeStacksInWindowingModes(int... windowingModes)525     protected void removeStacksInWindowingModes(int... windowingModes) {
526         SystemUtil.runWithShellPermissionIdentity(
527                 () -> mAtm.removeStacksInWindowingModes(windowingModes)
528         );
529         waitForIdle();
530     }
531 
executeShellCommand(String command)532     public static String executeShellCommand(String command) {
533         log("Shell command: " + command);
534         try {
535             return SystemUtil.runShellCommand(getInstrumentation(), command);
536         } catch (IOException e) {
537             //bubble it up
538             logE("Error running shell command: " + command);
539             throw new RuntimeException(e);
540         }
541     }
542 
takeScreenshot()543     protected Bitmap takeScreenshot() {
544         return getInstrumentation().getUiAutomation().takeScreenshot();
545     }
546 
launchActivity(final ComponentName activityName, final String... keyValuePairs)547     protected void launchActivity(final ComponentName activityName, final String... keyValuePairs) {
548         launchActivityNoWait(activityName, keyValuePairs);
549         mAmWmState.waitForValidState(activityName);
550     }
551 
launchActivityNoWait(final ComponentName activityName, final String... keyValuePairs)552     protected void launchActivityNoWait(final ComponentName activityName,
553             final String... keyValuePairs) {
554         executeShellCommand(getAmStartCmd(activityName, keyValuePairs));
555     }
556 
launchActivityInNewTask(final ComponentName activityName)557     protected void launchActivityInNewTask(final ComponentName activityName) {
558         executeShellCommand(getAmStartCmdInNewTask(activityName));
559         mAmWmState.waitForValidState(activityName);
560     }
561 
waitForIdle()562     private static void waitForIdle() {
563         getInstrumentation().waitForIdleSync();
564     }
565 
566     /** Returns the set of stack ids. */
getStackIds()567     private HashSet<Integer> getStackIds() {
568         mAmWmState.computeState(true);
569         final List<ActivityManagerState.ActivityStack> stacks = mAmWmState.getAmState().getStacks();
570         final HashSet<Integer> stackIds = new HashSet<>();
571         for (ActivityManagerState.ActivityStack s : stacks) {
572             stackIds.add(s.mStackId);
573         }
574         return stackIds;
575     }
576 
577     /** Returns the stack that contains the provided task. */
getStackForTaskId(int taskId)578     protected ActivityManagerState.ActivityStack getStackForTaskId(int taskId) {
579         mAmWmState.computeState(true);
580         final List<ActivityManagerState.ActivityStack> stacks = mAmWmState.getAmState().getStacks();
581         for (ActivityManagerState.ActivityStack stack : stacks) {
582             for (ActivityManagerState.ActivityTask task : stack.mTasks) {
583                 if (task.mTaskId == taskId) {
584                     return stack;
585                 }
586             }
587         }
588         return null;
589     }
590 
launchHomeActivity()591     protected void launchHomeActivity() {
592         executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
593         mAmWmState.waitForHomeActivityVisible();
594     }
595 
launchActivity(ComponentName activityName, int windowingMode, final String... keyValuePairs)596     protected void launchActivity(ComponentName activityName, int windowingMode,
597             final String... keyValuePairs) {
598         executeShellCommand(getAmStartCmd(activityName, keyValuePairs)
599                 + " --windowingMode " + windowingMode);
600         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
601                 .setWindowingMode(windowingMode)
602                 .build());
603     }
604 
launchActivityOnDisplay(ComponentName activityName, int windowingMode, int displayId, final String... keyValuePairs)605     protected void launchActivityOnDisplay(ComponentName activityName, int windowingMode,
606             int displayId, final String... keyValuePairs) {
607         executeShellCommand(getAmStartCmd(activityName, displayId, keyValuePairs)
608                 + " --windowingMode " + windowingMode);
609         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
610                 .setWindowingMode(windowingMode)
611                 .build());
612     }
613 
launchActivityOnDisplay(ComponentName activityName, int displayId, String... keyValuePairs)614     protected void launchActivityOnDisplay(ComponentName activityName, int displayId,
615             String... keyValuePairs) {
616         launchActivityOnDisplayNoWait(activityName, displayId, keyValuePairs);
617         mAmWmState.waitForValidState(activityName);
618     }
619 
launchActivityOnDisplayNoWait(ComponentName activityName, int displayId, String... keyValuePairs)620     protected void launchActivityOnDisplayNoWait(ComponentName activityName, int displayId,
621             String... keyValuePairs) {
622         executeShellCommand(getAmStartCmd(activityName, displayId, keyValuePairs));
623     }
624 
625     /**
626      * Launches {@param activityName} into split-screen primary windowing mode and also makes
627      * the recents activity visible to the side of it.
628      * NOTE: Recents view may be combined with home screen on some devices, so using this to wait
629      * for Recents only makes sense when {@link ActivityManagerState#isHomeRecentsComponent()} is
630      * {@code false}.
631      */
launchActivityInSplitScreenWithRecents(ComponentName activityName)632     protected void launchActivityInSplitScreenWithRecents(ComponentName activityName) {
633         launchActivityInSplitScreenWithRecents(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
634     }
635 
launchActivityInSplitScreenWithRecents(ComponentName activityName, int createMode)636     protected void launchActivityInSplitScreenWithRecents(ComponentName activityName,
637             int createMode) {
638         SystemUtil.runWithShellPermissionIdentity(() -> {
639             launchActivity(activityName);
640             final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
641             mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, createMode,
642                     true /* onTop */, false /* animate */,
643                     null /* initialBounds */, true /* showRecents */);
644 
645             mAmWmState.waitForValidState(
646                     new WaitForValidActivityState.Builder(activityName)
647                             .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
648                             .setActivityType(ACTIVITY_TYPE_STANDARD)
649                             .build());
650             mAmWmState.waitForRecentsActivityVisible();
651         });
652     }
653 
moveTaskToPrimarySplitScreen(int taskId)654     public void moveTaskToPrimarySplitScreen(int taskId) {
655         moveTaskToPrimarySplitScreen(taskId, false /* showRecents */);
656     }
657 
658     /**
659      * Moves the device into split-screen with the specified task into the primary stack.
660      * @param taskId             The id of the task to move into the primary stack.
661      * @param showSideActivity   Whether to show the Recents activity (or a placeholder activity in
662      *                           place of the Recents activity if home is the recents component)
663      */
moveTaskToPrimarySplitScreen(int taskId, boolean showSideActivity)664     public void moveTaskToPrimarySplitScreen(int taskId, boolean showSideActivity) {
665         final boolean isHomeRecentsComponent = mAmWmState.getAmState().isHomeRecentsComponent();
666         SystemUtil.runWithShellPermissionIdentity(() -> {
667             mAtm.setTaskWindowingModeSplitScreenPrimary(taskId,
668                     SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true /* onTop */,
669                     false /* animate */,
670                     null /* initialBounds */, showSideActivity && !isHomeRecentsComponent);
671             mAmWmState.waitForRecentsActivityVisible();
672 
673             if (isHomeRecentsComponent && showSideActivity) {
674                 // Launch Placeholder Side Activity
675                 final Activity sideActivity = mSideActivityRule.launchActivity(
676                         new Intent());
677                 mAmWmState.waitForActivityState(sideActivity.getComponentName(), STATE_RESUMED);
678             }
679         });
680     }
681 
682     /**
683      * Launches {@param primaryActivity} into split-screen primary windowing mode
684      * and {@param secondaryActivity} to the side in split-screen secondary windowing mode.
685      */
launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity, LaunchActivityBuilder secondaryActivity)686     protected void launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity,
687             LaunchActivityBuilder secondaryActivity) {
688         // Launch split-screen primary.
689         primaryActivity
690                 .setUseInstrumentation()
691                 .setWaitForLaunched(true)
692                 .execute();
693 
694         final int taskId = mAmWmState.getAmState().getTaskByActivity(
695                 primaryActivity.mTargetActivity).mTaskId;
696         moveTaskToPrimarySplitScreen(taskId);
697 
698         // Launch split-screen secondary
699         // Recents become focused, so we can just launch new task in focused stack
700         secondaryActivity
701                 .setUseInstrumentation()
702                 .setWaitForLaunched(true)
703                 .setNewTask(true)
704                 .setMultipleTask(true)
705                 .execute();
706     }
707 
setActivityTaskWindowingMode(ComponentName activityName, int windowingMode)708     protected void setActivityTaskWindowingMode(ComponentName activityName, int windowingMode) {
709         mAmWmState.computeState(activityName);
710         final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
711         SystemUtil.runWithShellPermissionIdentity(
712                 () -> mAtm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */));
713         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
714                 .setActivityType(ACTIVITY_TYPE_STANDARD)
715                 .setWindowingMode(windowingMode)
716                 .build());
717     }
718 
moveActivityToStack(ComponentName activityName, int stackId)719     protected void moveActivityToStack(ComponentName activityName, int stackId) {
720         mAmWmState.computeState(activityName);
721         final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
722         SystemUtil.runWithShellPermissionIdentity(
723                 () -> mAtm.moveTaskToStack(taskId, stackId, true));
724 
725         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
726                 .setStackId(stackId)
727                 .build());
728     }
729 
resizeActivityTask( ComponentName activityName, int left, int top, int right, int bottom)730     protected void resizeActivityTask(
731             ComponentName activityName, int left, int top, int right, int bottom) {
732         mAmWmState.computeState(activityName);
733         final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
734         SystemUtil.runWithShellPermissionIdentity(
735                 () -> mAtm.resizeTask(taskId, new Rect(left, top, right, bottom)));
736     }
737 
resizeDockedStack( int stackWidth, int stackHeight, int taskWidth, int taskHeight)738     protected void resizeDockedStack(
739             int stackWidth, int stackHeight, int taskWidth, int taskHeight) {
740         SystemUtil.runWithShellPermissionIdentity(() ->
741                 mAtm.resizeDockedStack(new Rect(0, 0, stackWidth, stackHeight),
742                         new Rect(0, 0, taskWidth, taskHeight)));
743     }
744 
resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth, int stackHeight)745     protected void resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth,
746             int stackHeight) {
747         SystemUtil.runWithShellPermissionIdentity(() -> mAtm.resizeStack(stackId,
748                 new Rect(stackLeft, stackTop, stackWidth, stackHeight)));
749     }
750 
pressAppSwitchButtonAndWaitForRecents()751     protected void pressAppSwitchButtonAndWaitForRecents() {
752         pressAppSwitchButton();
753         mAmWmState.waitForRecentsActivityVisible();
754         mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
755     }
756 
757     // Utility method for debugging, not used directly here, but useful, so kept around.
printStacksAndTasks()758     protected void printStacksAndTasks() {
759         SystemUtil.runWithShellPermissionIdentity(() -> {
760             final String output = mAtm.listAllStacks();
761             for (String line : output.split("\\n")) {
762                 log(line);
763             }
764         });
765     }
766 
supportsVrMode()767     protected boolean supportsVrMode() {
768         return hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE);
769     }
770 
supportsPip()771     protected boolean supportsPip() {
772         return hasDeviceFeature(FEATURE_PICTURE_IN_PICTURE)
773                 || PRETEND_DEVICE_SUPPORTS_PIP;
774     }
775 
supportsFreeform()776     protected boolean supportsFreeform() {
777         return hasDeviceFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
778                 || PRETEND_DEVICE_SUPPORTS_FREEFORM;
779     }
780 
781     /** Whether or not the device supports pin/pattern/password lock. */
supportsSecureLock()782     protected boolean supportsSecureLock() {
783         return hasDeviceFeature(FEATURE_SECURE_LOCK_SCREEN);
784     }
785 
786     /** Whether or not the device supports "swipe" lock. */
supportsInsecureLock()787     protected boolean supportsInsecureLock() {
788         return !hasDeviceFeature(FEATURE_LEANBACK)
789                 && !hasDeviceFeature(FEATURE_WATCH)
790                 && !hasDeviceFeature(FEATURE_EMBEDDED)
791                 && !hasDeviceFeature(FEATURE_AUTOMOTIVE)
792                 && getSupportsInsecureLockScreen();
793     }
794 
isWatch()795     protected boolean isWatch() {
796         return hasDeviceFeature(FEATURE_WATCH);
797     }
798 
isTablet()799     protected boolean isTablet() {
800         // Larger than approx 7" tablets
801         return mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
802     }
803 
waitAndAssertActivityState(ComponentName activityName, String state, String message)804     protected void waitAndAssertActivityState(ComponentName activityName,
805             String state, String message) {
806         mAmWmState.waitForActivityState(activityName, state);
807 
808         assertTrue(message, mAmWmState.getAmState().hasActivityState(activityName, state));
809     }
810 
waitAndAssertTopResumedActivity(ComponentName activityName, int displayId, String message)811     public void waitAndAssertTopResumedActivity(ComponentName activityName, int displayId,
812             String message) {
813         mAmWmState.waitForValidState(activityName);
814         mAmWmState.waitForActivityState(activityName, STATE_RESUMED);
815         final String activityClassName = getActivityName(activityName);
816         mAmWmState.waitForWithAmState(state ->
817                         activityClassName.equals(state.getFocusedActivity()),
818                 "Waiting for activity to be on top");
819 
820         mAmWmState.assertSanity();
821         mAmWmState.assertFocusedActivity(message, activityName);
822         assertTrue("Activity must be resumed",
823                 mAmWmState.getAmState().hasActivityState(activityName, STATE_RESUMED));
824         final int frontStackId = mAmWmState.getAmState().getFrontStackId(displayId);
825         ActivityManagerState.ActivityStack frontStackOnDisplay =
826                 mAmWmState.getAmState().getStackById(frontStackId);
827         assertEquals("Resumed activity of front stack of the target display must match. " + message,
828                 activityClassName, frontStackOnDisplay.mResumedActivity);
829         mAmWmState.assertFocusedStack("Top activity's stack must also be on top", frontStackId);
830         mAmWmState.assertVisibility(activityName, true /* visible */);
831     }
832 
833     // TODO: Switch to using a feature flag, when available.
isUiModeLockedToVrHeadset()834     protected static boolean isUiModeLockedToVrHeadset() {
835         final String output = runCommandAndPrintOutput("dumpsys uimode");
836 
837         Integer curUiMode = null;
838         Boolean uiModeLocked = null;
839         for (String line : output.split("\\n")) {
840             line = line.trim();
841             Matcher matcher = sCurrentUiModePattern.matcher(line);
842             if (matcher.find()) {
843                 curUiMode = Integer.parseInt(matcher.group(1), 16);
844             }
845             matcher = sUiModeLockedPattern.matcher(line);
846             if (matcher.find()) {
847                 uiModeLocked = matcher.group(1).equals("true");
848             }
849         }
850 
851         boolean uiModeLockedToVrHeadset = (curUiMode != null) && (uiModeLocked != null)
852                 && ((curUiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET) && uiModeLocked;
853 
854         if (uiModeLockedToVrHeadset) {
855             log("UI mode is locked to VR headset");
856         }
857 
858         return uiModeLockedToVrHeadset;
859     }
860 
supportsSplitScreenMultiWindow()861     protected boolean supportsSplitScreenMultiWindow() {
862         return ActivityTaskManager.supportsSplitScreenMultiWindow(mContext);
863     }
864 
hasHomeScreen()865     protected boolean hasHomeScreen() {
866         if (sHasHomeScreen == null) {
867             sHasHomeScreen = !noHomeScreen();
868         }
869         return sHasHomeScreen;
870     }
871 
supportsSystemDecorsOnSecondaryDisplays()872     protected boolean supportsSystemDecorsOnSecondaryDisplays() {
873         if (sSupportsSystemDecorsOnSecondaryDisplays == null) {
874             sSupportsSystemDecorsOnSecondaryDisplays = getSupportsSystemDecorsOnSecondaryDisplays();
875         }
876         return sSupportsSystemDecorsOnSecondaryDisplays;
877     }
878 
getSupportsInsecureLockScreen()879     protected boolean getSupportsInsecureLockScreen() {
880         if (sSupportsInsecureLockScreen == null) {
881             try {
882                 sSupportsInsecureLockScreen = mContext.getResources().getBoolean(
883                         Resources.getSystem().getIdentifier(
884                                 "config_supportsInsecureLockScreen", "bool", "android"));
885             } catch (Resources.NotFoundException e) {
886                 sSupportsInsecureLockScreen = true;
887             }
888         }
889         return sSupportsInsecureLockScreen;
890     }
891 
892     /**
893      * Rotation support is indicated by explicitly having both landscape and portrait
894      * features or not listing either at all.
895      */
supportsRotation()896     protected boolean supportsRotation() {
897         final boolean supportsLandscape = hasDeviceFeature(FEATURE_SCREEN_LANDSCAPE);
898         final boolean supportsPortrait = hasDeviceFeature(FEATURE_SCREEN_PORTRAIT);
899         return (supportsLandscape && supportsPortrait)
900                 || (!supportsLandscape && !supportsPortrait);
901     }
902 
hasDeviceFeature(final String requiredFeature)903     protected boolean hasDeviceFeature(final String requiredFeature) {
904         return mContext.getPackageManager()
905                 .hasSystemFeature(requiredFeature);
906     }
907 
isDisplayOn(int displayId)908     protected static boolean isDisplayOn(int displayId) {
909         final DisplayManager displayManager = getInstrumentation()
910                 .getContext().getSystemService(DisplayManager.class);
911         final Display display = displayManager.getDisplay(displayId);
912         return display != null && display.getState() == Display.STATE_ON;
913     }
914 
perDisplayFocusEnabled()915     protected static boolean perDisplayFocusEnabled() {
916         return getInstrumentation().getTargetContext().getResources()
917                 .getBoolean(android.R.bool.config_perDisplayFocusEnabled);
918     }
919 
920     /**
921      * Test @Rule class that disables screen doze settings before each test method running and
922      * restoring to initial values after test method finished.
923      */
924     protected static class DisableScreenDozeRule implements TestRule {
925 
926         /** Copied from android.provider.Settings.Secure since these keys are hiden. */
927         private static final String[] DOZE_SETTINGS = {
928                 "doze_enabled",
929                 "doze_always_on",
930                 "doze_pulse_on_pick_up",
931                 "doze_pulse_on_long_press",
932                 "doze_pulse_on_double_tap"
933         };
934 
get(String key)935         private String get(String key) {
936             return executeShellCommand("settings get secure " + key).trim();
937         }
938 
put(String key, String value)939         private void put(String key, String value) {
940             executeShellCommand("settings put secure " + key + " " + value);
941         }
942 
943         @Override
apply(final Statement base, final Description description)944         public Statement apply(final Statement base, final Description description) {
945             return new Statement() {
946                 @Override
947                 public void evaluate() throws Throwable {
948                     final Map<String, String> initialValues = new HashMap<>();
949                     Arrays.stream(DOZE_SETTINGS).forEach(k -> initialValues.put(k, get(k)));
950                     try {
951                         Arrays.stream(DOZE_SETTINGS).forEach(k -> put(k, "0"));
952                         base.evaluate();
953                     } finally {
954                         Arrays.stream(DOZE_SETTINGS).forEach(k -> put(k, initialValues.get(k)));
955                     }
956                 }
957             };
958         }
959     }
960 
961     /**
962      * HomeActivitySession is used to replace the default home component, so that you can use
963      * your preferred home for testing within the session. The original default home will be
964      * restored automatically afterward.
965      */
966     protected class HomeActivitySession implements AutoCloseable {
967         private PackageManager mPackageManager;
968         private ComponentName mOrigHome;
969         private ComponentName mSessionHome;
970 
971         public HomeActivitySession(ComponentName sessionHome) {
972             mSessionHome = sessionHome;
973             mPackageManager = mContext.getPackageManager();
974 
975             final Intent intent = new Intent(ACTION_MAIN);
976             intent.addCategory(CATEGORY_HOME);
977             intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
978             final ResolveInfo resolveInfo =
979                     mPackageManager.resolveActivity(intent, MATCH_DEFAULT_ONLY);
980             if (resolveInfo != null) {
981                 mOrigHome = new ComponentName(resolveInfo.activityInfo.packageName,
982                         resolveInfo.activityInfo.name);
983             }
984 
985             SystemUtil.runWithShellPermissionIdentity(
986                     () -> mPackageManager.setComponentEnabledSetting(mSessionHome,
987                             COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP));
988             setDefaultHome(mSessionHome);
989         }
990 
991         @Override
992         public void close() {
993             SystemUtil.runWithShellPermissionIdentity(
994                     () -> mPackageManager.setComponentEnabledSetting(mSessionHome,
995                             COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP));
996             if (mOrigHome != null) {
997                 setDefaultHome(mOrigHome);
998             }
999         }
1000 
1001         private void setDefaultHome(ComponentName componentName) {
1002             executeShellCommand("cmd package set-home-activity --user "
1003                     + android.os.Process.myUserHandle().getIdentifier() + " "
1004                     + componentName.flattenToString());
1005         }
1006     }
1007 
1008     protected class LockScreenSession implements AutoCloseable {
1009         private static final boolean DEBUG = false;
1010 
1011         private final boolean mIsLockDisabled;
1012         private boolean mLockCredentialSet;
1013         private boolean mRemoveActivitiesOnClose;
1014         private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
1015 
1016         public static final int FLAG_REMOVE_ACTIVITIES_ON_CLOSE = 1;
1017 
1018         public LockScreenSession() {
1019             this(0 /* flags */);
1020         }
1021 
1022         public LockScreenSession(int flags) {
1023             mIsLockDisabled = isLockDisabled();
1024             mLockCredentialSet = false;
1025             // Enable lock screen (swipe) by default.
1026             setLockDisabled(false);
1027             if ((flags & FLAG_REMOVE_ACTIVITIES_ON_CLOSE) != 0) {
1028                 mRemoveActivitiesOnClose = true;
1029             }
1030             mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
1031         }
1032 
1033         public LockScreenSession setLockCredential() {
1034             mLockCredentialSet = true;
1035             runCommandAndPrintOutput("locksettings set-pin " + LOCK_CREDENTIAL);
1036             return this;
1037         }
1038 
1039         public LockScreenSession enterAndConfirmLockCredential() {
1040             // Ensure focus will switch to default display. Meanwhile we cannot tap on center area,
1041             // which may tap on input credential area.
1042             tapOnDisplay(10, 10, DEFAULT_DISPLAY);
1043 
1044             waitForDeviceIdle(3000);
1045             SystemUtil.runWithShellPermissionIdentity(() ->
1046                     getInstrumentation().sendStringSync(LOCK_CREDENTIAL));
1047             pressEnterButton();
1048             return this;
1049         }
1050 
1051         private void removeLockCredential() {
1052             runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
1053             mLockCredentialSet = false;
1054         }
1055 
1056         LockScreenSession disableLockScreen() {
1057             setLockDisabled(true);
1058             return this;
1059         }
1060 
1061         LockScreenSession sleepDevice() {
1062             pressSleepButton();
1063             // Not all device variants lock when we go to sleep, so we need to explicitly lock the
1064             // device. Note that pressSleepButton() above is redundant because the action also
1065             // puts the device to sleep, but kept around for clarity.
1066             getInstrumentation().getUiAutomation().performGlobalAction(
1067                     AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
1068             if (mAmbientDisplayConfiguration.alwaysOnEnabled(
1069                     android.os.Process.myUserHandle().getIdentifier())) {
1070                 mAmWmState.waitForAodShowing();
1071             } else {
1072                 for (int retry = 1; isDisplayOn(DEFAULT_DISPLAY) && retry <= 5; retry++) {
1073                     logAlways("***Waiting for display to turn off... retry=" + retry);
1074                     SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
1075                 }
1076             }
1077             return this;
1078         }
1079 
1080         LockScreenSession wakeUpDevice() {
1081             pressWakeupButton();
1082             return this;
1083         }
1084 
1085         LockScreenSession unlockDevice() {
1086             pressUnlockButton();
1087             return this;
1088         }
1089 
1090         public LockScreenSession gotoKeyguard(ComponentName... showWhenLockedActivities) {
1091             if (DEBUG && isLockDisabled()) {
1092                 logE("LockScreenSession.gotoKeyguard() is called without lock enabled.");
1093             }
1094             sleepDevice();
1095             wakeUpDevice();
1096             if (showWhenLockedActivities.length == 0) {
1097                 mAmWmState.waitForKeyguardShowingAndNotOccluded();
1098             } else {
1099                 mAmWmState.waitForValidState(showWhenLockedActivities);
1100             }
1101             return this;
1102         }
1103 
1104         @Override
1105         public void close() {
1106             if (mRemoveActivitiesOnClose) {
1107                 removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
1108             }
1109 
1110             setLockDisabled(mIsLockDisabled);
1111             if (mLockCredentialSet) {
1112                 removeLockCredential();
1113             }
1114 
1115             // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for
1116             // the stale credential.
1117             // TODO (b/112015010) If keyguard is occluded, credential cannot be removed as expected.
1118             // LockScreenSession#close is always calls before stop all test activities,
1119             // which could cause keyguard stay at occluded after wakeup.
1120             // If Keyguard is occluded, press back key can close ShowWhenLocked activity.
1121             pressBackButton();
1122 
1123             // If device is unlocked, there might have ShowWhenLocked activity runs on,
1124             // use home key to clear all activity at foreground.
1125             pressHomeButton();
1126             sleepDevice();
1127             wakeUpDevice();
1128             unlockDevice();
1129         }
1130 
1131         /**
1132          * Returns whether the lock screen is disabled.
1133          *
1134          * @return true if the lock screen is disabled, false otherwise.
1135          */
1136         private boolean isLockDisabled() {
1137             final String isLockDisabled = runCommandAndPrintOutput(
1138                     "locksettings get-disabled").trim();
1139             return !"null".equals(isLockDisabled) && Boolean.parseBoolean(isLockDisabled);
1140         }
1141 
1142         /**
1143          * Disable the lock screen.
1144          *
1145          * @param lockDisabled true if should disable, false otherwise.
1146          */
1147         protected void setLockDisabled(boolean lockDisabled) {
1148             runCommandAndPrintOutput("locksettings set-disabled " + lockDisabled);
1149         }
1150     }
1151 
1152     /** Helper class to save, set & wait, and restore rotation related preferences. */
1153     protected class RotationSession extends SettingsSession<Integer> {
1154         private final SettingsSession<Integer> mUserRotation;
1155         private final HandlerThread mThread;
1156         private final Handler mRunnableHandler;
1157         private final SettingsObserver mRotationObserver;
1158         private int mPreviousDegree;
1159 
1160         public RotationSession() {
1161             // Save accelerometer_rotation preference.
1162             super(Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
1163                     Settings.System::getInt, Settings.System::putInt);
1164             mUserRotation = new SettingsSession<>(
1165                     Settings.System.getUriFor(Settings.System.USER_ROTATION),
1166                     Settings.System::getInt, Settings.System::putInt);
1167 
1168             mThread = new HandlerThread("Observer_Thread");
1169             mThread.start();
1170             mRunnableHandler = new Handler(mThread.getLooper());
1171             mRotationObserver = new SettingsObserver(mRunnableHandler);
1172 
1173             mPreviousDegree = mUserRotation.get();
1174             // Disable accelerometer_rotation.
1175             super.set(0);
1176         }
1177 
1178         @Override
1179         public void set(@NonNull Integer value) {
1180             // When the rotation is locked and the SystemUI receives the rotation becoming 0deg, it
1181             // will call freezeRotation to WMS, which will cause USER_ROTATION be set to zero again.
1182             // In order to prevent our test target from being overwritten by SystemUI during
1183             // rotation test, wait for the USER_ROTATION changed then continue testing.
1184             final boolean waitSystemUI = value == ROTATION_0 && mPreviousDegree != ROTATION_0;
1185             if (waitSystemUI) {
1186                 mRotationObserver.observe();
1187             }
1188             mUserRotation.set(value);
1189             mPreviousDegree = value;
1190 
1191             if (waitSystemUI) {
1192                 waitForRotationNotified();
1193             }
1194             // Wait for settling rotation.
1195             mAmWmState.waitForRotation(value);
1196 
1197             if (waitSystemUI) {
1198                 mRotationObserver.stopObserver();
1199             }
1200         }
1201 
1202         @Override
1203         public void close() throws Exception {
1204             mThread.quitSafely();
1205             mUserRotation.close();
1206             // Restore accelerometer_rotation preference.
1207             super.close();
1208         }
1209 
1210         private void waitForRotationNotified() {
1211             for (int retry = 1; retry <= 5; retry++) {
1212                 // There will receive USER_ROTATION changed twice because when the device rotates to
1213                 // 0deg, RotationContextButton will also set ROTATION_0 again.
1214                 if (mRotationObserver.count == 2) {
1215                     return;
1216                 }
1217                 logAlways("waitForRotationNotified retry=" + retry);
1218                 SystemClock.sleep(500);
1219             }
1220             logE("waitForRotationNotified skip");
1221         }
1222 
1223         private class SettingsObserver extends ContentObserver {
1224             int count;
1225 
1226             SettingsObserver(Handler handler) { super(handler); }
1227 
1228             void observe() {
1229                 count = 0;
1230                 final ContentResolver resolver = mContext.getContentResolver();
1231                 resolver.registerContentObserver(Settings.System.getUriFor(
1232                         Settings.System.USER_ROTATION), false, this);
1233             }
1234 
1235             void stopObserver() {
1236                 count = 0;
1237                 final ContentResolver resolver = mContext.getContentResolver();
1238                 resolver.unregisterContentObserver(this);
1239             }
1240 
1241             @Override
1242             public void onChange(boolean selfChange) {
1243                 count++;
1244             }
1245         }
1246     }
1247 
1248     /**
1249      * Returns whether the test device respects settings of locked user rotation mode.
1250      *
1251      * The method sets the locked user rotation settings to the rotation that rotates the display by
1252      * 180 degrees and checks if the actual display rotation changes after that.
1253      *
1254      * This is a necessary assumption check before leveraging user rotation mode to force display
1255      * rotation, because there is no requirement that an Android device that supports both
1256      * orientations needs to support user rotation mode.
1257      *
1258      * @param session   the rotation session used to set user rotation
1259      * @param displayId the display ID to check rotation against
1260      * @return {@code true} if test device respects settings of locked user rotation mode;
1261      * {@code false} if not.
1262      */
1263     protected boolean supportsLockedUserRotation(RotationSession session, int displayId)
1264             throws Exception {
1265         final int origRotation = getDeviceRotation(displayId);
1266         // Use the same orientation as target rotation to avoid affect of app-requested orientation.
1267         final int targetRotation = (origRotation + 2) % 4;
1268         session.set(targetRotation);
1269         final boolean result = (getDeviceRotation(displayId) == targetRotation);
1270         session.set(origRotation);
1271         return result;
1272     }
1273 
1274     protected int getDeviceRotation(int displayId) {
1275         final String displays = runCommandAndPrintOutput("dumpsys display displays").trim();
1276         Pattern pattern = Pattern.compile(
1277                 "(mDisplayId=" + displayId + ")([\\s\\S]*?)(mOverrideDisplayInfo)(.*)"
1278                         + "(rotation)(\\s+)(\\d+)");
1279         Matcher matcher = pattern.matcher(displays);
1280         if (matcher.find()) {
1281             final String match = matcher.group(7);
1282             return Integer.parseInt(match);
1283         }
1284 
1285         return INVALID_DEVICE_ROTATION;
1286     }
1287 
1288     /** Empties the test journal so the following events won't be mixed-up with previous records. */
1289     protected void separateTestJournal() {
1290         TestJournalContainer.start();
1291     }
1292 
1293     protected static String runCommandAndPrintOutput(String command) {
1294         final String output = executeShellCommand(command);
1295         log(output);
1296         return output;
1297     }
1298 
1299     protected static class LogSeparator {
1300         private final String mUniqueString;
1301 
1302         private LogSeparator() {
1303             mUniqueString = UUID.randomUUID().toString();
1304         }
1305 
1306         @Override
1307         public String toString() {
1308             return mUniqueString;
1309         }
1310     }
1311 
1312     /**
1313      * Inserts a log separator so we can always find the starting point from where to evaluate
1314      * following logs.
1315      *
1316      * @return Unique log separator.
1317      */
1318     protected LogSeparator separateLogs() {
1319         final LogSeparator logSeparator = new LogSeparator();
1320         executeShellCommand("log -t " + LOG_SEPARATOR + " " + logSeparator);
1321         EventLog.writeEvent(EVENT_LOG_SEPARATOR_TAG, logSeparator.mUniqueString);
1322         return logSeparator;
1323     }
1324 
1325     protected static String[] getDeviceLogsForComponents(
1326             LogSeparator logSeparator, String... logTags) {
1327         String filters = LOG_SEPARATOR + ":I ";
1328         for (String component : logTags) {
1329             filters += component + ":I ";
1330         }
1331         final String[] result = executeShellCommand("logcat -v brief -d " + filters + " *:S")
1332                 .split("\\n");
1333         if (logSeparator == null) {
1334             return result;
1335         }
1336 
1337         // Make sure that we only check logs after the separator.
1338         int i = 0;
1339         boolean lookingForSeparator = true;
1340         while (i < result.length && lookingForSeparator) {
1341             if (result[i].contains(logSeparator.toString())) {
1342                 lookingForSeparator = false;
1343             }
1344             i++;
1345         }
1346         final String[] filteredResult = new String[result.length - i];
1347         for (int curPos = 0; i < result.length; curPos++, i++) {
1348             filteredResult[curPos] = result[i];
1349         }
1350         return filteredResult;
1351     }
1352 
1353     protected static List<Event> getEventLogsForComponents(LogSeparator logSeparator, int... tags) {
1354         List<Event> events = new ArrayList<>();
1355 
1356         int[] searchTags = Arrays.copyOf(tags, tags.length + 1);
1357         searchTags[searchTags.length - 1] = EVENT_LOG_SEPARATOR_TAG;
1358 
1359         try {
1360             EventLog.readEvents(searchTags, events);
1361         } catch (IOException e) {
1362             fail("Could not read from event log." + e);
1363         }
1364 
1365         for (Iterator<Event> itr = events.iterator(); itr.hasNext(); ) {
1366             Event event = itr.next();
1367             itr.remove();
1368             if (event.getTag() == EVENT_LOG_SEPARATOR_TAG &&
1369                     logSeparator.mUniqueString.equals(event.getData())) {
1370                 break;
1371             }
1372         }
1373         return events;
1374     }
1375 
1376     protected boolean supportsMultiDisplay() {
1377         return mContext.getPackageManager().hasSystemFeature(
1378                 FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
1379     }
1380 
1381     /**
1382      * Base helper class for retrying validator success.
1383      */
1384     private abstract static class RetryValidator {
1385 
1386         private static final int RETRY_LIMIT = 5;
1387         private static final long RETRY_INTERVAL = TimeUnit.SECONDS.toMillis(1);
1388 
1389         /**
1390          * @return Error string if validation is failed, null if everything is fine.
1391          **/
1392         @Nullable
1393         protected abstract String validate();
1394 
1395         /**
1396          * Executes {@link #validate()}. Retries {@link #RETRY_LIMIT} times with
1397          * {@link #RETRY_INTERVAL} interval.
1398          *
1399          * @param waitingMessage logging message while waiting validation.
1400          */
1401         void assertValidator(String waitingMessage) {
1402             String resultString = null;
1403             for (int retry = 1; retry <= RETRY_LIMIT; retry++) {
1404                 resultString = validate();
1405                 if (resultString == null) {
1406                     return;
1407                 }
1408                 logAlways(waitingMessage + ": " + resultString);
1409                 SystemClock.sleep(RETRY_INTERVAL);
1410             }
1411             fail(resultString);
1412         }
1413     }
1414 
1415     static class CountSpec<T> {
1416         static final int DONT_CARE = Integer.MIN_VALUE;
1417         static final int EQUALS = 1;
1418         static final int GREATER_THAN = 2;
1419         static final int LESS_THAN = 3;
1420 
1421         final T mEvent;
1422         final int mRule;
1423         final int mCount;
1424         final String mMessage;
1425 
1426         CountSpec(T event, int rule, int count, String message) {
1427             mEvent = event;
1428             mRule = count == DONT_CARE ? DONT_CARE : rule;
1429             mCount = count;
1430             if (message != null) {
1431                 mMessage = message;
1432             } else {
1433                 switch (rule) {
1434                     case EQUALS:
1435                         mMessage = event + " + must equal to " + count;
1436                         break;
1437                     case GREATER_THAN:
1438                         mMessage = event + " + must be greater than " + count;
1439                         break;
1440                     case LESS_THAN:
1441                         mMessage = event + " + must be less than " + count;
1442                         break;
1443                     default:
1444                         mMessage = "Don't care";
1445                 }
1446             }
1447         }
1448 
1449         /** @return {@code true} if the given value is satisfied the condition. */
1450         boolean validate(int value) {
1451             switch (mRule) {
1452                 case DONT_CARE:
1453                     return true;
1454                 case EQUALS:
1455                     return value == mCount;
1456                 case GREATER_THAN:
1457                     return value > mCount;
1458                 case LESS_THAN:
1459                     return value < mCount;
1460                 default:
1461             }
1462             throw new RuntimeException("Unknown CountSpec rule");
1463         }
1464     }
1465 
1466     static <T> CountSpec<T> countSpec(T event, int rule, int count, String message) {
1467         return new CountSpec<>(event, rule, count, message);
1468     }
1469 
1470     static <T> CountSpec<T> countSpec(T event, int rule, int count) {
1471         return new CountSpec<>(event, rule, count, null /* message */);
1472     }
1473 
1474     static void assertLifecycleCounts(ComponentName activityName, String message,
1475             int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
1476             int destroyCount, int configChangeCount) {
1477         new ActivityLifecycleCounts(activityName).assertCountWithRetry(
1478                 message,
1479                 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, createCount),
1480                 countSpec(ActivityCallback.ON_START, CountSpec.EQUALS, startCount),
1481                 countSpec(ActivityCallback.ON_RESUME, CountSpec.EQUALS, resumeCount),
1482                 countSpec(ActivityCallback.ON_PAUSE, CountSpec.EQUALS, pauseCount),
1483                 countSpec(ActivityCallback.ON_STOP, CountSpec.EQUALS, stopCount),
1484                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, destroyCount),
1485                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS,
1486                         configChangeCount));
1487     }
1488 
1489     static void assertLifecycleCounts(ComponentName activityName,
1490             int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
1491             int destroyCount, int configChangeCount) {
1492         assertLifecycleCounts(activityName, "Assert lifecycle of " + getLogTag(activityName),
1493                 createCount, startCount, resumeCount, pauseCount, stopCount,
1494                 destroyCount, configChangeCount);
1495     }
1496 
1497     static void assertSingleLaunch(ComponentName activityName) {
1498         assertLifecycleCounts(activityName,
1499                 "***Waiting for activity create, start, and resume",
1500                 1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
1501                 0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */,
1502                 CountSpec.DONT_CARE /* configChangeCount */);
1503     }
1504 
1505     static void assertSingleLaunchAndStop(ComponentName activityName) {
1506         assertLifecycleCounts(activityName,
1507                 "***Waiting for activity create, start, resume, pause, and stop",
1508                 1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
1509                 1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */,
1510                 CountSpec.DONT_CARE /* configChangeCount */);
1511     }
1512 
1513     static void assertSingleStartAndStop(ComponentName activityName) {
1514         assertLifecycleCounts(activityName,
1515                 "***Waiting for activity start, resume, pause, and stop",
1516                 0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
1517                 1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */,
1518                 CountSpec.DONT_CARE /* configChangeCount */);
1519     }
1520 
1521     static void assertSingleStart(ComponentName activityName) {
1522         assertLifecycleCounts(activityName,
1523                 "***Waiting for activity start and resume",
1524                 0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
1525                 0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */,
1526                 CountSpec.DONT_CARE /* configChangeCount */);
1527     }
1528 
1529     /** Assert the activity is either relaunched or received configuration changed. */
1530     static void assertActivityLifecycle(ComponentName activityName, boolean relaunched) {
1531         new RetryValidator() {
1532 
1533             @Nullable
1534             @Override
1535             protected String validate() {
1536                 final String failedReason = checkActivityIsRelaunchedOrConfigurationChanged(
1537                         getActivityName(activityName),
1538                         TestJournalContainer.get(activityName).callbacks, relaunched);
1539                 if (failedReason != null) {
1540                     return failedReason;
1541                 }
1542                 return null;
1543             }
1544         }.assertValidator("***Waiting for valid lifecycle state");
1545     }
1546 
1547     /** Assert the activity is either relaunched or received configuration changed. */
1548     static List<ActivityCallback> assertActivityLifecycle(ActivitySession activitySession,
1549             boolean relaunched) {
1550         final String name = activitySession.getName();
1551         final List<ActivityCallback> callbackHistory = activitySession.takeCallbackHistory();
1552         String failedReason = checkActivityIsRelaunchedOrConfigurationChanged(
1553                 name, callbackHistory, relaunched);
1554         if (failedReason != null) {
1555             fail(failedReason);
1556         }
1557         return callbackHistory;
1558     }
1559 
1560     private static String checkActivityIsRelaunchedOrConfigurationChanged(String name,
1561             List<ActivityCallback> callbackHistory, boolean relaunched) {
1562         final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(callbackHistory);
1563         if (relaunched) {
1564             return lifecycles.validateCount(
1565                     countSpec(ActivityCallback.ON_DESTROY, CountSpec.GREATER_THAN, 0,
1566                             name + " must have been destroyed."),
1567                     countSpec(ActivityCallback.ON_CREATE, CountSpec.GREATER_THAN, 0,
1568                             name + " must have been (re)created."));
1569         }
1570         return lifecycles.validateCount(
1571                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.LESS_THAN, 1,
1572                         name + " must *NOT* have been destroyed."),
1573                 countSpec(ActivityCallback.ON_CREATE, CountSpec.LESS_THAN, 1,
1574                         name + " must *NOT* have been (re)created."),
1575                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.GREATER_THAN, 0,
1576                                 name + " must have received configuration changed."));
1577     }
1578 
1579     static void assertRelaunchOrConfigChanged(ComponentName activityName, int numRelaunch,
1580             int numConfigChange) {
1581         new ActivityLifecycleCounts(activityName).assertCountWithRetry(
1582                 "***Waiting for relaunch or config changed",
1583                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, numRelaunch),
1584                 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, numRelaunch),
1585                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS,
1586                         numConfigChange));
1587     }
1588 
1589     static void assertActivityDestroyed(ComponentName activityName) {
1590         new ActivityLifecycleCounts(activityName).assertCountWithRetry(
1591                 "***Waiting for activity destroyed",
1592                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, 1),
1593                 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, 0),
1594                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 0));
1595     }
1596 
1597     private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)");
1598     private static final Pattern sUiModeLockedPattern =
1599             Pattern.compile("mUiModeLocked=(true|false)");
1600 
1601     @Nullable
1602     SizeInfo getLastReportedSizesForActivity(ComponentName activityName) {
1603         for (int retry = 1; retry <= 5; retry++) {
1604             final ConfigInfo result = TestJournalContainer.get(activityName).lastConfigInfo;
1605             if (result != null && result.sizeInfo != null) {
1606                 return result.sizeInfo;
1607             }
1608             logAlways("***Waiting for sizes to be reported... retry=" + retry);
1609             SystemClock.sleep(1000);
1610         }
1611         logE("***Waiting for activity size failed: activityName=" + getActivityName(activityName));
1612         return null;
1613     }
1614 
1615     /** Check if a device has display cutout. */
1616     boolean hasDisplayCutout() {
1617         // Launch an activity to report cutout state
1618         separateTestJournal();
1619         launchActivity(BROADCAST_RECEIVER_ACTIVITY);
1620 
1621         // Read the logs to check if cutout is present
1622         final Boolean displayCutoutPresent = getCutoutStateForActivity(BROADCAST_RECEIVER_ACTIVITY);
1623         assertNotNull("The activity should report cutout state", displayCutoutPresent);
1624 
1625         // Finish activity
1626         mBroadcastActionTrigger.finishBroadcastReceiverActivity();
1627         mAmWmState.waitForWithAmState(
1628                 (state) -> !state.containsActivity(BROADCAST_RECEIVER_ACTIVITY),
1629                 "Waiting for activity to be removed");
1630 
1631         return displayCutoutPresent;
1632     }
1633 
1634     /**
1635      * Wait for activity to report cutout state in logs and return it. Will return {@code null}
1636      * after timeout.
1637      */
1638     @Nullable
1639     private Boolean getCutoutStateForActivity(ComponentName activityName) {
1640         final String logTag = getLogTag(activityName);
1641         for (int retry = 1; retry <= 5; retry++) {
1642             final Bundle extras = TestJournalContainer.get(activityName).extras;
1643             if (extras.containsKey(EXTRA_CUTOUT_EXISTS)) {
1644                 return extras.getBoolean(EXTRA_CUTOUT_EXISTS);
1645             }
1646             logAlways("***Waiting for cutout state to be reported... retry=" + retry);
1647             SystemClock.sleep(1000);
1648         }
1649         logE("***Waiting for activity cutout state failed: activityName=" + logTag);
1650         return null;
1651     }
1652 
1653     /** Waits for at least one onMultiWindowModeChanged event. */
1654     ActivityLifecycleCounts waitForOnMultiWindowModeChanged(ComponentName activityName) {
1655         int retry = 1;
1656         ActivityLifecycleCounts result;
1657         do {
1658             result = new ActivityLifecycleCounts(activityName);
1659             if (result.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED) >= 1) {
1660                 return result;
1661             }
1662             logAlways("***waitForOnMultiWindowModeChanged... retry=" + retry);
1663             SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
1664         } while (retry++ <= 5);
1665         return result;
1666     }
1667 
1668     static class ActivityLifecycleCounts {
1669         final int[] mCounts = new int[ActivityCallback.SIZE];
1670         final int[] mLastIndexes = new int[ActivityCallback.SIZE];
1671         final List<ActivityCallback> mCallbackHistory;
1672 
1673         ActivityLifecycleCounts(ComponentName componentName) {
1674             this(TestJournalContainer.get(componentName).callbacks);
1675         }
1676 
1677         ActivityLifecycleCounts(List<ActivityCallback> callbacks) {
1678             mCallbackHistory = callbacks;
1679             for (int i = 0; i < callbacks.size(); i++) {
1680                 final ActivityCallback callback = callbacks.get(i);
1681                 final int ordinal = callback.ordinal();
1682                 mCounts[ordinal]++;
1683                 mLastIndexes[ordinal] = i;
1684             }
1685         }
1686 
1687         int getCount(ActivityCallback callback) {
1688             return mCounts[callback.ordinal()];
1689         }
1690 
1691         int getLastIndex(ActivityCallback callback) {
1692             return mLastIndexes[callback.ordinal()];
1693         }
1694 
1695         @SafeVarargs
1696         final void assertCountWithRetry(String message, CountSpec<ActivityCallback>... countSpecs) {
1697             new RetryValidator() {
1698                 @Override
1699                 protected String validate() {
1700                     return validateCount(countSpecs);
1701                 }
1702             }.assertValidator(message);
1703         }
1704 
1705         @SafeVarargs
1706         final String validateCount(CountSpec<ActivityCallback>... countSpecs) {
1707             ArrayList<String> failedReasons = null;
1708             for (CountSpec<ActivityCallback> spec : countSpecs) {
1709                 final int realCount = mCounts[spec.mEvent.ordinal()];
1710                 if (!spec.validate(realCount)) {
1711                     if (failedReasons == null) {
1712                         failedReasons = new ArrayList<>();
1713                     }
1714                     failedReasons.add(spec.mMessage);
1715                 }
1716             }
1717             return failedReasons == null ? null : String.join("\n", failedReasons);
1718         }
1719     }
1720 
1721     protected void stopTestPackage(final String packageName) {
1722         SystemUtil.runWithShellPermissionIdentity(() -> mAm.forceStopPackage(packageName));
1723     }
1724 
1725     protected LaunchActivityBuilder getLaunchActivityBuilder() {
1726         return new LaunchActivityBuilder(mAmWmState);
1727     }
1728 
1729     protected static class LaunchActivityBuilder implements LaunchProxy {
1730         private final ActivityAndWindowManagersState mAmWmState;
1731 
1732         // The activity to be launched
1733         private ComponentName mTargetActivity = TEST_ACTIVITY;
1734         private boolean mUseApplicationContext;
1735         private boolean mToSide;
1736         private boolean mRandomData;
1737         private boolean mNewTask;
1738         private boolean mMultipleTask;
1739         private boolean mAllowMultipleInstances = true;
1740         private int mDisplayId = INVALID_DISPLAY;
1741         private int mActivityType = ACTIVITY_TYPE_UNDEFINED;
1742         // A proxy activity that launches other activities including mTargetActivityName
1743         private ComponentName mLaunchingActivity = LAUNCHING_ACTIVITY;
1744         private boolean mReorderToFront;
1745         private boolean mWaitForLaunched;
1746         private boolean mSuppressExceptions;
1747         private boolean mWithShellPermission;
1748         // Use of the following variables indicates that a broadcast receiver should be used instead
1749         // of a launching activity;
1750         private ComponentName mBroadcastReceiver;
1751         private String mBroadcastReceiverAction;
1752         private int mIntentFlags;
1753         private Bundle mExtras;
1754         private LaunchInjector mLaunchInjector;
1755 
1756         private enum LauncherType {
1757             INSTRUMENTATION, LAUNCHING_ACTIVITY, BROADCAST_RECEIVER
1758         }
1759 
1760         private LauncherType mLauncherType = LauncherType.LAUNCHING_ACTIVITY;
1761 
1762         public LaunchActivityBuilder(ActivityAndWindowManagersState amWmState) {
1763             mAmWmState = amWmState;
1764             mWaitForLaunched = true;
1765             mWithShellPermission = true;
1766         }
1767 
1768         public LaunchActivityBuilder setToSide(boolean toSide) {
1769             mToSide = toSide;
1770             return this;
1771         }
1772 
1773         public LaunchActivityBuilder setRandomData(boolean randomData) {
1774             mRandomData = randomData;
1775             return this;
1776         }
1777 
1778         public LaunchActivityBuilder setNewTask(boolean newTask) {
1779             mNewTask = newTask;
1780             return this;
1781         }
1782 
1783         public LaunchActivityBuilder setMultipleTask(boolean multipleTask) {
1784             mMultipleTask = multipleTask;
1785             return this;
1786         }
1787 
1788         public LaunchActivityBuilder allowMultipleInstances(boolean allowMultipleInstances) {
1789             mAllowMultipleInstances = allowMultipleInstances;
1790             return this;
1791         }
1792 
1793         public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) {
1794             mReorderToFront = reorderToFront;
1795             return this;
1796         }
1797 
1798         public LaunchActivityBuilder setUseApplicationContext(boolean useApplicationContext) {
1799             mUseApplicationContext = useApplicationContext;
1800             return this;
1801         }
1802 
1803         public ComponentName getTargetActivity() {
1804             return mTargetActivity;
1805         }
1806 
1807         public boolean isTargetActivityTranslucent() {
1808             return mAmWmState.getAmState().isActivityTranslucent(mTargetActivity);
1809         }
1810 
1811         public LaunchActivityBuilder setTargetActivity(ComponentName targetActivity) {
1812             mTargetActivity = targetActivity;
1813             return this;
1814         }
1815 
1816         public LaunchActivityBuilder setDisplayId(int id) {
1817             mDisplayId = id;
1818             return this;
1819         }
1820 
1821         public LaunchActivityBuilder setActivityType(int type) {
1822             mActivityType = type;
1823             return this;
1824         }
1825 
1826         public LaunchActivityBuilder setLaunchingActivity(ComponentName launchingActivity) {
1827             mLaunchingActivity = launchingActivity;
1828             mLauncherType = LauncherType.LAUNCHING_ACTIVITY;
1829             return this;
1830         }
1831 
1832         public LaunchActivityBuilder setWaitForLaunched(boolean shouldWait) {
1833             mWaitForLaunched = shouldWait;
1834             return this;
1835         }
1836 
1837         /** Use broadcast receiver as a launchpad for activities. */
1838         public LaunchActivityBuilder setUseBroadcastReceiver(final ComponentName broadcastReceiver,
1839                 final String broadcastAction) {
1840             mBroadcastReceiver = broadcastReceiver;
1841             mBroadcastReceiverAction = broadcastAction;
1842             mLauncherType = LauncherType.BROADCAST_RECEIVER;
1843             return this;
1844         }
1845 
1846         /** Use {@link android.app.Instrumentation} as a launchpad for activities. */
1847         public LaunchActivityBuilder setUseInstrumentation() {
1848             mLauncherType = LauncherType.INSTRUMENTATION;
1849             // Calling startActivity() from outside of an Activity context requires the
1850             // FLAG_ACTIVITY_NEW_TASK flag.
1851             setNewTask(true);
1852             return this;
1853         }
1854 
1855         public LaunchActivityBuilder setSuppressExceptions(boolean suppress) {
1856             mSuppressExceptions = suppress;
1857             return this;
1858         }
1859 
1860         public LaunchActivityBuilder setWithShellPermission(boolean withShellPermission) {
1861             mWithShellPermission = withShellPermission;
1862             return this;
1863         }
1864 
1865         @Override
1866         public boolean shouldWaitForLaunched() {
1867             return mWaitForLaunched;
1868         }
1869 
1870         public LaunchActivityBuilder setIntentFlags(int flags) {
1871             mIntentFlags = flags;
1872             return this;
1873         }
1874 
1875         public LaunchActivityBuilder setIntentExtra(Consumer<Bundle> extrasConsumer) {
1876             if (extrasConsumer != null) {
1877                 mExtras = new Bundle();
1878                 extrasConsumer.accept(mExtras);
1879             }
1880             return this;
1881         }
1882 
1883         @Override
1884         public Bundle getExtras() {
1885             return mExtras;
1886         }
1887 
1888         @Override
1889         public void setLaunchInjector(LaunchInjector injector) {
1890             mLaunchInjector = injector;
1891         }
1892 
1893         @Override
1894         public void execute() {
1895             switch (mLauncherType) {
1896                 case INSTRUMENTATION:
1897                     if (mWithShellPermission) {
1898                         SystemUtil.runWithShellPermissionIdentity(this::launchUsingInstrumentation);
1899                     } else {
1900                         launchUsingInstrumentation();
1901                     }
1902                     break;
1903                 case LAUNCHING_ACTIVITY:
1904                 case BROADCAST_RECEIVER:
1905                     launchUsingShellCommand();
1906             }
1907 
1908             if (mWaitForLaunched) {
1909                 mAmWmState.waitForValidState(mTargetActivity);
1910             }
1911         }
1912 
1913         /** Launch an activity using instrumentation. */
1914         private void launchUsingInstrumentation() {
1915             final Bundle b = new Bundle();
1916             b.putBoolean(KEY_USE_INSTRUMENTATION, true);
1917             b.putBoolean(KEY_LAUNCH_ACTIVITY, true);
1918             b.putBoolean(KEY_LAUNCH_TO_SIDE, mToSide);
1919             b.putBoolean(KEY_RANDOM_DATA, mRandomData);
1920             b.putBoolean(KEY_NEW_TASK, mNewTask);
1921             b.putBoolean(KEY_MULTIPLE_TASK, mMultipleTask);
1922             b.putBoolean(KEY_MULTIPLE_INSTANCES, mAllowMultipleInstances);
1923             b.putBoolean(KEY_REORDER_TO_FRONT, mReorderToFront);
1924             b.putInt(KEY_DISPLAY_ID, mDisplayId);
1925             b.putInt(KEY_ACTIVITY_TYPE, mActivityType);
1926             b.putBoolean(KEY_USE_APPLICATION_CONTEXT, mUseApplicationContext);
1927             b.putString(KEY_TARGET_COMPONENT, getActivityName(mTargetActivity));
1928             b.putBoolean(KEY_SUPPRESS_EXCEPTIONS, mSuppressExceptions);
1929             b.putInt(KEY_INTENT_FLAGS, mIntentFlags);
1930             b.putBundle(KEY_INTENT_EXTRAS, getExtras());
1931             final Context context = getInstrumentation().getContext();
1932             launchActivityFromExtras(context, b, mLaunchInjector);
1933         }
1934 
1935         /** Build and execute a shell command to launch an activity. */
1936         private void launchUsingShellCommand() {
1937             StringBuilder commandBuilder = new StringBuilder();
1938             if (mBroadcastReceiver != null && mBroadcastReceiverAction != null) {
1939                 // Use broadcast receiver to launch the target.
1940                 commandBuilder.append("am broadcast -a ").append(mBroadcastReceiverAction)
1941                         .append(" -p ").append(mBroadcastReceiver.getPackageName())
1942                         // Include stopped packages
1943                         .append(" -f 0x00000020");
1944             } else {
1945                 // Use launching activity to launch the target.
1946                 commandBuilder.append(getAmStartCmd(mLaunchingActivity))
1947                         .append(" -f 0x20000020");
1948             }
1949 
1950             // Add a flag to ensure we actually mean to launch an activity.
1951             commandBuilder.append(" --ez " + KEY_LAUNCH_ACTIVITY + " true");
1952 
1953             if (mToSide) {
1954                 commandBuilder.append(" --ez " + KEY_LAUNCH_TO_SIDE + " true");
1955             }
1956             if (mRandomData) {
1957                 commandBuilder.append(" --ez " + KEY_RANDOM_DATA + " true");
1958             }
1959             if (mNewTask) {
1960                 commandBuilder.append(" --ez " + KEY_NEW_TASK + " true");
1961             }
1962             if (mMultipleTask) {
1963                 commandBuilder.append(" --ez " + KEY_MULTIPLE_TASK + " true");
1964             }
1965             if (mAllowMultipleInstances) {
1966                 commandBuilder.append(" --ez " + KEY_MULTIPLE_INSTANCES + " true");
1967             }
1968             if (mReorderToFront) {
1969                 commandBuilder.append(" --ez " + KEY_REORDER_TO_FRONT + " true");
1970             }
1971             if (mDisplayId != INVALID_DISPLAY) {
1972                 commandBuilder.append(" --ei " + KEY_DISPLAY_ID + " ").append(mDisplayId);
1973             }
1974             if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {
1975                 commandBuilder.append(" --ei " + KEY_ACTIVITY_TYPE + " ").append(mActivityType);
1976             }
1977 
1978             if (mUseApplicationContext) {
1979                 commandBuilder.append(" --ez " + KEY_USE_APPLICATION_CONTEXT + " true");
1980             }
1981 
1982             if (mTargetActivity != null) {
1983                 // {@link ActivityLauncher} parses this extra string by
1984                 // {@link ComponentName#unflattenFromString(String)}.
1985                 commandBuilder.append(" --es " + KEY_TARGET_COMPONENT + " ")
1986                         .append(getActivityName(mTargetActivity));
1987             }
1988 
1989             if (mSuppressExceptions) {
1990                 commandBuilder.append(" --ez " + KEY_SUPPRESS_EXCEPTIONS + " true");
1991             }
1992 
1993             if (mIntentFlags != 0) {
1994                 commandBuilder.append(" --ei " + KEY_INTENT_FLAGS + " ").append(mIntentFlags);
1995             }
1996 
1997             if (mLaunchInjector != null) {
1998                 mLaunchInjector.setupShellCommand(commandBuilder);
1999             }
2000             executeShellCommand(commandBuilder.toString());
2001         }
2002     }
2003 
2004     // Activity used in place of recents when home is the recents component.
2005     public static class SideActivity extends Activity {
2006     }
2007 }
2008