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