Home | History | Annotate | Download | only in cts
      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.cts;
     18 
     19 import com.android.ddmlib.Log.LogLevel;
     20 import com.android.tradefed.device.CollectingOutputReceiver;
     21 import com.android.tradefed.device.DeviceNotAvailableException;
     22 import com.android.tradefed.device.ITestDevice;
     23 import com.android.tradefed.log.LogUtil.CLog;
     24 import com.android.tradefed.testtype.DeviceTestCase;
     25 
     26 import java.lang.Exception;
     27 import java.lang.Integer;
     28 import java.lang.String;
     29 import java.util.HashSet;
     30 import java.util.regex.Matcher;
     31 import java.util.regex.Pattern;
     32 
     33 import static android.server.cts.StateLogger.log;
     34 
     35 public abstract class ActivityManagerTestBase extends DeviceTestCase {
     36     private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false;
     37     private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false;
     38 
     39     // Constants copied from ActivityManager.StackId. If they are changed there, these must be
     40     // updated.
     41     /** First static stack ID. */
     42     public static final int FIRST_STATIC_STACK_ID = 0;
     43 
     44     /** Home activity stack ID. */
     45     public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
     46 
     47     /** ID of stack where fullscreen activities are normally launched into. */
     48     public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
     49 
     50     /** ID of stack where freeform/resized activities are normally launched into. */
     51     public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
     52 
     53     /** ID of stack that occupies a dedicated region of the screen. */
     54     public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
     55 
     56     /** ID of stack that always on top (always visible) when it exist. */
     57     public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
     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.app";
     64 
     65     private static final String AM_REMOVE_STACK = "am stack remove ";
     66 
     67     protected static final String AM_START_HOME_ACTIVITY_COMMAND =
     68             "am start -a android.intent.action.MAIN -c android.intent.category.HOME";
     69 
     70     protected static final String AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND =
     71             "am stack move-top-activity-to-pinned-stack 1 0 0 500 500";
     72 
     73     private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
     74 
     75     private static final String AM_MOVE_TASK = "am stack movetask ";
     76 
     77     /** A reference to the device under test. */
     78     protected ITestDevice mDevice;
     79 
     80     private HashSet<String> mAvailableFeatures;
     81 
     82     protected static String getAmStartCmd(final String activityName) {
     83         return "am start -n " + getActivityComponentName(activityName);
     84     }
     85 
     86     protected static String getAmStartCmdOverHome(final String activityName) {
     87         return "am start --activity-task-on-home -n " + getActivityComponentName(activityName);
     88     }
     89 
     90     static String getActivityComponentName(final String activityName) {
     91         return "android.server.app/." + activityName;
     92     }
     93 
     94     static String getWindowName(final String activityName) {
     95         return "android.server.app/android.server.app." + activityName;
     96     }
     97 
     98     protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
     99 
    100     private int mInitialAccelerometerRotation;
    101     private int mUserRotation;
    102 
    103     @Override
    104     protected void setUp() throws Exception {
    105         super.setUp();
    106 
    107         // Get the device, this gives a handle to run commands and install APKs.
    108         mDevice = getDevice();
    109         unlockDevice();
    110         // Remove special stacks.
    111         executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
    112         executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
    113         executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID);
    114         // Store rotation settings.
    115         mInitialAccelerometerRotation = getAccelerometerRotation();
    116         mUserRotation = getUserRotation();
    117     }
    118 
    119     @Override
    120     protected void tearDown() throws Exception {
    121         super.tearDown();
    122         try {
    123             unlockDevice();
    124             executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
    125             // Restore rotation settings to the state they were before test.
    126             setAccelerometerRotation(mInitialAccelerometerRotation);
    127             setUserRotation(mUserRotation);
    128             // Remove special stacks.
    129             executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
    130             executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
    131             executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID);
    132         } catch (DeviceNotAvailableException e) {
    133         }
    134     }
    135 
    136     protected String executeShellCommand(String command) throws DeviceNotAvailableException {
    137         log("adb shell " + command);
    138         return mDevice.executeShellCommand(command);
    139     }
    140 
    141     protected void executeShellCommand(String command, CollectingOutputReceiver outputReceiver)
    142             throws DeviceNotAvailableException {
    143         log("adb shell " + command);
    144         mDevice.executeShellCommand(command, outputReceiver);
    145     }
    146 
    147     protected void launchActivityInStack(String activityName, int stackId) throws Exception {
    148         executeShellCommand(getAmStartCmd(activityName) + " --stack " + stackId);
    149     }
    150 
    151     protected void launchActivityInDockStack(String activityName) throws Exception {
    152         executeShellCommand(getAmStartCmd(activityName));
    153         moveActivityToDockStack(activityName);
    154     }
    155 
    156     protected void moveActivityToDockStack(String activityName) throws Exception {
    157         moveActivityToStack(activityName, DOCKED_STACK_ID);
    158     }
    159 
    160     protected void moveActivityToStack(String activityName, int stackId) throws Exception {
    161         final int taskId = getActivityTaskId(activityName);
    162         final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true";
    163         executeShellCommand(cmd);
    164     }
    165 
    166     protected void resizeActivityTask(String activityName, int left, int top, int right, int bottom)
    167             throws Exception {
    168         final int taskId = getActivityTaskId(activityName);
    169         final String cmd = "am task resize "
    170                 + taskId + " " + left + " " + top + " " + right + " " + bottom;
    171         executeShellCommand(cmd);
    172     }
    173 
    174     protected void resizeDockedStack(
    175             int stackWidth, int stackHeight, int taskWidth, int taskHeight)
    176                     throws DeviceNotAvailableException {
    177         executeShellCommand(AM_RESIZE_DOCKED_STACK
    178                 + "0 0 " + stackWidth + " " + stackHeight
    179                 + " 0 0 " + taskWidth + " " + taskHeight);
    180     }
    181 
    182     // Utility method for debugging, not used directly here, but useful, so kept around.
    183     protected void printStacksAndTasks() throws DeviceNotAvailableException {
    184         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
    185         executeShellCommand(AM_STACK_LIST, outputReceiver);
    186         String output = outputReceiver.getOutput();
    187         for (String line : output.split("\\n")) {
    188             CLog.logAndDisplay(LogLevel.INFO, line);
    189         }
    190     }
    191 
    192     protected int getActivityTaskId(String name) throws DeviceNotAvailableException {
    193         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
    194         executeShellCommand(AM_STACK_LIST, outputReceiver);
    195         final String output = outputReceiver.getOutput();
    196         final Pattern activityPattern = Pattern.compile("(.*) " + getWindowName(name) + " (.*)");
    197         for (String line : output.split("\\n")) {
    198             Matcher matcher = activityPattern.matcher(line);
    199             if (matcher.matches()) {
    200                 for (String word : line.split("\\s+")) {
    201                     if (word.startsWith(TASK_ID_PREFIX)) {
    202                         final String withColon = word.split("=")[1];
    203                         return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
    204                     }
    205                 }
    206             }
    207         }
    208         return -1;
    209     }
    210 
    211     protected boolean supportsPip() throws DeviceNotAvailableException {
    212         return hasDeviceFeature("android.software.picture_in_picture")
    213                 || PRETEND_DEVICE_SUPPORTS_PIP;
    214     }
    215 
    216     protected boolean supportsFreeform() throws DeviceNotAvailableException {
    217         return hasDeviceFeature("android.software.freeform_window_management")
    218                 || PRETEND_DEVICE_SUPPORTS_FREEFORM;
    219     }
    220 
    221     protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
    222         if (mAvailableFeatures == null) {
    223             // TODO: Move this logic to ITestDevice.
    224             final String output = runCommandAndPrintOutput("pm list features");
    225 
    226             // Extract the id of the new user.
    227             mAvailableFeatures = new HashSet<>();
    228             for (String feature: output.split("\\s+")) {
    229                 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
    230                 String[] tokens = feature.split(":");
    231                 assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
    232                         tokens.length > 1);
    233                 assertEquals(feature, "feature", tokens[0]);
    234                 mAvailableFeatures.add(tokens[1]);
    235             }
    236         }
    237         boolean result = mAvailableFeatures.contains(requiredFeature);
    238         if (!result) {
    239             CLog.logAndDisplay(LogLevel.INFO, "Device doesn't support " + requiredFeature);
    240         }
    241         return result;
    242     }
    243 
    244     private boolean isDisplayOn() throws DeviceNotAvailableException {
    245         final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
    246         mDevice.executeShellCommand("dumpsys power", outputReceiver);
    247 
    248         for (String line : outputReceiver.getOutput().split("\\n")) {
    249             line = line.trim();
    250 
    251             final Matcher matcher = sDisplayStatePattern.matcher(line);
    252             if (matcher.matches()) {
    253                 final String state = matcher.group(1);
    254                 log("power state=" + state);
    255                 return "ON".equals(state);
    256             }
    257         }
    258         log("power state :(");
    259         return false;
    260     }
    261 
    262     protected void lockDevice() throws DeviceNotAvailableException {
    263         int retriesLeft = 5;
    264         runCommandAndPrintOutput("input keyevent 26");
    265         do {
    266             if (isDisplayOn()) {
    267                 log("***Waiting for display to turn off...");
    268                 try {
    269                     Thread.sleep(1000);
    270                 } catch (InterruptedException e) {
    271                     log(e.toString());
    272                     // Well I guess we are not waiting...
    273                 }
    274             } else {
    275                 break;
    276             }
    277         } while (retriesLeft-- > 0);
    278     }
    279 
    280     protected void unlockDevice() throws DeviceNotAvailableException {
    281         if (!isDisplayOn()) {
    282             runCommandAndPrintOutput("input keyevent 224");
    283             runCommandAndPrintOutput("input keyevent 82");
    284         }
    285     }
    286 
    287     protected void setDeviceRotation(int rotation) throws DeviceNotAvailableException {
    288         setAccelerometerRotation(0);
    289         setUserRotation(rotation);
    290     }
    291 
    292     private int getAccelerometerRotation() throws DeviceNotAvailableException {
    293         final String rotation =
    294                 runCommandAndPrintOutput("settings get system accelerometer_rotation");
    295         return Integer.parseInt(rotation.trim());
    296     }
    297 
    298     private void setAccelerometerRotation(int rotation) throws DeviceNotAvailableException {
    299         runCommandAndPrintOutput(
    300                 "settings put system accelerometer_rotation " + rotation);
    301     }
    302 
    303     private int getUserRotation() throws DeviceNotAvailableException {
    304         final String rotation =
    305                 runCommandAndPrintOutput("settings get system user_rotation").trim();
    306         if ("null".equals(rotation)) {
    307             return -1;
    308         }
    309         return Integer.parseInt(rotation);
    310     }
    311 
    312     private void setUserRotation(int rotation) throws DeviceNotAvailableException {
    313         if (rotation == -1) {
    314             runCommandAndPrintOutput(
    315                     "settings delete system user_rotation");
    316         } else {
    317             runCommandAndPrintOutput(
    318                     "settings put system user_rotation " + rotation);
    319         }
    320     }
    321 
    322     protected String runCommandAndPrintOutput(String command) throws DeviceNotAvailableException {
    323         final String output = executeShellCommand(command);
    324         log(output);
    325         return output;
    326     }
    327 
    328     protected void clearLogcat() throws DeviceNotAvailableException {
    329         mDevice.executeAdbCommand("logcat", "-c");
    330     }
    331 
    332     protected void assertActivityLifecycle(String activityName, boolean relaunched)
    333             throws DeviceNotAvailableException {
    334         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName);
    335 
    336         if (relaunched) {
    337             if (lifecycleCounts.mDestroyCount < 1) {
    338                 fail(activityName + " must have been destroyed. mDestroyCount="
    339                         + lifecycleCounts.mDestroyCount);
    340             }
    341             if (lifecycleCounts.mCreateCount < 1) {
    342                 fail(activityName + " must have been (re)created. mCreateCount="
    343                         + lifecycleCounts.mCreateCount);
    344             }
    345         } else {
    346             if (lifecycleCounts.mDestroyCount > 0) {
    347                 fail(activityName + " must *NOT* have been destroyed. mDestroyCount="
    348                         + lifecycleCounts.mDestroyCount);
    349             }
    350             if (lifecycleCounts.mCreateCount > 0) {
    351                 fail(activityName + " must *NOT* have been (re)created. mCreateCount="
    352                         + lifecycleCounts.mCreateCount);
    353             }
    354             if (lifecycleCounts.mConfigurationChangedCount < 1) {
    355                 fail(activityName + " must have received configuration changed. "
    356                         + "mConfigurationChangedCount="
    357                         + lifecycleCounts.mConfigurationChangedCount);
    358             }
    359         }
    360     }
    361 
    362     private String[] getDeviceLogsForActivity(String activityName)
    363             throws DeviceNotAvailableException {
    364         return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", activityName + ":I", "*:S")
    365                 .split("\\n");
    366     }
    367 
    368     private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate");
    369     private static final Pattern sConfigurationChangedPattern =
    370             Pattern.compile("(.+): onConfigurationChanged");
    371     private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy");
    372     private static final Pattern sNewConfigPattern = Pattern.compile(
    373             "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)" +
    374             " metricsSize=\\((\\d+),(\\d+)\\)");
    375     private static final Pattern sDisplayStatePattern =
    376             Pattern.compile("Display Power: state=(.+)");
    377 
    378     protected class ReportedSizes {
    379         int widthDp;
    380         int heightDp;
    381         int displayWidth;
    382         int displayHeight;
    383         int metricsWidth;
    384         int metricsHeight;
    385     }
    386 
    387     protected ReportedSizes getLastReportedSizesForActivity(String activityName)
    388             throws DeviceNotAvailableException {
    389         final String[] lines = getDeviceLogsForActivity(activityName);
    390         for (int i = lines.length - 1; i >= 0; i--) {
    391             final String line = lines[i].trim();
    392             final Matcher matcher = sNewConfigPattern.matcher(line);
    393             if (matcher.matches()) {
    394                 ReportedSizes details = new ReportedSizes();
    395                 details.widthDp = Integer.parseInt(matcher.group(2));
    396                 details.heightDp = Integer.parseInt(matcher.group(3));
    397                 details.displayWidth = Integer.parseInt(matcher.group(4));
    398                 details.displayHeight = Integer.parseInt(matcher.group(5));
    399                 details.metricsWidth = Integer.parseInt(matcher.group(6));
    400                 details.metricsHeight = Integer.parseInt(matcher.group(7));
    401                 return details;
    402             }
    403         }
    404         return null;
    405     }
    406 
    407     private class ActivityLifecycleCounts {
    408         int mCreateCount;
    409         int mConfigurationChangedCount;
    410         int mDestroyCount;
    411 
    412         public ActivityLifecycleCounts(String activityName) throws DeviceNotAvailableException {
    413             for (String line : getDeviceLogsForActivity(activityName)) {
    414                 line = line.trim();
    415 
    416                 Matcher matcher = sCreatePattern.matcher(line);
    417                 if (matcher.matches()) {
    418                     mCreateCount++;
    419                     continue;
    420                 }
    421 
    422                 matcher = sConfigurationChangedPattern.matcher(line);
    423                 if (matcher.matches()) {
    424                     mConfigurationChangedCount++;
    425                     continue;
    426                 }
    427 
    428                 matcher = sDestroyPattern.matcher(line);
    429                 if (matcher.matches()) {
    430                     mDestroyCount++;
    431                     continue;
    432                 }
    433             }
    434         }
    435     }
    436 }
    437