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.am;
18 
19 import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
20 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
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.FLAG_ACTIVITY_MULTIPLE_TASK;
27 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
28 import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
29 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
30 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
31 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
32 import static android.content.pm.PackageManager.FEATURE_SCREEN_LANDSCAPE;
33 import static android.content.pm.PackageManager.FEATURE_SCREEN_PORTRAIT;
34 import static android.content.pm.PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE;
35 import static android.content.pm.PackageManager.FEATURE_WATCH;
36 import static android.server.am.ActivityLauncher.KEY_DISPLAY_ID;
37 import static android.server.am.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
38 import static android.server.am.ActivityLauncher.KEY_LAUNCH_TO_SIDE;
39 import static android.server.am.ActivityLauncher.KEY_MULTIPLE_TASK;
40 import static android.server.am.ActivityLauncher.KEY_NEW_TASK;
41 import static android.server.am.ActivityLauncher.KEY_RANDOM_DATA;
42 import static android.server.am.ActivityLauncher.KEY_REORDER_TO_FRONT;
43 import static android.server.am.ActivityLauncher.KEY_SUPPRESS_EXCEPTIONS;
44 import static android.server.am.ActivityLauncher.KEY_TARGET_COMPONENT;
45 import static android.server.am.ActivityLauncher.KEY_USE_APPLICATION_CONTEXT;
46 import static android.server.am.ActivityLauncher.KEY_USE_INSTRUMENTATION;
47 import static android.server.am.ActivityLauncher.launchActivityFromExtras;
48 import static android.server.am.ActivityManagerState.STATE_RESUMED;
49 import static android.server.am.ComponentNameUtils.getActivityName;
50 import static android.server.am.ComponentNameUtils.getLogTag;
51 import static android.server.am.Components.LAUNCHING_ACTIVITY;
52 import static android.server.am.Components.TEST_ACTIVITY;
53 import static android.server.am.StateLogger.log;
54 import static android.server.am.StateLogger.logAlways;
55 import static android.server.am.StateLogger.logE;
56 import static android.server.am.UiDeviceUtils.pressAppSwitchButton;
57 import static android.server.am.UiDeviceUtils.pressBackButton;
58 import static android.server.am.UiDeviceUtils.pressEnterButton;
59 import static android.server.am.UiDeviceUtils.pressHomeButton;
60 import static android.server.am.UiDeviceUtils.pressSleepButton;
61 import static android.server.am.UiDeviceUtils.pressUnlockButton;
62 import static android.server.am.UiDeviceUtils.pressWakeupButton;
63 import static android.server.am.UiDeviceUtils.waitForDeviceIdle;
64 import static android.view.Display.DEFAULT_DISPLAY;
65 import static android.view.Display.INVALID_DISPLAY;
66 
67 import static org.junit.Assert.assertTrue;
68 import static org.junit.Assert.fail;
69 
70 import static java.lang.Integer.toHexString;
71 
72 import android.accessibilityservice.AccessibilityService;
73 import android.app.Activity;
74 import android.app.ActivityManager;
75 import android.content.ComponentName;
76 import android.content.Context;
77 import android.graphics.Bitmap;
78 import android.content.Intent;
79 import android.os.Bundle;
80 import android.os.SystemClock;
81 import android.provider.Settings;
82 import android.server.am.settings.SettingsSession;
83 import android.support.test.InstrumentationRegistry;
84 
85 import android.support.test.rule.ActivityTestRule;
86 
87 import com.android.compatibility.common.util.SystemUtil;
88 
89 import org.junit.After;
90 import org.junit.Before;
91 import org.junit.Rule;
92 import org.junit.rules.TestRule;
93 import org.junit.runner.Description;
94 import org.junit.runners.model.Statement;
95 
96 import java.io.IOException;
97 import java.util.Arrays;
98 import java.util.HashMap;
99 import java.util.HashSet;
100 import java.util.List;
101 import java.util.Map;
102 import java.util.UUID;
103 import java.util.concurrent.TimeUnit;
104 import java.util.regex.Matcher;
105 import java.util.regex.Pattern;
106 import java.util.stream.Collectors;
107 import java.util.stream.IntStream;
108 
109 import androidx.annotation.NonNull;
110 import androidx.annotation.Nullable;
111 
112 public abstract class ActivityManagerTestBase {
113     private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false;
114     private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false;
115     private static final String LOG_SEPARATOR = "LOG_SEPARATOR";
116 
117     protected static final int[] ALL_ACTIVITY_TYPE_BUT_HOME = {
118             ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
119             ACTIVITY_TYPE_UNDEFINED
120     };
121 
122     private static final String TASK_ID_PREFIX = "taskId";
123 
124     private static final String AM_STACK_LIST = "am stack list";
125 
126     private static final String AM_FORCE_STOP_TEST_PACKAGE = "am force-stop android.server.am";
127     private static final String AM_FORCE_STOP_SECOND_TEST_PACKAGE
128             = "am force-stop android.server.am.second";
129     private static final String AM_FORCE_STOP_THIRD_TEST_PACKAGE
130             = "am force-stop android.server.am.third";
131 
132     protected static final String AM_START_HOME_ACTIVITY_COMMAND =
133             "am start -a android.intent.action.MAIN -c android.intent.category.HOME";
134 
135     private static final String AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND_FORMAT =
136             "am stack move-top-activity-to-pinned-stack %1d 0 0 500 500";
137 
138     private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
139     private static final String AM_RESIZE_STACK = "am stack resize ";
140 
141     static final String AM_MOVE_TASK = "am stack move-task ";
142 
143     private static final String AM_NO_HOME_SCREEN = "am no-home-screen";
144 
145     private static final String LOCK_CREDENTIAL = "1234";
146 
147     private static final int UI_MODE_TYPE_MASK = 0x0f;
148     private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
149 
150     private static Boolean sHasHomeScreen = null;
151 
152     protected static final int INVALID_DEVICE_ROTATION = -1;
153 
154     protected Context mContext;
155     protected ActivityManager mAm;
156 
157     @Rule
158     public final ActivityTestRule<SideActivity> mSideActivityRule =
159             new ActivityTestRule<>(SideActivity.class, true /* initialTouchMode */,
160                     false /* launchActivity */);
161 
162     /**
163      * @return the am command to start the given activity with the following extra key/value pairs.
164      *         {@param keyValuePairs} must be a list of arguments defining each key/value extra.
165      */
166     // TODO: Make this more generic, for instance accepting flags or extras of other types.
getAmStartCmd(final ComponentName activityName, final String... keyValuePairs)167     protected static String getAmStartCmd(final ComponentName activityName,
168             final String... keyValuePairs) {
169         return getAmStartCmdInternal(getActivityName(activityName), keyValuePairs);
170     }
171 
getAmStartCmdInternal(final String activityName, final String... keyValuePairs)172     private static String getAmStartCmdInternal(final String activityName,
173             final String... keyValuePairs) {
174         return appendKeyValuePairs(
175                 new StringBuilder("am start -n ").append(activityName),
176                 keyValuePairs);
177     }
178 
appendKeyValuePairs( final StringBuilder cmd, final String... keyValuePairs)179     private static String appendKeyValuePairs(
180             final StringBuilder cmd, final String... keyValuePairs) {
181         if (keyValuePairs.length % 2 != 0) {
182             throw new RuntimeException("keyValuePairs must be pairs of key/value arguments");
183         }
184         for (int i = 0; i < keyValuePairs.length; i += 2) {
185             final String key = keyValuePairs[i];
186             final String value = keyValuePairs[i + 1];
187             cmd.append(" --es ")
188                     .append(key)
189                     .append(" ")
190                     .append(value);
191         }
192         return cmd.toString();
193     }
194 
getAmStartCmd(final ComponentName activityName, final int displayId, final String... keyValuePair)195     protected static String getAmStartCmd(final ComponentName activityName, final int displayId,
196             final String... keyValuePair) {
197         return getAmStartCmdInternal(getActivityName(activityName), displayId, keyValuePair);
198     }
199 
getAmStartCmdInternal(final String activityName, final int displayId, final String... keyValuePairs)200     private static String getAmStartCmdInternal(final String activityName, final int displayId,
201             final String... keyValuePairs) {
202         return appendKeyValuePairs(
203                 new StringBuilder("am start -n ")
204                         .append(activityName)
205                         .append(" -f 0x")
206                         .append(toHexString(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK))
207                         .append(" --display ")
208                         .append(displayId),
209                 keyValuePairs);
210     }
211 
getAmStartCmdInNewTask(final ComponentName activityName)212     protected static String getAmStartCmdInNewTask(final ComponentName activityName) {
213         return "am start -n " + getActivityName(activityName) + " -f 0x18000000";
214     }
215 
getAmStartCmdOverHome(final ComponentName activityName)216     protected static String getAmStartCmdOverHome(final ComponentName activityName) {
217         return "am start --activity-task-on-home -n " + getActivityName(activityName);
218     }
219 
getMoveToPinnedStackCommand(int stackId)220     protected static String getMoveToPinnedStackCommand(int stackId) {
221         return String.format(AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND_FORMAT, stackId);
222     }
223 
224     protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
225 
226     @Before
setUp()227     public void setUp() throws Exception {
228         mContext = InstrumentationRegistry.getContext();
229         mAm = mContext.getSystemService(ActivityManager.class);
230 
231         InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
232                 mContext.getPackageName(), android.Manifest.permission.MANAGE_ACTIVITY_STACKS);
233         InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
234                 mContext.getPackageName(), android.Manifest.permission.ACTIVITY_EMBEDDING);
235 
236         pressWakeupButton();
237         pressUnlockButton();
238         pressHomeButton();
239         removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
240     }
241 
242     @After
tearDown()243     public void tearDown() throws Exception {
244         // Synchronous execution of removeStacksWithActivityTypes() ensures that all activities but
245         // home are cleaned up from the stack at the end of each test. Am force stop shell commands
246         // might be asynchronous and could interrupt the stack cleanup process if executed first.
247         removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
248         executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
249         executeShellCommand(AM_FORCE_STOP_SECOND_TEST_PACKAGE);
250         executeShellCommand(AM_FORCE_STOP_THIRD_TEST_PACKAGE);
251         pressHomeButton();
252     }
253 
removeStacksWithActivityTypes(int... activityTypes)254     protected void removeStacksWithActivityTypes(int... activityTypes) {
255         mAm.removeStacksWithActivityTypes(activityTypes);
256         waitForIdle();
257     }
258 
removeStacksInWindowingModes(int... windowingModes)259     protected void removeStacksInWindowingModes(int... windowingModes) {
260         mAm.removeStacksInWindowingModes(windowingModes);
261         waitForIdle();
262     }
263 
executeShellCommand(String command)264     public static String executeShellCommand(String command) {
265         log("Shell command: " + command);
266         try {
267             return SystemUtil
268                     .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
269         } catch (IOException e) {
270             //bubble it up
271             logE("Error running shell command: " + command);
272             throw new RuntimeException(e);
273         }
274     }
275 
takeScreenshot()276     protected Bitmap takeScreenshot() {
277         return InstrumentationRegistry.getInstrumentation().getUiAutomation().takeScreenshot();
278     }
279 
launchActivity(final ComponentName activityName, final String... keyValuePairs)280     protected void launchActivity(final ComponentName activityName, final String... keyValuePairs) {
281         launchActivityNoWait(activityName, keyValuePairs);
282         mAmWmState.waitForValidState(activityName);
283     }
284 
launchActivityNoWait(final ComponentName activityName, final String... keyValuePairs)285     protected void launchActivityNoWait(final ComponentName activityName,
286             final String... keyValuePairs) {
287         executeShellCommand(getAmStartCmd(activityName, keyValuePairs));
288     }
289 
launchActivityInNewTask(final ComponentName activityName)290     protected void launchActivityInNewTask(final ComponentName activityName) {
291         executeShellCommand(getAmStartCmdInNewTask(activityName));
292         mAmWmState.waitForValidState(activityName);
293     }
294 
295     /**
296      * Starts an activity in a new stack.
297      * @return the stack id of the newly created stack.
298      */
299     @Deprecated
launchActivityInNewDynamicStack(ComponentName activityName)300     protected int launchActivityInNewDynamicStack(ComponentName activityName) {
301         HashSet<Integer> stackIds = getStackIds();
302         executeShellCommand("am stack start " + DEFAULT_DISPLAY + " "
303                 + getActivityName(activityName));
304         HashSet<Integer> newStackIds = getStackIds();
305         newStackIds.removeAll(stackIds);
306         if (newStackIds.isEmpty()) {
307             return INVALID_STACK_ID;
308         } else {
309             assertTrue(newStackIds.size() == 1);
310             return newStackIds.iterator().next();
311         }
312     }
313 
waitForIdle()314     private static void waitForIdle() {
315         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
316     }
317 
318     /** Returns the set of stack ids. */
getStackIds()319     private HashSet<Integer> getStackIds() {
320         mAmWmState.computeState(true);
321         final List<ActivityManagerState.ActivityStack> stacks = mAmWmState.getAmState().getStacks();
322         final HashSet<Integer> stackIds = new HashSet<>();
323         for (ActivityManagerState.ActivityStack s : stacks) {
324             stackIds.add(s.mStackId);
325         }
326         return stackIds;
327     }
328 
launchHomeActivity()329     protected void launchHomeActivity() {
330         executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
331         mAmWmState.waitForHomeActivityVisible();
332     }
333 
launchActivity(ComponentName activityName, int windowingMode, final String... keyValuePairs)334     protected void launchActivity(ComponentName activityName, int windowingMode,
335             final String... keyValuePairs) {
336         executeShellCommand(getAmStartCmd(activityName, keyValuePairs)
337                 + " --windowingMode " + windowingMode);
338         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
339                 .setWindowingMode(windowingMode)
340                 .build());
341     }
342 
launchActivityOnDisplay(ComponentName activityName, int displayId, String... keyValuePairs)343     protected void launchActivityOnDisplay(ComponentName activityName, int displayId,
344             String... keyValuePairs) {
345         launchActivityOnDisplayNoWait(activityName, displayId, keyValuePairs);
346         mAmWmState.waitForValidState(activityName);
347     }
348 
launchActivityOnDisplayNoWait(ComponentName activityName, int displayId, String... keyValuePairs)349     protected void launchActivityOnDisplayNoWait(ComponentName activityName, int displayId,
350             String... keyValuePairs) {
351         executeShellCommand(getAmStartCmd(activityName, displayId, keyValuePairs));
352     }
353 
354     /**
355      * Launches {@param  activityName} into split-screen primary windowing mode and also makes
356      * the recents activity visible to the side of it.
357      * NOTE: Recents view may be combined with home screen on some devices, so using this to wait
358      * for Recents only makes sense when {@link ActivityManagerState#isHomeRecentsComponent()} is
359      * {@code false}.
360      */
launchActivityInSplitScreenWithRecents(ComponentName activityName)361     protected void launchActivityInSplitScreenWithRecents(ComponentName activityName) {
362         launchActivityInSplitScreenWithRecents(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
363     }
364 
launchActivityInSplitScreenWithRecents(ComponentName activityName, int createMode)365     protected void launchActivityInSplitScreenWithRecents(ComponentName activityName,
366             int createMode) {
367         launchActivity(activityName);
368         final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
369         mAm.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, true /* onTop */,
370                 false /* animate */, null /* initialBounds */, true /* showRecents */);
371 
372         mAmWmState.waitForValidState(
373                 new WaitForValidActivityState.Builder(activityName)
374                         .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
375                         .setActivityType(ACTIVITY_TYPE_STANDARD)
376                         .build());
377         mAmWmState.waitForRecentsActivityVisible();
378     }
379 
moveTaskToPrimarySplitScreen(int taskId)380     public void moveTaskToPrimarySplitScreen(int taskId) {
381         moveTaskToPrimarySplitScreen(taskId, false /* launchSideActivityIfNeeded */);
382     }
383 
384     /**
385      * Moves the device into split-screen with the specified task into the primary stack.
386      * @param taskId                        The id of the task to move into the primary stack.
387      * @param launchSideActivityIfNeeded    Whether a placeholder activity should be launched if no
388      *                                      recents activity is available.
389      */
moveTaskToPrimarySplitScreen(int taskId, boolean launchSideActivityIfNeeded)390     public void moveTaskToPrimarySplitScreen(int taskId, boolean launchSideActivityIfNeeded) {
391         mAm.setTaskWindowingModeSplitScreenPrimary(taskId, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
392                 true /* onTop */, false /* animate */, null /* initialBounds */,
393                 true /* showRecents */);
394         mAmWmState.waitForRecentsActivityVisible();
395 
396         if (mAmWmState.getAmState().isHomeRecentsComponent() && launchSideActivityIfNeeded) {
397             // Launch Placeholder Recents
398             final Activity recentsActivity = mSideActivityRule.launchActivity(new Intent());
399             mAmWmState.waitForActivityState(recentsActivity.getComponentName(), STATE_RESUMED);
400         }
401     }
402 
403     /**
404      * Launches {@param primaryActivity} into split-screen primary windowing mode
405      * and {@param secondaryActivity} to the side in split-screen secondary windowing mode.
406      */
launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity, LaunchActivityBuilder secondaryActivity)407     protected void launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity,
408             LaunchActivityBuilder secondaryActivity) {
409         // Launch split-screen primary.
410         primaryActivity
411                 .setUseInstrumentation()
412                 .setWaitForLaunched(true)
413                 .execute();
414 
415         final int taskId = mAmWmState.getAmState().getTaskByActivity(
416                 primaryActivity.mTargetActivity).mTaskId;
417         moveTaskToPrimarySplitScreen(taskId);
418 
419         // Launch split-screen secondary
420         // Recents become focused, so we can just launch new task in focused stack
421         secondaryActivity
422                 .setUseInstrumentation()
423                 .setWaitForLaunched(true)
424                 .setNewTask(true)
425                 .setMultipleTask(true)
426                 .execute();
427     }
428 
setActivityTaskWindowingMode(ComponentName activityName, int windowingMode)429     protected void setActivityTaskWindowingMode(ComponentName activityName, int windowingMode) {
430         mAmWmState.computeState(activityName);
431         final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
432         mAm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */);
433         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
434                 .setActivityType(ACTIVITY_TYPE_STANDARD)
435                 .setWindowingMode(windowingMode)
436                 .build());
437     }
438 
moveActivityToStack(ComponentName activityName, int stackId)439     protected void moveActivityToStack(ComponentName activityName, int stackId) {
440         mAmWmState.computeState(activityName);
441         final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
442         final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true";
443         executeShellCommand(cmd);
444 
445         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
446                 .setStackId(stackId)
447                 .build());
448     }
449 
resizeActivityTask( ComponentName activityName, int left, int top, int right, int bottom)450     protected void resizeActivityTask(
451             ComponentName activityName, int left, int top, int right, int bottom) {
452         mAmWmState.computeState(activityName);
453         final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId;
454         final String cmd = "am task resize "
455                 + taskId + " " + left + " " + top + " " + right + " " + bottom;
456         executeShellCommand(cmd);
457     }
458 
resizeDockedStack( int stackWidth, int stackHeight, int taskWidth, int taskHeight)459     protected void resizeDockedStack(
460             int stackWidth, int stackHeight, int taskWidth, int taskHeight) {
461         executeShellCommand(AM_RESIZE_DOCKED_STACK
462                 + "0 0 " + stackWidth + " " + stackHeight
463                 + " 0 0 " + taskWidth + " " + taskHeight);
464     }
465 
resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth, int stackHeight)466     protected void resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth,
467             int stackHeight) {
468         executeShellCommand(AM_RESIZE_STACK + String.format("%d %d %d %d %d", stackId, stackLeft,
469                 stackTop, stackWidth, stackHeight));
470     }
471 
pressAppSwitchButtonAndWaitForRecents()472     protected void pressAppSwitchButtonAndWaitForRecents() {
473         pressAppSwitchButton();
474         mAmWmState.waitForRecentsActivityVisible();
475         mAmWmState.waitForAppTransitionIdle();
476     }
477 
478     // Utility method for debugging, not used directly here, but useful, so kept around.
printStacksAndTasks()479     protected void printStacksAndTasks() {
480         String output = executeShellCommand(AM_STACK_LIST);
481         for (String line : output.split("\\n")) {
482             log(line);
483         }
484     }
485 
supportsVrMode()486     protected boolean supportsVrMode() {
487         return hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE);
488     }
489 
supportsPip()490     protected boolean supportsPip() {
491         return hasDeviceFeature(FEATURE_PICTURE_IN_PICTURE)
492                 || PRETEND_DEVICE_SUPPORTS_PIP;
493     }
494 
supportsFreeform()495     protected boolean supportsFreeform() {
496         return hasDeviceFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
497                 || PRETEND_DEVICE_SUPPORTS_FREEFORM;
498     }
499 
500     /** Whether or not the device pin/pattern/password lock. */
supportsSecureLock()501     protected boolean supportsSecureLock() {
502         return !hasDeviceFeature(FEATURE_LEANBACK)
503                 && !hasDeviceFeature(FEATURE_EMBEDDED);
504     }
505 
506     /** Whether or not the device supports "swipe" lock. */
supportsInsecureLock()507     protected boolean supportsInsecureLock() {
508         return !hasDeviceFeature(FEATURE_LEANBACK)
509                 && !hasDeviceFeature(FEATURE_WATCH)
510                 && !hasDeviceFeature(FEATURE_EMBEDDED);
511     }
512 
isWatch()513     protected boolean isWatch() {
514         return hasDeviceFeature(FEATURE_WATCH);
515     }
516 
isTablet()517     protected boolean isTablet() {
518         // Larger than approx 7" tablets
519         return mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
520     }
521 
522     // TODO: Switch to using a feature flag, when available.
isUiModeLockedToVrHeadset()523     protected static boolean isUiModeLockedToVrHeadset() {
524         final String output = runCommandAndPrintOutput("dumpsys uimode");
525 
526         Integer curUiMode = null;
527         Boolean uiModeLocked = null;
528         for (String line : output.split("\\n")) {
529             line = line.trim();
530             Matcher matcher = sCurrentUiModePattern.matcher(line);
531             if (matcher.find()) {
532                 curUiMode = Integer.parseInt(matcher.group(1), 16);
533             }
534             matcher = sUiModeLockedPattern.matcher(line);
535             if (matcher.find()) {
536                 uiModeLocked = matcher.group(1).equals("true");
537             }
538         }
539 
540         boolean uiModeLockedToVrHeadset = (curUiMode != null) && (uiModeLocked != null)
541                 && ((curUiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET) && uiModeLocked;
542 
543         if (uiModeLockedToVrHeadset) {
544             log("UI mode is locked to VR headset");
545         }
546 
547         return uiModeLockedToVrHeadset;
548     }
549 
supportsSplitScreenMultiWindow()550     protected boolean supportsSplitScreenMultiWindow() {
551         return ActivityManager.supportsSplitScreenMultiWindow(mContext);
552     }
553 
hasHomeScreen()554     protected boolean hasHomeScreen() {
555         if (sHasHomeScreen == null) {
556             sHasHomeScreen = !executeShellCommand(AM_NO_HOME_SCREEN).startsWith("true");
557         }
558         return sHasHomeScreen;
559     }
560 
561     /**
562      * Rotation support is indicated by explicitly having both landscape and portrait
563      * features or not listing either at all.
564      */
supportsRotation()565     protected boolean supportsRotation() {
566         final boolean supportsLandscape = hasDeviceFeature(FEATURE_SCREEN_LANDSCAPE);
567         final boolean supportsPortrait = hasDeviceFeature(FEATURE_SCREEN_PORTRAIT);
568         return (supportsLandscape && supportsPortrait)
569                 || (!supportsLandscape && !supportsPortrait);
570     }
571 
hasDeviceFeature(final String requiredFeature)572     protected boolean hasDeviceFeature(final String requiredFeature) {
573         return InstrumentationRegistry.getContext()
574                 .getPackageManager()
575                 .hasSystemFeature(requiredFeature);
576     }
577 
isDisplayOn()578     protected boolean isDisplayOn() {
579         final String output = executeShellCommand("dumpsys power");
580         final Matcher matcher = sDisplayStatePattern.matcher(output);
581         if (matcher.find()) {
582             final String state = matcher.group(1);
583             log("power state=" + state);
584             return "ON".equals(state);
585         }
586         logAlways("power state :(");
587         return false;
588     }
589 
590     /**
591      * Test @Rule class that disables screen doze settings before each test method running and
592      * restoring to initial values after test method finished.
593      */
594     protected static class DisableScreenDozeRule implements TestRule {
595 
596         /** Copied from android.provider.Settings.Secure since these keys are hiden. */
597         private static final String[] DOZE_SETTINGS = {
598                 "doze_enabled",
599                 "doze_always_on",
600                 "doze_pulse_on_pick_up",
601                 "doze_pulse_on_long_press",
602                 "doze_pulse_on_double_tap"
603         };
604 
get(String key)605         private String get(String key) {
606             return executeShellCommand("settings get secure " + key).trim();
607         }
608 
put(String key, String value)609         private void put(String key, String value) {
610             executeShellCommand("settings put secure " + key + " " + value);
611         }
612 
613         @Override
apply(final Statement base, final Description description)614         public Statement apply(final Statement base, final Description description) {
615             return new Statement() {
616                 @Override
617                 public void evaluate() throws Throwable {
618                     final Map<String, String> initialValues = new HashMap<>();
619                     Arrays.stream(DOZE_SETTINGS).forEach(k -> initialValues.put(k, get(k)));
620                     try {
621                         Arrays.stream(DOZE_SETTINGS).forEach(k -> put(k, "0"));
622                         base.evaluate();
623                     } finally {
624                         Arrays.stream(DOZE_SETTINGS).forEach(k -> put(k, initialValues.get(k)));
625                     }
626                 }
627             };
628         }
629     }
630 
631     protected class LockScreenSession implements AutoCloseable {
632         private static final boolean DEBUG = false;
633 
634         private final boolean mIsLockDisabled;
635         private boolean mLockCredentialSet;
636 
637         public LockScreenSession() {
638             mIsLockDisabled = isLockDisabled();
639             mLockCredentialSet = false;
640             // Enable lock screen (swipe) by default.
641             setLockDisabled(false);
642         }
643 
644         public LockScreenSession setLockCredential() {
645             mLockCredentialSet = true;
646             runCommandAndPrintOutput("locksettings set-pin " + LOCK_CREDENTIAL);
647             return this;
648         }
649 
650         public LockScreenSession enterAndConfirmLockCredential() {
651             waitForDeviceIdle(3000);
652 
653             runCommandAndPrintOutput("input text " + LOCK_CREDENTIAL);
654             pressEnterButton();
655             return this;
656         }
657 
658         private void removeLockCredential() {
659             runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
660             mLockCredentialSet = false;
661         }
662 
663         LockScreenSession disableLockScreen() {
664             setLockDisabled(true);
665             return this;
666         }
667 
668         LockScreenSession sleepDevice() {
669             pressSleepButton();
670             // Not all device variants lock when we go to sleep, so we need to explicitly lock the
671             // device. Note that pressSleepButton() above is redundant because the action also
672             // puts the device to sleep, but kept around for clarity.
673             InstrumentationRegistry.getInstrumentation().getUiAutomation().performGlobalAction(
674                     AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
675             for (int retry = 1; isDisplayOn() && retry <= 5; retry++) {
676                 logAlways("***Waiting for display to turn off... retry=" + retry);
677                 SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
678             }
679             return this;
680         }
681 
682         LockScreenSession wakeUpDevice() {
683             pressWakeupButton();
684             return this;
685         }
686 
687         LockScreenSession unlockDevice() {
688             pressUnlockButton();
689             return this;
690         }
691 
692         public LockScreenSession gotoKeyguard() {
693             if (DEBUG && isLockDisabled()) {
694                 logE("LockScreenSession.gotoKeyguard() is called without lock enabled.");
695             }
696             sleepDevice();
697             wakeUpDevice();
698             mAmWmState.waitForKeyguardShowingAndNotOccluded();
699             return this;
700         }
701 
702         @Override
703         public void close() throws Exception {
704             setLockDisabled(mIsLockDisabled);
705             if (mLockCredentialSet) {
706                 removeLockCredential();
707             }
708 
709             // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for
710             // the stale credential.
711             pressBackButton();
712             sleepDevice();
713             wakeUpDevice();
714             unlockDevice();
715         }
716 
717         /**
718          * Returns whether the lock screen is disabled.
719          *
720          * @return true if the lock screen is disabled, false otherwise.
721          */
722         private boolean isLockDisabled() {
723             final String isLockDisabled = runCommandAndPrintOutput(
724                     "locksettings get-disabled").trim();
725             return !"null".equals(isLockDisabled) && Boolean.parseBoolean(isLockDisabled);
726         }
727 
728         /**
729          * Disable the lock screen.
730          *
731          * @param lockDisabled true if should disable, false otherwise.
732          */
733         protected void setLockDisabled(boolean lockDisabled) {
734             runCommandAndPrintOutput("locksettings set-disabled " + lockDisabled);
735         }
736     }
737 
738     /** Helper class to save, set & wait, and restore rotation related preferences. */
739     protected class RotationSession extends SettingsSession<Integer> {
740         private final SettingsSession<Integer> mUserRotation;
741 
742         public RotationSession() throws Exception {
743             // Save accelerometer_rotation preference.
744             super(Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
745                     Settings.System::getInt, Settings.System::putInt);
746             mUserRotation = new SettingsSession<>(
747                     Settings.System.getUriFor(Settings.System.USER_ROTATION),
748                     Settings.System::getInt, Settings.System::putInt);
749             // Disable accelerometer_rotation.
750             super.set(0);
751         }
752 
753         @Override
754         public void set(@NonNull Integer value) throws Exception {
755             mUserRotation.set(value);
756             // Wait for settling rotation.
757             mAmWmState.waitForRotation(value);
758         }
759 
760         @Override
761         public void close() throws Exception {
762             mUserRotation.close();
763             // Restore accelerometer_rotation preference.
764             super.close();
765         }
766     }
767 
768     protected int getDeviceRotation(int displayId) {
769         final String displays = runCommandAndPrintOutput("dumpsys display displays").trim();
770         Pattern pattern = Pattern.compile(
771                 "(mDisplayId=" + displayId + ")([\\s\\S]*)(mOverrideDisplayInfo)(.*)"
772                         + "(rotation)(\\s+)(\\d+)");
773         Matcher matcher = pattern.matcher(displays);
774         if (matcher.find()) {
775             final String match = matcher.group(7);
776             return Integer.parseInt(match);
777         }
778 
779         return INVALID_DEVICE_ROTATION;
780     }
781 
782     protected static String runCommandAndPrintOutput(String command) {
783         final String output = executeShellCommand(command);
784         log(output);
785         return output;
786     }
787 
788     protected static class LogSeparator {
789         private final String mUniqueString;
790 
791         private LogSeparator() {
792             mUniqueString = UUID.randomUUID().toString();
793         }
794 
795         @Override
796         public String toString() {
797             return mUniqueString;
798         }
799     }
800 
801     /**
802      * Inserts a log separator so we can always find the starting point from where to evaluate
803      * following logs.
804      * @return Unique log separator.
805      */
806     protected LogSeparator separateLogs() {
807         final LogSeparator logSeparator = new LogSeparator();
808         executeShellCommand("log -t " + LOG_SEPARATOR + " " + logSeparator);
809         return logSeparator;
810     }
811 
812     protected static String[] getDeviceLogsForComponents(
813             LogSeparator logSeparator, String... logTags) {
814         String filters = LOG_SEPARATOR + ":I ";
815         for (String component : logTags) {
816             filters += component + ":I ";
817         }
818         final String[] result = executeShellCommand("logcat -v brief -d " + filters + " *:S")
819                 .split("\\n");
820         if (logSeparator == null) {
821             return result;
822         }
823 
824         // Make sure that we only check logs after the separator.
825         int i = 0;
826         boolean lookingForSeparator = true;
827         while (i < result.length && lookingForSeparator) {
828             if (result[i].contains(logSeparator.toString())) {
829                 lookingForSeparator = false;
830             }
831             i++;
832         }
833         final String[] filteredResult = new String[result.length - i];
834         for (int curPos = 0; i < result.length; curPos++, i++) {
835             filteredResult[curPos] = result[i];
836         }
837         return filteredResult;
838     }
839 
840     /**
841      * Base helper class for retrying validator success.
842      */
843     private abstract static class RetryValidator {
844 
845         private static final int RETRY_LIMIT = 5;
846         private static final long RETRY_INTERVAL = TimeUnit.SECONDS.toMillis(1);
847 
848         /**
849          * @return Error string if validation is failed, null if everything is fine.
850          **/
851         @Nullable
852         protected abstract String validate();
853 
854         /**
855          * Executes {@link #validate()}. Retries {@link #RETRY_LIMIT} times with
856          * {@link #RETRY_INTERVAL} interval.
857          *
858          * @param waitingMessage logging message while waiting validation.
859          */
860         void assertValidator(String waitingMessage) {
861             String resultString = null;
862             for (int retry = 1; retry <= RETRY_LIMIT; retry++) {
863                 resultString = validate();
864                 if (resultString == null) {
865                     return;
866                 }
867                 logAlways(waitingMessage + ": " + resultString);
868                 SystemClock.sleep(RETRY_INTERVAL);
869             }
870             fail(resultString);
871         }
872     }
873 
874     private static class ActivityLifecycleCountsValidator extends RetryValidator {
875         private final ComponentName mActivityName;
876         private final LogSeparator mLogSeparator;
877         private final int mCreateCount;
878         private final int mStartCount;
879         private final int mResumeCount;
880         private final int mPauseCount;
881         private final int mStopCount;
882         private final int mDestroyCount;
883 
884         ActivityLifecycleCountsValidator(ComponentName activityName, LogSeparator logSeparator,
885                 int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
886                 int destroyCount) {
887             mActivityName = activityName;
888             mLogSeparator = logSeparator;
889             mCreateCount = createCount;
890             mStartCount = startCount;
891             mResumeCount = resumeCount;
892             mPauseCount = pauseCount;
893             mStopCount = stopCount;
894             mDestroyCount = destroyCount;
895         }
896 
897         @Override
898         @Nullable
899         protected String validate() {
900             final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
901                     mActivityName, mLogSeparator);
902             if (lifecycleCounts.mCreateCount == mCreateCount
903                     && lifecycleCounts.mStartCount == mStartCount
904                     && lifecycleCounts.mResumeCount == mResumeCount
905                     && lifecycleCounts.mPauseCount == mPauseCount
906                     && lifecycleCounts.mStopCount == mStopCount
907                     && lifecycleCounts.mDestroyCount == mDestroyCount) {
908                 return null;
909             }
910             final String expected = IntStream.of(mCreateCount, mStopCount, mResumeCount,
911                     mPauseCount, mStopCount, mDestroyCount)
912                     .mapToObj(Integer::toString)
913                     .collect(Collectors.joining("/"));
914             return getActivityName(mActivityName) + " lifecycle count mismatched:"
915                     + " expected=" + expected
916                     + " actual=" + lifecycleCounts.counters();
917         }
918     }
919 
920     void assertActivityLifecycle(ComponentName activityName, boolean relaunched,
921             LogSeparator logSeparator) {
922         new RetryValidator() {
923 
924             @Nullable
925             @Override
926             protected String validate() {
927                 final ActivityLifecycleCounts lifecycleCounts =
928                         new ActivityLifecycleCounts(activityName, logSeparator);
929                 final String logTag = getLogTag(activityName);
930                 if (relaunched) {
931                     if (lifecycleCounts.mDestroyCount < 1) {
932                         return logTag + " must have been destroyed. mDestroyCount="
933                                 + lifecycleCounts.mDestroyCount;
934                     }
935                     if (lifecycleCounts.mCreateCount < 1) {
936                         return logTag + " must have been (re)created. mCreateCount="
937                                 + lifecycleCounts.mCreateCount;
938                     }
939                     return null;
940                 }
941                 if (lifecycleCounts.mDestroyCount > 0) {
942                     return logTag + " must *NOT* have been destroyed. mDestroyCount="
943                             + lifecycleCounts.mDestroyCount;
944                 }
945                 if (lifecycleCounts.mCreateCount > 0) {
946                     return logTag + " must *NOT* have been (re)created. mCreateCount="
947                             + lifecycleCounts.mCreateCount;
948                 }
949                 if (lifecycleCounts.mConfigurationChangedCount < 1) {
950                     return logTag + " must have received configuration changed. "
951                             + "mConfigurationChangedCount="
952                             + lifecycleCounts.mConfigurationChangedCount;
953                 }
954                 return null;
955             }
956         }.assertValidator("***Waiting for valid lifecycle state");
957     }
958 
959     protected void assertRelaunchOrConfigChanged(ComponentName activityName, int numRelaunch,
960             int numConfigChange, LogSeparator logSeparator) {
961         new RetryValidator() {
962 
963             @Nullable
964             @Override
965             protected String validate() {
966                 final ActivityLifecycleCounts lifecycleCounts =
967                         new ActivityLifecycleCounts(activityName, logSeparator);
968                 final String logTag = getLogTag(activityName);
969                 if (lifecycleCounts.mDestroyCount != numRelaunch) {
970                     return logTag + " has been destroyed " + lifecycleCounts.mDestroyCount
971                             + " time(s), expecting " + numRelaunch;
972                 } else if (lifecycleCounts.mCreateCount != numRelaunch) {
973                     return logTag + " has been (re)created " + lifecycleCounts.mCreateCount
974                             + " time(s), expecting " + numRelaunch;
975                 } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) {
976                     return logTag + " has received "
977                             + lifecycleCounts.mConfigurationChangedCount
978                             + " onConfigurationChanged() calls, expecting " + numConfigChange;
979                 }
980                 return null;
981             }
982         }.assertValidator("***Waiting for relaunch or config changed");
983     }
984 
985     protected void assertActivityDestroyed(ComponentName activityName, LogSeparator logSeparator) {
986         new RetryValidator() {
987 
988             @Nullable
989             @Override
990             protected String validate() {
991                 final ActivityLifecycleCounts lifecycleCounts =
992                         new ActivityLifecycleCounts(activityName, logSeparator);
993                 final String logTag = getLogTag(activityName);
994                 if (lifecycleCounts.mDestroyCount != 1) {
995                     return logTag + " has been destroyed " + lifecycleCounts.mDestroyCount
996                             + " time(s), expecting single destruction.";
997                 }
998                 if (lifecycleCounts.mCreateCount != 0) {
999                     return logTag + " has been (re)created " + lifecycleCounts.mCreateCount
1000                             + " time(s), not expecting any.";
1001                 }
1002                 if (lifecycleCounts.mConfigurationChangedCount != 0) {
1003                     return logTag + " has received " + lifecycleCounts.mConfigurationChangedCount
1004                             + " onConfigurationChanged() calls, not expecting any.";
1005                 }
1006                 return null;
1007             }
1008         }.assertValidator("***Waiting for activity destroyed");
1009     }
1010 
1011     void assertLifecycleCounts(ComponentName activityName, LogSeparator logSeparator,
1012             int createCount, int startCount, int resumeCount, int pauseCount, int stopCount,
1013             int destroyCount, int configurationChangeCount) {
1014         new RetryValidator() {
1015             @Override
1016             protected String validate() {
1017                 final ActivityLifecycleCounts lifecycleCounts =
1018                         new ActivityLifecycleCounts(activityName, logSeparator);
1019                 final String logTag = getLogTag(activityName);
1020                 if (createCount != lifecycleCounts.mCreateCount) {
1021                     return logTag + " has been created " + lifecycleCounts.mCreateCount
1022                             + " time(s), expecting " + createCount;
1023                 }
1024                 if (startCount != lifecycleCounts.mStartCount) {
1025                     return logTag + " has been started " + lifecycleCounts.mStartCount
1026                             + " time(s), expecting " + startCount;
1027                 }
1028                 if (resumeCount != lifecycleCounts.mResumeCount) {
1029                     return logTag + " has been resumed " + lifecycleCounts.mResumeCount
1030                             + " time(s), expecting " + resumeCount;
1031                 }
1032                 if (pauseCount != lifecycleCounts.mPauseCount) {
1033                     return logTag + " has been paused " + lifecycleCounts.mPauseCount
1034                             + " time(s), expecting " + pauseCount;
1035                 }
1036                 if (stopCount != lifecycleCounts.mStopCount) {
1037                     return logTag + " has been stopped " + lifecycleCounts.mStopCount
1038                             + " time(s), expecting " + stopCount;
1039                 }
1040                 if (destroyCount != lifecycleCounts.mDestroyCount) {
1041                     return logTag + " has been destroyed " + lifecycleCounts.mDestroyCount
1042                             + " time(s), expecting " + destroyCount;
1043                 }
1044                 if (configurationChangeCount != lifecycleCounts.mConfigurationChangedCount) {
1045                     return logTag + " has received config changes "
1046                             + lifecycleCounts.mConfigurationChangedCount
1047                             + " time(s), expecting " + configurationChangeCount;
1048                 }
1049                 return null;
1050             }
1051         }.assertValidator("***Waiting for activity lifecycle counts");
1052     }
1053 
1054     void assertSingleLaunch(ComponentName activityName, LogSeparator logSeparator) {
1055         new ActivityLifecycleCountsValidator(activityName, logSeparator, 1 /* createCount */,
1056                 1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */,
1057                 0 /* destroyCount */)
1058                 .assertValidator("***Waiting for activity create, start, and resume");
1059     }
1060 
1061     void assertSingleLaunchAndStop(ComponentName activityName, LogSeparator logSeparator) {
1062         new ActivityLifecycleCountsValidator(activityName, logSeparator, 1 /* createCount */,
1063                 1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */,
1064                 0 /* destroyCount */)
1065                 .assertValidator("***Waiting for activity create, start, resume, pause, and stop");
1066     }
1067 
1068     void assertSingleStartAndStop(ComponentName activityName, LogSeparator logSeparator) {
1069         new ActivityLifecycleCountsValidator(activityName, logSeparator, 0 /* createCount */,
1070                 1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */,
1071                 0 /* destroyCount */)
1072                 .assertValidator("***Waiting for activity start, resume, pause, and stop");
1073     }
1074 
1075     void assertSingleStart(ComponentName activityName, LogSeparator logSeparator) {
1076         new ActivityLifecycleCountsValidator(activityName, logSeparator, 0 /* createCount */,
1077                 1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */,
1078                 0 /* destroyCount */)
1079                 .assertValidator("***Waiting for activity start and resume");
1080     }
1081 
1082     // TODO: Now that our test are device side, we can convert these to a more direct communication
1083     // channel vs. depending on logs.
1084     private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate");
1085     private static final Pattern sStartPattern = Pattern.compile("(.+): onStart");
1086     private static final Pattern sResumePattern = Pattern.compile("(.+): onResume");
1087     private static final Pattern sPausePattern = Pattern.compile("(.+): onPause");
1088     private static final Pattern sConfigurationChangedPattern =
1089             Pattern.compile("(.+): onConfigurationChanged");
1090     private static final Pattern sMovedToDisplayPattern =
1091             Pattern.compile("(.+): onMovedToDisplay");
1092     private static final Pattern sStopPattern = Pattern.compile("(.+): onStop");
1093     private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy");
1094     private static final Pattern sMultiWindowModeChangedPattern =
1095             Pattern.compile("(.+): onMultiWindowModeChanged");
1096     private static final Pattern sPictureInPictureModeChangedPattern =
1097             Pattern.compile("(.+): onPictureInPictureModeChanged");
1098     private static final Pattern sUserLeaveHintPattern = Pattern.compile("(.+): onUserLeaveHint");
1099     private static final Pattern sNewConfigPattern = Pattern.compile(
1100             "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)"
1101             + " metricsSize=\\((\\d+),(\\d+)\\) smallestScreenWidth=(\\d+) densityDpi=(\\d+)"
1102             + " orientation=(\\d+)");
1103     private static final Pattern sDisplayStatePattern =
1104             Pattern.compile("Display Power: state=(.+)");
1105     private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)");
1106     private static final Pattern sUiModeLockedPattern =
1107             Pattern.compile("mUiModeLocked=(true|false)");
1108 
1109     static class ReportedSizes {
1110         int widthDp;
1111         int heightDp;
1112         int displayWidth;
1113         int displayHeight;
1114         int metricsWidth;
1115         int metricsHeight;
1116         int smallestWidthDp;
1117         int densityDpi;
1118         int orientation;
1119 
1120         @Override
1121         public String toString() {
1122             return "ReportedSizes: {widthDp=" + widthDp + " heightDp=" + heightDp
1123                     + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight
1124                     + " metricsWidth=" + metricsWidth + " metricsHeight=" + metricsHeight
1125                     + " smallestWidthDp=" + smallestWidthDp + " densityDpi=" + densityDpi
1126                     + " orientation=" + orientation + "}";
1127         }
1128 
1129         @Override
1130         public boolean equals(Object obj) {
1131             if ( this == obj ) return true;
1132             if ( !(obj instanceof ReportedSizes) ) return false;
1133             ReportedSizes that = (ReportedSizes) obj;
1134             return widthDp == that.widthDp
1135                     && heightDp == that.heightDp
1136                     && displayWidth == that.displayWidth
1137                     && displayHeight == that.displayHeight
1138                     && metricsWidth == that.metricsWidth
1139                     && metricsHeight == that.metricsHeight
1140                     && smallestWidthDp == that.smallestWidthDp
1141                     && densityDpi == that.densityDpi
1142                     && orientation == that.orientation;
1143         }
1144     }
1145 
1146     @Nullable
1147     ReportedSizes getLastReportedSizesForActivity(
1148             ComponentName activityName, LogSeparator logSeparator) {
1149         final String logTag = getLogTag(activityName);
1150         for (int retry = 1; retry <= 5; retry++ ) {
1151             final ReportedSizes result = readLastReportedSizes(logSeparator, logTag);
1152             if (result != null) {
1153                 return result;
1154             }
1155             logAlways("***Waiting for sizes to be reported... retry=" + retry);
1156             SystemClock.sleep(1000);
1157         }
1158         logE("***Waiting for activity size failed: activityName=" + logTag);
1159         return null;
1160     }
1161 
1162     private ReportedSizes readLastReportedSizes(LogSeparator logSeparator, String logTag) {
1163         final String[] lines = getDeviceLogsForComponents(logSeparator, logTag);
1164         for (int i = lines.length - 1; i >= 0; i--) {
1165             final String line = lines[i].trim();
1166             final Matcher matcher = sNewConfigPattern.matcher(line);
1167             if (matcher.matches()) {
1168                 ReportedSizes details = new ReportedSizes();
1169                 details.widthDp = Integer.parseInt(matcher.group(2));
1170                 details.heightDp = Integer.parseInt(matcher.group(3));
1171                 details.displayWidth = Integer.parseInt(matcher.group(4));
1172                 details.displayHeight = Integer.parseInt(matcher.group(5));
1173                 details.metricsWidth = Integer.parseInt(matcher.group(6));
1174                 details.metricsHeight = Integer.parseInt(matcher.group(7));
1175                 details.smallestWidthDp = Integer.parseInt(matcher.group(8));
1176                 details.densityDpi = Integer.parseInt(matcher.group(9));
1177                 details.orientation = Integer.parseInt(matcher.group(10));
1178                 return details;
1179             }
1180         }
1181         return null;
1182     }
1183 
1184     /** Waits for at least one onMultiWindowModeChanged event. */
1185     ActivityLifecycleCounts waitForOnMultiWindowModeChanged(ComponentName activityName,
1186             LogSeparator logSeparator) {
1187         int retry = 1;
1188         ActivityLifecycleCounts result;
1189         do {
1190             result = new ActivityLifecycleCounts(activityName, logSeparator);
1191             if (result.mMultiWindowModeChangedCount >= 1) {
1192                 return result;
1193             }
1194             logAlways("***waitForOnMultiWindowModeChanged... retry=" + retry);
1195             SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
1196         } while (retry++ <= 5);
1197         return result;
1198     }
1199 
1200     // TODO: Now that our test are device side, we can convert these to a more direct communication
1201     // channel vs. depending on logs.
1202     static class ActivityLifecycleCounts {
1203         int mCreateCount;
1204         int mStartCount;
1205         int mResumeCount;
1206         int mConfigurationChangedCount;
1207         int mLastConfigurationChangedLineIndex;
1208         int mMovedToDisplayCount;
1209         int mMultiWindowModeChangedCount;
1210         int mLastMultiWindowModeChangedLineIndex;
1211         int mPictureInPictureModeChangedCount;
1212         int mLastPictureInPictureModeChangedLineIndex;
1213         int mUserLeaveHintCount;
1214         int mPauseCount;
1215         int mStopCount;
1216         int mLastStopLineIndex;
1217         int mDestroyCount;
1218 
1219         ActivityLifecycleCounts(ComponentName componentName, LogSeparator logSeparator) {
1220             int lineIndex = 0;
1221             waitForIdle();
1222             for (String line : getDeviceLogsForComponents(logSeparator, getLogTag(componentName))) {
1223                 line = line.trim();
1224                 lineIndex++;
1225 
1226                 Matcher matcher = sCreatePattern.matcher(line);
1227                 if (matcher.matches()) {
1228                     mCreateCount++;
1229                     continue;
1230                 }
1231 
1232                 matcher = sStartPattern.matcher(line);
1233                 if (matcher.matches()) {
1234                     mStartCount++;
1235                     continue;
1236                 }
1237 
1238                 matcher = sResumePattern.matcher(line);
1239                 if (matcher.matches()) {
1240                     mResumeCount++;
1241                     continue;
1242                 }
1243 
1244                 matcher = sConfigurationChangedPattern.matcher(line);
1245                 if (matcher.matches()) {
1246                     mConfigurationChangedCount++;
1247                     mLastConfigurationChangedLineIndex = lineIndex;
1248                     continue;
1249                 }
1250 
1251                 matcher = sMovedToDisplayPattern.matcher(line);
1252                 if (matcher.matches()) {
1253                     mMovedToDisplayCount++;
1254                     continue;
1255                 }
1256 
1257                 matcher = sMultiWindowModeChangedPattern.matcher(line);
1258                 if (matcher.matches()) {
1259                     mMultiWindowModeChangedCount++;
1260                     mLastMultiWindowModeChangedLineIndex = lineIndex;
1261                     continue;
1262                 }
1263 
1264                 matcher = sPictureInPictureModeChangedPattern.matcher(line);
1265                 if (matcher.matches()) {
1266                     mPictureInPictureModeChangedCount++;
1267                     mLastPictureInPictureModeChangedLineIndex = lineIndex;
1268                     continue;
1269                 }
1270 
1271                 matcher = sUserLeaveHintPattern.matcher(line);
1272                 if (matcher.matches()) {
1273                     mUserLeaveHintCount++;
1274                     continue;
1275                 }
1276 
1277                 matcher = sPausePattern.matcher(line);
1278                 if (matcher.matches()) {
1279                     mPauseCount++;
1280                     continue;
1281                 }
1282 
1283                 matcher = sStopPattern.matcher(line);
1284                 if (matcher.matches()) {
1285                     mStopCount++;
1286                     mLastStopLineIndex = lineIndex;
1287                     continue;
1288                 }
1289 
1290                 matcher = sDestroyPattern.matcher(line);
1291                 if (matcher.matches()) {
1292                     mDestroyCount++;
1293                     continue;
1294                 }
1295             }
1296         }
1297 
1298         String counters() {
1299             return IntStream.of(mCreateCount, mStartCount, mResumeCount, mPauseCount, mStopCount,
1300                     mDestroyCount)
1301                     .mapToObj(Integer::toString)
1302                     .collect(Collectors.joining("/"));
1303         }
1304     }
1305 
1306     protected void stopTestPackage(final ComponentName activityName) {
1307         executeShellCommand("am force-stop " + activityName.getPackageName());
1308     }
1309 
1310     protected LaunchActivityBuilder getLaunchActivityBuilder() {
1311         return new LaunchActivityBuilder(mAmWmState);
1312     }
1313 
1314     protected static class LaunchActivityBuilder {
1315         private final ActivityAndWindowManagersState mAmWmState;
1316 
1317         // The activity to be launched
1318         private ComponentName mTargetActivity = TEST_ACTIVITY;
1319         private boolean mUseApplicationContext;
1320         private boolean mToSide;
1321         private boolean mRandomData;
1322         private boolean mNewTask;
1323         private boolean mMultipleTask;
1324         private int mDisplayId = INVALID_DISPLAY;
1325         // A proxy activity that launches other activities including mTargetActivityName
1326         private ComponentName mLaunchingActivity = LAUNCHING_ACTIVITY;
1327         private boolean mReorderToFront;
1328         private boolean mWaitForLaunched;
1329         private boolean mSuppressExceptions;
1330         // Use of the following variables indicates that a broadcast receiver should be used instead
1331         // of a launching activity;
1332         private ComponentName mBroadcastReceiver;
1333         private String mBroadcastReceiverAction;
1334 
1335         private enum LauncherType {
1336             INSTRUMENTATION, LAUNCHING_ACTIVITY, BROADCAST_RECEIVER
1337         }
1338         private LauncherType mLauncherType = LauncherType.LAUNCHING_ACTIVITY;
1339 
1340         public LaunchActivityBuilder(ActivityAndWindowManagersState amWmState) {
1341             mAmWmState = amWmState;
1342             mWaitForLaunched = true;
1343         }
1344 
1345         public LaunchActivityBuilder setToSide(boolean toSide) {
1346             mToSide = toSide;
1347             return this;
1348         }
1349 
1350         public LaunchActivityBuilder setRandomData(boolean randomData) {
1351             mRandomData = randomData;
1352             return this;
1353         }
1354 
1355         public LaunchActivityBuilder setNewTask(boolean newTask) {
1356             mNewTask = newTask;
1357             return this;
1358         }
1359 
1360         public LaunchActivityBuilder setMultipleTask(boolean multipleTask) {
1361             mMultipleTask = multipleTask;
1362             return this;
1363         }
1364 
1365         public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) {
1366             mReorderToFront = reorderToFront;
1367             return this;
1368         }
1369 
1370         public LaunchActivityBuilder setUseApplicationContext(boolean useApplicationContext) {
1371             mUseApplicationContext = useApplicationContext;
1372             return this;
1373         }
1374 
1375         public ComponentName getTargetActivity() {
1376             return mTargetActivity;
1377         }
1378 
1379         public boolean isTargetActivityTranslucent() {
1380             return mAmWmState.getAmState().isActivityTranslucent(mTargetActivity);
1381         }
1382 
1383         public LaunchActivityBuilder setTargetActivity(ComponentName targetActivity) {
1384             mTargetActivity = targetActivity;
1385             return this;
1386         }
1387 
1388         public LaunchActivityBuilder setDisplayId(int id) {
1389             mDisplayId = id;
1390             return this;
1391         }
1392 
1393         public LaunchActivityBuilder setLaunchingActivity(ComponentName launchingActivity) {
1394             mLaunchingActivity = launchingActivity;
1395             mLauncherType = LauncherType.LAUNCHING_ACTIVITY;
1396             return this;
1397         }
1398 
1399         public LaunchActivityBuilder setWaitForLaunched(boolean shouldWait) {
1400             mWaitForLaunched = shouldWait;
1401             return this;
1402         }
1403 
1404         /** Use broadcast receiver as a launchpad for activities. */
1405         public LaunchActivityBuilder setUseBroadcastReceiver(final ComponentName broadcastReceiver,
1406                 final String broadcastAction) {
1407             mBroadcastReceiver = broadcastReceiver;
1408             mBroadcastReceiverAction = broadcastAction;
1409             mLauncherType = LauncherType.BROADCAST_RECEIVER;
1410             return this;
1411         }
1412 
1413         /** Use {@link android.app.Instrumentation} as a launchpad for activities. */
1414         public LaunchActivityBuilder setUseInstrumentation() {
1415             mLauncherType = LauncherType.INSTRUMENTATION;
1416             // Calling startActivity() from outside of an Activity context requires the
1417             // FLAG_ACTIVITY_NEW_TASK flag.
1418             setNewTask(true);
1419             return this;
1420         }
1421 
1422         public LaunchActivityBuilder setSuppressExceptions(boolean suppress) {
1423             mSuppressExceptions = suppress;
1424             return this;
1425         }
1426 
1427         public void execute() {
1428             switch (mLauncherType) {
1429                 case INSTRUMENTATION:
1430                     launchUsingInstrumentation();
1431                     break;
1432                 case LAUNCHING_ACTIVITY:
1433                 case BROADCAST_RECEIVER:
1434                     launchUsingShellCommand();
1435             }
1436 
1437             if (mWaitForLaunched) {
1438                 mAmWmState.waitForValidState(mTargetActivity);
1439             }
1440         }
1441 
1442         /** Launch an activity using instrumentation. */
1443         private void launchUsingInstrumentation() {
1444             final Bundle b = new Bundle();
1445             b.putBoolean(KEY_USE_INSTRUMENTATION, true);
1446             b.putBoolean(KEY_LAUNCH_ACTIVITY, true);
1447             b.putBoolean(KEY_LAUNCH_TO_SIDE, mToSide);
1448             b.putBoolean(KEY_RANDOM_DATA, mRandomData);
1449             b.putBoolean(KEY_NEW_TASK, mNewTask);
1450             b.putBoolean(KEY_MULTIPLE_TASK, mMultipleTask);
1451             b.putBoolean(KEY_REORDER_TO_FRONT, mReorderToFront);
1452             b.putInt(KEY_DISPLAY_ID, mDisplayId);
1453             b.putBoolean(KEY_USE_APPLICATION_CONTEXT, mUseApplicationContext);
1454             b.putString(KEY_TARGET_COMPONENT, getActivityName(mTargetActivity));
1455             b.putBoolean(KEY_SUPPRESS_EXCEPTIONS, mSuppressExceptions);
1456             final Context context = InstrumentationRegistry.getContext();
1457             launchActivityFromExtras(context, b);
1458         }
1459 
1460         /** Build and execute a shell command to launch an activity. */
1461         private void launchUsingShellCommand() {
1462             StringBuilder commandBuilder = new StringBuilder();
1463             if (mBroadcastReceiver != null && mBroadcastReceiverAction != null) {
1464                 // Use broadcast receiver to launch the target.
1465                 commandBuilder.append("am broadcast -a ").append(mBroadcastReceiverAction)
1466                         .append(" -p ").append(mBroadcastReceiver.getPackageName())
1467                         // Include stopped packages
1468                         .append(" -f 0x00000020");
1469             } else {
1470                 // Use launching activity to launch the target.
1471                 commandBuilder.append(getAmStartCmd(mLaunchingActivity))
1472                         .append(" -f 0x20000020");
1473             }
1474 
1475             // Add a flag to ensure we actually mean to launch an activity.
1476             commandBuilder.append(" --ez " + KEY_LAUNCH_ACTIVITY + " true");
1477 
1478             if (mToSide) {
1479                 commandBuilder.append(" --ez " + KEY_LAUNCH_TO_SIDE + " true");
1480             }
1481             if (mRandomData) {
1482                 commandBuilder.append(" --ez " + KEY_RANDOM_DATA + " true");
1483             }
1484             if (mNewTask) {
1485                 commandBuilder.append(" --ez " + KEY_NEW_TASK + " true");
1486             }
1487             if (mMultipleTask) {
1488                 commandBuilder.append(" --ez " + KEY_MULTIPLE_TASK + " true");
1489             }
1490             if (mReorderToFront) {
1491                 commandBuilder.append(" --ez " + KEY_REORDER_TO_FRONT + " true");
1492             }
1493             if (mDisplayId != INVALID_DISPLAY) {
1494                 commandBuilder.append(" --ei " + KEY_DISPLAY_ID + " ").append(mDisplayId);
1495             }
1496 
1497             if (mUseApplicationContext) {
1498                 commandBuilder.append(" --ez " + KEY_USE_APPLICATION_CONTEXT + " true");
1499             }
1500 
1501             if (mTargetActivity != null) {
1502                 // {@link ActivityLauncher} parses this extra string by
1503                 // {@link ComponentName#unflattenFromString(String)}.
1504                 commandBuilder.append(" --es " + KEY_TARGET_COMPONENT + " ")
1505                         .append(getActivityName(mTargetActivity));
1506             }
1507 
1508             if (mSuppressExceptions) {
1509                 commandBuilder.append(" --ez " + KEY_SUPPRESS_EXCEPTIONS + " true");
1510             }
1511             executeShellCommand(commandBuilder.toString());
1512         }
1513     }
1514 
1515     // Activity used in place of recents when home is the recents component.
1516     public static class SideActivity extends Activity {
1517     }
1518 }
1519