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.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE; 20 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE; 21 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 22 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 23 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS; 24 import static android.view.Display.DEFAULT_DISPLAY; 25 26 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 27 28 import static com.google.common.truth.Truth.assertThat; 29 30 import static org.junit.Assert.assertEquals; 31 import static org.junit.Assert.assertFalse; 32 import static org.junit.Assert.assertNotNull; 33 import static org.junit.Assert.assertTrue; 34 import static org.junit.Assume.assumeFalse; 35 36 import android.app.Activity; 37 import android.compat.testing.PlatformCompatChangeRule; 38 import android.content.ComponentName; 39 import android.content.pm.ActivityInfo; 40 import android.content.pm.PackageManager; 41 import android.graphics.Rect; 42 import android.platform.test.annotations.Presubmit; 43 import android.provider.DeviceConfig; 44 import android.provider.DeviceConfig.Properties; 45 import android.server.wm.WindowManagerTestBase.FocusableActivity; 46 import android.util.Size; 47 48 import androidx.annotation.Nullable; 49 import androidx.test.filters.FlakyTest; 50 51 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; 52 53 import org.junit.Before; 54 import org.junit.Rule; 55 import org.junit.Test; 56 import org.junit.rules.TestRule; 57 58 /** 59 * The test is focused on compatibility changes that have an effect on WM logic, and tests that 60 * enabling these changes has the correct effect. 61 * 62 * This is achieved by launching a custom activity with certain properties (e.g., a resizeable 63 * portrait activity) that behaves in a certain way (e.g., enter size compat mode after resizing the 64 * display) and enabling a compatibility change (e.g., {@link ActivityInfo#FORCE_RESIZE_APP}) that 65 * changes that behavior (e.g., not enter size compat mode). 66 * 67 * The behavior without enabling a compatibility change is also tested as a baseline. 68 * 69 * <p>Build/Install/Run: 70 * atest CtsWindowManagerDeviceTestCases:CompatChangeTests 71 */ 72 @Presubmit 73 public final class CompatChangeTests extends MultiDisplayTestBase { 74 private static final ComponentName RESIZEABLE_PORTRAIT_ACTIVITY = 75 component(ResizeablePortraitActivity.class); 76 private static final ComponentName RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY = 77 component(ResizeableLargeAspectRatioActivity.class); 78 private static final ComponentName NON_RESIZEABLE_PORTRAIT_ACTIVITY = 79 component(NonResizeablePortraitActivity.class); 80 private static final ComponentName NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY = 81 component(NonResizeableAspectRatioActivity.class); 82 private static final ComponentName NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY = 83 component(NonResizeableLargeAspectRatioActivity.class); 84 private static final ComponentName SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY = 85 component(SupportsSizeChangesPortraitActivity.class); 86 87 // Device aspect ratio (both portrait and landscape orientations) for min aspect ratio tests 88 private static final float SIZE_COMPAT_DISPLAY_ASPECT_RATIO = 1.4f; 89 // Fixed orientation min aspect ratio 90 private static final float FIXED_ORIENTATION_MIN_ASPECT_RATIO = 1.03f; 91 // The min aspect ratio of NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY (as defined in the manifest). 92 private static final float ACTIVITY_MIN_ASPECT_RATIO = 1.6f; 93 // The min aspect ratio of NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY (as defined in the 94 // manifest). This needs to be higher than the aspect ratio of any device, which according to 95 // CDD is at most 21:9. 96 private static final float ACTIVITY_LARGE_MIN_ASPECT_RATIO = 3f; 97 98 private static final float FLOAT_EQUALITY_DELTA = 0.01f; 99 100 @Rule 101 public TestRule compatChangeRule = new PlatformCompatChangeRule(); 102 103 private DisplayMetricsSession mDisplayMetricsSession; 104 105 @Before 106 @Override setUp()107 public void setUp() throws Exception { 108 super.setUp(); 109 110 mDisplayMetricsSession = 111 createManagedDisplayMetricsSession(DEFAULT_DISPLAY); 112 createManagedLetterboxAspectRatioSession(DEFAULT_DISPLAY, 113 FIXED_ORIENTATION_MIN_ASPECT_RATIO); 114 createManagedConstrainDisplayApisFlagsSession(); 115 } 116 117 /** 118 * Test that a non-resizeable portrait activity enters size compat mode after resizing the 119 * display. 120 */ 121 @Test testSizeCompatForNonResizeableActivity()122 public void testSizeCompatForNonResizeableActivity() { 123 runSizeCompatTest( 124 NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize= */ true); 125 } 126 127 /** 128 * Test that a non-resizeable portrait activity doesn't enter size compat mode after resizing 129 * the display, when the {@link ActivityInfo#FORCE_RESIZE_APP} compat change is enabled. 130 */ 131 @Test 132 @EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP}) testSizeCompatForNonResizeableActivityForceResizeEnabled()133 public void testSizeCompatForNonResizeableActivityForceResizeEnabled() { 134 runSizeCompatTest( 135 NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize= */ false); 136 } 137 138 /** 139 * Test that a resizeable portrait activity doesn't enter size compat mode after resizing 140 * the display. 141 */ 142 @Test testSizeCompatForResizeableActivity()143 public void testSizeCompatForResizeableActivity() { 144 runSizeCompatTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize= */ false); 145 } 146 147 /** 148 * Test that a non-resizeable portrait activity that supports size changes doesn't enter size 149 * compat mode after resizing the display. 150 */ 151 @Test testSizeCompatForSupportsSizeChangesActivity()152 public void testSizeCompatForSupportsSizeChangesActivity() { 153 runSizeCompatTest( 154 SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize= */ false); 155 } 156 157 /** 158 * Test that a resizeable portrait activity enters size compat mode after resizing 159 * the display, when the {@link ActivityInfo#FORCE_NON_RESIZE_APP} compat change is enabled. 160 */ 161 @Test 162 @EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP}) testSizeCompatForResizeableActivityForceNonResizeEnabled()163 public void testSizeCompatForResizeableActivityForceNonResizeEnabled() { 164 runSizeCompatTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize= */ true); 165 } 166 167 /** 168 * Test that a non-resizeable portrait activity that supports size changes enters size compat 169 * mode after resizing the display, when the {@link ActivityInfo#FORCE_NON_RESIZE_APP} compat 170 * change is enabled. 171 */ 172 @Test 173 @EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP}) testSizeCompatForSupportsSizeChangesActivityForceNonResizeEnabled()174 public void testSizeCompatForSupportsSizeChangesActivityForceNonResizeEnabled() { 175 runSizeCompatTest( 176 SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize= */ true); 177 } 178 179 /** 180 * Test that a min aspect ratio activity eligible for size compat mode results in sandboxed 181 * Display APIs. 182 */ 183 @Test testSandboxForNonResizableAspectRatioActivity()184 public void testSandboxForNonResizableAspectRatioActivity() { 185 runSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */ true); 186 } 187 188 /** 189 * Test that a min aspect ratio activity eligible for size compat mode does not have the Display 190 * APIs sandboxed when the {@link ActivityInfo#NEVER_SANDBOX_DISPLAY_APIS} compat change is 191 * enabled. 192 */ 193 @Test 194 @EnableCompatChanges({ActivityInfo.NEVER_SANDBOX_DISPLAY_APIS}) testSandboxForNonResizableAspectRatioActivityNeverSandboxDisplayApisEnabled()195 public void testSandboxForNonResizableAspectRatioActivityNeverSandboxDisplayApisEnabled() { 196 runSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */ false); 197 } 198 199 /** 200 * Test that a min aspect ratio activity eligible for size compat mode does not have the 201 * Display APIs sandboxed when the 'never_constrain_display_apis_all_packages' Device Config 202 * flag is true. 203 */ 204 @Test testSandboxForNonResizableActivityNeverSandboxDeviceConfigAllPackagesFlagTrue()205 public void testSandboxForNonResizableActivityNeverSandboxDeviceConfigAllPackagesFlagTrue() { 206 setNeverConstrainDisplayApisAllPackagesFlag("true"); 207 // Setting 'never_constrain_display_apis' as well to make sure it is ignored. 208 setNeverConstrainDisplayApisFlag("com.android.other::"); 209 runSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */ false); 210 } 211 212 /** 213 * Test that a min aspect ratio activity eligible for size compat mode does not have the Display 214 * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test 215 * package with an open ended range. 216 */ 217 @Test testSandboxForNonResizableActivityPackageUnboundedInNeverSandboxDeviceConfigFlag()218 public void testSandboxForNonResizableActivityPackageUnboundedInNeverSandboxDeviceConfigFlag() { 219 ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY; 220 setNeverConstrainDisplayApisFlag( 221 "com.android.other::," + activity.getPackageName() + "::"); 222 runSandboxTest(activity, /* isSandboxed= */ false); 223 } 224 225 /** 226 * Test that a min aspect ratio activity eligible for size compat mode does not have the Display 227 * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test 228 * package with a version range that matches the installed version of the package. 229 */ 230 @Test testSandboxForNonResizableActivityPackageWithinRangeInNeverSandboxDeviceConfig()231 public void testSandboxForNonResizableActivityPackageWithinRangeInNeverSandboxDeviceConfig() { 232 ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY; 233 long version = getPackageVersion(activity); 234 setNeverConstrainDisplayApisFlag( 235 "com.android.other::," + activity.getPackageName() + ":" + String.valueOf( 236 version - 1) + ":" + String.valueOf(version + 1)); 237 runSandboxTest(activity, /* isSandboxed= */ false); 238 } 239 240 /** 241 * Test that a min aspect ratio activity eligible for size compat mode does have the Display 242 * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test 243 * package with a version range that doesn't match the installed version of the package. 244 */ 245 @Test testSandboxForNonResizableActivityPackageOutsideRangeInNeverSandboxDeviceConfig()246 public void testSandboxForNonResizableActivityPackageOutsideRangeInNeverSandboxDeviceConfig() { 247 ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY; 248 long version = getPackageVersion(activity); 249 setNeverConstrainDisplayApisFlag( 250 "com.android.other::," + activity.getPackageName() + ":" + String.valueOf( 251 version + 1) + ":"); 252 runSandboxTest(activity, /* isSandboxed= */ true); 253 } 254 255 /** 256 * Test that a min aspect ratio activity eligible for size compat mode does have the Display 257 * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag doesn't contain the 258 * test package. 259 */ 260 @Test testSandboxForNonResizableActivityPackageNotInNeverSandboxDeviceConfigFlag()261 public void testSandboxForNonResizableActivityPackageNotInNeverSandboxDeviceConfigFlag() { 262 setNeverConstrainDisplayApisFlag("com.android.other::,com.android.other2::"); 263 runSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */ true); 264 } 265 266 /** 267 * Test that a min aspect ratio activity eligible for size compat mode does have the Display 268 * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag is empty. 269 */ 270 @Test testSandboxForNonResizableActivityNeverSandboxDeviceConfigFlagEmpty()271 public void testSandboxForNonResizableActivityNeverSandboxDeviceConfigFlagEmpty() { 272 setNeverConstrainDisplayApisFlag(""); 273 runSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */ true); 274 } 275 276 /** 277 * Test that a min aspect ratio activity eligible for size compat mode does have the Display 278 * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains an invalid 279 * entry for the test package. 280 */ 281 @Test testSandboxForNonResizableActivityInvalidEntryInNeverSandboxDeviceConfigFlag()282 public void testSandboxForNonResizableActivityInvalidEntryInNeverSandboxDeviceConfigFlag() { 283 ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY; 284 setNeverConstrainDisplayApisFlag( 285 "com.android.other::," + activity.getPackageName() + ":::"); 286 runSandboxTest(activity, /* isSandboxed= */ true); 287 } 288 289 /** 290 * Test that a min aspect ratio activity not eligible for size compat mode does have the 291 * Display APIs sandboxed when the {@link ActivityInfo#ALWAYS_SANDBOX_DISPLAY_APIS} compat 292 * change is enabled. 293 */ 294 @Test 295 @EnableCompatChanges({ActivityInfo.ALWAYS_SANDBOX_DISPLAY_APIS}) testSandboxForResizableAspectRatioActivityAlwaysSandboxDisplayApisEnabled()296 public void testSandboxForResizableAspectRatioActivityAlwaysSandboxDisplayApisEnabled() { 297 runSandboxTest(RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */ 298 true, /* inSizeCompatModeAfterResize= */ false); 299 } 300 301 /** 302 * Test that a min aspect ratio activity non eligible for size compat mode does not have the 303 * Display APIs sandboxed when the 'always_constrain_display_apis' Device Config flag is empty. 304 */ 305 @Test testSandboxResizableActivityAlwaysSandboxDeviceConfigFlagEmpty()306 public void testSandboxResizableActivityAlwaysSandboxDeviceConfigFlagEmpty() { 307 setAlwaysConstrainDisplayApisFlag(""); 308 runSandboxTest(RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */ 309 false, /* inSizeCompatModeAfterResize= */ false); 310 } 311 312 /** 313 * Test that a min aspect ratio activity eligible for size compat mode does have the Display 314 * APIs sandboxed when the 'always_constrain_display_apis' Device Config flag contains the test 315 * package. 316 */ 317 @Test testSandboxResizableActivityPackageInAlwaysSandboxDeviceConfigFlag()318 public void testSandboxResizableActivityPackageInAlwaysSandboxDeviceConfigFlag() { 319 ComponentName activity = RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY; 320 setAlwaysConstrainDisplayApisFlag( 321 "com.android.other::," + activity.getPackageName() + "::"); 322 runSandboxTest(activity, /* isSandboxed= */ true, /* inSizeCompatModeAfterResize= */ false); 323 } 324 325 /** 326 * Test that only applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO} has no effect on its 327 * own. The aspect ratio of the activity should be the same as that of the task, which should be 328 * in line with that of the display. 329 */ 330 @Test 331 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO}) testOverrideMinAspectRatioMissingSpecificOverride()332 public void testOverrideMinAspectRatioMissingSpecificOverride() { 333 // Note that we're using getBounds() in portrait, rather than getAppBounds() like other 334 // tests, because we're comparing to the display size and therefore need to consider insets. 335 runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, 336 /* expected= */ SIZE_COMPAT_DISPLAY_ASPECT_RATIO, 337 /* useAppBoundsInPortrait= */false); 338 } 339 340 /** 341 * Test that only applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on 342 * its own without the presence of {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO}. 343 */ 344 @Test 345 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) testOverrideMinAspectRatioMissingGeneralOverride()346 public void testOverrideMinAspectRatioMissingGeneralOverride() { 347 // Note that we're using getBounds() in portrait, rather than getAppBounds() like other 348 // tests, because we're comparing to the display size and therefore need to consider insets. 349 runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, 350 /* expected= */ SIZE_COMPAT_DISPLAY_ASPECT_RATIO, 351 /* useAppBoundsInPortrait= */false); 352 } 353 354 /** 355 * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} sets the min aspect 356 * ratio to {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE}. 357 */ 358 @Test 359 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 360 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) testOverrideMinAspectRatioLargeAspectRatio()361 public void testOverrideMinAspectRatioLargeAspectRatio() { 362 runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, 363 /* expected= */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE); 364 } 365 366 /** 367 * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_MEDIUM} sets the min aspect 368 * ratio to {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE}. 369 */ 370 @Test 371 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 372 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) testOverrideMinAspectRatioMediumAspectRatio()373 public void testOverrideMinAspectRatioMediumAspectRatio() { 374 runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, 375 /* expected= */ OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE); 376 } 377 378 /** 379 * Test that applying multiple min aspect ratio overrides result in the largest one taking 380 * effect. 381 */ 382 @Test 383 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 384 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE, 385 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) testOverrideMinAspectRatioBothAspectRatios()386 public void testOverrideMinAspectRatioBothAspectRatios() { 387 runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, 388 /* expected= */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE); 389 } 390 391 /** 392 * Test that the min aspect ratio of the activity as defined in the manifest is ignored if 393 * there is an override for a larger min aspect ratio present (16:9 > 1.6). 394 */ 395 @Test 396 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 397 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) testOverrideMinAspectRatioActivityMinAspectRatioSmallerThanOverride()398 public void testOverrideMinAspectRatioActivityMinAspectRatioSmallerThanOverride() { 399 runMinAspectRatioTest(NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY, 400 /* expected= */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE); 401 } 402 403 /** 404 * Test that the min aspect ratio of the activity as defined in the manifest is upheld if 405 * there is a n override for a smaller min aspect ratio present (3:2 < 1.6). 406 */ 407 @Test 408 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 409 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) testOverrideMinAspectRatioActivityMinAspectRatioLargerThanOverride()410 public void testOverrideMinAspectRatioActivityMinAspectRatioLargerThanOverride() { 411 runMinAspectRatioTest(NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY, 412 /* expected= */ ACTIVITY_MIN_ASPECT_RATIO); 413 } 414 415 /** 416 * Launches the provided activity into size compat mode twice. The first time, the display 417 * is resized to be half the size. The second time, the display is resized to be twice the 418 * original size. 419 * 420 * @param activity the activity under test. 421 * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after 422 * resizing the display 423 */ runSizeCompatTest(ComponentName activity, boolean inSizeCompatModeAfterResize)424 private void runSizeCompatTest(ComponentName activity, boolean inSizeCompatModeAfterResize) { 425 runSizeCompatTest(activity, /* resizeRatio= */ 0.5, inSizeCompatModeAfterResize); 426 restoreDisplay(activity); 427 runSizeCompatTest(activity, /* resizeRatio= */ 2, inSizeCompatModeAfterResize); 428 } 429 430 /** 431 * Launches the provided activity on the default display, initially not in size compat mode. 432 * After resizing the display, verifies if activity is in size compat mode or not 433 * 434 * @param activity the activity under test 435 * @param resizeRatio the ratio to resize the display 436 * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after 437 * resizing the display 438 */ runSizeCompatTest(ComponentName activity, double resizeRatio, boolean inSizeCompatModeAfterResize)439 private void runSizeCompatTest(ComponentName activity, double resizeRatio, 440 boolean inSizeCompatModeAfterResize) { 441 launchActivity(activity); 442 443 assertSizeCompatMode(activity, /* expectedInSizeCompatMode= */ false); 444 445 resizeDisplay(activity, resizeRatio); 446 447 assertSizeCompatMode(activity, inSizeCompatModeAfterResize); 448 } 449 assertSizeCompatMode(ComponentName activity, boolean expectedInSizeCompatMode)450 private void assertSizeCompatMode(ComponentName activity, boolean expectedInSizeCompatMode) { 451 WindowManagerState.Activity activityContainer = mWmState.getActivity(activity); 452 assertNotNull(activityContainer); 453 if (expectedInSizeCompatMode) { 454 assertTrue("The Window must be in size compat mode", 455 activityContainer.inSizeCompatMode()); 456 } else { 457 assertFalse("The Window must not be in size compat mode", 458 activityContainer.inSizeCompatMode()); 459 } 460 } 461 runSandboxTest(ComponentName activity, boolean isSandboxed)462 private void runSandboxTest(ComponentName activity, boolean isSandboxed) { 463 runSandboxTest(activity, isSandboxed, /* inSizeCompatModeAfterResize= */ true); 464 } 465 466 /** 467 * Similar to {@link #runSizeCompatTest(ComponentName, boolean)}, but the activity is expected 468 * to be in size compat mode after resizing the display. 469 * 470 * @param activity the activity under test 471 * @param isSandboxed when {@code true}, {@link android.app.WindowConfiguration#getMaxBounds()} 472 * are sandboxed to the activity bounds. Otherwise, they inherit the 473 * DisplayArea bounds 474 * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after 475 * resizing the display 476 */ runSandboxTest(ComponentName activity, boolean isSandboxed, boolean inSizeCompatModeAfterResize)477 private void runSandboxTest(ComponentName activity, boolean isSandboxed, 478 boolean inSizeCompatModeAfterResize) { 479 assertThat(getInitialDisplayAspectRatio()).isLessThan(ACTIVITY_LARGE_MIN_ASPECT_RATIO); 480 runSizeCompatTest(activity, /* resizeRatio= */ 0.5, inSizeCompatModeAfterResize); 481 assertSandboxed(activity, isSandboxed); 482 restoreDisplay(activity); 483 runSizeCompatTest(activity, /* resizeRatio= */ 2, inSizeCompatModeAfterResize); 484 assertSandboxed(activity, isSandboxed); 485 } 486 assertSandboxed(ComponentName activityName, boolean expectedSandboxed)487 private void assertSandboxed(ComponentName activityName, boolean expectedSandboxed) { 488 mWmState.computeState(new WaitForValidActivityState(activityName)); 489 final WindowManagerState.Activity activity = mWmState.getActivity(activityName); 490 assertNotNull(activity); 491 final Rect activityBounds = activity.getBounds(); 492 final Rect maxBounds = activity.getMaxBounds(); 493 WindowManagerState.DisplayArea tda = mWmState.getTaskDisplayArea(activityName); 494 assertNotNull(tda); 495 if (expectedSandboxed) { 496 assertEquals( 497 "The Window has max bounds sandboxed to the window bounds", 498 activityBounds, maxBounds); 499 } else { 500 assertEquals( 501 "The Window is not sandboxed, with max bounds reflecting the DisplayArea", 502 tda.getBounds(), maxBounds); 503 } 504 } 505 506 private class ConstrainDisplayApisFlagsSession implements AutoCloseable { 507 private Properties mInitialProperties; 508 ConstrainDisplayApisFlagsSession()509 ConstrainDisplayApisFlagsSession() { 510 runWithShellPermission( 511 () -> { 512 mInitialProperties = DeviceConfig.getProperties( 513 NAMESPACE_CONSTRAIN_DISPLAY_APIS); 514 try { 515 DeviceConfig.setProperties(new Properties.Builder( 516 NAMESPACE_CONSTRAIN_DISPLAY_APIS).build()); 517 } catch (Exception e) { 518 } 519 }); 520 } 521 522 @Override close()523 public void close() { 524 runWithShellPermission( 525 () -> { 526 try { 527 DeviceConfig.setProperties(mInitialProperties); 528 } catch (Exception e) { 529 } 530 }); 531 } 532 } 533 534 /** @see ObjectTracker#manage(AutoCloseable) */ createManagedConstrainDisplayApisFlagsSession()535 private ConstrainDisplayApisFlagsSession createManagedConstrainDisplayApisFlagsSession() { 536 return mObjectTracker.manage(new ConstrainDisplayApisFlagsSession()); 537 } 538 setNeverConstrainDisplayApisFlag(@ullable String value)539 private void setNeverConstrainDisplayApisFlag(@Nullable String value) { 540 setConstrainDisplayApisFlag("never_constrain_display_apis", value); 541 } 542 setNeverConstrainDisplayApisAllPackagesFlag(@ullable String value)543 private void setNeverConstrainDisplayApisAllPackagesFlag(@Nullable String value) { 544 setConstrainDisplayApisFlag("never_constrain_display_apis_all_packages", value); 545 } 546 setAlwaysConstrainDisplayApisFlag(@ullable String value)547 private void setAlwaysConstrainDisplayApisFlag(@Nullable String value) { 548 setConstrainDisplayApisFlag("always_constrain_display_apis", value); 549 } 550 setConstrainDisplayApisFlag(String flagName, @Nullable String value)551 private void setConstrainDisplayApisFlag(String flagName, @Nullable String value) { 552 runWithShellPermission( 553 () -> { 554 DeviceConfig.setProperty(NAMESPACE_CONSTRAIN_DISPLAY_APIS, flagName, 555 value, /* makeDefault= */ false); 556 }); 557 } 558 559 /** 560 * Launches the provided activity twice. The first time, the display is resized to a portrait 561 * aspect ratio. The second time, the display is resized to a landscape aspect ratio. 562 * 563 * @param activity the activity under test. 564 * @param expected the expected aspect ratio in both portrait and landscape displays. 565 */ runMinAspectRatioTest(ComponentName activity, float expected)566 private void runMinAspectRatioTest(ComponentName activity, float expected) { 567 runMinAspectRatioTest(activity, expected, /* useAppBoundsInPortrait= */ true); 568 } 569 570 /** 571 * Launches the provided activity twice. The first time, the display is resized to a portrait 572 * aspect ratio. The second time, the display is resized to a landscape aspect ratio. 573 * 574 * @param activity the activity under test. 575 * @param expected the expected aspect ratio in both a portrait and a landscape 576 * display. 577 * @param useAppBoundsInPortrait whether to use {@code activity#getAppBounds} rather than 578 * {@code activity.getBounds} in portrait display. 579 */ runMinAspectRatioTest(ComponentName activity, float expected, boolean useAppBoundsInPortrait)580 private void runMinAspectRatioTest(ComponentName activity, float expected, 581 boolean useAppBoundsInPortrait) { 582 // Change the aspect ratio of the display to something that is smaller than all the aspect 583 // ratios used throughout those tests but still portrait. This ensures we're using 584 // enforcing aspect ratio behaviour within orientation. 585 // NOTE: using a smaller aspect ratio (e.g., 1.2) might cause activities to have a landscape 586 // window because of insets. 587 mDisplayMetricsSession.changeAspectRatio(SIZE_COMPAT_DISPLAY_ASPECT_RATIO, 588 ORIENTATION_PORTRAIT); 589 launchActivity(activity); 590 assertEquals(expected, 591 getActivityAspectRatio(activity, /* useAppBounds= */ useAppBoundsInPortrait), 592 FLOAT_EQUALITY_DELTA); 593 594 // Change the orientation of the display to landscape. In this case we should see 595 // fixed orientation letterboxing and the aspect ratio should be applied there. 596 mDisplayMetricsSession.changeAspectRatio(SIZE_COMPAT_DISPLAY_ASPECT_RATIO, 597 ORIENTATION_LANDSCAPE); 598 launchActivity(activity); 599 assertEquals(expected, 600 getActivityAspectRatio(activity, /* useAppBounds= */ true), 601 FLOAT_EQUALITY_DELTA); 602 } 603 604 /** 605 * Restore the display size and ensure configuration changes are complete. 606 */ restoreDisplay(ComponentName activity)607 private void restoreDisplay(ComponentName activity) { 608 final Rect originalTaskBounds = mWmState.getTaskByActivity(activity).getBounds(); 609 mDisplayMetricsSession.restoreDisplayMetrics(); 610 // Ensure configuration changes are complete after resizing the display. 611 waitForTaskBoundsChanged(activity, originalTaskBounds); 612 } 613 614 /** 615 * Resize the display and ensure configuration changes are complete. 616 */ resizeDisplay(ComponentName activity, double sizeRatio)617 private void resizeDisplay(ComponentName activity, double sizeRatio) { 618 Size originalDisplaySize = mDisplayMetricsSession.getInitialDisplayMetrics().getSize(); 619 final Rect originalTaskBounds = mWmState.getTaskByActivity(activity).getBounds(); 620 mDisplayMetricsSession.changeDisplayMetrics(sizeRatio, /* densityRatio= */ 1); 621 mWmState.computeState(new WaitForValidActivityState(activity)); 622 623 Size currentDisplaySize = mDisplayMetricsSession.getDisplayMetrics().getSize(); 624 assumeFalse("If a display size is capped, resizing may be a no-op", 625 originalDisplaySize.equals(currentDisplaySize)); 626 627 // Ensure configuration changes are complete after resizing the display. 628 waitForTaskBoundsChanged(activity, originalTaskBounds); 629 } 630 631 /** 632 * Waits until the given activity has updated task bounds. 633 */ waitForTaskBoundsChanged(ComponentName activityName, Rect priorTaskBounds)634 private void waitForTaskBoundsChanged(ComponentName activityName, Rect priorTaskBounds) { 635 mWmState.waitForWithAmState(wmState -> { 636 WindowManagerState.ActivityTask task = wmState.getTaskByActivity(activityName); 637 return task != null && !task.getBounds().equals(priorTaskBounds); 638 }, "checking task bounds updated"); 639 } 640 getActivityAspectRatio(ComponentName componentName, boolean useAppBounds)641 private float getActivityAspectRatio(ComponentName componentName, boolean useAppBounds) { 642 WindowManagerState.Activity activity = mWmState.getActivity(componentName); 643 assertNotNull(activity); 644 Rect bounds = useAppBounds ? activity.getAppBounds() : activity.getBounds(); 645 assertNotNull(bounds); 646 return Math.max(bounds.height(), bounds.width()) 647 / (float) (Math.min(bounds.height(), bounds.width())); 648 } 649 getInitialDisplayAspectRatio()650 private float getInitialDisplayAspectRatio() { 651 Size size = mDisplayMetricsSession.getInitialDisplayMetrics().getSize(); 652 return Math.max(size.getHeight(), size.getWidth()) 653 / (float) (Math.min(size.getHeight(), size.getWidth())); 654 } 655 launchActivity(ComponentName activity)656 private void launchActivity(ComponentName activity) { 657 getLaunchActivityBuilder() 658 .setDisplayId(DEFAULT_DISPLAY) 659 .setTargetActivity(activity) 660 .setUseInstrumentation() 661 .execute(); 662 } 663 getPackageVersion(ComponentName activity)664 private long getPackageVersion(ComponentName activity) { 665 try { 666 return mContext.getPackageManager().getPackageInfo(activity.getPackageName(), 667 /* flags= */ 0).getLongVersionCode(); 668 } catch (PackageManager.NameNotFoundException e) { 669 throw new RuntimeException(e); 670 } 671 } 672 component(Class<? extends Activity> activity)673 private static ComponentName component(Class<? extends Activity> activity) { 674 return new ComponentName(getInstrumentation().getContext(), activity); 675 } 676 677 public static class ResizeablePortraitActivity extends FocusableActivity { 678 } 679 680 public static class ResizeableLargeAspectRatioActivity extends FocusableActivity { 681 } 682 683 public static class NonResizeablePortraitActivity extends FocusableActivity { 684 } 685 686 public static class NonResizeableAspectRatioActivity extends FocusableActivity { 687 } 688 689 public static class NonResizeableLargeAspectRatioActivity extends FocusableActivity { 690 } 691 692 public static class SupportsSizeChangesPortraitActivity extends FocusableActivity { 693 } 694 } 695