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