1 /*
2  * Copyright (C) 2023 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.wm.other;
18 
19 import static android.server.wm.app.Components.UI_SCALING_TEST_ACTIVITY;
20 import static android.server.wm.app.Components.UiScalingTestActivity.COMMAND_ADD_SUBVIEW;
21 import static android.server.wm.app.Components.UiScalingTestActivity.COMMAND_CLEAR_DEFAULT_VIEW;
22 import static android.server.wm.app.Components.UiScalingTestActivity.COMMAND_GET_RESOURCES_CONFIG;
23 import static android.server.wm.app.Components.UiScalingTestActivity.COMMAND_GET_SUBVIEW_SIZE;
24 import static android.server.wm.app.Components.UiScalingTestActivity.COMMAND_UPDATE_RESOURCES_CONFIG;
25 import static android.server.wm.app.Components.UiScalingTestActivity.KEY_COMMAND_SUCCESS;
26 import static android.server.wm.app.Components.UiScalingTestActivity.KEY_RESOURCES_CONFIG;
27 import static android.server.wm.app.Components.UiScalingTestActivity.KEY_SUBVIEW_ID;
28 import static android.server.wm.app.Components.UiScalingTestActivity.KEY_TEXT_SIZE;
29 import static android.server.wm.app.Components.UiScalingTestActivity.KEY_VIEW_SIZE;
30 import static android.server.wm.app.Components.UiScalingTestActivity.SUBVIEW_ID1;
31 import static android.server.wm.app.Components.UiScalingTestActivity.SUBVIEW_ID2;
32 
33 import static org.hamcrest.Matchers.closeTo;
34 import static org.hamcrest.Matchers.equalTo;
35 import static org.hamcrest.Matchers.is;
36 import static org.hamcrest.Matchers.not;
37 
38 import android.content.ComponentName;
39 import android.content.res.Configuration;
40 import android.graphics.Rect;
41 import android.os.Bundle;
42 import android.os.LocaleList;
43 import android.server.wm.ActivityManagerTestBase;
44 import android.server.wm.CommandSession;
45 import android.server.wm.WindowManagerState;
46 
47 import org.junit.Rule;
48 import org.junit.Test;
49 import org.junit.rules.ErrorCollector;
50 import org.junit.runner.RunWith;
51 import org.junit.runners.Parameterized;
52 
53 import java.util.Arrays;
54 
55 /**
56  * The test is focused on compatibility scaling, and tests the feature form two sides. 1. It checks
57  * that the applications "sees" the metrics in PXs, but the DP metrics remain the same. 2. It checks
58  * the WindowManagerServer state, and makes sure that the scaling is correctly reflected in the
59  * WindowState.
60  *
61  * <p>This is achieved by launching a {@link android.server.wm.app.UiScalingTestActivity} and having
62  * it reporting the metrics it receives. The Activity also draws 3 UI elements: a text, a red square
63  * with a 100dp side and a blue square with a 100px side. The text and the red square should have
64  * the same when rendered on the screen (by HWC) both when the compat downscaling is enabled and
65  * disabled. TODO(b/180098454): Add tests to make sure that the UI elements, which have their sizes
66  * declared in DPs (the text and the red square) have the same sizes on the screen (after
67  * composition).
68  *
69  * <p>Build/Install/Run: atest CtsWindowManagerDeviceOther:CompatScaleTests
70  */
71 @RunWith(Parameterized.class)
72 public class CompatScaleTests extends ActivityManagerTestBase {
73     /**
74      * If application size is 1280, then Upscaling by 0.3 will make the surface 1280/0.3 = 4267.
75      * Some devices do not support this high resolution, so limiting Upscaling test case for scaling
76      * >= 0.5.
77      */
78     public static float MAX_UPSCALING_TESTED = 0.5f;
79 
80     @Parameterized.Parameters(name = "{0}")
data()81     public static Iterable<Object[]> data() {
82         return Arrays.asList(
83                 new Object[][] {
84                     {"DOWNSCALE_30", 0.3f},
85                     {"DOWNSCALE_35", 0.35f},
86                     {"DOWNSCALE_40", 0.4f},
87                     {"DOWNSCALE_45", 0.45f},
88                     {"DOWNSCALE_50", 0.5f},
89                     {"DOWNSCALE_55", 0.55f},
90                     {"DOWNSCALE_60", 0.6f},
91                     {"DOWNSCALE_65", 0.65f},
92                     {"DOWNSCALE_70", 0.7f},
93                     {"DOWNSCALE_75", 0.75f},
94                     {"DOWNSCALE_80", 0.8f},
95                     {"DOWNSCALE_85", 0.85f},
96                     {"DOWNSCALE_90", 0.9f},
97                 });
98     }
99 
100     @Rule public ErrorCollector collector = new ErrorCollector();
101 
102     private static final ComponentName ACTIVITY_UNDER_TEST = UI_SCALING_TEST_ACTIVITY;
103     private static final String PACKAGE_UNDER_TEST = ACTIVITY_UNDER_TEST.getPackageName();
104     private static final float EPSILON_GLOBAL_SCALE = 0.01f;
105     private final String mCompatChangeName;
106     private final float mCompatScale;
107     private final float mInvCompatScale;
108     private CommandSession.SizeInfo mAppSizesNormal;
109     private CommandSession.SizeInfo mAppSizesDownscaled;
110     private CommandSession.SizeInfo mAppSizesUpscaled;
111     private WindowManagerState.WindowState mWindowStateNormal;
112     private WindowManagerState.WindowState mWindowStateDownscaled;
113     private WindowManagerState.WindowState mWindowStateUpscaled;
114 
CompatScaleTests(String compatChangeName, float compatScale)115     public CompatScaleTests(String compatChangeName, float compatScale) {
116         mCompatChangeName = compatChangeName;
117         mCompatScale = compatScale;
118         mInvCompatScale = 1 / mCompatScale;
119     }
120 
121     @Test
testUpdateResourcesConfiguration()122     public void testUpdateResourcesConfiguration() {
123         // Launch activity with down/up scaling *disabled*
124         try (var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) {
125             runTestUpdateResourcesConfiguration(session.getActivitySession());
126         }
127 
128         try (var scale = new CompatChangeCloseable(mCompatChangeName, PACKAGE_UNDER_TEST)) {
129             // Now launch the same activity with downscaling *enabled*
130             try (var down = new CompatChangeCloseable("DOWNSCALED", PACKAGE_UNDER_TEST);
131                     var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) {
132                 runTestUpdateResourcesConfiguration(session.getActivitySession());
133             }
134 
135             if (mCompatScale >= MAX_UPSCALING_TESTED) {
136                 // Now launch the same activity with upscaling *enabled*
137                 try (var up = new CompatChangeCloseable("DOWNSCALED_INVERSE", PACKAGE_UNDER_TEST);
138                         var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) {
139                     runTestUpdateResourcesConfiguration(session.getActivitySession());
140                 }
141             }
142         }
143     }
144 
runTestUpdateResourcesConfiguration(CommandSession.ActivitySession activity)145     private void runTestUpdateResourcesConfiguration(CommandSession.ActivitySession activity) {
146         activity.sendCommandAndWaitReply(COMMAND_CLEAR_DEFAULT_VIEW);
147         addSubview(activity, SUBVIEW_ID1);
148         Bundle subviewSize1 = getSubViewSize(activity, SUBVIEW_ID1);
149         collector.checkThat(
150                 subviewSize1.getParcelable(KEY_TEXT_SIZE, Rect.class), not(equalTo(new Rect())));
151         collector.checkThat(
152                 subviewSize1.getParcelable(KEY_VIEW_SIZE, Rect.class), not(equalTo(new Rect())));
153         collector.checkThat(subviewSize1.getBoolean(KEY_COMMAND_SUCCESS), is(true));
154         Configuration config =
155                 activity.sendCommandAndWaitReply(COMMAND_GET_RESOURCES_CONFIG)
156                         .getParcelable(KEY_RESOURCES_CONFIG, Configuration.class);
157         config.setLocales(LocaleList.forLanguageTags("en-US,en-XC"));
158         Bundle data = new Bundle();
159         data.putParcelable(KEY_RESOURCES_CONFIG, config);
160         collector.checkThat(
161                 "Failed to update resources configuration",
162                 activity.sendCommandAndWaitReply(COMMAND_UPDATE_RESOURCES_CONFIG, data)
163                         .getBoolean(KEY_COMMAND_SUCCESS),
164                 is(true));
165 
166         addSubview(activity, SUBVIEW_ID2);
167         Bundle subviewSize2 = getSubViewSize(activity, SUBVIEW_ID2);
168         collector.checkThat(subviewSize2.getBoolean(KEY_COMMAND_SUCCESS), is(true));
169         collector.checkThat(
170                 subviewSize1.getParcelable(KEY_TEXT_SIZE, Rect.class),
171                 equalTo(subviewSize2.getParcelable(KEY_TEXT_SIZE, Rect.class)));
172         collector.checkThat(
173                 subviewSize1.getParcelable(KEY_VIEW_SIZE, Rect.class),
174                 equalTo(subviewSize2.getParcelable(KEY_VIEW_SIZE, Rect.class)));
175     }
176 
177     /**
178      * Tests that the parameters that the application receives from the {@link
179      * android.content.res.Configuration} are correctly scaled.
180      */
181     @Test
test_scalesCorrectly()182     public void test_scalesCorrectly() {
183         // Launch activity with down/up scaling *disabled* and get the sizes it reports and its
184         // Window state.
185         try (var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) {
186             mAppSizesNormal = session.getActivitySession().getConfigInfo().sizeInfo;
187             mWindowStateNormal = getPackageWindowState();
188         }
189 
190         try (var scale = new CompatChangeCloseable(mCompatChangeName, PACKAGE_UNDER_TEST)) {
191             // Now launch the same activity with downscaling *enabled* and get the sizes it reports
192             // and its Window state.
193             try (var down = new CompatChangeCloseable("DOWNSCALED", PACKAGE_UNDER_TEST);
194                     var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) {
195                 mAppSizesDownscaled = session.getActivitySession().getConfigInfo().sizeInfo;
196                 mWindowStateDownscaled = getPackageWindowState();
197             }
198             test_scalesCorrectly_inCompatDownscalingMode();
199             test_windowState_inCompatDownscalingMode();
200 
201             if (mCompatScale >= MAX_UPSCALING_TESTED) {
202                 // Now launch the same activity with upscaling *enabled* and get the sizes it
203                 // reports and its Window state.
204                 try (var up = new CompatChangeCloseable("DOWNSCALED_INVERSE", PACKAGE_UNDER_TEST);
205                         var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) {
206                     mAppSizesUpscaled = session.getActivitySession().getConfigInfo().sizeInfo;
207                     mWindowStateUpscaled = getPackageWindowState();
208                 }
209                 test_scalesCorrectly_inCompatUpscalingMode();
210                 test_windowState_inCompatUpscalingMode();
211             }
212         }
213     }
214 
test_scalesCorrectly_inCompatDownscalingMode()215     private void test_scalesCorrectly_inCompatDownscalingMode() {
216         checkScaled(
217                 "Density DPI should scale by " + mCompatScale,
218                 mAppSizesNormal.densityDpi,
219                 mCompatScale,
220                 mAppSizesDownscaled.densityDpi);
221         collector.checkThat(
222                 "Width shouldn't change",
223                 mAppSizesNormal.widthDp,
224                 equalTo(mAppSizesDownscaled.widthDp));
225         collector.checkThat(
226                 "Height shouldn't change",
227                 mAppSizesNormal.heightDp,
228                 equalTo(mAppSizesDownscaled.heightDp));
229         collector.checkThat(
230                 "Smallest Width shouldn't change",
231                 mAppSizesNormal.smallestWidthDp,
232                 equalTo(mAppSizesDownscaled.smallestWidthDp));
233         checkScaled(
234                 "Width should scale by " + mCompatScale,
235                 mAppSizesNormal.windowWidth,
236                 mCompatScale,
237                 mAppSizesDownscaled.windowWidth);
238         checkScaled(
239                 "Height should scale by " + mCompatScale,
240                 mAppSizesNormal.windowHeight,
241                 mCompatScale,
242                 mAppSizesDownscaled.windowHeight);
243         checkScaled(
244                 "App width should scale by " + mCompatScale,
245                 mAppSizesNormal.windowAppWidth,
246                 mCompatScale,
247                 mAppSizesDownscaled.windowAppWidth);
248         checkScaled(
249                 "App height should scale by " + mCompatScale,
250                 mAppSizesNormal.windowAppHeight,
251                 mCompatScale,
252                 mAppSizesDownscaled.windowAppHeight);
253         checkScaled(
254                 "Width should scale by " + mCompatScale,
255                 mAppSizesNormal.metricsWidth,
256                 mCompatScale,
257                 mAppSizesDownscaled.metricsWidth);
258         checkScaled(
259                 "Height should scale by " + mCompatScale,
260                 mAppSizesNormal.metricsHeight,
261                 mCompatScale,
262                 mAppSizesDownscaled.metricsHeight);
263         checkScaled(
264                 "Width should scale by " + mCompatScale,
265                 mAppSizesNormal.displayWidth,
266                 mCompatScale,
267                 mAppSizesDownscaled.displayWidth);
268         checkScaled(
269                 "Height should scale by " + mCompatScale,
270                 mAppSizesNormal.displayHeight,
271                 mCompatScale,
272                 mAppSizesDownscaled.displayHeight);
273     }
274 
test_scalesCorrectly_inCompatUpscalingMode()275     private void test_scalesCorrectly_inCompatUpscalingMode() {
276         checkScaled(
277                 "Density DPI should scale by " + mInvCompatScale,
278                 mAppSizesNormal.densityDpi,
279                 mInvCompatScale,
280                 mAppSizesUpscaled.densityDpi);
281         collector.checkThat(
282                 "Width shouldn't change",
283                 mAppSizesNormal.widthDp,
284                 equalTo(mAppSizesUpscaled.widthDp));
285         collector.checkThat(
286                 "Height shouldn't change",
287                 mAppSizesNormal.heightDp,
288                 equalTo(mAppSizesUpscaled.heightDp));
289         collector.checkThat(
290                 "Smallest Width shouldn't change",
291                 mAppSizesNormal.smallestWidthDp,
292                 equalTo(mAppSizesUpscaled.smallestWidthDp));
293         checkScaled(
294                 "Width should scale by " + mInvCompatScale,
295                 mAppSizesNormal.windowWidth,
296                 mInvCompatScale,
297                 mAppSizesUpscaled.windowWidth);
298         checkScaled(
299                 "Height should scale by " + mInvCompatScale,
300                 mAppSizesNormal.windowHeight,
301                 mInvCompatScale,
302                 mAppSizesUpscaled.windowHeight);
303         checkScaled(
304                 "App width should scale by " + mInvCompatScale,
305                 mAppSizesNormal.windowAppWidth,
306                 mInvCompatScale,
307                 mAppSizesUpscaled.windowAppWidth);
308         checkScaled(
309                 "App height should scale by " + mInvCompatScale,
310                 mAppSizesNormal.windowAppHeight,
311                 mInvCompatScale,
312                 mAppSizesUpscaled.windowAppHeight);
313         checkScaled(
314                 "Width should scale by " + mInvCompatScale,
315                 mAppSizesNormal.metricsWidth,
316                 mInvCompatScale,
317                 mAppSizesUpscaled.metricsWidth);
318         checkScaled(
319                 "Height should scale by " + mInvCompatScale,
320                 mAppSizesNormal.metricsHeight,
321                 mInvCompatScale,
322                 mAppSizesUpscaled.metricsHeight);
323         checkScaled(
324                 "Width should scale by " + mInvCompatScale,
325                 mAppSizesNormal.displayWidth,
326                 mInvCompatScale,
327                 mAppSizesUpscaled.displayWidth);
328         checkScaled(
329                 "Height should scale by " + mInvCompatScale,
330                 mAppSizesNormal.displayHeight,
331                 mInvCompatScale,
332                 mAppSizesUpscaled.displayHeight);
333     }
334 
test_windowState_inCompatDownscalingMode()335     private void test_windowState_inCompatDownscalingMode() {
336         // Check the "normal" window's state for disabled compat mode and appropriate global scale.
337         collector.checkThat(
338                 "The Window should not be in the size compat mode",
339                 mWindowStateNormal.hasCompatScale(),
340                 is(false));
341         collector.checkThat(
342                 "The window should not be scaled",
343                 1d,
344                 closeTo(mWindowStateNormal.getGlobalScale(), EPSILON_GLOBAL_SCALE));
345 
346         // Check the "downscaled" window's state for enabled compat mode and appropriate global
347         // scale.
348         collector.checkThat(
349                 "The Window should be in the size compat mode",
350                 mWindowStateDownscaled.hasCompatScale(),
351                 is(true));
352         collector.checkThat(
353                 "The window should have global scale of " + mInvCompatScale,
354                 (double) mInvCompatScale,
355                 closeTo(mWindowStateDownscaled.getGlobalScale(), EPSILON_GLOBAL_SCALE));
356 
357         // Make sure the frame sizes changed correctly.
358         collector.checkThat(
359                 "Window frame on should not change",
360                 mWindowStateNormal.getFrame(),
361                 equalTo(mWindowStateDownscaled.getFrame()));
362         checkScaled(
363                 "Requested width should scale by " + mCompatScale,
364                 mWindowStateNormal.getRequestedWidth(),
365                 mCompatScale,
366                 mWindowStateDownscaled.getRequestedWidth());
367         checkScaled(
368                 "Requested height "
369                         + mWindowStateNormal.getRequestedHeight()
370                         + " should scale by "
371                         + mCompatScale,
372                 mWindowStateNormal.getRequestedHeight(),
373                 mCompatScale,
374                 mWindowStateDownscaled.getRequestedHeight());
375     }
376 
test_windowState_inCompatUpscalingMode()377     private void test_windowState_inCompatUpscalingMode() {
378         // Check the "normal" window's state for disabled compat mode and appropriate global scale.
379         collector.checkThat(
380                 "The Window should not be in the size compat mode",
381                 mWindowStateNormal.hasCompatScale(),
382                 is(false));
383         collector.checkThat(
384                 "The window should not be scaled",
385                 1d,
386                 closeTo(mWindowStateNormal.getGlobalScale(), EPSILON_GLOBAL_SCALE));
387 
388         // Check the "upscaled" window's state for enabled compat mode and appropriate global
389         // scale.
390         collector.checkThat(
391                 "The Window should be in the size compat mode",
392                 mWindowStateUpscaled.hasCompatScale(),
393                 is(true));
394         collector.checkThat(
395                 "The window should have global scale of " + mCompatScale,
396                 (double) mCompatScale,
397                 closeTo(mWindowStateUpscaled.getGlobalScale(), EPSILON_GLOBAL_SCALE));
398 
399         // Make sure the frame sizes changed correctly.
400         collector.checkThat(
401                 "Window frame on should not change",
402                 mWindowStateNormal.getFrame(),
403                 equalTo(mWindowStateUpscaled.getFrame()));
404         checkScaled(
405                 "Requested width should scale by " + mInvCompatScale,
406                 mWindowStateNormal.getRequestedWidth(),
407                 mInvCompatScale,
408                 mWindowStateUpscaled.getRequestedWidth());
409         checkScaled(
410                 "Requested height should scale by " + mInvCompatScale,
411                 mWindowStateNormal.getRequestedHeight(),
412                 mInvCompatScale,
413                 mWindowStateUpscaled.getRequestedHeight());
414     }
415 
getPackageWindowState()416     private WindowManagerState.WindowState getPackageWindowState() {
417         return getPackageWindowState(PACKAGE_UNDER_TEST);
418     }
419 
addSubview(CommandSession.ActivitySession activity, String subviewId)420     private void addSubview(CommandSession.ActivitySession activity, String subviewId) {
421         Bundle data = new Bundle();
422         data.putString(KEY_SUBVIEW_ID, subviewId);
423         Bundle res = activity.sendCommandAndWaitReply(COMMAND_ADD_SUBVIEW, data);
424         collector.checkThat(
425                 "Failed to add subview " + subviewId,
426                 res.getBoolean(KEY_COMMAND_SUCCESS),
427                 is(true));
428     }
429 
getSubViewSize(CommandSession.ActivitySession activity, String subviewId)430     private Bundle getSubViewSize(CommandSession.ActivitySession activity, String subviewId) {
431         Bundle data = new Bundle();
432         data.putString(KEY_SUBVIEW_ID, subviewId);
433         return activity.sendCommandAndWaitReply(COMMAND_GET_SUBVIEW_SIZE, data);
434     }
435 
checkScaled(String message, int baseValue, double expectedScale, int actualValue)436     private void checkScaled(String message, int baseValue, double expectedScale, int actualValue) {
437         // In order to account for possible rounding errors, let's calculate the actual scale and
438         // compare it's against the expected scale (allowing a small delta).
439         final double actualScale = ((double) actualValue) / baseValue;
440         collector.checkThat(message, actualScale, closeTo(expectedScale, EPSILON_GLOBAL_SCALE));
441     }
442 }
443