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