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