Home | History | Annotate | Download | only in am
      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.
    167     protected static String getAmStartCmd(final ComponentName activityName,
    168             final String... keyValuePairs) {
    169         return getAmStartCmdInternal(getActivityName(activityName), keyValuePairs);
    170     }
    171 
    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 
    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 
    195     protected static String getAmStartCmd(final ComponentName activityName, final int displayId,
    196             final String... keyValuePair) {
    197         return getAmStartCmdInternal(getActivityName(activityName), displayId, keyValuePair);
    198     }
    199 
    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 
    212     protected static String getAmStartCmdInNewTask(final ComponentName activityName) {
    213         return "am start -n " + getActivityName(activityName) + " -f 0x18000000";
    214     }
    215 
    216     protected static String getAmStartCmdOverHome(final ComponentName activityName) {
    217         return "am start --activity-task-on-home -n " + getActivityName(activityName);
    218     }
    219 
    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
    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
    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 
    254     protected void removeStacksWithActivityTypes(int... activityTypes) {
    255         mAm.removeStacksWithActivityTypes(activityTypes);
    256         waitForIdle();
    257     }
    258 
    259     protected void removeStacksInWindowingModes(int... windowingModes) {
    260         mAm.removeStacksInWindowingModes(windowingModes);
    261         waitForIdle();
    262     }
    263 
    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 
    276     protected Bitmap takeScreenshot() {
    277         return InstrumentationRegistry.getInstrumentation().getUiAutomation().takeScreenshot();
    278     }
    279 
    280     protected void launchActivity(final ComponentName activityName, final String... keyValuePairs) {
    281         launchActivityNoWait(activityName, keyValuePairs);
    282         mAmWmState.waitForValidState(activityName);
    283     }
    284 
    285     protected void launchActivityNoWait(final ComponentName activityName,
    286             final String... keyValuePairs) {
    287         executeShellCommand(getAmStartCmd(activityName, keyValuePairs));
    288     }
    289 
    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
    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 
    314     private static void waitForIdle() {
    315         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
    316     }
    317 
    318     /** Returns the set of stack ids. */
    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 
    329     protected void launchHomeActivity() {
    330         executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
    331         mAmWmState.waitForHomeActivityVisible();
    332     }
    333 
    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 
    343     protected void launchActivityOnDisplay(ComponentName activityName, int displayId,
    344             String... keyValuePairs) {
    345         launchActivityOnDisplayNoWait(activityName, displayId, keyValuePairs);
    346         mAmWmState.waitForValidState(activityName);
    347     }
    348 
    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      */
    361     protected void launchActivityInSplitScreenWithRecents(ComponentName activityName) {
    362         launchActivityInSplitScreenWithRecents(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
    363     }
    364 
    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 
    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      */
    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      */
    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 
    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 
    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 
    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 
    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 
    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 
    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.
    479     protected void printStacksAndTasks() {
    480         String output = executeShellCommand(AM_STACK_LIST);
    481         for (String line : output.split("\\n")) {
    482             log(line);
    483         }
    484     }
    485 
    486     protected boolean supportsVrMode() {
    487         return hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE);
    488     }
    489 
    490     protected boolean supportsPip() {
    491         return hasDeviceFeature(FEATURE_PICTURE_IN_PICTURE)
    492                 || PRETEND_DEVICE_SUPPORTS_PIP;
    493     }
    494 
    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. */
    501     protected boolean supportsSecureLock() {
    502         return !hasDeviceFeature(FEATURE_LEANBACK)
    503                 && !hasDeviceFeature(FEATURE_EMBEDDED);
    504     }
    505 
    506     /** Whether or not the device supports "swipe" lock. */
    507     protected boolean supportsInsecureLock() {
    508         return !hasDeviceFeature(FEATURE_LEANBACK)
    509                 && !hasDeviceFeature(FEATURE_WATCH)
    510                 && !hasDeviceFeature(FEATURE_EMBEDDED);
    511     }
    512 
    513     protected boolean isWatch() {
    514         return hasDeviceFeature(FEATURE_WATCH);
    515     }
    516 
    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.
    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 
    550     protected boolean supportsSplitScreenMultiWindow() {
    551         return ActivityManager.supportsSplitScreenMultiWindow(mContext);
    552     }
    553 
    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      */
    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 
    572     protected boolean hasDeviceFeature(final String requiredFeature) {
    573         return InstrumentationRegistry.getContext()
    574                 .getPackageManager()
    575                 .hasSystemFeature(requiredFeature);
    576     }
    577 
    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 
    605         private String get(String key) {
    606             return executeShellCommand("settings get secure " + key).trim();
    607         }
    608 
    609         private void put(String key, String value) {
    610             executeShellCommand("settings put secure " + key + " " + value);
    611         }
    612 
    613         @Override
    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