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