1 /* 2 * Copyright (C) 2021 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; 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 import static org.hamcrest.Matchers.notNullValue; 38 39 import android.content.ComponentName; 40 import android.content.res.Configuration; 41 import android.graphics.Rect; 42 import android.os.Bundle; 43 import android.os.LocaleList; 44 45 import org.junit.Rule; 46 import org.junit.Test; 47 import org.junit.rules.ErrorCollector; 48 import org.junit.runner.RunWith; 49 import org.junit.runners.Parameterized; 50 51 import java.util.Arrays; 52 53 /** 54 * The test is focused on compatibility scaling, and tests the feature form two sides. 55 * 1. It checks that the applications "sees" the metrics in PXs, but the DP metrics remain the same. 56 * 2. It checks the WindowManagerServer state, and makes sure that the scaling is correctly 57 * reflected in the WindowState. 58 * 59 * This is achieved by launching a {@link android.server.wm.app.UiScalingTestActivity} and having it 60 * reporting the metrics it receives. 61 * The Activity also draws 3 UI elements: a text, a red square with a 100dp side and a blue square 62 * with a 100px side. 63 * The text and the red square should have the same when rendered on the screen (by HWC) both when 64 * the compat downscaling is enabled and disabled. 65 * TODO(b/180098454): Add tests to make sure that the UI elements, which have their sizes declared 66 * in DPs (the text and the red square) have the same sizes on the screen (after composition). 67 * 68 * <p>Build/Install/Run: 69 * atest CtsWindowManagerDeviceTestCases: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 76 * scaling >= 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(new Object[][] { 83 { "DOWNSCALE_30", 0.3f }, 84 { "DOWNSCALE_35", 0.35f }, 85 { "DOWNSCALE_40", 0.4f }, 86 { "DOWNSCALE_45", 0.45f }, 87 { "DOWNSCALE_50", 0.5f }, 88 { "DOWNSCALE_55", 0.55f }, 89 { "DOWNSCALE_60", 0.6f }, 90 { "DOWNSCALE_65", 0.65f }, 91 { "DOWNSCALE_70", 0.7f }, 92 { "DOWNSCALE_75", 0.75f }, 93 { "DOWNSCALE_80", 0.8f }, 94 { "DOWNSCALE_85", 0.85f }, 95 { "DOWNSCALE_90", 0.9f }, 96 }); 97 } 98 99 @Rule 100 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(subviewSize1.getParcelable(KEY_TEXT_SIZE, Rect.class), 150 not(equalTo(new Rect()))); 151 collector.checkThat(subviewSize1.getParcelable(KEY_VIEW_SIZE, Rect.class), 152 not(equalTo(new Rect()))); 153 collector.checkThat(subviewSize1.getBoolean(KEY_COMMAND_SUCCESS), is(true)); 154 Configuration config = activity.sendCommandAndWaitReply( 155 COMMAND_GET_RESOURCES_CONFIG).getParcelable(KEY_RESOURCES_CONFIG, 156 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("Failed to update resources configuration", 161 activity.sendCommandAndWaitReply(COMMAND_UPDATE_RESOURCES_CONFIG, 162 data).getBoolean( 163 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(subviewSize1.getParcelable(KEY_TEXT_SIZE, Rect.class), 170 equalTo(subviewSize2.getParcelable(KEY_TEXT_SIZE, Rect.class))); 171 collector.checkThat(subviewSize1.getParcelable(KEY_VIEW_SIZE, Rect.class), 172 equalTo(subviewSize2.getParcelable(KEY_VIEW_SIZE, Rect.class))); 173 } 174 175 /** 176 * Tests that the parameters that the application receives from the 177 * {@link android.content.res.Configuration} are correctly scaled. 178 */ 179 @Test test_scalesCorrectly()180 public void test_scalesCorrectly() { 181 // Launch activity with down/up scaling *disabled* and get the sizes it reports and its 182 // Window state. 183 try (var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) { 184 mAppSizesNormal = getActivityReportedSizes(); 185 mWindowStateNormal = getPackageWindowState(); 186 } 187 188 try (var scale = new CompatChangeCloseable(mCompatChangeName, PACKAGE_UNDER_TEST)) { 189 // Now launch the same activity with downscaling *enabled* and get the sizes it reports 190 // and its Window state. 191 try (var down = new CompatChangeCloseable("DOWNSCALED", PACKAGE_UNDER_TEST); 192 var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) { 193 mAppSizesDownscaled = getActivityReportedSizes(); 194 mWindowStateDownscaled = getPackageWindowState(); 195 } 196 test_scalesCorrectly_inCompatDownscalingMode(); 197 test_windowState_inCompatDownscalingMode(); 198 199 if (mCompatScale >= MAX_UPSCALING_TESTED) { 200 // Now launch the same activity with upscaling *enabled* and get the sizes it 201 // reports and its Window state. 202 try (var up = new CompatChangeCloseable("DOWNSCALED_INVERSE", PACKAGE_UNDER_TEST); 203 var session = new BaseActivitySessionCloseable(ACTIVITY_UNDER_TEST)) { 204 mAppSizesUpscaled = getActivityReportedSizes(); 205 mWindowStateUpscaled = getPackageWindowState(); 206 } 207 test_scalesCorrectly_inCompatUpscalingMode(); 208 test_windowState_inCompatUpscalingMode(); 209 } 210 } 211 212 } 213 test_scalesCorrectly_inCompatDownscalingMode()214 private void test_scalesCorrectly_inCompatDownscalingMode() { 215 checkScaled("Density DPI should scale by " + mCompatScale, 216 mAppSizesNormal.densityDpi, mCompatScale, mAppSizesDownscaled.densityDpi); 217 collector.checkThat("Width shouldn't change", 218 mAppSizesNormal.widthDp, equalTo(mAppSizesDownscaled.widthDp)); 219 collector.checkThat("Height shouldn't change", 220 mAppSizesNormal.heightDp, equalTo(mAppSizesDownscaled.heightDp)); 221 collector.checkThat("Smallest Width shouldn't change", 222 mAppSizesNormal.smallestWidthDp, equalTo(mAppSizesDownscaled.smallestWidthDp)); 223 checkScaled("Width should scale by " + mCompatScale, 224 mAppSizesNormal.windowWidth, mCompatScale, mAppSizesDownscaled.windowWidth); 225 checkScaled("Height should scale by " + mCompatScale, 226 mAppSizesNormal.windowHeight, mCompatScale, mAppSizesDownscaled.windowHeight); 227 checkScaled("App width should scale by " + mCompatScale, 228 mAppSizesNormal.windowAppWidth, mCompatScale, mAppSizesDownscaled.windowAppWidth); 229 checkScaled("App height should scale by " + mCompatScale, 230 mAppSizesNormal.windowAppHeight, mCompatScale, mAppSizesDownscaled.windowAppHeight); 231 checkScaled("Width should scale by " + mCompatScale, 232 mAppSizesNormal.metricsWidth, mCompatScale, mAppSizesDownscaled.metricsWidth); 233 checkScaled("Height should scale by " + mCompatScale, 234 mAppSizesNormal.metricsHeight, mCompatScale, mAppSizesDownscaled.metricsHeight); 235 checkScaled("Width should scale by " + mCompatScale, 236 mAppSizesNormal.displayWidth, mCompatScale, mAppSizesDownscaled.displayWidth); 237 checkScaled("Height should scale by " + mCompatScale, 238 mAppSizesNormal.displayHeight, mCompatScale, mAppSizesDownscaled.displayHeight); 239 } 240 test_scalesCorrectly_inCompatUpscalingMode()241 private void test_scalesCorrectly_inCompatUpscalingMode() { 242 checkScaled("Density DPI should scale by " + mInvCompatScale, 243 mAppSizesNormal.densityDpi, mInvCompatScale, mAppSizesUpscaled.densityDpi); 244 collector.checkThat("Width shouldn't change", 245 mAppSizesNormal.widthDp, equalTo(mAppSizesUpscaled.widthDp)); 246 collector.checkThat("Height shouldn't change", 247 mAppSizesNormal.heightDp, equalTo(mAppSizesUpscaled.heightDp)); 248 collector.checkThat("Smallest Width shouldn't change", 249 mAppSizesNormal.smallestWidthDp, equalTo(mAppSizesUpscaled.smallestWidthDp)); 250 checkScaled("Width should scale by " + mInvCompatScale, 251 mAppSizesNormal.windowWidth, mInvCompatScale, mAppSizesUpscaled.windowWidth); 252 checkScaled("Height should scale by " + mInvCompatScale, 253 mAppSizesNormal.windowHeight, mInvCompatScale, mAppSizesUpscaled.windowHeight); 254 checkScaled("App width should scale by " + mInvCompatScale, 255 mAppSizesNormal.windowAppWidth, mInvCompatScale, mAppSizesUpscaled.windowAppWidth); 256 checkScaled("App height should scale by " + mInvCompatScale, 257 mAppSizesNormal.windowAppHeight, mInvCompatScale, 258 mAppSizesUpscaled.windowAppHeight); 259 checkScaled("Width should scale by " + mInvCompatScale, 260 mAppSizesNormal.metricsWidth, mInvCompatScale, mAppSizesUpscaled.metricsWidth); 261 checkScaled("Height should scale by " + mInvCompatScale, 262 mAppSizesNormal.metricsHeight, mInvCompatScale, mAppSizesUpscaled.metricsHeight); 263 checkScaled("Width should scale by " + mInvCompatScale, 264 mAppSizesNormal.displayWidth, mInvCompatScale, mAppSizesUpscaled.displayWidth); 265 checkScaled("Height should scale by " + mInvCompatScale, 266 mAppSizesNormal.displayHeight, mInvCompatScale, mAppSizesUpscaled.displayHeight); 267 } 268 test_windowState_inCompatDownscalingMode()269 private void test_windowState_inCompatDownscalingMode() { 270 // Check the "normal" window's state for disabled compat mode and appropriate global scale. 271 collector.checkThat("The Window should not be in the size compat mode", 272 mWindowStateNormal.hasCompatScale(), is(false)); 273 collector.checkThat("The window should not be scaled", 274 1d, closeTo(mWindowStateNormal.getGlobalScale(), EPSILON_GLOBAL_SCALE)); 275 276 // Check the "downscaled" window's state for enabled compat mode and appropriate global 277 // scale. 278 collector.checkThat("The Window should be in the size compat mode", 279 mWindowStateDownscaled.hasCompatScale(), is(true)); 280 collector.checkThat("The window should have global scale of " + mInvCompatScale, 281 (double) mInvCompatScale, 282 closeTo(mWindowStateDownscaled.getGlobalScale(), EPSILON_GLOBAL_SCALE)); 283 284 // Make sure the frame sizes changed correctly. 285 collector.checkThat("Window frame on should not change", 286 mWindowStateNormal.getFrame(), equalTo(mWindowStateDownscaled.getFrame())); 287 checkScaled("Requested width should scale by " + mCompatScale, 288 mWindowStateNormal.getRequestedWidth(), mCompatScale, 289 mWindowStateDownscaled.getRequestedWidth()); 290 checkScaled("Requested height " + mWindowStateNormal.getRequestedHeight() 291 + " should scale by " + mCompatScale, 292 mWindowStateNormal.getRequestedHeight(), mCompatScale, 293 mWindowStateDownscaled.getRequestedHeight()); 294 } 295 test_windowState_inCompatUpscalingMode()296 private void test_windowState_inCompatUpscalingMode() { 297 // Check the "normal" window's state for disabled compat mode and appropriate global scale. 298 collector.checkThat("The Window should not be in the size compat mode", 299 mWindowStateNormal.hasCompatScale(), is(false)); 300 collector.checkThat("The window should not be scaled", 301 1d, closeTo(mWindowStateNormal.getGlobalScale(), EPSILON_GLOBAL_SCALE)); 302 303 // Check the "upscaled" window's state for enabled compat mode and appropriate global 304 // scale. 305 collector.checkThat("The Window should be in the size compat mode", 306 mWindowStateUpscaled.hasCompatScale(), is(true)); 307 collector.checkThat("The window should have global scale of " + mCompatScale, 308 (double) mCompatScale, 309 closeTo(mWindowStateUpscaled.getGlobalScale(), EPSILON_GLOBAL_SCALE)); 310 311 // Make sure the frame sizes changed correctly. 312 collector.checkThat("Window frame on should not change", 313 mWindowStateNormal.getFrame(), equalTo(mWindowStateUpscaled.getFrame())); 314 checkScaled("Requested width should scale by " + mInvCompatScale, 315 mWindowStateNormal.getRequestedWidth(), mInvCompatScale, 316 mWindowStateUpscaled.getRequestedWidth()); 317 checkScaled("Requested height should scale by " + mInvCompatScale, 318 mWindowStateNormal.getRequestedHeight(), mInvCompatScale, 319 mWindowStateUpscaled.getRequestedHeight()); 320 } 321 getActivityReportedSizes()322 private CommandSession.SizeInfo getActivityReportedSizes() { 323 final CommandSession.SizeInfo details = 324 getLastReportedSizesForActivity(ACTIVITY_UNDER_TEST); 325 collector.checkThat(details, notNullValue()); 326 return details; 327 } 328 getPackageWindowState()329 private WindowManagerState.WindowState getPackageWindowState() { 330 return getPackageWindowState(PACKAGE_UNDER_TEST); 331 } 332 addSubview(CommandSession.ActivitySession activity, String subviewId)333 private void addSubview(CommandSession.ActivitySession activity, String subviewId) { 334 Bundle data = new Bundle(); 335 data.putString(KEY_SUBVIEW_ID, subviewId); 336 Bundle res = activity.sendCommandAndWaitReply(COMMAND_ADD_SUBVIEW, data); 337 collector.checkThat("Failed to add subview " + subviewId, 338 res.getBoolean(KEY_COMMAND_SUCCESS), is(true)); 339 } 340 getSubViewSize(CommandSession.ActivitySession activity, String subviewId)341 private Bundle getSubViewSize(CommandSession.ActivitySession activity, String subviewId) { 342 Bundle data = new Bundle(); 343 data.putString(KEY_SUBVIEW_ID, subviewId); 344 return activity.sendCommandAndWaitReply(COMMAND_GET_SUBVIEW_SIZE, data); 345 } 346 checkScaled(String message, int baseValue, double expectedScale, int actualValue)347 private void checkScaled(String message, int baseValue, double expectedScale, 348 int actualValue) { 349 // In order to account for possible rounding errors, let's calculate the actual scale and 350 // compare it's against the expected scale (allowing a small delta). 351 final double actualScale = ((double) actualValue) / baseValue; 352 collector.checkThat(message, actualScale, closeTo(expectedScale, EPSILON_GLOBAL_SCALE)); 353 } 354 } 355