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