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     protected static final String LAUNCHING_ACTIVITY = "LaunchingActivity";
     74 
     75     private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack ";
     76 
     77     private static final String AM_MOVE_TASK = "am stack movetask ";
     78 
     79     private static final String INPUT_KEYEVENT_HOME = "input keyevent 3";
     80 
     81     /** A reference to the device under test. */
     82     protected ITestDevice mDevice;
     83 
     84     private HashSet<String> mAvailableFeatures;
     85 
     86     protected static String getAmStartCmd(final String activityName) {
     87         return "am start -n " + getActivityComponentName(activityName);
     88     }
     89 
     90     protected static String getAmStartCmdOverHome(final String activityName) {
     91         return "am start --activity-task-on-home -n " + getActivityComponentName(activityName);
     92     }
     93 
     94     static String getActivityComponentName(final String activityName) {
     95         return "android.server.app/." + activityName;
     96     }
     97 
     98     static String getWindowName(final String activityName) {
     99         return "android.server.app/android.server.app." + activityName;
    100     }
    101 
    102     protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
    103 
    104     private int mInitialAccelerometerRotation;
    105     private int mUserRotation;
    106     private float mFontScale;
    107 
    108     @Override
    109     protected void setUp() throws Exception {
    110         super.setUp();
    111 
    112         // Get the device, this gives a handle to run commands and install APKs.
    113         mDevice = getDevice();
    114         unlockDevice();
    115         // Remove special stacks.
    116         executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
    117         executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
    118         executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID);
    119         // Store rotation settings.
    120         mInitialAccelerometerRotation = getAccelerometerRotation();
    121         mUserRotation = getUserRotation();
    122         mFontScale = getFontScale();
    123     }
    124 
    125     @Override
    126     protected void tearDown() throws Exception {
    127         super.tearDown();
    128         try {
    129             unlockDevice();
    130             executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
    131             // Restore rotation settings to the state they were before test.
    132             setAccelerometerRotation(mInitialAccelerometerRotation);
    133             setUserRotation(mUserRotation);
    134             setFontScale(mFontScale);
    135             // Remove special stacks.
    136             executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
    137             executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
    138             executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID);
    139         } catch (DeviceNotAvailableException e) {
    140         }
    141     }
    142 
    143     protected String executeShellCommand(String command) throws DeviceNotAvailableException {
    144         log("adb shell " + command);
    145         return mDevice.executeShellCommand(command);
    146     }
    147 
    148     protected void executeShellCommand(String command, CollectingOutputReceiver outputReceiver)
    149             throws DeviceNotAvailableException {
    150         log("adb shell " + command);
    151         mDevice.executeShellCommand(command, outputReceiver);
    152     }
    153 
    154     /**
    155      * Launch specific target activity. It uses existing instance of {@link #LAUNCHING_ACTIVITY}, so
    156      * that one should be started first.
    157      * @param toSide Launch to side in split-screen.
    158      * @param randomData Make intent URI random by generating random data.
    159      * @param multipleTask Allow multiple task launch.
    160      * @param targetActivityName Target activity to be launched. Only class name should be provided,
    161      *                           package name of {@link #LAUNCHING_ACTIVITY} will be added
    162      *                           automatically.
    163      * @throws Exception
    164      */
    165     protected void launchActivity(boolean toSide, boolean randomData, boolean multipleTask,
    166             String targetActivityName) throws Exception {
    167         StringBuilder commandBuilder = new StringBuilder(getAmStartCmd(LAUNCHING_ACTIVITY));
    168         commandBuilder.append(" -f 0x20000000");
    169         if (toSide) {
    170             commandBuilder.append(" --ez launch_to_the_side true");
    171         }
    172         if (randomData) {
    173             commandBuilder.append(" --ez random_data true");
    174         }
    175         if (multipleTask) {
    176             commandBuilder.append(" --ez multiple_task true");
    177         }
    178         if (targetActivityName != null) {
    179             commandBuilder.append(" --es target_activity ").append(targetActivityName);
    180         }
    181         executeShellCommand(commandBuilder.toString());
    182     }
    183 
    184     protected void launchActivityInStack(String activityName, int stackId) throws Exception {
    185         executeShellCommand(getAmStartCmd(activityName) + " --stack " + stackId);
    186     }
    187 
    188     protected void launchActivityInDockStack(String activityName) throws Exception {
    189         executeShellCommand(getAmStartCmd(activityName));
    190         moveActivityToDockStack(activityName);
    191     }
    192 
    193     protected void moveActivityToDockStack(String activityName) throws Exception {
    194         moveActivityToStack(activityName, DOCKED_STACK_ID);
    195     }
    196 
    197     protected void moveActivityToStack(String activityName, int stackId) throws Exception {
    198         final int taskId = getActivityTaskId(activityName);
    199         final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true";
    200         executeShellCommand(cmd);
    201     }
    202 
    203     protected void resizeActivityTask(String activityName, int left, int top, int right, int bottom)
    204             throws Exception {
    205         final int taskId = getActivityTaskId(activityName);
    206         final String cmd = "am task resize "
    207                 + taskId + " " + left + " " + top + " " + right + " " + bottom;
    208         executeShellCommand(cmd);
    209     }
    210 
    211     protected void resizeDockedStack(
    212             int stackWidth, int stackHeight, int taskWidth, int taskHeight)
    213                     throws DeviceNotAvailableException {
    214         executeShellCommand(AM_RESIZE_DOCKED_STACK
    215                 + "0 0 " + stackWidth + " " + stackHeight
    216                 + " 0 0 " + taskWidth + " " + taskHeight);
    217     }
    218 
    219     protected void pressHomeButton() throws DeviceNotAvailableException {
    220         executeShellCommand(INPUT_KEYEVENT_HOME);
    221     }
    222 
    223     // Utility method for debugging, not used directly here, but useful, so kept around.
    224     protected void printStacksAndTasks() throws DeviceNotAvailableException {
    225         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
    226         executeShellCommand(AM_STACK_LIST, outputReceiver);
    227         String output = outputReceiver.getOutput();
    228         for (String line : output.split("\\n")) {
    229             CLog.logAndDisplay(LogLevel.INFO, line);
    230         }
    231     }
    232 
    233     protected int getActivityTaskId(String name) throws DeviceNotAvailableException {
    234         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
    235         executeShellCommand(AM_STACK_LIST, outputReceiver);
    236         final String output = outputReceiver.getOutput();
    237         final Pattern activityPattern = Pattern.compile("(.*) " + getWindowName(name) + " (.*)");
    238         for (String line : output.split("\\n")) {
    239             Matcher matcher = activityPattern.matcher(line);
    240             if (matcher.matches()) {
    241                 for (String word : line.split("\\s+")) {
    242                     if (word.startsWith(TASK_ID_PREFIX)) {
    243                         final String withColon = word.split("=")[1];
    244                         return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
    245                     }
    246                 }
    247             }
    248         }
    249         return -1;
    250     }
    251 
    252     protected boolean supportsPip() throws DeviceNotAvailableException {
    253         return hasDeviceFeature("android.software.picture_in_picture")
    254                 || PRETEND_DEVICE_SUPPORTS_PIP;
    255     }
    256 
    257     protected boolean supportsFreeform() throws DeviceNotAvailableException {
    258         return hasDeviceFeature("android.software.freeform_window_management")
    259                 || PRETEND_DEVICE_SUPPORTS_FREEFORM;
    260     }
    261 
    262     protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
    263         if (mAvailableFeatures == null) {
    264             // TODO: Move this logic to ITestDevice.
    265             final String output = runCommandAndPrintOutput("pm list features");
    266 
    267             // Extract the id of the new user.
    268             mAvailableFeatures = new HashSet<>();
    269             for (String feature: output.split("\\s+")) {
    270                 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
    271                 String[] tokens = feature.split(":");
    272                 assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
    273                         tokens.length > 1);
    274                 assertEquals(feature, "feature", tokens[0]);
    275                 mAvailableFeatures.add(tokens[1]);
    276             }
    277         }
    278         boolean result = mAvailableFeatures.contains(requiredFeature);
    279         if (!result) {
    280             CLog.logAndDisplay(LogLevel.INFO, "Device doesn't support " + requiredFeature);
    281         }
    282         return result;
    283     }
    284 
    285     private boolean isDisplayOn() throws DeviceNotAvailableException {
    286         final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
    287         mDevice.executeShellCommand("dumpsys power", outputReceiver);
    288 
    289         for (String line : outputReceiver.getOutput().split("\\n")) {
    290             line = line.trim();
    291 
    292             final Matcher matcher = sDisplayStatePattern.matcher(line);
    293             if (matcher.matches()) {
    294                 final String state = matcher.group(1);
    295                 log("power state=" + state);
    296                 return "ON".equals(state);
    297             }
    298         }
    299         log("power state :(");
    300         return false;
    301     }
    302 
    303     protected void lockDevice() throws DeviceNotAvailableException {
    304         int retriesLeft = 5;
    305         runCommandAndPrintOutput("input keyevent 26");
    306         do {
    307             if (isDisplayOn()) {
    308                 log("***Waiting for display to turn off...");
    309                 try {
    310                     Thread.sleep(1000);
    311                 } catch (InterruptedException e) {
    312                     log(e.toString());
    313                     // Well I guess we are not waiting...
    314                 }
    315             } else {
    316                 break;
    317             }
    318         } while (retriesLeft-- > 0);
    319     }
    320 
    321     protected void unlockDevice() throws DeviceNotAvailableException {
    322         if (!isDisplayOn()) {
    323             runCommandAndPrintOutput("input keyevent 224");
    324             runCommandAndPrintOutput("input keyevent 82");
    325         }
    326     }
    327 
    328     protected void setDeviceRotation(int rotation) throws DeviceNotAvailableException {
    329         setAccelerometerRotation(0);
    330         setUserRotation(rotation);
    331     }
    332 
    333     private int getAccelerometerRotation() throws DeviceNotAvailableException {
    334         final String rotation =
    335                 runCommandAndPrintOutput("settings get system accelerometer_rotation");
    336         return Integer.parseInt(rotation.trim());
    337     }
    338 
    339     private void setAccelerometerRotation(int rotation) throws DeviceNotAvailableException {
    340         runCommandAndPrintOutput(
    341                 "settings put system accelerometer_rotation " + rotation);
    342     }
    343 
    344     private int getUserRotation() throws DeviceNotAvailableException {
    345         final String rotation =
    346                 runCommandAndPrintOutput("settings get system user_rotation").trim();
    347         if ("null".equals(rotation)) {
    348             return -1;
    349         }
    350         return Integer.parseInt(rotation);
    351     }
    352 
    353     private void setUserRotation(int rotation) throws DeviceNotAvailableException {
    354         if (rotation == -1) {
    355             runCommandAndPrintOutput(
    356                     "settings delete system user_rotation");
    357         } else {
    358             runCommandAndPrintOutput(
    359                     "settings put system user_rotation " + rotation);
    360         }
    361     }
    362 
    363     protected void setFontScale(float fontScale) throws DeviceNotAvailableException {
    364         if (fontScale == 0.0f) {
    365             runCommandAndPrintOutput(
    366                     "settings delete system font_scale");
    367         } else {
    368             runCommandAndPrintOutput(
    369                     "settings put system font_scale " + fontScale);
    370         }
    371     }
    372 
    373     protected float getFontScale() throws DeviceNotAvailableException {
    374         try {
    375             final String fontScale =
    376                     runCommandAndPrintOutput("settings get system font_scale").trim();
    377             return Float.parseFloat(fontScale);
    378         } catch (NumberFormatException e) {
    379             // If we don't have a valid font scale key, return 0.0f now so
    380             // that we delete the key in tearDown().
    381             return 0.0f;
    382         }
    383     }
    384 
    385     protected String runCommandAndPrintOutput(String command) throws DeviceNotAvailableException {
    386         final String output = executeShellCommand(command);
    387         log(output);
    388         return output;
    389     }
    390 
    391     protected void clearLogcat() throws DeviceNotAvailableException {
    392         mDevice.executeAdbCommand("logcat", "-c");
    393     }
    394 
    395     protected void assertActivityLifecycle(String activityName, boolean relaunched)
    396             throws DeviceNotAvailableException {
    397         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName);
    398 
    399         if (relaunched) {
    400             if (lifecycleCounts.mDestroyCount < 1) {
    401                 fail(activityName + " must have been destroyed. mDestroyCount="
    402                         + lifecycleCounts.mDestroyCount);
    403             }
    404             if (lifecycleCounts.mCreateCount < 1) {
    405                 fail(activityName + " must have been (re)created. mCreateCount="
    406                         + lifecycleCounts.mCreateCount);
    407             }
    408         } else {
    409             if (lifecycleCounts.mDestroyCount > 0) {
    410                 fail(activityName + " must *NOT* have been destroyed. mDestroyCount="
    411                         + lifecycleCounts.mDestroyCount);
    412             }
    413             if (lifecycleCounts.mCreateCount > 0) {
    414                 fail(activityName + " must *NOT* have been (re)created. mCreateCount="
    415                         + lifecycleCounts.mCreateCount);
    416             }
    417             if (lifecycleCounts.mConfigurationChangedCount < 1) {
    418                 fail(activityName + " must have received configuration changed. "
    419                         + "mConfigurationChangedCount="
    420                         + lifecycleCounts.mConfigurationChangedCount);
    421             }
    422         }
    423     }
    424 
    425     protected void assertRelaunchOrConfigChanged(
    426             String activityName, int numRelaunch, int numConfigChange)
    427             throws DeviceNotAvailableException {
    428         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName);
    429 
    430         if (lifecycleCounts.mDestroyCount != numRelaunch) {
    431             fail(activityName + " has been destroyed " + lifecycleCounts.mDestroyCount
    432                     + " time(s), expecting " + numRelaunch);
    433         } else if (lifecycleCounts.mCreateCount != numRelaunch) {
    434             fail(activityName + " has been (re)created " + lifecycleCounts.mCreateCount
    435                     + " time(s), expecting " + numRelaunch);
    436         } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) {
    437             fail(activityName + " has received " + lifecycleCounts.mConfigurationChangedCount
    438                     + " onConfigurationChanged() calls, expecting " + numConfigChange);
    439         }
    440     }
    441 
    442     protected String[] getDeviceLogsForComponent(String componentName)
    443             throws DeviceNotAvailableException {
    444         return mDevice.executeAdbCommand(
    445                 "logcat", "-v", "brief", "-d", componentName + ":I", "*:S").split("\\n");
    446     }
    447 
    448     protected String[] getDeviceLogsForComponents(final String[] componentNames)
    449             throws DeviceNotAvailableException {
    450         String filters = "";
    451         for (int i = 0; i < componentNames.length; i++) {
    452             filters += componentNames[i] + ":I ";
    453         }
    454         return mDevice.executeAdbCommand(
    455                 "logcat", "-v", "brief", "-d", filters, "*:S").split("\\n");
    456     }
    457 
    458     private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate");
    459     private static final Pattern sConfigurationChangedPattern =
    460             Pattern.compile("(.+): onConfigurationChanged");
    461     private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy");
    462     private static final Pattern sNewConfigPattern = Pattern.compile(
    463             "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)" +
    464             " metricsSize=\\((\\d+),(\\d+)\\)");
    465     private static final Pattern sDisplayStatePattern =
    466             Pattern.compile("Display Power: state=(.+)");
    467 
    468     protected class ReportedSizes {
    469         int widthDp;
    470         int heightDp;
    471         int displayWidth;
    472         int displayHeight;
    473         int metricsWidth;
    474         int metricsHeight;
    475     }
    476 
    477     protected ReportedSizes getLastReportedSizesForActivity(String activityName)
    478             throws DeviceNotAvailableException {
    479         final String[] lines = getDeviceLogsForComponent(activityName);
    480         for (int i = lines.length - 1; i >= 0; i--) {
    481             final String line = lines[i].trim();
    482             final Matcher matcher = sNewConfigPattern.matcher(line);
    483             if (matcher.matches()) {
    484                 ReportedSizes details = new ReportedSizes();
    485                 details.widthDp = Integer.parseInt(matcher.group(2));
    486                 details.heightDp = Integer.parseInt(matcher.group(3));
    487                 details.displayWidth = Integer.parseInt(matcher.group(4));
    488                 details.displayHeight = Integer.parseInt(matcher.group(5));
    489                 details.metricsWidth = Integer.parseInt(matcher.group(6));
    490                 details.metricsHeight = Integer.parseInt(matcher.group(7));
    491                 return details;
    492             }
    493         }
    494         return null;
    495     }
    496 
    497     private class ActivityLifecycleCounts {
    498         int mCreateCount;
    499         int mConfigurationChangedCount;
    500         int mDestroyCount;
    501 
    502         public ActivityLifecycleCounts(String activityName) throws DeviceNotAvailableException {
    503             for (String line : getDeviceLogsForComponent(activityName)) {
    504                 line = line.trim();
    505 
    506                 Matcher matcher = sCreatePattern.matcher(line);
    507                 if (matcher.matches()) {
    508                     mCreateCount++;
    509                     continue;
    510                 }
    511 
    512                 matcher = sConfigurationChangedPattern.matcher(line);
    513                 if (matcher.matches()) {
    514                     mConfigurationChangedCount++;
    515                     continue;
    516                 }
    517 
    518                 matcher = sDestroyPattern.matcher(line);
    519                 if (matcher.matches()) {
    520                     mDestroyCount++;
    521                     continue;
    522                 }
    523             }
    524         }
    525     }
    526 }
    527