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_ENABLE_COMPAT_FAKE_FOCUS; 21 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE; 22 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE; 23 import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS; 24 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 25 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; 26 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 27 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 28 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; 29 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 30 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 31 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS; 32 import static android.server.wm.allowdisplayorientationoverride.Components.ALLOW_DISPLAY_ORIENTATION_OVERRIDE_ACTIVITY; 33 import static android.server.wm.alloworientationoverride.Components.ALLOW_ORIENTATION_OVERRIDE_LANDSCAPE_ACTIVITY; 34 import static android.server.wm.alloworientationoverride.Components.ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY; 35 import static android.server.wm.allowsandboxingviewboundsapis.Components.ACTION_TEST_VIEW_SANDBOX_ALLOWED_PASSED; 36 import static android.server.wm.allowsandboxingviewboundsapis.Components.ACTION_TEST_VIEW_SANDBOX_NOT_ALLOWED_PASSED; 37 import static android.server.wm.allowsandboxingviewboundsapis.Components.TEST_VIEW_SANDBOX_ALLOWED_ACTIVITY; 38 import static android.server.wm.allowsandboxingviewboundsapis.Components.TEST_VIEW_SANDBOX_ALLOWED_TIMEOUT_MS; 39 import static android.server.wm.enablefakefocusoptin.Components.ENABLE_FAKE_FOCUS_OPT_IN_LEFT_ACTIVITY; 40 import static android.server.wm.enablefakefocusoptin.Components.ENABLE_FAKE_FOCUS_OPT_IN_RIGHT_ACTIVITY; 41 import static android.server.wm.enablefakefocusoptout.Components.ENABLE_FAKE_FOCUS_OPT_OUT_LEFT_ACTIVITY; 42 import static android.server.wm.enablefakefocusoptout.Components.ENABLE_FAKE_FOCUS_OPT_OUT_RIGHT_ACTIVITY; 43 import static android.server.wm.ignorerequestedorientationoverrideoptin.Components.OPT_IN_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY; 44 import static android.server.wm.ignorerequestedorientationoverrideoptout.Components.OPT_OUT_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY; 45 import static android.server.wm.optoutsandboxingviewboundsapis.Components.ACTION_TEST_VIEW_SANDBOX_OPT_OUT_PASSED; 46 import static android.server.wm.optoutsandboxingviewboundsapis.Components.TEST_VIEW_SANDBOX_OPT_OUT_ACTIVITY; 47 import static android.server.wm.optoutsandboxingviewboundsapis.Components.TEST_VIEW_SANDBOX_OPT_OUT_TIMEOUT_MS; 48 import static android.server.wm.propertycameracompatallowforcerotation.Components.CAMERA_COMPAT_ALLOW_FORCE_ROTATION_ACTIVITY; 49 import static android.server.wm.propertycameracompatallowrefresh.Components.CAMERA_COMPAT_ALLOW_REFRESH_ACTIVITY; 50 import static android.server.wm.propertycameracompatenablerefreshviapauseoptin.Components.CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_IN_ACTIVITY; 51 import static android.server.wm.propertycameracompatenablerefreshviapauseoptout.Components.CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_OUT_ACTIVITY; 52 import static android.view.Surface.ROTATION_0; 53 import static android.view.Surface.ROTATION_90; 54 55 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 56 57 import static com.google.common.truth.Truth.assertThat; 58 59 import static org.junit.Assert.assertEquals; 60 import static org.junit.Assert.assertFalse; 61 import static org.junit.Assert.assertNotNull; 62 import static org.junit.Assert.assertTrue; 63 import static org.junit.Assume.assumeTrue; 64 65 import android.app.Activity; 66 import android.app.WindowConfiguration; 67 import android.compat.testing.PlatformCompatChangeRule; 68 import android.content.BroadcastReceiver; 69 import android.content.ComponentName; 70 import android.content.Context; 71 import android.content.Intent; 72 import android.content.IntentFilter; 73 import android.content.pm.ActivityInfo; 74 import android.content.pm.PackageManager; 75 import android.content.res.Resources; 76 import android.graphics.Rect; 77 import android.os.Bundle; 78 import android.os.ConditionVariable; 79 import android.platform.test.annotations.Presubmit; 80 import android.provider.DeviceConfig; 81 import android.server.wm.app.AbstractLifecycleLogActivity; 82 import android.util.Size; 83 84 import androidx.annotation.NonNull; 85 import androidx.annotation.Nullable; 86 87 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; 88 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; 89 90 import org.junit.Before; 91 import org.junit.Rule; 92 import org.junit.Test; 93 import org.junit.rules.TestRule; 94 95 import java.util.Collections; 96 import java.util.HashMap; 97 import java.util.Map; 98 99 /** 100 * The test is focused on compatibility changes that have an effect on WM logic, and tests that 101 * enabling these changes has the correct effect. 102 * 103 * This is achieved by launching a custom activity with certain properties (e.g., a resizeable 104 * portrait activity) that behaves in a certain way (e.g., enter size compat mode after resizing the 105 * display) and enabling a compatibility change (e.g., {@link ActivityInfo#FORCE_RESIZE_APP}) that 106 * changes that behavior (e.g., not enter size compat mode). 107 * 108 * The behavior without enabling a compatibility change is also tested as a baseline. 109 * 110 * <p>Build/Install/Run: 111 * atest CtsWindowManagerDeviceTestCases:CompatChangeTests 112 */ 113 @Presubmit 114 public final class CompatChangeTests extends MultiDisplayTestBase { 115 private static final ComponentName RESIZEABLE_PORTRAIT_ACTIVITY = 116 component(ResizeablePortraitActivity.class); 117 private static final ComponentName NON_RESIZEABLE_PORTRAIT_ACTIVITY = 118 component(NonResizeablePortraitActivity.class); 119 private static final ComponentName NON_RESIZEABLE_LANDSCAPE_ACTIVITY = 120 component(NonResizeableLandscapeActivity.class); 121 private static final ComponentName NON_RESIZEABLE_NON_FIXED_ORIENTATION_ACTIVITY = 122 component(NonResizeableNonFixedOrientationActivity.class); 123 private static final ComponentName NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY = 124 component(NonResizeableAspectRatioActivity.class); 125 private static final ComponentName NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY = 126 component(NonResizeableLargeAspectRatioActivity.class); 127 private static final ComponentName SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY = 128 component(SupportsSizeChangesPortraitActivity.class); 129 private static final ComponentName RESIZEABLE_LEFT_ACTIVITY = 130 component(ResizeableLeftActivity.class); 131 private static final ComponentName RESIZEABLE_RIGHT_ACTIVITY = 132 component(ResizeableRightActivity.class); 133 private static final ComponentName RESPONSIVE_ACTIVITY = 134 component(ResponsiveActivity.class); 135 private static final ComponentName NO_PROPERTY_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY = 136 component(NoPropertyChangeOrientationWhileRelaunchingActivity.class); 137 138 // Fixed orientation min aspect ratio 139 private static final float FIXED_ORIENTATION_MIN_ASPECT_RATIO = 1.03f; 140 // The min aspect ratio of NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY (as defined in the manifest). 141 private static final float ACTIVITY_MIN_ASPECT_RATIO = 1.6f; 142 // The min aspect ratio of NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY (as defined in the 143 // manifest). This needs to be higher than the aspect ratio of any device, which according to 144 // CDD is at most 21:9. 145 private static final float ACTIVITY_LARGE_MIN_ASPECT_RATIO = 4f; 146 147 private static final float FLOAT_EQUALITY_DELTA = 0.01f; 148 149 @Rule 150 public TestRule compatChangeRule = new PlatformCompatChangeRule(); 151 152 @Before 153 @Override setUp()154 public void setUp() throws Exception { 155 super.setUp(); 156 enableAndAssumeGestureNavigationMode(); 157 createManagedLetterboxAspectRatioSession(FIXED_ORIENTATION_MIN_ASPECT_RATIO); 158 mObjectTracker.manage(setAlwaysConstrainDisplayApisFlag(null)); 159 mObjectTracker.manage(setNeverConstrainDisplayApisAllPackagesFlag(null)); 160 mObjectTracker.manage(setNeverConstrainDisplayApisFlag(null)); 161 } 162 163 @Test testOverrideUndefinedOrientationToPortrait_propertyIsFalse_overrideNotApplied()164 public void testOverrideUndefinedOrientationToPortrait_propertyIsFalse_overrideNotApplied() { 165 try (var compatChange = new CompatChangeCloseable( 166 ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, 167 ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY.getPackageName()); 168 var session = new ActivitySessionCloseable( 169 ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY)) { 170 waitAssertEquals("expected unspecified orientation", 171 SCREEN_ORIENTATION_UNSPECIFIED, 172 () -> session.getActivityState().getOverrideOrientation()); 173 } 174 } 175 176 @Test 177 @EnableCompatChanges({ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) testOverrideUndefinedOrientationToPortrait()178 public void testOverrideUndefinedOrientationToPortrait() { 179 try (var session = new ActivitySessionCloseable(RESPONSIVE_ACTIVITY)) { 180 waitAssertEquals("expected portrait orientation", SCREEN_ORIENTATION_PORTRAIT, 181 () -> session.getActivityState().getOverrideOrientation()); 182 } 183 } 184 185 @Test testOverrideUndefinedOrientationToNoSensor_propertyIsFalse_overrideNotApplied()186 public void testOverrideUndefinedOrientationToNoSensor_propertyIsFalse_overrideNotApplied() { 187 try (var compatChange = new CompatChangeCloseable( 188 ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR, 189 ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY.getPackageName()); 190 var session = new ActivitySessionCloseable( 191 ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY)) { 192 waitAssertEquals("expected unspecified orientation", 193 SCREEN_ORIENTATION_UNSPECIFIED, 194 () -> session.getActivityState().getOverrideOrientation()); 195 } 196 } 197 198 @Test 199 @EnableCompatChanges({ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR}) testOverrideUndefinedOrientationToNosensor()200 public void testOverrideUndefinedOrientationToNosensor() { 201 try (var session = new ActivitySessionCloseable(RESPONSIVE_ACTIVITY)) { 202 waitAssertEquals("expected no-sensor orientation", 203 SCREEN_ORIENTATION_NOSENSOR, 204 () -> session.getActivityState().getOverrideOrientation()); 205 } 206 } 207 208 @Test 209 public void testOverrideLandscapeOrientationToReverseLandscape_propertyIsFalse_overrideNotApply()210 testOverrideLandscapeOrientationToReverseLandscape_propertyIsFalse_overrideNotApply() { 211 try (var compatChange = new CompatChangeCloseable( 212 ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE, 213 ALLOW_ORIENTATION_OVERRIDE_LANDSCAPE_ACTIVITY.getPackageName()); 214 var session = new ActivitySessionCloseable( 215 ALLOW_ORIENTATION_OVERRIDE_LANDSCAPE_ACTIVITY)) { 216 waitAssertEquals("expected landscape orientation", SCREEN_ORIENTATION_LANDSCAPE, 217 () -> session.getActivityState().getOverrideOrientation()); 218 } 219 } 220 221 @Test 222 @EnableCompatChanges({ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE}) testOverrideLandscapeOrientationToReverseLandscape()223 public void testOverrideLandscapeOrientationToReverseLandscape() { 224 try (var session = new ActivitySessionCloseable(NON_RESIZEABLE_LANDSCAPE_ACTIVITY)) { 225 waitAssertEquals("expected reverse landscape orientation", 226 SCREEN_ORIENTATION_REVERSE_LANDSCAPE, 227 () -> session.getActivityState().getOverrideOrientation()); 228 } 229 } 230 231 @Test 232 @EnableCompatChanges({ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION}) testOverrideUseDisplayLandscapeNaturalOrientation()233 public void testOverrideUseDisplayLandscapeNaturalOrientation() { 234 try (var displayMetricsSession = new DisplayMetricsWaitCloseable()) { 235 // Run this test only when natural orientation is landscape 236 Size displaySize = displayMetricsSession.getInitialDisplayMetrics().getSize(); 237 assumeTrue(displaySize.getHeight() < displaySize.getWidth()); 238 } 239 240 try (var portrait = new DeviceOrientationCloseable(ORIENTATION_PORTRAIT); 241 var session = new ActivitySessionCloseable(RESPONSIVE_ACTIVITY)) { 242 // Verifying that orientation is overridden 243 waitAssertEquals("expected rotation 0", 244 ROTATION_0, () -> mWmState.getRotation()); 245 } 246 } 247 248 @Test 249 public void testOverrideUseDisplayLandscapeNaturalOrientation_propertyIsFalse_overrideNotApplied()250 testOverrideUseDisplayLandscapeNaturalOrientation_propertyIsFalse_overrideNotApplied() { 251 try (var displayMetricsSession = new DisplayMetricsWaitCloseable()) { 252 // Run this test only when natural orientation is landscape 253 Size displaySize = displayMetricsSession.getInitialDisplayMetrics().getSize(); 254 assumeTrue(displaySize.getHeight() < displaySize.getWidth()); 255 } 256 257 final int originalRotation = mWmState.getRotation(); 258 259 try (var portrait = new DeviceOrientationCloseable(ORIENTATION_PORTRAIT); 260 var compatChange = new CompatChangeCloseable( 261 ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION, 262 ALLOW_DISPLAY_ORIENTATION_OVERRIDE_ACTIVITY.getPackageName()); 263 var session = new ActivitySessionCloseable( 264 ALLOW_DISPLAY_ORIENTATION_OVERRIDE_ACTIVITY)) { 265 266 // Verifying that orientation not overridden 267 if (portrait.isRotationApplied()) { 268 // If the screen was rotated away from natural orientation (to portrait) 269 waitAssertEquals("expected rotation 90", 270 ROTATION_90, () -> mWmState.getRotation()); 271 } else { 272 // If the screen was already in portrait (rotated away from natural orientation) 273 waitAssertEquals("expected originalRotation=" + originalRotation, 274 originalRotation, () -> mWmState.getRotation()); 275 } 276 } 277 } 278 279 @Test testEnableFakeFocus_propertyIsFalse_overrideNotApplied()280 public void testEnableFakeFocus_propertyIsFalse_overrideNotApplied() { 281 assumeTrue("Skipping test: no split multi-window support", 282 supportsSplitScreenMultiWindow()); 283 assumeTrue("Skipping test: config_isCompatFakeFocusEnabled not enabled", 284 getFakeFocusEnabledConfig()); 285 286 try (var compatChange = new CompatChangeCloseable( 287 OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS, 288 ENABLE_FAKE_FOCUS_OPT_OUT_LEFT_ACTIVITY.getPackageName()); 289 var splitScreen = new SplitScreenActivitiesCloseable( 290 ENABLE_FAKE_FOCUS_OPT_OUT_LEFT_ACTIVITY, 291 ENABLE_FAKE_FOCUS_OPT_OUT_RIGHT_ACTIVITY)) { 292 waitAssertEquals("expected should not send compat fake focus", 293 /* expected */ false, 294 () -> splitScreen.getPrimaryActivity() 295 .getActivityState().getShouldSendCompatFakeFocus()); 296 } 297 } 298 299 @Test testEnableFakeFocus_propertyIsTrue_returnsTrue()300 public void testEnableFakeFocus_propertyIsTrue_returnsTrue() { 301 assumeTrue("Skipping test: no split multi-window support", 302 supportsSplitScreenMultiWindow()); 303 assumeTrue("Skipping test: config_isCompatFakeFocusEnabled not enabled", 304 getFakeFocusEnabledConfig()); 305 306 try (var splitScreen = new SplitScreenActivitiesCloseable( 307 ENABLE_FAKE_FOCUS_OPT_IN_LEFT_ACTIVITY, 308 ENABLE_FAKE_FOCUS_OPT_IN_RIGHT_ACTIVITY)) { 309 waitAssertEquals("expected should send compat fake focus", /* expected */ true, 310 () -> splitScreen.getPrimaryActivity() 311 .getActivityState().getShouldSendCompatFakeFocus()); 312 } 313 } 314 315 @Test 316 @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS}) testEnableFakeFocus_overrideApplied_returnsTrue()317 public void testEnableFakeFocus_overrideApplied_returnsTrue() { 318 assumeTrue("Skipping test: no split multi-window support", 319 supportsSplitScreenMultiWindow()); 320 assumeTrue("Skipping test: config_isCompatFakeFocusEnabled not enabled", 321 getFakeFocusEnabledConfig()); 322 323 try (var splitScreen = new SplitScreenActivitiesCloseable( 324 RESIZEABLE_LEFT_ACTIVITY, 325 RESIZEABLE_RIGHT_ACTIVITY)) { 326 waitAssertEquals("expected should send compat fake focus", /* expected */ true, 327 () -> splitScreen.getPrimaryActivity() 328 .getActivityState().getShouldSendCompatFakeFocus()); 329 } 330 } 331 getFakeFocusEnabledConfig()332 boolean getFakeFocusEnabledConfig() { 333 return mContext.getResources().getBoolean( 334 Resources.getSystem().getIdentifier( 335 "config_isCompatFakeFocusEnabled", 336 "bool", "android")); 337 } 338 339 @Test testOverrideIgnoreRequestedOrientation_propertyIsFalse_overrideNotApplied()340 public void testOverrideIgnoreRequestedOrientation_propertyIsFalse_overrideNotApplied() { 341 assumeTrue("Skipping test: " 342 + "config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled not enabled", 343 isPolicyForIgnoringRequestedOrientationEnabled()); 344 345 try (var compatChange = new CompatChangeCloseable( 346 ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION, 347 OPT_OUT_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY.getPackageName()); 348 var session = new ActivitySessionCloseable( 349 OPT_OUT_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY)) { 350 waitAssertEquals("expected landscape orientation", 351 SCREEN_ORIENTATION_LANDSCAPE, 352 () -> session.getActivityState().getOverrideOrientation()); 353 } 354 } 355 356 @Test testOverrideIgnoreRequestedOrientation_isDisabled_propertyIsTrue_overrideApplied()357 public void testOverrideIgnoreRequestedOrientation_isDisabled_propertyIsTrue_overrideApplied() { 358 assumeTrue("Skipping test: " 359 + "config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled not enabled", 360 isPolicyForIgnoringRequestedOrientationEnabled()); 361 362 try (var session = new ActivitySessionCloseable( 363 OPT_IN_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY)) { 364 waitAssertEquals("expected portrait orientation", 365 SCREEN_ORIENTATION_PORTRAIT, 366 () -> session.getActivityState().getOverrideOrientation()); 367 } 368 } 369 370 @Test 371 @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION}) testOverrideIgnoreRequestedOrientation()372 public void testOverrideIgnoreRequestedOrientation() { 373 assumeTrue("Skipping test: " 374 + "config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled not enabled", 375 isPolicyForIgnoringRequestedOrientationEnabled()); 376 377 try (var session = new ActivitySessionCloseable( 378 NO_PROPERTY_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY)) { 379 waitAssertEquals("expected portrait orientation", 380 SCREEN_ORIENTATION_PORTRAIT, 381 () -> session.getActivityState().getOverrideOrientation()); 382 } 383 } 384 385 @Test testOptOutPropertyCameraCompatForceRotation_rotationDisabled()386 public void testOptOutPropertyCameraCompatForceRotation_rotationDisabled() { 387 assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled", 388 isCameraCompatForceRotationTreatmentConfigEnabled()); 389 390 try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) { 391 // Activity without property or override is eligible for force rotation. 392 waitAssertEquals("expected to force rotate for camera compat", 393 /* expected */ true, 394 () -> session.getActivityState().getShouldForceRotateForCameraCompat()); 395 } 396 397 try (var session = new ActivitySessionCloseable( 398 CAMERA_COMPAT_ALLOW_FORCE_ROTATION_ACTIVITY)) { 399 waitAssertEquals("expected to not force rotate for camera compat", 400 /* expected */ false, 401 () -> session.getActivityState().getShouldForceRotateForCameraCompat()); 402 } 403 } 404 405 @Test 406 @EnableCompatChanges({ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION}) testOverrideForCameraCompatForceRotation_rotationDisabled()407 public void testOverrideForCameraCompatForceRotation_rotationDisabled() { 408 assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled", 409 isCameraCompatForceRotationTreatmentConfigEnabled()); 410 411 try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) { 412 waitAssertEquals("expected to not force rotate for camera compat", 413 /* expected */ false, 414 () -> session.getActivityState().getShouldForceRotateForCameraCompat()); 415 } 416 } 417 418 @Test testOptOutPropertyCameraCompatRefresh()419 public void testOptOutPropertyCameraCompatRefresh() { 420 assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled", 421 isCameraCompatForceRotationTreatmentConfigEnabled()); 422 423 try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) { 424 // Activity without property or override is eligible for refresh. 425 waitAssertEquals("expected to refresh activity for camera compat", 426 /* expected */ true, 427 () -> session.getActivityState().getShouldRefreshActivityForCameraCompat()); 428 } 429 430 try (var session = new ActivitySessionCloseable(CAMERA_COMPAT_ALLOW_REFRESH_ACTIVITY)) { 431 waitAssertEquals("expected to not refresh activity for camera compat", 432 /* expected */ false, 433 () -> session.getActivityState().getShouldRefreshActivityForCameraCompat()); 434 } 435 } 436 437 @Test 438 @EnableCompatChanges({ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH}) testOverrideForCameraCompatRefresh()439 public void testOverrideForCameraCompatRefresh() { 440 assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled", 441 isCameraCompatForceRotationTreatmentConfigEnabled()); 442 443 try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) { 444 waitAssertEquals("expected to not refresh activity for camera compat", 445 /* expected */ false, 446 () -> session.getActivityState().getShouldRefreshActivityForCameraCompat()); 447 } 448 } 449 450 @Test testOptInPropertyCameraCompatRefreshViaPause()451 public void testOptInPropertyCameraCompatRefreshViaPause() { 452 assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled", 453 isCameraCompatForceRotationTreatmentConfigEnabled()); 454 455 try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) { 456 // Activity without property or override doesn't refresh via 457 // "resumed -> paused -> resumed". 458 waitAssertEquals("expected to not refresh activity via pause for camera compat", 459 /* expected */ false, 460 () -> session.getActivityState() 461 .getShouldRefreshActivityViaPauseForCameraCompat()); 462 } 463 464 try (var session = new ActivitySessionCloseable( 465 CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_IN_ACTIVITY)) { 466 waitAssertEquals("expected to Refresh activity via pause for camera compat", 467 /* expected */ true, 468 () -> session.getActivityState() 469 .getShouldRefreshActivityViaPauseForCameraCompat()); 470 } 471 } 472 473 @Test 474 @EnableCompatChanges({ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE}) testOverrideForCameraCompatRefreshViaPause()475 public void testOverrideForCameraCompatRefreshViaPause() { 476 assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled", 477 isCameraCompatForceRotationTreatmentConfigEnabled()); 478 479 try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) { 480 waitAssertEquals("expected to Refresh activity via pause for camera compat", 481 /* expected */ true, 482 () -> session.getActivityState() 483 .getShouldRefreshActivityViaPauseForCameraCompat()); 484 } 485 } 486 487 @Test testOptOutPropertyCameraCompatRefreshViaPause()488 public void testOptOutPropertyCameraCompatRefreshViaPause() { 489 assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled", 490 isCameraCompatForceRotationTreatmentConfigEnabled()); 491 492 try (var compatChange = new CompatChangeCloseable( 493 ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, 494 CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_OUT_ACTIVITY.getPackageName()); 495 var session = new ActivitySessionCloseable( 496 CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_OUT_ACTIVITY)) { 497 waitAssertEquals("expected to not refresh activity via pause for camera compat", 498 /* expected */ false, 499 () -> session.getActivityState() 500 .getShouldRefreshActivityViaPauseForCameraCompat()); 501 } 502 } 503 504 /** 505 * Test that a non-resizeable portrait activity enters size compat mode after resizing the 506 * display. 507 */ 508 @Test testSizeCompatForNonResizeableActivity()509 public void testSizeCompatForNonResizeableActivity() { 510 runSizeCompatTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ true); 511 } 512 513 /** 514 * Test that a non-resizeable portrait activity doesn't enter size compat mode after resizing 515 * the display, when the {@link ActivityInfo#FORCE_RESIZE_APP} compat change is enabled. 516 */ 517 @Test 518 @EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP}) testSizeCompatForNonResizeableActivityForceResizeEnabled()519 public void testSizeCompatForNonResizeableActivityForceResizeEnabled() { 520 runSizeCompatTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /*inSizeCompatModeAfterResize */ false); 521 } 522 523 /** 524 * Test that a resizeable portrait activity doesn't enter size compat mode after resizing 525 * the display. 526 */ 527 @Test testSizeCompatForResizeableActivity()528 public void testSizeCompatForResizeableActivity() { 529 runSizeCompatTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ false); 530 } 531 532 /** 533 * Test that a non-resizeable portrait activity that supports size changes doesn't enter size 534 * compat mode after resizing the display. 535 */ 536 @Test testSizeCompatForSupportsSizeChangesActivity()537 public void testSizeCompatForSupportsSizeChangesActivity() { 538 runSizeCompatTest( 539 SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ false); 540 } 541 542 /** 543 * Test that a resizeable portrait activity enters size compat mode after resizing 544 * the display, when the {@link ActivityInfo#FORCE_NON_RESIZE_APP} compat change is enabled. 545 */ 546 @Test 547 @EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP}) testSizeCompatForResizeableActivityForceNonResizeEnabled()548 public void testSizeCompatForResizeableActivityForceNonResizeEnabled() { 549 runSizeCompatTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ true); 550 } 551 552 /** 553 * Test that a non-resizeable portrait activity that supports size changes enters size compat 554 * mode after resizing the display, when the {@link ActivityInfo#FORCE_NON_RESIZE_APP} compat 555 * change is enabled. 556 */ 557 @Test 558 @EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP}) testSizeCompatForSupportsSizeChangesActivityForceNonResizeEnabled()559 public void testSizeCompatForSupportsSizeChangesActivityForceNonResizeEnabled() { 560 runSizeCompatTest( 561 SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ true); 562 } 563 564 /** 565 * Test that a min aspect ratio activity eligible for size compat mode results in sandboxed 566 * Display APIs. 567 */ 568 @Test testSandboxForNonResizableAspectRatioActivity()569 public void testSandboxForNonResizableAspectRatioActivity() { 570 runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, 571 /* isSandboxed */ true, /* inSizeCompatModeAfterResize */ true); 572 } 573 574 // ================= 575 // NEVER_SANDBOX test cases 576 // ================= 577 // Validates that an activity forced into size compat mode never has sandboxing applied to the 578 // max bounds. It is expected that an activity in size compat mode normally always has 579 // sandboxing applied. 580 581 /** 582 * Test that a min aspect ratio activity eligible for size compat mode does not have the Display 583 * APIs sandboxed when the {@link ActivityInfo#NEVER_SANDBOX_DISPLAY_APIS} compat change is 584 * enabled. 585 */ 586 @Test 587 @EnableCompatChanges({ActivityInfo.NEVER_SANDBOX_DISPLAY_APIS}) testSandboxForNonResizableAspectRatioActivityNeverSandboxDisplayApisEnabled()588 public void testSandboxForNonResizableAspectRatioActivityNeverSandboxDisplayApisEnabled() { 589 runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, 590 /* isSandboxed */ false, /* inSizeCompatModeAfterResize */ true); 591 } 592 593 /** 594 * Test that a min aspect ratio activity eligible for size compat mode does not have the 595 * Display APIs sandboxed when the 'never_constrain_display_apis_all_packages' Device Config 596 * flag is true. 597 */ 598 @Test testSandboxForNonResizableActivityNeverSandboxDeviceConfigAllPackagesFlagTrue()599 public void testSandboxForNonResizableActivityNeverSandboxDeviceConfigAllPackagesFlagTrue() { 600 try (var neverForAll = setNeverConstrainDisplayApisAllPackagesFlag("true"); 601 var neverAnApp = setNeverConstrainDisplayApisFlag("com.android.other::")) { 602 runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, 603 /* isSandboxed */ false, /* inSizeCompatModeAfterResize */ true); 604 } 605 } 606 607 /** 608 * Test that a min aspect ratio activity eligible for size compat mode does not have the Display 609 * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test 610 * package with an open ended range. 611 */ 612 @Test testSandboxForNonResizableActivityPackageUnboundedInNeverSandboxDeviceConfigFlag()613 public void testSandboxForNonResizableActivityPackageUnboundedInNeverSandboxDeviceConfigFlag() { 614 ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY; 615 try (var neverForApp = setNeverConstrainDisplayApisFlag( 616 "com.android.other::," + activity.getPackageName() + "::")) { 617 runSizeCompatModeSandboxTest(activity, /* isSandboxed */ false, 618 /* inSizeCompatModeAfterResize */ true); 619 } 620 } 621 622 /** 623 * Test that a min aspect ratio activity eligible for size compat mode does not have the Display 624 * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test 625 * package with a version range that matches the installed version of the package. 626 */ 627 @Test testSandboxForNonResizableActivityPackageWithinRangeInNeverSandboxDeviceConfig()628 public void testSandboxForNonResizableActivityPackageWithinRangeInNeverSandboxDeviceConfig() { 629 ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY; 630 long version = getPackageVersion(activity); 631 try (var neverForApp = setNeverConstrainDisplayApisFlag( 632 "com.android.other::," + activity.getPackageName() + ":" + String.valueOf( 633 version - 1) + ":" + String.valueOf(version + 1))) { 634 runSizeCompatModeSandboxTest(activity, /* isSandboxed */ false, 635 /* inSizeCompatModeAfterResize */ true); 636 } 637 } 638 639 /** 640 * Test that a min aspect ratio activity eligible for size compat mode does have the Display 641 * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test 642 * package with a version range that doesn't match the installed version of the package. 643 */ 644 @Test testSandboxForNonResizableActivityPackageOutsideRangeInNeverSandboxDeviceConfig()645 public void testSandboxForNonResizableActivityPackageOutsideRangeInNeverSandboxDeviceConfig() { 646 ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY; 647 long version = getPackageVersion(activity); 648 try (var neverForApp = setNeverConstrainDisplayApisFlag( 649 "com.android.other::," + activity.getPackageName() + ":" + String.valueOf( 650 version + 1) + ":")) { 651 runSizeCompatModeSandboxTest(activity, /* isSandboxed */ true, 652 /* inSizeCompatModeAfterResize */ true); 653 } 654 } 655 656 /** 657 * Test that a min aspect ratio activity eligible for size compat mode does have the Display 658 * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag doesn't contain the 659 * test package. 660 */ 661 @Test testSandboxForNonResizableActivityPackageNotInNeverSandboxDeviceConfigFlag()662 public void testSandboxForNonResizableActivityPackageNotInNeverSandboxDeviceConfigFlag() { 663 try (var neverForApp = setNeverConstrainDisplayApisFlag( 664 "com.android.other::,com.android.other2::")) { 665 runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, 666 /* isSandboxed */ true, /* inSizeCompatModeAfterResize */ true); 667 } 668 } 669 670 /** 671 * Test that a min aspect ratio activity eligible for size compat mode does have the Display 672 * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag is empty. 673 */ 674 @Test testSandboxForNonResizableActivityNeverSandboxDeviceConfigFlagEmpty()675 public void testSandboxForNonResizableActivityNeverSandboxDeviceConfigFlagEmpty() { 676 try (var empty = setNeverConstrainDisplayApisFlag("")) { 677 runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, 678 /* isSandboxed */ true, /* inSizeCompatModeAfterResize */ true); 679 } 680 } 681 682 /** 683 * Test that a min aspect ratio activity eligible for size compat mode does have the Display 684 * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains an invalid 685 * entry for the test package. 686 */ 687 @Test testSandboxForNonResizableActivityInvalidEntryInNeverSandboxDeviceConfigFlag()688 public void testSandboxForNonResizableActivityInvalidEntryInNeverSandboxDeviceConfigFlag() { 689 ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY; 690 try (var neverForApp = setNeverConstrainDisplayApisFlag( 691 "com.android.other::," + activity.getPackageName() + ":::")) { 692 runSizeCompatModeSandboxTest(activity, /* isSandboxed */ true, 693 /* inSizeCompatModeAfterResize */ true); 694 } 695 } 696 697 /** ================= 698 * SANDBOX_VIEW_BOUNDS_APIS test cases 699 * @see #testSandbox_viewApiForLetterboxedActivity 700 * @see #testNoSandbox_viewApiForLetterboxedActivity 701 * @see #testNoSandbox_viewApiForLetterboxedActivityOptOut 702 * ================= 703 * Validates that an activity in letterbox mode has sandboxing applied to the 704 * view bounds when OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is set. 705 * Without this flag or with 706 * {@link android.view.WindowManager#PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS} 707 * value=false in AndroidManifest.xml 708 * {@link android.view.View#getLocationOnScreen}, 709 * {@link android.view.View#getWindowDisplayFrame} 710 * {@link android.view.View#getBoundsOnScreen} 711 * and {@link android.view.View#getWindowVisibleDisplayFrame} 712 * return location or display frame offset by the window location on the screen: 713 * {@link WindowConfiguration#getBounds} 714 */ 715 @Test testSandbox_viewApiForLetterboxedActivity()716 public void testSandbox_viewApiForLetterboxedActivity() { 717 // Enable OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS changeId for the test application 718 try (var aspectRatio = new DisplayAspectRatioCloseable(ORIENTATION_LANDSCAPE, 2.0f); 719 var compatChange = new CompatChangeCloseable( 720 OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS, 721 TEST_VIEW_SANDBOX_ALLOWED_ACTIVITY.getPackageName()); 722 var receiver = new BroadcastReceiverCloseable(mContext, 723 ACTION_TEST_VIEW_SANDBOX_ALLOWED_PASSED)) { 724 725 try (var session = new ActivitySessionCloseable(TEST_VIEW_SANDBOX_ALLOWED_ACTIVITY)) { 726 // Wait for the broadcast action 727 boolean testPassed = receiver 728 .getBroadcastReceivedVariable(ACTION_TEST_VIEW_SANDBOX_ALLOWED_PASSED) 729 .block(TEST_VIEW_SANDBOX_ALLOWED_TIMEOUT_MS); 730 731 assertThat(testPassed).isTrue(); 732 } 733 } 734 } 735 736 @Test testNoSandbox_viewApiForLetterboxedActivity()737 public void testNoSandbox_viewApiForLetterboxedActivity() { 738 // Enable OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS changeId for the test application 739 try (var aspectRatio = new DisplayAspectRatioCloseable(ORIENTATION_LANDSCAPE, 2.0f); 740 var receiver = new BroadcastReceiverCloseable(mContext, 741 ACTION_TEST_VIEW_SANDBOX_NOT_ALLOWED_PASSED)) { 742 743 try (var session = new ActivitySessionCloseable(TEST_VIEW_SANDBOX_ALLOWED_ACTIVITY)) { 744 // Wait for the broadcast action 745 boolean testPassed = receiver 746 .getBroadcastReceivedVariable(ACTION_TEST_VIEW_SANDBOX_NOT_ALLOWED_PASSED) 747 .block(TEST_VIEW_SANDBOX_ALLOWED_TIMEOUT_MS); 748 749 assertThat(testPassed).isTrue(); 750 } 751 } 752 } 753 754 @Test testNoSandbox_viewApiForLetterboxedActivityOptOut()755 public void testNoSandbox_viewApiForLetterboxedActivityOptOut() { 756 // Enable OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS changeId for the test application 757 try (var aspectRatio = new DisplayAspectRatioCloseable(ORIENTATION_LANDSCAPE, 2.0f); 758 var compatChange = new CompatChangeCloseable( 759 OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS, 760 TEST_VIEW_SANDBOX_OPT_OUT_ACTIVITY.getPackageName()); 761 var receiver = new BroadcastReceiverCloseable(mContext, 762 ACTION_TEST_VIEW_SANDBOX_OPT_OUT_PASSED)) { 763 764 try (var session = new ActivitySessionCloseable(TEST_VIEW_SANDBOX_OPT_OUT_ACTIVITY)) { 765 // Wait for the broadcast action 766 boolean testPassed = receiver 767 .getBroadcastReceivedVariable(ACTION_TEST_VIEW_SANDBOX_OPT_OUT_PASSED) 768 .block(TEST_VIEW_SANDBOX_OPT_OUT_TIMEOUT_MS); 769 770 assertThat(testPassed).isTrue(); 771 } 772 } 773 } 774 775 // ================= 776 // ALWAYS_SANDBOX test cases 777 // ================= 778 // Validates that an activity simply in letterbox mode has sandboxing applied to the max 779 // bounds when ALWAYS_SANDBOX is set. Without the flag, we would not expect a letterbox activity 780 // to be sandboxed, unless it is also eligible for size compat mode. 781 782 /** 783 * Test that a portrait activity not eligible for size compat mode does have the 784 * Display APIs sandboxed when the {@link ActivityInfo#ALWAYS_SANDBOX_DISPLAY_APIS} compat 785 * change is enabled. 786 */ 787 @Test 788 @EnableCompatChanges({ActivityInfo.ALWAYS_SANDBOX_DISPLAY_APIS}) testSandboxForResizableActivityAlwaysSandboxDisplayApisEnabled()789 public void testSandboxForResizableActivityAlwaysSandboxDisplayApisEnabled() { 790 runLetterboxSandboxTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* isSandboxed */ true); 791 } 792 793 /** 794 * Test that a portrait activity not eligible for size compat mode does not have the 795 * Display APIs sandboxed when the 'always_constrain_display_apis' Device Config flag is empty. 796 */ 797 @Test testSandboxResizableActivityAlwaysSandboxDeviceConfigFlagEmpty()798 public void testSandboxResizableActivityAlwaysSandboxDeviceConfigFlagEmpty() { 799 runLetterboxSandboxTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* isSandboxed */ false); 800 } 801 802 /** 803 * Test that a portrait activity not eligible for size compat mode does have the Display 804 * APIs sandboxed when the 'always_constrain_display_apis' Device Config flag contains the test 805 * package. 806 */ 807 @Test testSandboxResizableActivityPackageInAlwaysSandboxDeviceConfigFlag()808 public void testSandboxResizableActivityPackageInAlwaysSandboxDeviceConfigFlag() { 809 ComponentName activity = RESIZEABLE_PORTRAIT_ACTIVITY; 810 try (var alwaysForApp = setAlwaysConstrainDisplayApisFlag( 811 "com.android.other::," + activity.getPackageName() + "::")) { 812 runLetterboxSandboxTest(activity, /* isSandboxed */ true); 813 } 814 } 815 816 /** 817 * Test that only applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO} has no effect on its 818 * own. The aspect ratio of the activity should be the same as that of the task, which should be 819 * in line with that of the display. 820 */ 821 @Test 822 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO}) testOverrideMinAspectRatioMissingSpecificOverride()823 public void testOverrideMinAspectRatioMissingSpecificOverride() { 824 runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* expected */ 0); 825 } 826 827 /** 828 * Test that only applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on 829 * its own without the presence of {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO}. 830 */ 831 @Test 832 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) testOverrideMinAspectRatioMissingGeneralOverride()833 public void testOverrideMinAspectRatioMissingGeneralOverride() { 834 runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* expected */ 0); 835 } 836 837 /** 838 * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on 839 * activities whose orientation is fixed to landscape. 840 */ 841 @Test 842 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 843 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) testOverrideMinAspectRatioForLandscapeActivity()844 public void testOverrideMinAspectRatioForLandscapeActivity() { 845 runMinAspectRatioTest(NON_RESIZEABLE_LANDSCAPE_ACTIVITY, /* expected */ 0); 846 } 847 848 /** 849 * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on 850 * activities whose orientation isn't fixed. 851 */ 852 @Test 853 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 854 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) 855 @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY}) testOverrideMinAspectRatioForNonFixedOrientationActivityPortraitOnlyDisabled()856 public void testOverrideMinAspectRatioForNonFixedOrientationActivityPortraitOnlyDisabled() { 857 runMinAspectRatioTest(NON_RESIZEABLE_NON_FIXED_ORIENTATION_ACTIVITY, /* expected */ 858 OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE); 859 } 860 861 /** 862 * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on 863 * activities whose orientation is fixed to landscape. 864 */ 865 @Test 866 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 867 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) 868 @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY}) testOverrideMinAspectRatioForLandscapeActivityPortraitOnlyDisabled()869 public void testOverrideMinAspectRatioForLandscapeActivityPortraitOnlyDisabled() { 870 runMinAspectRatioTest(NON_RESIZEABLE_LANDSCAPE_ACTIVITY, /* expected */ 871 OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE); 872 } 873 874 /** 875 * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on 876 * activities whose orientation isn't fixed. 877 */ 878 @Test 879 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 880 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) testOverrideMinAspectRatioForNonFixedOrientationActivity()881 public void testOverrideMinAspectRatioForNonFixedOrientationActivity() { 882 runMinAspectRatioTest(NON_RESIZEABLE_NON_FIXED_ORIENTATION_ACTIVITY, /* expected */ 0); 883 } 884 885 /** 886 * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} sets the min aspect 887 * ratio to {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE}. 888 */ 889 @Test 890 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 891 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) testOverrideMinAspectRatioLargeAspectRatio()892 public void testOverrideMinAspectRatioLargeAspectRatio() { 893 runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, 894 /* expected */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE); 895 } 896 897 /** 898 * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_MEDIUM} sets the min aspect 899 * ratio to {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE}. 900 */ 901 @Test 902 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 903 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) testOverrideMinAspectRatioMediumAspectRatio()904 public void testOverrideMinAspectRatioMediumAspectRatio() { 905 runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, 906 /* expected */ OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE); 907 } 908 909 /** 910 * Test that applying multiple min aspect ratio overrides result in the largest one taking 911 * effect. 912 */ 913 @Test 914 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 915 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE, 916 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) testOverrideMinAspectRatioBothAspectRatios()917 public void testOverrideMinAspectRatioBothAspectRatios() { 918 runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, 919 /* expected */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE); 920 } 921 922 /** 923 * Test that the min aspect ratio of the activity as defined in the manifest is ignored if 924 * there is an override for a larger min aspect ratio present (16:9 > 1.6). 925 */ 926 @Test 927 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 928 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) testOverrideMinAspectRatioActivityMinAspectRatioSmallerThanOverride()929 public void testOverrideMinAspectRatioActivityMinAspectRatioSmallerThanOverride() { 930 runMinAspectRatioTest(NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY, 931 /* expected */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE); 932 } 933 934 /** 935 * Test that the min aspect ratio of the activity as defined in the manifest is upheld if 936 * there is an override for a smaller min aspect ratio present (3:2 < 1.6). 937 */ 938 @Test 939 @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, 940 ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) testOverrideMinAspectRatioActivityMinAspectRatioLargerThanOverride()941 public void testOverrideMinAspectRatioActivityMinAspectRatioLargerThanOverride() { 942 runMinAspectRatioTest(NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY, 943 /* expected */ ACTIVITY_MIN_ASPECT_RATIO); 944 } 945 isCameraCompatForceRotationTreatmentConfigEnabled()946 private boolean isCameraCompatForceRotationTreatmentConfigEnabled() { 947 return getBooleanConfig("config_isWindowManagerCameraCompatTreatmentEnabled"); 948 } 949 isPolicyForIgnoringRequestedOrientationEnabled()950 private boolean isPolicyForIgnoringRequestedOrientationEnabled() { 951 return getBooleanConfig("config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled"); 952 } 953 getBooleanConfig(String configName)954 private boolean getBooleanConfig(String configName) { 955 return mContext.getResources().getBoolean( 956 Resources.getSystem().getIdentifier(configName, "bool", "android")); 957 } 958 959 /** 960 * Launches the provided activity into size compat mode twice. The first time, the display 961 * is resized to be half the size. The second time, the display is resized to be twice the 962 * original size. 963 * 964 * @param activity the activity under test. 965 * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after 966 * resizing the display 967 */ runSizeCompatTest(ComponentName activity, boolean inSizeCompatModeAfterResize)968 private void runSizeCompatTest(ComponentName activity, boolean inSizeCompatModeAfterResize) { 969 try (var session = new ActivitySessionCloseable(activity)) { 970 runSizeCompatTestForActivity(activity, /* resizeRatio */ 0.5, 971 inSizeCompatModeAfterResize); 972 } 973 974 try (var session = new ActivitySessionCloseable(activity)) { 975 runSizeCompatTestForActivity(activity, /* resizeRatio */ 2, 976 inSizeCompatModeAfterResize); 977 } 978 } 979 980 /** 981 * Launches the provided activity on the default display, initially not in size compat mode. 982 * After resizing the display, verifies if activity is in size compat mode or not 983 * 984 * @param activity the activity under test 985 * @param resizeRatio the ratio to resize the display 986 * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after 987 * resizing the display 988 */ runSizeCompatTestForActivity(ComponentName activity, final double resizeRatio, final boolean inSizeCompatModeAfterResize)989 private void runSizeCompatTestForActivity(ComponentName activity, final double resizeRatio, 990 final boolean inSizeCompatModeAfterResize) { 991 992 waitAndAssertSizeCompatMode(activity, /* expectedInSizeCompatMode */ false); 993 994 try (var resized = new DisplaySizeScaleCloseable(resizeRatio, activity)) { 995 assertThat(resized.getInitialDisplayAspectRatio()) 996 .isLessThan(ACTIVITY_LARGE_MIN_ASPECT_RATIO); 997 waitAndAssertSizeCompatMode(activity, inSizeCompatModeAfterResize); 998 } 999 } 1000 waitAndAssertSizeCompatMode(final ComponentName activity, final boolean expectedInSizeCompatMode)1001 private void waitAndAssertSizeCompatMode(final ComponentName activity, 1002 final boolean expectedInSizeCompatMode) { 1003 waitAssertEquals("The window must be inSizeCompatMode==" + expectedInSizeCompatMode, 1004 expectedInSizeCompatMode, 1005 () -> getActivityWaitState(activity).inSizeCompatMode()); 1006 } 1007 1008 /** 1009 * Similar to {@link #runSizeCompatTest(ComponentName, boolean)}, but the activity is 1010 * expected to be in size compat mode after resizing the display. 1011 * 1012 * @param activity the activity under test 1013 * @param isSandboxed when {@code true}, 1014 * {@link android.app.WindowConfiguration#getMaxBounds()} 1015 * are sandboxed to the activity bounds. Otherwise, they 1016 * inherit the 1017 * DisplayArea bounds 1018 * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after 1019 * resizing the display 1020 */ runSizeCompatModeSandboxTest(ComponentName activity, boolean isSandboxed, boolean inSizeCompatModeAfterResize)1021 private void runSizeCompatModeSandboxTest(ComponentName activity, boolean isSandboxed, 1022 boolean inSizeCompatModeAfterResize) { 1023 try (var session = new ActivitySessionCloseable(activity, WINDOWING_MODE_FULLSCREEN)) { 1024 runSizeCompatTestForActivity(activity, /* resizeRatio */ 0.5, 1025 inSizeCompatModeAfterResize); 1026 assertSandboxedByProvidesMaxBounds(activity, isSandboxed); 1027 assertSandboxedByBounds(activity, isSandboxed); 1028 } 1029 1030 try (var session = new ActivitySessionCloseable(activity, WINDOWING_MODE_FULLSCREEN)) { 1031 runSizeCompatTestForActivity(activity, /* resizeRatio */ 2, 1032 inSizeCompatModeAfterResize); 1033 assertSandboxedByProvidesMaxBounds(activity, isSandboxed); 1034 assertSandboxedByBounds(activity, isSandboxed); 1035 } 1036 } 1037 1038 /** 1039 * Similar to {@link #runSizeCompatModeSandboxTest(ComponentName, boolean, boolean)}, but the 1040 * activity is put into letterbox mode after resizing the display. 1041 * 1042 * @param activityName the activity under test 1043 * @param isSandboxed when {@code true}, {@link android.app.WindowConfiguration#getMaxBounds()} 1044 * are sandboxed to the activity bounds. Otherwise, they inherit the 1045 * DisplayArea bounds 1046 */ runLetterboxSandboxTest(ComponentName activityName, boolean isSandboxed)1047 private void runLetterboxSandboxTest(ComponentName activityName, boolean isSandboxed) { 1048 try (var aspectRatio = new DisplayAspectRatioCloseable(ORIENTATION_LANDSCAPE, 2.0f)) { 1049 assertThat(aspectRatio.getInitialDisplayAspectRatio()) 1050 .isLessThan(ACTIVITY_LARGE_MIN_ASPECT_RATIO); 1051 1052 try (var session = new ActivitySessionCloseable(activityName)) { 1053 assertSandboxedByProvidesMaxBounds(activityName, isSandboxed); 1054 } 1055 } 1056 } 1057 assertSandboxedByBounds(ComponentName activityName, boolean isSandboxed)1058 private void assertSandboxedByBounds(ComponentName activityName, boolean isSandboxed) { 1059 final WindowManagerState.Activity activity = getActivityWaitState(activityName); 1060 assertNotNull(activity); 1061 final Rect activityBounds = activity.getBounds(); 1062 final Rect maxBounds = activity.getMaxBounds(); 1063 WindowManagerState.DisplayArea tda = mWmState.getTaskDisplayArea(activityName); 1064 assertNotNull(tda); 1065 if (isSandboxed) { 1066 assertEquals( 1067 "The window has max bounds sandboxed to the window bounds", 1068 activityBounds, maxBounds); 1069 } else { 1070 assertEquals( 1071 "The window is not sandboxed, with max bounds reflecting the DisplayArea", 1072 tda.getBounds(), maxBounds); 1073 } 1074 } 1075 assertSandboxedByProvidesMaxBounds(ComponentName activityName, boolean isSandboxed)1076 private void assertSandboxedByProvidesMaxBounds(ComponentName activityName, 1077 boolean isSandboxed) { 1078 final WindowManagerState.Activity activity = getActivityWaitState(activityName); 1079 assertNotNull(activity); 1080 if (isSandboxed) { 1081 assertTrue( 1082 "The window should have max bounds sandboxed to the window bounds", 1083 activity.providesMaxBounds()); 1084 } else { 1085 assertFalse( 1086 "The window should not be sandboxed; max bounds should reflect the DisplayArea", 1087 activity.providesMaxBounds()); 1088 } 1089 } 1090 setNeverConstrainDisplayApisFlag(@ullable String value)1091 private CloseableDeviceConfig setNeverConstrainDisplayApisFlag(@Nullable String value) { 1092 return setConstrainDisplayApisFlag("never_constrain_display_apis", value); 1093 } 1094 setNeverConstrainDisplayApisAllPackagesFlag( @ullable String value)1095 private CloseableDeviceConfig setNeverConstrainDisplayApisAllPackagesFlag( 1096 @Nullable String value) { 1097 return setConstrainDisplayApisFlag("never_constrain_display_apis_all_packages", value); 1098 } 1099 setAlwaysConstrainDisplayApisFlag( @ullable String value)1100 private CloseableDeviceConfig setAlwaysConstrainDisplayApisFlag( 1101 @Nullable String value) { 1102 return setConstrainDisplayApisFlag("always_constrain_display_apis", value); 1103 } 1104 setConstrainDisplayApisFlag(@onNull String flagName, @Nullable String value)1105 private CloseableDeviceConfig setConstrainDisplayApisFlag(@NonNull String flagName, 1106 @Nullable String value) { 1107 return new CloseableDeviceConfig(NAMESPACE_CONSTRAIN_DISPLAY_APIS, flagName, value); 1108 } 1109 1110 /** 1111 * Launches the provided activity and verifies that its min aspect ratio is equal to {@code 1112 * expected}. 1113 * 1114 * @param activity the activity under test. 1115 * @param expected the expected min aspect ratio in both portrait and landscape displays. 1116 */ runMinAspectRatioTest(ComponentName activity, float expected)1117 private void runMinAspectRatioTest(ComponentName activity, float expected) { 1118 try (var session = new ActivitySessionCloseable(activity)) { 1119 assertNotNull(session.getActivityState()); 1120 assertEquals(expected, 1121 session.getActivityState().getMinAspectRatio(), 1122 FLOAT_EQUALITY_DELTA); 1123 } 1124 } 1125 getPackageVersion(ComponentName activity)1126 private long getPackageVersion(ComponentName activity) { 1127 try { 1128 return mContext.getPackageManager().getPackageInfo(activity.getPackageName(), 1129 /* flags */ 0).getLongVersionCode(); 1130 } catch (PackageManager.NameNotFoundException e) { 1131 throw new RuntimeException(e); 1132 } 1133 } 1134 component(Class<? extends Activity> activity)1135 private static ComponentName component(Class<? extends Activity> activity) { 1136 return new ComponentName(getInstrumentation().getContext(), activity); 1137 } 1138 1139 public static class ResizeablePortraitActivity extends AbstractLifecycleLogActivity { 1140 } 1141 1142 public static class ResponsiveActivity extends AbstractLifecycleLogActivity { 1143 } 1144 1145 public static class NonResizeablePortraitActivity extends AbstractLifecycleLogActivity { 1146 } 1147 1148 public static class NonResizeableLandscapeActivity extends AbstractLifecycleLogActivity { 1149 } 1150 1151 public static class NonResizeableNonFixedOrientationActivity extends 1152 AbstractLifecycleLogActivity { 1153 } 1154 1155 public static class NonResizeableAspectRatioActivity extends AbstractLifecycleLogActivity { 1156 } 1157 1158 public static class NonResizeableLargeAspectRatioActivity extends AbstractLifecycleLogActivity { 1159 } 1160 1161 public static class SupportsSizeChangesPortraitActivity extends AbstractLifecycleLogActivity { 1162 } 1163 1164 public static class ResizeableLeftActivity extends AbstractLifecycleLogActivity { 1165 } 1166 1167 public static class ResizeableRightActivity extends AbstractLifecycleLogActivity { 1168 } 1169 1170 public static class NoPropertyChangeOrientationWhileRelaunchingActivity extends 1171 AbstractLifecycleLogActivity { 1172 1173 private static boolean sHasChangeOrientationInOnResume; 1174 1175 @Override onCreate(Bundle instance)1176 protected void onCreate(Bundle instance) { 1177 super.onCreate(instance); 1178 // When OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION is enabled this request 1179 // should be ignored if sHasChangeOrientationInOnResume is true. 1180 setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); 1181 } 1182 1183 @Override onResume()1184 protected void onResume() { 1185 super.onResume(); 1186 if (!sHasChangeOrientationInOnResume) { 1187 setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); 1188 sHasChangeOrientationInOnResume = true; 1189 } 1190 } 1191 } 1192 1193 /** 1194 * Registers broadcast receiver which receives result actions from Activities under test. 1195 */ 1196 private static class BroadcastReceiverCloseable implements AutoCloseable { 1197 private final Context mContext; 1198 private final Map<String, ConditionVariable> mBroadcastsReceived; 1199 private final BroadcastReceiver mAppCommunicator = new BroadcastReceiver() { 1200 @Override 1201 public void onReceive(Context context, Intent intent) { 1202 getBroadcastReceivedVariable(intent.getAction()).open(); 1203 } 1204 }; 1205 BroadcastReceiverCloseable(final Context context, final String action)1206 BroadcastReceiverCloseable(final Context context, final String action) { 1207 this.mContext = context; 1208 // Keep the received broadcast items in the map. 1209 mBroadcastsReceived = Collections.synchronizedMap(new HashMap<>()); 1210 // Register for broadcast actions. 1211 IntentFilter filter = new IntentFilter(); 1212 filter.addAction(action); 1213 mContext.registerReceiver(mAppCommunicator, filter, Context.RECEIVER_EXPORTED); 1214 } 1215 getBroadcastReceivedVariable(String action)1216 ConditionVariable getBroadcastReceivedVariable(String action) { 1217 return mBroadcastsReceived.computeIfAbsent(action, key -> new ConditionVariable()); 1218 } 1219 1220 @Override close()1221 public void close() { 1222 mContext.unregisterReceiver(mAppCommunicator); 1223 } 1224 } 1225 1226 /** 1227 * Resets device config to the original value after the try-with-resources block finishes 1228 * try (var dc = new CloseableDeviceConfig()) {...} 1229 */ 1230 private class CloseableDeviceConfig implements AutoCloseable { 1231 private String mOriginalValue; 1232 private final String mNamespace; 1233 private final String mFlagName; 1234 CloseableDeviceConfig(@onNull String namespace, @NonNull String flagName, @Nullable String value)1235 CloseableDeviceConfig(@NonNull String namespace, @NonNull String flagName, 1236 @Nullable String value) { 1237 mNamespace = namespace; 1238 mFlagName = flagName; 1239 runWithShellPermission(() -> { 1240 mOriginalValue = DeviceConfig.getProperty(namespace, flagName); 1241 DeviceConfig.setProperty(namespace, flagName, value, /* makeDefault */ 1242 false); 1243 }); 1244 } 1245 1246 @Override close()1247 public void close() { 1248 runWithShellPermission(() -> DeviceConfig.setProperty(mNamespace, mFlagName, 1249 mOriginalValue, /* makeDefault */ false)); 1250 } 1251 } 1252 } 1253