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