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.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
     20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
     21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
     22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
     23 import static android.server.am.ComponentNameUtils.getActivityName;
     24 import static android.server.am.ComponentNameUtils.getWindowName;
     25 import static android.server.am.StateLogger.log;
     26 import static android.server.am.StateLogger.logAlways;
     27 import static android.server.am.StateLogger.logE;
     28 import static android.server.am.UiDeviceUtils.pressBackButton;
     29 import static android.server.am.UiDeviceUtils.pressEnterButton;
     30 import static android.server.am.UiDeviceUtils.pressHomeButton;
     31 import static android.server.am.UiDeviceUtils.pressSleepButton;
     32 import static android.server.am.UiDeviceUtils.pressUnlockButton;
     33 import static android.server.am.UiDeviceUtils.pressWakeupButton;
     34 import static android.server.am.UiDeviceUtils.waitForDeviceIdle;
     35 
     36 import android.app.ActivityManager;
     37 import android.content.ComponentName;
     38 import android.content.Context;
     39 import android.os.SystemClock;
     40 import android.support.test.InstrumentationRegistry;
     41 
     42 import com.android.compatibility.common.util.SystemUtil;
     43 
     44 import org.junit.After;
     45 import org.junit.Before;
     46 
     47 import java.io.IOException;
     48 import java.util.UUID;
     49 import java.util.concurrent.TimeUnit;
     50 import java.util.regex.Matcher;
     51 import java.util.regex.Pattern;
     52 
     53 public abstract class ActivityManagerTestBase {
     54     protected static final int[] ALL_ACTIVITY_TYPE_BUT_HOME = {
     55             ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS,
     56             ACTIVITY_TYPE_UNDEFINED
     57     };
     58 
     59     private static final String TASK_ID_PREFIX = "taskId";
     60 
     61     private static final String AM_STACK_LIST = "am stack list";
     62 
     63     private static final String AM_FORCE_STOP_TEST_PACKAGE = "am force-stop android.server.am";
     64     private static final String AM_FORCE_STOP_SECOND_TEST_PACKAGE
     65             = "am force-stop android.server.am.second";
     66     private static final String AM_FORCE_STOP_THIRD_TEST_PACKAGE
     67             = "am force-stop android.server.am.third";
     68 
     69     protected static final String AM_START_HOME_ACTIVITY_COMMAND =
     70             "am start -a android.intent.action.MAIN -c android.intent.category.HOME";
     71 
     72     private static final String LOCK_CREDENTIAL = "1234";
     73 
     74     private static final int UI_MODE_TYPE_MASK = 0x0f;
     75     private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
     76 
     77     protected Context mContext;
     78     protected ActivityManager mAm;
     79 
     80     /**
     81      * @return the am command to start the given activity with the following extra key/value pairs.
     82      *         {@param keyValuePairs} must be a list of arguments defining each key/value extra.
     83      */
     84     // TODO: Make this more generic, for instance accepting flags or extras of other types.
     85     protected static String getAmStartCmd(final ComponentName activityName,
     86             final String... keyValuePairs) {
     87         return getAmStartCmdInternal(getActivityName(activityName), keyValuePairs);
     88     }
     89 
     90     private static String getAmStartCmdInternal(final String activityName,
     91             final String... keyValuePairs) {
     92         return appendKeyValuePairs(
     93                 new StringBuilder("am start -n ").append(activityName),
     94                 keyValuePairs);
     95     }
     96 
     97     private static String appendKeyValuePairs(
     98             final StringBuilder cmd, final String... keyValuePairs) {
     99         if (keyValuePairs.length % 2 != 0) {
    100             throw new RuntimeException("keyValuePairs must be pairs of key/value arguments");
    101         }
    102         for (int i = 0; i < keyValuePairs.length; i += 2) {
    103             final String key = keyValuePairs[i];
    104             final String value = keyValuePairs[i + 1];
    105             cmd.append(" --es ")
    106                     .append(key)
    107                     .append(" ")
    108                     .append(value);
    109         }
    110         return cmd.toString();
    111     }
    112 
    113     protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
    114 
    115     @Before
    116     public void setUp() throws Exception {
    117         mContext = InstrumentationRegistry.getContext();
    118         mAm = mContext.getSystemService(ActivityManager.class);
    119 
    120         pressWakeupButton();
    121         pressUnlockButton();
    122         pressHomeButton();
    123         removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
    124     }
    125 
    126     @After
    127     public void tearDown() throws Exception {
    128         // Synchronous execution of removeStacksWithActivityTypes() ensures that all activities but
    129         // home are cleaned up from the stack at the end of each test. Am force stop shell commands
    130         // might be asynchronous and could interrupt the stack cleanup process if executed first.
    131         removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
    132         executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
    133         executeShellCommand(AM_FORCE_STOP_SECOND_TEST_PACKAGE);
    134         executeShellCommand(AM_FORCE_STOP_THIRD_TEST_PACKAGE);
    135         pressHomeButton();
    136     }
    137 
    138     protected void removeStacksWithActivityTypes(int... activityTypes) {
    139         mAm.removeStacksWithActivityTypes(activityTypes);
    140         waitForIdle();
    141     }
    142 
    143     public static String executeShellCommand(String command) {
    144         log("Shell command: " + command);
    145         try {
    146             return SystemUtil
    147                     .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
    148         } catch (IOException e) {
    149             //bubble it up
    150             logE("Error running shell command: " + command);
    151             throw new RuntimeException(e);
    152         }
    153     }
    154 
    155     protected void launchActivity(final ComponentName activityName, final String... keyValuePairs) {
    156         executeShellCommand(getAmStartCmd(activityName, keyValuePairs));
    157         mAmWmState.waitForValidState(new WaitForValidActivityState(activityName));
    158     }
    159 
    160     private static void waitForIdle() {
    161         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
    162     }
    163 
    164     protected void launchHomeActivity() {
    165         executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
    166         mAmWmState.waitForHomeActivityVisible();
    167     }
    168 
    169     protected void launchActivity(ComponentName activityName, int windowingMode,
    170             final String... keyValuePairs) {
    171         executeShellCommand(getAmStartCmd(activityName, keyValuePairs)
    172                 + " --windowingMode " + windowingMode);
    173         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
    174                 .setWindowingMode(windowingMode)
    175                 .build());
    176     }
    177 
    178     protected void setActivityTaskWindowingMode(ComponentName activityName, int windowingMode) {
    179         final int taskId = getActivityTaskId(activityName);
    180         mAm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */);
    181         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
    182                 .setActivityType(ACTIVITY_TYPE_STANDARD)
    183                 .setWindowingMode(windowingMode)
    184                 .build());
    185     }
    186 
    187     @Deprecated
    188     protected int getActivityTaskId(final ComponentName activityName) {
    189         final String windowName = getWindowName(activityName);
    190         final String output = executeShellCommand(AM_STACK_LIST);
    191         final Pattern activityPattern = Pattern.compile("(.*) " + windowName + " (.*)");
    192         for (final String line : output.split("\\n")) {
    193             final Matcher matcher = activityPattern.matcher(line);
    194             if (matcher.matches()) {
    195                 for (String word : line.split("\\s+")) {
    196                     if (word.startsWith(TASK_ID_PREFIX)) {
    197                         final String withColon = word.split("=")[1];
    198                         return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
    199                     }
    200                 }
    201             }
    202         }
    203         return -1;
    204     }
    205 
    206     protected boolean isTablet() {
    207         // Larger than approx 7" tablets
    208         return mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
    209     }
    210 
    211     // TODO: Switch to using a feature flag, when available.
    212     protected boolean isUiModeLockedToVrHeadset() {
    213         final String output = runCommandAndPrintOutput("dumpsys uimode");
    214 
    215         Integer curUiMode = null;
    216         Boolean uiModeLocked = null;
    217         for (String line : output.split("\\n")) {
    218             line = line.trim();
    219             Matcher matcher = sCurrentUiModePattern.matcher(line);
    220             if (matcher.find()) {
    221                 curUiMode = Integer.parseInt(matcher.group(1), 16);
    222             }
    223             matcher = sUiModeLockedPattern.matcher(line);
    224             if (matcher.find()) {
    225                 uiModeLocked = matcher.group(1).equals("true");
    226             }
    227         }
    228 
    229         boolean uiModeLockedToVrHeadset = (curUiMode != null) && (uiModeLocked != null)
    230                 && ((curUiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET) && uiModeLocked;
    231 
    232         if (uiModeLockedToVrHeadset) {
    233             log("UI mode is locked to VR headset");
    234         }
    235 
    236         return uiModeLockedToVrHeadset;
    237     }
    238 
    239     protected boolean supportsSplitScreenMultiWindow() {
    240         return ActivityManager.supportsSplitScreenMultiWindow(mContext);
    241     }
    242 
    243     protected boolean hasDeviceFeature(final String requiredFeature) {
    244         return InstrumentationRegistry.getContext()
    245                 .getPackageManager()
    246                 .hasSystemFeature(requiredFeature);
    247     }
    248 
    249     protected boolean isDisplayOn() {
    250         final String output = executeShellCommand("dumpsys power");
    251         final Matcher matcher = sDisplayStatePattern.matcher(output);
    252         if (matcher.find()) {
    253             final String state = matcher.group(1);
    254             log("power state=" + state);
    255             return "ON".equals(state);
    256         }
    257         logAlways("power state :(");
    258         return false;
    259     }
    260 
    261     protected class LockScreenSession implements AutoCloseable {
    262         private static final boolean DEBUG = false;
    263 
    264         private final boolean mIsLockDisabled;
    265         private boolean mLockCredentialSet;
    266 
    267         public LockScreenSession() {
    268             mIsLockDisabled = isLockDisabled();
    269             mLockCredentialSet = false;
    270             // Enable lock screen (swipe) by default.
    271             setLockDisabled(false);
    272         }
    273 
    274         public LockScreenSession setLockCredential() {
    275             mLockCredentialSet = true;
    276             runCommandAndPrintOutput("locksettings set-pin " + LOCK_CREDENTIAL);
    277             return this;
    278         }
    279 
    280         public LockScreenSession enterAndConfirmLockCredential() {
    281             waitForDeviceIdle(3000);
    282 
    283             runCommandAndPrintOutput("input text " + LOCK_CREDENTIAL);
    284             pressEnterButton();
    285             return this;
    286         }
    287 
    288         private void removeLockCredential() {
    289             runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
    290             mLockCredentialSet = false;
    291         }
    292 
    293         LockScreenSession disableLockScreen() {
    294             setLockDisabled(true);
    295             return this;
    296         }
    297 
    298         public LockScreenSession sleepDevice() {
    299             pressSleepButton();
    300             waitForDisplayStateWithRetry(false /* displayOn */ );
    301             return this;
    302         }
    303 
    304         protected LockScreenSession wakeUpDevice() {
    305             pressWakeupButton();
    306             waitForDisplayStateWithRetry(true /* displayOn */ );
    307             return this;
    308         }
    309 
    310         /**
    311          * If the device has a PIN or password set, this doesn't immediately unlock the device.
    312          * Instead, it shows the keyguard and brings the entry field into focus, ready for a
    313          * call to enterAndConfirmLockCredential().
    314          */
    315         protected LockScreenSession unlockDevice() {
    316             pressUnlockButton();
    317             SystemClock.sleep(200);
    318             return this;
    319         }
    320 
    321         public LockScreenSession gotoKeyguard() {
    322             if (DEBUG && isLockDisabled()) {
    323                 logE("LockScreenSession.gotoKeyguard() is called without lock enabled.");
    324             }
    325             sleepDevice();
    326             wakeUpDevice();
    327             unlockDevice();
    328             mAmWmState.waitForKeyguardShowingAndNotOccluded();
    329             return this;
    330         }
    331 
    332         @Override
    333         public void close() throws Exception {
    334             setLockDisabled(mIsLockDisabled);
    335             if (mLockCredentialSet) {
    336                 removeLockCredential();
    337             }
    338 
    339             // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for
    340             // the stale credential.
    341             pressBackButton();
    342             sleepDevice();
    343             wakeUpDevice();
    344             unlockDevice();
    345         }
    346 
    347         /**
    348          * Returns whether the lock screen is disabled.
    349          *
    350          * @return true if the lock screen is disabled, false otherwise.
    351          */
    352         private boolean isLockDisabled() {
    353             final String isLockDisabled = runCommandAndPrintOutput(
    354                     "locksettings get-disabled").trim();
    355             return !"null".equals(isLockDisabled) && Boolean.parseBoolean(isLockDisabled);
    356         }
    357 
    358         /**
    359          * Disable the lock screen.
    360          *
    361          * @param lockDisabled true if should disable, false otherwise.
    362          */
    363         protected void setLockDisabled(boolean lockDisabled) {
    364             runCommandAndPrintOutput("locksettings set-disabled " + lockDisabled);
    365         }
    366 
    367         protected void waitForDisplayStateWithRetry(boolean displayOn) {
    368             for (int retry = 1; isDisplayOn() != displayOn && retry <= 5; retry++) {
    369                 logAlways("***Waiting for display state... retry " + retry);
    370                 SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
    371             }
    372         }
    373     }
    374 
    375     protected static String runCommandAndPrintOutput(String command) {
    376         final String output = executeShellCommand(command);
    377         log(output);
    378         return output;
    379     }
    380 
    381     protected static class LogSeparator {
    382         private final String mUniqueString;
    383 
    384         private LogSeparator() {
    385             mUniqueString = UUID.randomUUID().toString();
    386         }
    387 
    388         @Override
    389         public String toString() {
    390             return mUniqueString;
    391         }
    392     }
    393 
    394     // TODO: Now that our test are device side, we can convert these to a more direct communication
    395     // channel vs. depending on logs.
    396     private static final Pattern sDisplayStatePattern =
    397             Pattern.compile("Display Power: state=(.+)");
    398     private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)");
    399     private static final Pattern sUiModeLockedPattern =
    400             Pattern.compile("mUiModeLocked=(true|false)");
    401 
    402     protected void stopTestPackage(final ComponentName activityName) {
    403         executeShellCommand("am force-stop " + activityName.getPackageName());
    404     }
    405 }
    406