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 android.platform.test.annotations.Presubmit;
     20 
     21 import static android.server.cts.ActivityManagerState.STATE_RESUMED;
     22 import static android.server.cts.StateLogger.logE;
     23 
     24 import com.android.ddmlib.Log.LogLevel;
     25 import com.android.tradefed.log.LogUtil.CLog;
     26 
     27 import java.util.regex.Matcher;
     28 import java.util.regex.Pattern;
     29 
     30 /**
     31  * Build: mmma -j32 cts/hostsidetests/services
     32  * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test CtsServicesHostTestCases android.server.cts.ActivityManagerConfigChangeTests
     33  */
     34 public class ActivityManagerConfigChangeTests extends ActivityManagerTestBase {
     35     private static final String TEST_ACTIVITY_NAME = "TestActivity";
     36     private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
     37 
     38     private static final String FONT_SCALE_ACTIVITY_NAME = "FontScaleActivity";
     39     private static final String FONT_SCALE_NO_RELAUNCH_ACTIVITY_NAME =
     40             "FontScaleNoRelaunchActivity";
     41 
     42     private static final float EXPECTED_FONT_SIZE_SP = 10.0f;
     43 
     44     public void testRotation90Relaunch() throws Exception{
     45         // Should relaunch on every rotation and receive no onConfigurationChanged()
     46         testRotation(TEST_ACTIVITY_NAME, 1, 1, 0);
     47     }
     48 
     49     public void testRotation90NoRelaunch() throws Exception {
     50         // Should receive onConfigurationChanged() on every rotation and no relaunch
     51         testRotation(NO_RELAUNCH_ACTIVITY_NAME, 1, 0, 1);
     52     }
     53 
     54     public void testRotation180Relaunch() throws Exception {
     55         // Should receive nothing
     56         testRotation(TEST_ACTIVITY_NAME, 2, 0, 0);
     57     }
     58 
     59     public void testRotation180NoRelaunch() throws Exception {
     60         // Should receive nothing
     61         testRotation(NO_RELAUNCH_ACTIVITY_NAME, 2, 0, 0);
     62     }
     63 
     64     @Presubmit
     65     public void testChangeFontScaleRelaunch() throws Exception {
     66         // Should relaunch and receive no onConfigurationChanged()
     67         testChangeFontScale(FONT_SCALE_ACTIVITY_NAME, true /* relaunch */);
     68     }
     69 
     70     @Presubmit
     71     public void testChangeFontScaleNoRelaunch() throws Exception {
     72         // Should receive onConfigurationChanged() and no relaunch
     73         testChangeFontScale(FONT_SCALE_NO_RELAUNCH_ACTIVITY_NAME, false /* relaunch */);
     74     }
     75 
     76     private void testRotation(
     77             String activityName, int rotationStep, int numRelaunch, int numConfigChange)
     78                     throws Exception {
     79         launchActivity(activityName);
     80 
     81         final String[] waitForActivitiesVisible = new String[] {activityName};
     82         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
     83 
     84         final int initialRotation = 4 - rotationStep;
     85         setDeviceRotation(initialRotation);
     86         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
     87         final int actualStackId = mAmWmState.getAmState().getTaskByActivityName(
     88                 activityName).mStackId;
     89         final int displayId = mAmWmState.getAmState().getStackById(actualStackId).mDisplayId;
     90         final int newDeviceRotation = getDeviceRotation(displayId);
     91         if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
     92             CLog.logAndDisplay(LogLevel.WARN, "Got an invalid device rotation value. "
     93                     + "Continuing the test despite of that, but it is likely to fail.");
     94         } else if (newDeviceRotation != initialRotation) {
     95             CLog.logAndDisplay(LogLevel.INFO, "This device doesn't support user rotation "
     96                     + "mode. Not continuing the rotation checks.");
     97             return;
     98         }
     99 
    100         for (int rotation = 0; rotation < 4; rotation += rotationStep) {
    101             final String logSeparator = clearLogcat();
    102             setDeviceRotation(rotation);
    103             mAmWmState.computeState(mDevice, waitForActivitiesVisible);
    104             assertRelaunchOrConfigChanged(activityName, numRelaunch, numConfigChange, logSeparator);
    105         }
    106     }
    107 
    108     private void testChangeFontScale(
    109             String activityName, boolean relaunch) throws Exception {
    110         launchActivity(activityName);
    111         final String[] waitForActivitiesVisible = new String[] {activityName};
    112         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
    113 
    114         setFontScale(1.0f);
    115         mAmWmState.computeState(mDevice, waitForActivitiesVisible);
    116 
    117         final int densityDpi = getGlobalDensityDpi();
    118 
    119         for (float fontScale = 0.85f; fontScale <= 1.3f; fontScale += 0.15f) {
    120             final String logSeparator = clearLogcat();
    121             setFontScale(fontScale);
    122             mAmWmState.computeState(mDevice, waitForActivitiesVisible);
    123             assertRelaunchOrConfigChanged(activityName, relaunch ? 1 : 0, relaunch ? 0 : 1,
    124                     logSeparator);
    125 
    126             // Verify that the display metrics are updated, and therefore the text size is also
    127             // updated accordingly.
    128             assertExpectedFontPixelSize(activityName,
    129                     scaledPixelsToPixels(EXPECTED_FONT_SIZE_SP, fontScale, densityDpi),
    130                     logSeparator);
    131         }
    132     }
    133 
    134     /**
    135      * Test updating application info when app is running. An activity with matching package name
    136      * must be recreated and its asset sequence number must be incremented.
    137      */
    138     public void testUpdateApplicationInfo() throws Exception {
    139         final String firstLogSeparator = clearLogcat();
    140 
    141         // Launch an activity that prints applied config.
    142         launchActivity(TEST_ACTIVITY_NAME);
    143         final int assetSeq = readAssetSeqNumber(TEST_ACTIVITY_NAME, firstLogSeparator);
    144 
    145         final String logSeparator = clearLogcat();
    146         // Update package info.
    147         executeShellCommand("am update-appinfo all " + componentName);
    148         mAmWmState.waitForWithAmState(mDevice, (amState) -> {
    149             // Wait for activity to be resumed and asset seq number to be updated.
    150             try {
    151                 return readAssetSeqNumber(TEST_ACTIVITY_NAME, logSeparator) == assetSeq + 1
    152                         && amState.hasActivityState(TEST_ACTIVITY_NAME, STATE_RESUMED);
    153             } catch (Exception e) {
    154                 logE("Error waiting for valid state: " + e.getMessage());
    155                 return false;
    156             }
    157         }, "Waiting asset sequence number to be updated and for activity to be resumed.");
    158 
    159         // Check if activity is relaunched and asset seq is updated.
    160         assertRelaunchOrConfigChanged(TEST_ACTIVITY_NAME, 1 /* numRelaunch */,
    161                 0 /* numConfigChange */, logSeparator);
    162         final int newAssetSeq = readAssetSeqNumber(TEST_ACTIVITY_NAME, logSeparator);
    163         assertEquals("Asset sequence number must be incremented.", assetSeq + 1, newAssetSeq);
    164     }
    165 
    166     private static final Pattern sConfigurationPattern = Pattern.compile(
    167             "(.+): Configuration: \\{(.*) as.(\\d+)(.*)\\}");
    168 
    169     /** Read asset sequence number in last applied configuration from logs. */
    170     private int readAssetSeqNumber(String activityName, String logSeparator) throws Exception {
    171         final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
    172         for (int i = lines.length - 1; i >= 0; i--) {
    173             final String line = lines[i].trim();
    174             final Matcher matcher = sConfigurationPattern.matcher(line);
    175             if (matcher.matches()) {
    176                 final String assetSeqNumber = matcher.group(3);
    177                 try {
    178                     return Integer.valueOf(assetSeqNumber);
    179                 } catch (NumberFormatException e) {
    180                     // Ignore, asset seq number is not printed when not set.
    181                 }
    182             }
    183         }
    184         return 0;
    185     }
    186 
    187     // Calculate the scaled pixel size just like the device is supposed to.
    188     private static int scaledPixelsToPixels(float sp, float fontScale, int densityDpi) {
    189         final int DEFAULT_DENSITY = 160;
    190         float f = densityDpi * (1.0f / DEFAULT_DENSITY) * fontScale * sp;
    191         return (int) ((f >= 0) ? (f + 0.5f) : (f - 0.5f));
    192     }
    193 
    194     private static Pattern sDeviceDensityPattern =
    195             Pattern.compile(".*?-(l|m|tv|h|xh|xxh|xxxh|\\d+)dpi-.*?");
    196 
    197     private int getGlobalDensityDpi() throws Exception {
    198         final String result = getDevice().executeShellCommand("am get-config");
    199         final String[] lines = result.split("\n");
    200         if (lines.length < 1) {
    201             throw new IllegalStateException("Invalid config returned from device: " + result);
    202         }
    203 
    204         final Matcher matcher = sDeviceDensityPattern.matcher(lines[0]);
    205         if (!matcher.matches()) {
    206             throw new IllegalStateException("Invalid config returned from device: " + lines[0]);
    207         }
    208         switch (matcher.group(1)) {
    209             case "l": return 120;
    210             case "m": return 160;
    211             case "tv": return 213;
    212             case "h": return 240;
    213             case "xh": return 320;
    214             case "xxh": return 480;
    215             case "xxxh": return 640;
    216         }
    217         return Integer.parseInt(matcher.group(1));
    218     }
    219 
    220     private static final Pattern sFontSizePattern = Pattern.compile("^(.+): fontPixelSize=(.+)$");
    221 
    222     /** Read the font size in the last log line. */
    223     private void assertExpectedFontPixelSize(String activityName, int fontPixelSize,
    224             String logSeparator) throws Exception {
    225         final String[] lines = getDeviceLogsForComponent(activityName, logSeparator);
    226         for (int i = lines.length - 1; i >= 0; i--) {
    227             final String line = lines[i].trim();
    228             final Matcher matcher = sFontSizePattern.matcher(line);
    229             if (matcher.matches()) {
    230                 assertEquals("Expected font pixel size does not match", fontPixelSize,
    231                         Integer.parseInt(matcher.group(2)));
    232                 return;
    233             }
    234         }
    235         fail("No fontPixelSize reported from activity " + activityName);
    236     }
    237 }
    238