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