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
testRotation90Relaunch()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
testRotation90NoRelaunch()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
testRotation180Relaunch()63     public void testRotation180Relaunch() throws Exception {
64         // Should receive nothing
65         testRotation(TEST_ACTIVITY, 2, 0, 0);
66     }
67 
68     @Test
testRotation180NoRelaunch()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
testChangeFontScaleRelaunch()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
testChangeFontScaleNoRelaunch()85     public void testChangeFontScaleNoRelaunch() throws Exception {
86         // Should receive onConfigurationChanged() and no relaunch
87         testChangeFontScale(FONT_SCALE_NO_RELAUNCH_ACTIVITY, false /* relaunch */);
88     }
89 
testRotation(ComponentName activityName, int rotationStep, int numRelaunch, int numConfigChange)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> {
FontScaleSession()125         FontScaleSession() {
126             super(Settings.System.getUriFor(Settings.System.FONT_SCALE),
127                     Settings.System::getFloat,
128                     Settings.System::putFloat);
129         }
130     }
131 
testChangeFontScale( ComponentName activityName, boolean relaunch)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
testUpdateApplicationInfo()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. */
readAssetSeqNumber(ComponentName activityName, LogSeparator logSeparator)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.
scaledPixelsToPixels(float sp, float fontScale, int densityDpi)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 
getActivityDensityDpi(ComponentName activityName, LogSeparator logSeparator)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. */
assertExpectedFontPixelSize(ComponentName activityName, int fontPixelSize, LogSeparator logSeparator)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