1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package android.server.wm; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; 22 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; 23 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 24 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 25 import static android.server.wm.ActivityAndWindowManagersState.dpToPx; 26 import static android.server.wm.ActivityManagerState.STATE_RESUMED; 27 import static android.server.wm.ComponentNameUtils.getWindowName; 28 import static android.server.wm.StateLogger.logE; 29 import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY; 30 import static android.server.wm.app.Components.DIALOG_WHEN_LARGE_ACTIVITY; 31 import static android.server.wm.app.Components.LANDSCAPE_ORIENTATION_ACTIVITY; 32 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY; 33 import static android.server.wm.app.Components.NIGHT_MODE_ACTIVITY; 34 import static android.server.wm.app.Components.PORTRAIT_ORIENTATION_ACTIVITY; 35 import static android.server.wm.app.Components.RESIZEABLE_ACTIVITY; 36 import static android.server.wm.app.Components.TEST_ACTIVITY; 37 import static android.server.wm.translucentapp.Components.TRANSLUCENT_LANDSCAPE_ACTIVITY; 38 import static android.server.wm.translucentapp26.Components.SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY; 39 import static android.view.Surface.ROTATION_0; 40 import static android.view.Surface.ROTATION_180; 41 import static android.view.Surface.ROTATION_270; 42 import static android.view.Surface.ROTATION_90; 43 44 import static org.hamcrest.MatcherAssert.assertThat; 45 import static org.hamcrest.Matchers.lessThan; 46 import static org.junit.Assert.assertEquals; 47 import static org.junit.Assert.assertFalse; 48 import static org.junit.Assert.assertNotEquals; 49 import static org.junit.Assert.assertNotNull; 50 import static org.junit.Assert.assertNull; 51 import static org.junit.Assert.fail; 52 import static org.junit.Assume.assumeFalse; 53 import static org.junit.Assume.assumeTrue; 54 55 import android.content.ComponentName; 56 import android.content.res.Configuration; 57 import android.graphics.Rect; 58 import android.platform.test.annotations.Presubmit; 59 import android.server.wm.CommandSession.SizeInfo; 60 61 import androidx.test.filters.FlakyTest; 62 63 import org.junit.Ignore; 64 import org.junit.Test; 65 66 import java.util.List; 67 68 /** 69 * Build/Install/Run: 70 * atest CtsWindowManagerDeviceTestCases:AppConfigurationTests 71 */ 72 @Presubmit 73 public class AppConfigurationTests extends ActivityManagerTestBase { 74 75 private static final int SMALL_WIDTH_DP = 426; 76 private static final int SMALL_HEIGHT_DP = 320; 77 78 /** 79 * Tests that the WindowManager#getDefaultDisplay() and the Configuration of the Activity 80 * has an updated size when the Activity is resized from fullscreen to docked state. 81 * 82 * The Activity handles configuration changes, so it will not be restarted between resizes. 83 * On Configuration changes, the Activity logs the Display size and Configuration width 84 * and heights. The values reported in fullscreen should be larger than those reported in 85 * docked state. 86 */ 87 @Test testConfigurationUpdatesWhenResizedFromFullscreen()88 public void testConfigurationUpdatesWhenResizedFromFullscreen() { 89 assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow()); 90 91 separateTestJournal(); 92 launchActivity(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); 93 final SizeInfo fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY); 94 95 separateTestJournal(); 96 setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); 97 final SizeInfo dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY); 98 99 assertSizesAreSane(fullscreenSizes, dockedSizes); 100 } 101 102 /** 103 * Same as {@link #testConfigurationUpdatesWhenResizedFromFullscreen()} but resizing 104 * from docked state to fullscreen (reverse). 105 */ 106 @Test 107 @FlakyTest(bugId = 71792393) testConfigurationUpdatesWhenResizedFromDockedStack()108 public void testConfigurationUpdatesWhenResizedFromDockedStack() { 109 assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow()); 110 111 separateTestJournal(); 112 launchActivity(RESIZEABLE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); 113 final SizeInfo dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY); 114 115 separateTestJournal(); 116 setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN); 117 final SizeInfo fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY); 118 119 assertSizesAreSane(fullscreenSizes, dockedSizes); 120 } 121 122 /** 123 * Tests whether the Display sizes change when rotating the device. 124 */ 125 @Test testConfigurationUpdatesWhenRotatingWhileFullscreen()126 public void testConfigurationUpdatesWhenRotatingWhileFullscreen() throws Exception { 127 assumeTrue("Skipping test: no rotation support", supportsRotation()); 128 129 try (final RotationSession rotationSession = new RotationSession()) { 130 rotationSession.set(ROTATION_0); 131 132 separateTestJournal(); 133 launchActivity(RESIZEABLE_ACTIVITY, 134 WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); 135 final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY); 136 137 rotateAndCheckSizes(rotationSession, initialSizes); 138 } 139 } 140 141 /** 142 * Same as {@link #testConfigurationUpdatesWhenRotatingWhileFullscreen()} but when the Activity 143 * is in the docked stack. 144 */ 145 @Test testConfigurationUpdatesWhenRotatingWhileDocked()146 public void testConfigurationUpdatesWhenRotatingWhileDocked() throws Exception { 147 assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow()); 148 149 try (final RotationSession rotationSession = new RotationSession()) { 150 rotationSession.set(ROTATION_0); 151 152 separateTestJournal(); 153 // Launch our own activity to side in case Recents (or other activity to side) doesn't 154 // support rotation. 155 launchActivitiesInSplitScreen( 156 getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY), 157 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)); 158 // Launch target activity in docked stack. 159 getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY).execute(); 160 final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY); 161 162 rotateAndCheckSizes(rotationSession, initialSizes); 163 } 164 } 165 166 /** 167 * Same as {@link #testConfigurationUpdatesWhenRotatingWhileDocked()} but when the Activity 168 * is launched to side from docked stack. 169 */ 170 @Test testConfigurationUpdatesWhenRotatingToSideFromDocked()171 public void testConfigurationUpdatesWhenRotatingToSideFromDocked() throws Exception { 172 assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow()); 173 174 try (final RotationSession rotationSession = new RotationSession()) { 175 rotationSession.set(ROTATION_0); 176 177 separateTestJournal(); 178 launchActivitiesInSplitScreen( 179 getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY), 180 getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY)); 181 final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY); 182 183 rotateAndCheckSizes(rotationSession, initialSizes); 184 } 185 } 186 rotateAndCheckSizes(RotationSession rotationSession, SizeInfo prevSizes)187 private void rotateAndCheckSizes(RotationSession rotationSession, SizeInfo prevSizes) 188 throws Exception { 189 final ActivityManagerState.ActivityTask task = 190 mAmWmState.getAmState().getTaskByActivity(RESIZEABLE_ACTIVITY); 191 final int displayId = mAmWmState.getAmState().getStackById(task.mStackId).mDisplayId; 192 193 assumeTrue(supportsLockedUserRotation(rotationSession, displayId)); 194 195 final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 }; 196 for (final int rotation : rotations) { 197 separateTestJournal(); 198 rotationSession.set(rotation); 199 final int newDeviceRotation = getDeviceRotation(displayId); 200 if (newDeviceRotation == INVALID_DEVICE_ROTATION) { 201 logE("Got an invalid device rotation value. " 202 + "Continuing the test despite of that, but it is likely to fail."); 203 } 204 205 final SizeInfo rotatedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY); 206 assertSizesRotate(prevSizes, rotatedSizes, 207 // Skip orientation checks if we are not in fullscreen mode. 208 task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN); 209 prevSizes = rotatedSizes; 210 } 211 } 212 213 /** 214 * Tests when activity moved from fullscreen stack to docked and back. Activity will be 215 * relaunched twice and it should have same config as initial one. 216 */ 217 @Test testSameConfigurationFullSplitFullRelaunch()218 public void testSameConfigurationFullSplitFullRelaunch() { 219 moveActivityFullSplitFull(TEST_ACTIVITY); 220 } 221 222 /** 223 * Same as {@link #testSameConfigurationFullSplitFullRelaunch} but without relaunch. 224 */ 225 @Test testSameConfigurationFullSplitFullNoRelaunch()226 public void testSameConfigurationFullSplitFullNoRelaunch() { 227 moveActivityFullSplitFull(RESIZEABLE_ACTIVITY); 228 } 229 230 /** 231 * Launches activity in fullscreen stack, moves to docked stack and back to fullscreen stack. 232 * Last operation is done in a way which simulates split-screen divider movement maximizing 233 * docked stack size and then moving task to fullscreen stack - the same way it is done when 234 * user long-presses overview/recents button to exit split-screen. 235 * Asserts that initial and final reported sizes in fullscreen stack are the same. 236 */ moveActivityFullSplitFull(ComponentName activityName)237 private void moveActivityFullSplitFull(ComponentName activityName) { 238 assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow()); 239 240 // Launch to fullscreen stack and record size. 241 separateTestJournal(); 242 launchActivity(activityName, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); 243 final SizeInfo initialFullscreenSizes = getActivityDisplaySize(activityName); 244 final Rect displayRect = getDisplayRect(activityName); 245 246 // Move to docked stack. 247 separateTestJournal(); 248 setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); 249 final SizeInfo dockedSizes = getActivityDisplaySize(activityName); 250 assertSizesAreSane(initialFullscreenSizes, dockedSizes); 251 // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack 252 // will come up. 253 launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); 254 mAmWmState.computeState(false /* compareTaskAndStackBounds */, 255 new WaitForValidActivityState.Builder(activityName).build()); 256 final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState() 257 .getStandardStackByWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); 258 259 // Resize docked stack to fullscreen size. This will trigger activity relaunch with 260 // non-empty override configuration corresponding to fullscreen size. 261 separateTestJournal(); 262 resizeStack(stack.mStackId, displayRect.left, displayRect.top, displayRect.width(), 263 displayRect.height()); 264 265 // Move activity back to fullscreen stack. 266 setActivityTaskWindowingMode(activityName, 267 WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); 268 final SizeInfo finalFullscreenSizes = getActivityDisplaySize(activityName); 269 270 // After activity configuration was changed twice it must report same size as original one. 271 assertSizesAreSame(initialFullscreenSizes, finalFullscreenSizes); 272 } 273 274 /** 275 * Tests when activity moved from docked stack to fullscreen and back. Activity will be 276 * relaunched twice and it should have same config as initial one. 277 */ 278 @Test 279 @FlakyTest testSameConfigurationSplitFullSplitRelaunch()280 public void testSameConfigurationSplitFullSplitRelaunch() { 281 moveActivitySplitFullSplit(TEST_ACTIVITY); 282 } 283 284 /** 285 * Same as {@link #testSameConfigurationSplitFullSplitRelaunch} but without relaunch. 286 */ 287 @Test 288 @FlakyTest testSameConfigurationSplitFullSplitNoRelaunch()289 public void testSameConfigurationSplitFullSplitNoRelaunch() { 290 moveActivitySplitFullSplit(RESIZEABLE_ACTIVITY); 291 } 292 293 /** 294 * Tests that an activity with the DialogWhenLarge theme can transform properly when in split 295 * screen. 296 */ 297 @Test 298 @FlakyTest(bugId = 110276714) testDialogWhenLargeSplitSmall()299 public void testDialogWhenLargeSplitSmall() { 300 assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow()); 301 302 launchActivity(DIALOG_WHEN_LARGE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); 303 final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState() 304 .getStandardStackByWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); 305 final WindowManagerState.Display display = 306 mAmWmState.getWmState().getDisplay(stack.mDisplayId); 307 final int density = display.getDpi(); 308 final int smallWidthPx = dpToPx(SMALL_WIDTH_DP, density); 309 final int smallHeightPx = dpToPx(SMALL_HEIGHT_DP, density); 310 311 resizeStack(stack.mStackId, 0, 0, smallWidthPx, smallHeightPx); 312 mAmWmState.waitForValidState( 313 new WaitForValidActivityState.Builder(DIALOG_WHEN_LARGE_ACTIVITY) 314 .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) 315 .setActivityType(ACTIVITY_TYPE_STANDARD) 316 .build()); 317 } 318 319 /** 320 * Test that device handles consequent requested orientations and displays the activities. 321 */ 322 @Test 323 @FlakyTest(bugId = 71875755) testFullscreenAppOrientationRequests()324 public void testFullscreenAppOrientationRequests() { 325 assumeTrue("Skipping test: no rotation support", supportsRotation()); 326 327 separateTestJournal(); 328 launchActivity(PORTRAIT_ORIENTATION_ACTIVITY); 329 mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */); 330 SizeInfo reportedSizes = getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY); 331 assertEquals("portrait activity should be in portrait", 332 1 /* portrait */, reportedSizes.orientation); 333 separateTestJournal(); 334 335 launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY); 336 mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */); 337 reportedSizes = getLastReportedSizesForActivity(LANDSCAPE_ORIENTATION_ACTIVITY); 338 assertEquals("landscape activity should be in landscape", 339 2 /* landscape */, reportedSizes.orientation); 340 separateTestJournal(); 341 342 launchActivity(PORTRAIT_ORIENTATION_ACTIVITY); 343 mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */); 344 reportedSizes = getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY); 345 assertEquals("portrait activity should be in portrait", 346 1 /* portrait */, reportedSizes.orientation); 347 } 348 349 @Test testNonfullscreenAppOrientationRequests()350 public void testNonfullscreenAppOrientationRequests() { 351 assumeTrue("Skipping test: no rotation support", supportsRotation()); 352 353 separateTestJournal(); 354 launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN); 355 final SizeInfo initialReportedSizes = 356 getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY); 357 assertEquals("portrait activity should be in portrait", 358 1 /* portrait */, initialReportedSizes.orientation); 359 separateTestJournal(); 360 361 launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY, WINDOWING_MODE_FULLSCREEN); 362 assertEquals("Legacy non-fullscreen activity requested landscape orientation", 363 0 /* landscape */, mAmWmState.getWmState().getLastOrientation()); 364 365 // TODO(b/36897968): uncomment once we can suppress unsupported configurations 366 // final ReportedSizes updatedReportedSizes = 367 // getLastReportedSizesForActivity(PORTRAIT_ACTIVITY_NAME, logSeparator); 368 // assertEquals("portrait activity should not have moved from portrait", 369 // 1 /* portrait */, updatedReportedSizes.orientation); 370 } 371 372 /** 373 * Test that device handles consequent requested orientations and will not report a config 374 * change to an invisible activity. 375 */ 376 @Test 377 @FlakyTest testAppOrientationRequestConfigChanges()378 public void testAppOrientationRequestConfigChanges() { 379 assumeTrue("Skipping test: no rotation support", supportsRotation()); 380 381 separateTestJournal(); 382 launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN); 383 mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */); 384 385 assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY, 386 1 /* create */, 1 /* start */, 1 /* resume */, 387 0 /* pause */, 0 /* stop */, 0 /* destroy */, 0 /* config */); 388 389 launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN); 390 mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */); 391 392 assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY, 393 1 /* create */, 1 /* start */, 1 /* resume */, 394 1 /* pause */, 1 /* stop */, 0 /* destroy */, 0 /* config */); 395 assertLifecycleCounts(LANDSCAPE_ORIENTATION_ACTIVITY, 396 1 /* create */, 1 /* start */, 1 /* resume */, 397 0 /* pause */, 0 /* stop */, 0 /* destroy */, 0 /* config */); 398 399 launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN); 400 mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */); 401 402 assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY, 403 2 /* create */, 2 /* start */, 2 /* resume */, 404 1 /* pause */, 1 /* stop */, 0 /* destroy */, 0 /* config */); 405 assertLifecycleCounts(LANDSCAPE_ORIENTATION_ACTIVITY, 406 1 /* create */, 1 /* start */, 1 /* resume */, 407 1 /* pause */, 1 /* stop */, 0 /* destroy */, 0 /* config */); 408 } 409 410 /** 411 * Test that device orientation is restored when an activity that requests it is no longer 412 * visible. 413 */ 414 @Test testAppOrientationRequestConfigClears()415 public void testAppOrientationRequestConfigClears() { 416 assumeTrue("Skipping test: no rotation support", supportsRotation()); 417 418 separateTestJournal(); 419 launchActivity(TEST_ACTIVITY); 420 mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */); 421 final SizeInfo initialReportedSizes = getLastReportedSizesForActivity(TEST_ACTIVITY); 422 final int initialOrientation = initialReportedSizes.orientation; 423 424 // Launch an activity that requests different orientation and check that it will be applied 425 final boolean launchingPortrait; 426 if (initialOrientation == 2 /* landscape */) { 427 launchingPortrait = true; 428 } else if (initialOrientation == 1 /* portrait */) { 429 launchingPortrait = false; 430 } else { 431 fail("Unexpected orientation value: " + initialOrientation); 432 return; 433 } 434 final ComponentName differentOrientationActivity = launchingPortrait 435 ? PORTRAIT_ORIENTATION_ACTIVITY : LANDSCAPE_ORIENTATION_ACTIVITY; 436 separateTestJournal(); 437 launchActivity(differentOrientationActivity); 438 mAmWmState.assertVisibility(differentOrientationActivity, true /* visible */); 439 final SizeInfo rotatedReportedSizes = 440 getLastReportedSizesForActivity(differentOrientationActivity); 441 assertEquals("Applied orientation must correspond to activity request", 442 launchingPortrait ? 1 : 2, rotatedReportedSizes.orientation); 443 444 // Launch another activity on top and check that its orientation is not affected by previous 445 // activity. 446 separateTestJournal(); 447 launchActivity(RESIZEABLE_ACTIVITY); 448 mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */); 449 final SizeInfo finalReportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY); 450 assertEquals("Applied orientation must not be influenced by previously visible activity", 451 initialOrientation, finalReportedSizes.orientation); 452 } 453 454 // TODO(b/70870253): This test seems malfunction. 455 @Ignore("b/70870253") 456 @Test testNonFullscreenActivityProhibited()457 public void testNonFullscreenActivityProhibited() { 458 // We do not wait for the activity as it should not launch based on the restrictions around 459 // specifying orientation. We instead start an activity known to launch immediately after 460 // so that we can ensure processing the first activity occurred. 461 launchActivityNoWait(TRANSLUCENT_LANDSCAPE_ACTIVITY); 462 launchActivity(PORTRAIT_ORIENTATION_ACTIVITY); 463 464 assertFalse("target SDK > 26 non-fullscreen activity should not reach onResume", 465 mAmWmState.getAmState().containsActivity(TRANSLUCENT_LANDSCAPE_ACTIVITY)); 466 } 467 468 @Test testNonFullscreenActivityPermitted()469 public void testNonFullscreenActivityPermitted() throws Exception { 470 if(!supportsRotation()) { 471 //cannot physically rotate the screen on automotive device, skip 472 return; 473 } 474 try (final RotationSession rotationSession = new RotationSession()) { 475 rotationSession.set(ROTATION_0); 476 477 launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY); 478 mAmWmState.assertResumedActivity( 479 "target SDK <= 26 non-fullscreen activity should be allowed to launch", 480 SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY); 481 assertEquals("non-fullscreen activity requested landscape orientation", 482 0 /* landscape */, mAmWmState.getWmState().getLastOrientation()); 483 } 484 } 485 486 /** 487 * Test that device handles moving between two tasks with different orientations. 488 */ 489 @Test testTaskCloseRestoreFixedOrientation()490 public void testTaskCloseRestoreFixedOrientation() { 491 assumeTrue("Skipping test: no rotation support", supportsRotation()); 492 493 // Start landscape activity. 494 launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY); 495 mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */); 496 assertEquals("Fullscreen app requested landscape orientation", 497 0 /* landscape */, mAmWmState.getWmState().getLastOrientation()); 498 499 // Start another activity in a different task. 500 launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY); 501 502 // Request portrait 503 mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_PORTRAIT); 504 mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT); 505 waitForBroadcastActivityReady(Configuration.ORIENTATION_PORTRAIT); 506 507 // Finish activity 508 mBroadcastActionTrigger.finishBroadcastReceiverActivity(); 509 510 // Verify that activity brought to front is in originally requested orientation. 511 mAmWmState.computeState(LANDSCAPE_ORIENTATION_ACTIVITY); 512 assertEquals("Should return to app in landscape orientation", 513 0 /* landscape */, mAmWmState.getWmState().getLastOrientation()); 514 } 515 516 /** 517 * Test that device handles moving between two tasks with different orientations. 518 */ 519 @Test testTaskCloseRestoreFreeOrientation()520 public void testTaskCloseRestoreFreeOrientation() { 521 assumeTrue("Skipping test: no rotation support", supportsRotation()); 522 523 // Start landscape activity. 524 launchActivity(RESIZEABLE_ACTIVITY); 525 mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */); 526 final int initialServerOrientation = mAmWmState.getWmState().getLastOrientation(); 527 528 // Verify fixed-landscape 529 separateTestJournal(); 530 launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY); 531 mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_LANDSCAPE); 532 mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_LANDSCAPE); 533 waitForBroadcastActivityReady(Configuration.ORIENTATION_LANDSCAPE); 534 mBroadcastActionTrigger.finishBroadcastReceiverActivity(); 535 536 // Verify that activity brought to front is in originally requested orientation. 537 mAmWmState.waitForActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED); 538 SizeInfo reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY); 539 assertNull("Should come back in original orientation", reportedSizes); 540 assertEquals("Should come back in original server orientation", 541 initialServerOrientation, mAmWmState.getWmState().getLastOrientation()); 542 assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */, 543 0 /* numConfigChange */); 544 545 // Verify fixed-portrait 546 separateTestJournal(); 547 launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY); 548 mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_PORTRAIT); 549 mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT); 550 waitForBroadcastActivityReady(Configuration.ORIENTATION_PORTRAIT); 551 mBroadcastActionTrigger.finishBroadcastReceiverActivity(); 552 553 // Verify that activity brought to front is in originally requested orientation. 554 mAmWmState.waitForActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED); 555 reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY); 556 assertNull("Should come back in original orientation", reportedSizes); 557 assertEquals("Should come back in original server orientation", 558 initialServerOrientation, mAmWmState.getWmState().getLastOrientation()); 559 assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */, 560 0 /* numConfigChange */); 561 } 562 563 /** 564 * Test that activity orientation will change when device is rotated. 565 * Also verify that occluded activity will not get config changes. 566 */ 567 @Test 568 @FlakyTest testAppOrientationWhenRotating()569 public void testAppOrientationWhenRotating() throws Exception { 570 assumeTrue("Skipping test: no rotation support", supportsRotation()); 571 572 // Start resizeable activity that handles configuration changes. 573 separateTestJournal(); 574 launchActivity(TEST_ACTIVITY); 575 launchActivity(RESIZEABLE_ACTIVITY); 576 mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */); 577 578 final int displayId = mAmWmState.getAmState().getDisplayByActivity(RESIZEABLE_ACTIVITY); 579 580 // Rotate the activity and check that it receives configuration changes with a different 581 // orientation each time. 582 try (final RotationSession rotationSession = new RotationSession()) { 583 assumeTrue("Skipping test: no locked user rotation mode support.", 584 supportsLockedUserRotation(rotationSession, displayId)); 585 586 rotationSession.set(ROTATION_0); 587 SizeInfo reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY); 588 int prevOrientation = reportedSizes.orientation; 589 590 final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 }; 591 for (final int rotation : rotations) { 592 separateTestJournal(); 593 rotationSession.set(rotation); 594 595 // Verify lifecycle count and orientation changes. 596 assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */, 597 1 /* numConfigChange */); 598 reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY); 599 assertNotEquals(prevOrientation, reportedSizes.orientation); 600 assertRelaunchOrConfigChanged(TEST_ACTIVITY, 0 /* numRelaunch */, 601 0 /* numConfigChange */); 602 603 prevOrientation = reportedSizes.orientation; 604 } 605 } 606 } 607 608 /** 609 * Test that activity orientation will not change when trying to rotate fixed-orientation 610 * activity. 611 * Also verify that occluded activity will not get config changes. 612 */ 613 @Test testFixedOrientationWhenRotating()614 public void testFixedOrientationWhenRotating() throws Exception { 615 assumeTrue("Skipping test: no rotation support", supportsRotation()); 616 // TODO(b/110533226): Fix test on devices with display cutout 617 assumeFalse("Skipping test: display cutout present, can't predict exact lifecycle", 618 hasDisplayCutout()); 619 620 // Start portrait-fixed activity 621 separateTestJournal(); 622 launchActivity(RESIZEABLE_ACTIVITY); 623 launchActivity(PORTRAIT_ORIENTATION_ACTIVITY); 624 mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */); 625 626 final int displayId = mAmWmState.getAmState() 627 .getDisplayByActivity(PORTRAIT_ORIENTATION_ACTIVITY); 628 629 // Rotate the activity and check that the orientation doesn't change 630 try (final RotationSession rotationSession = new RotationSession()) { 631 assumeTrue("Skipping test: no user locked rotation support.", 632 supportsLockedUserRotation(rotationSession, displayId)); 633 634 rotationSession.set(ROTATION_0); 635 636 final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 }; 637 for (final int rotation : rotations) { 638 separateTestJournal(); 639 rotationSession.set(rotation); 640 641 // Verify lifecycle count and orientation changes. 642 assertRelaunchOrConfigChanged(PORTRAIT_ORIENTATION_ACTIVITY, 0 /* numRelaunch */, 643 0 /* numConfigChange */); 644 final SizeInfo reportedSizes = getLastReportedSizesForActivity( 645 PORTRAIT_ORIENTATION_ACTIVITY); 646 assertNull("No new sizes must be reported", reportedSizes); 647 assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */, 648 0 /* numConfigChange */); 649 } 650 } 651 } 652 653 /** 654 * Test that device handles moving between two tasks with different orientations. 655 */ 656 @Test 657 @FlakyTest(bugId = 71792393) testTaskMoveToBackOrientation()658 public void testTaskMoveToBackOrientation() { 659 assumeTrue("Skipping test: no rotation support", supportsRotation()); 660 661 // Start landscape activity. 662 launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY); 663 mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */); 664 assertEquals("Fullscreen app requested landscape orientation", 665 0 /* landscape */, mAmWmState.getWmState().getLastOrientation()); 666 667 // Start another activity in a different task. 668 launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY); 669 670 // Request portrait 671 mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_PORTRAIT); 672 mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT); 673 waitForBroadcastActivityReady(Configuration.ORIENTATION_PORTRAIT); 674 675 // Finish activity 676 mBroadcastActionTrigger.moveTopTaskToBack(); 677 678 // Verify that activity brought to front is in originally requested orientation. 679 mAmWmState.waitForValidState(LANDSCAPE_ORIENTATION_ACTIVITY); 680 assertEquals("Should return to app in landscape orientation", 681 0 /* landscape */, mAmWmState.getWmState().getLastOrientation()); 682 } 683 684 /** 685 * Test that device doesn't change device orientation by app request while in multi-window. 686 */ 687 @FlakyTest(bugId = 71918731) 688 @Test testSplitscreenPortraitAppOrientationRequests()689 public void testSplitscreenPortraitAppOrientationRequests() throws Exception { 690 assumeTrue("Skipping test: no rotation support", supportsRotation()); 691 assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow()); 692 693 try (final RotationSession rotationSession = new RotationSession()) { 694 requestOrientationInSplitScreen(rotationSession, 695 ROTATION_90 /* portrait */, LANDSCAPE_ORIENTATION_ACTIVITY); 696 } 697 } 698 699 /** 700 * Test that device doesn't change device orientation by app request while in multi-window. 701 */ 702 @Test testSplitscreenLandscapeAppOrientationRequests()703 public void testSplitscreenLandscapeAppOrientationRequests() throws Exception { 704 assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow()); 705 706 try (final RotationSession rotationSession = new RotationSession()) { 707 requestOrientationInSplitScreen(rotationSession, 708 ROTATION_0 /* landscape */, PORTRAIT_ORIENTATION_ACTIVITY); 709 } 710 } 711 712 /** 713 * Rotate the device and launch specified activity in split-screen, checking if orientation 714 * didn't change. 715 */ requestOrientationInSplitScreen(RotationSession rotationSession, int orientation, ComponentName activity)716 private void requestOrientationInSplitScreen(RotationSession rotationSession, int orientation, 717 ComponentName activity) throws Exception { 718 assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow()); 719 720 // Set initial orientation. 721 rotationSession.set(orientation); 722 723 // Launch activities that request orientations and check that device doesn't rotate. 724 launchActivitiesInSplitScreen( 725 getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY), 726 getLaunchActivityBuilder().setTargetActivity(activity).setMultipleTask(true)); 727 728 mAmWmState.assertVisibility(activity, true /* visible */); 729 assertEquals("Split-screen apps shouldn't influence device orientation", 730 orientation, mAmWmState.getWmState().getRotation()); 731 732 getLaunchActivityBuilder().setMultipleTask(true).setTargetActivity(activity).execute(); 733 mAmWmState.computeState(activity); 734 mAmWmState.assertVisibility(activity, true /* visible */); 735 assertEquals("Split-screen apps shouldn't influence device orientation", 736 orientation, mAmWmState.getWmState().getRotation()); 737 } 738 739 /** 740 * Launches activity in docked stack, moves to fullscreen stack and back to docked stack. 741 * Asserts that initial and final reported sizes in docked stack are the same. 742 */ moveActivitySplitFullSplit(ComponentName activityName)743 private void moveActivitySplitFullSplit(ComponentName activityName) { 744 assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow()); 745 746 // Launch to docked stack and record size. 747 separateTestJournal(); 748 launchActivityInSplitScreenWithRecents(activityName); 749 final SizeInfo initialDockedSizes = getActivityDisplaySize(activityName); 750 // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack 751 // will come up. 752 launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); 753 mAmWmState.computeState(false /* compareTaskAndStackBounds */, 754 new WaitForValidActivityState.Builder(activityName).build()); 755 756 // Move to fullscreen stack. 757 separateTestJournal(); 758 setActivityTaskWindowingMode( 759 activityName, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY); 760 final SizeInfo fullscreenSizes = getActivityDisplaySize(activityName); 761 assertSizesAreSane(fullscreenSizes, initialDockedSizes); 762 763 // Move activity back to docked stack. 764 separateTestJournal(); 765 setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); 766 final SizeInfo finalDockedSizes = getActivityDisplaySize(activityName); 767 768 // After activity configuration was changed twice it must report same size as original one. 769 assertSizesAreSame(initialDockedSizes, finalDockedSizes); 770 } 771 772 /** 773 * Asserts that after rotation, the aspect ratios of display size, metrics, and configuration 774 * have flipped. 775 */ assertSizesRotate(SizeInfo rotationA, SizeInfo rotationB, boolean skipOrientationCheck)776 private static void assertSizesRotate(SizeInfo rotationA, SizeInfo rotationB, 777 boolean skipOrientationCheck) { 778 assertEquals(rotationA.displayWidth, rotationA.metricsWidth); 779 assertEquals(rotationA.displayHeight, rotationA.metricsHeight); 780 assertEquals(rotationB.displayWidth, rotationB.metricsWidth); 781 assertEquals(rotationB.displayHeight, rotationB.metricsHeight); 782 783 if (skipOrientationCheck) { 784 // All done if we are not doing orientation check. 785 return; 786 } 787 final boolean beforePortrait = rotationA.displayWidth < rotationA.displayHeight; 788 final boolean afterPortrait = rotationB.displayWidth < rotationB.displayHeight; 789 assertFalse(beforePortrait == afterPortrait); 790 791 final boolean beforeConfigPortrait = rotationA.widthDp < rotationA.heightDp; 792 final boolean afterConfigPortrait = rotationB.widthDp < rotationB.heightDp; 793 assertEquals(beforePortrait, beforeConfigPortrait); 794 assertEquals(afterPortrait, afterConfigPortrait); 795 796 assertEquals(rotationA.smallestWidthDp, rotationB.smallestWidthDp); 797 } 798 799 /** 800 * Throws an AssertionError if fullscreenSizes has widths/heights (depending on aspect ratio) 801 * that are smaller than the dockedSizes. 802 */ 803 private static void assertSizesAreSane(SizeInfo fullscreenSizes, SizeInfo dockedSizes) { 804 final boolean portrait = fullscreenSizes.displayWidth < fullscreenSizes.displayHeight; 805 if (portrait) { 806 assertThat(dockedSizes.displayHeight, lessThan(fullscreenSizes.displayHeight)); 807 assertThat(dockedSizes.heightDp, lessThan(fullscreenSizes.heightDp)); 808 assertThat(dockedSizes.metricsHeight, lessThan(fullscreenSizes.metricsHeight)); 809 } else { 810 assertThat(dockedSizes.displayWidth, lessThan(fullscreenSizes.displayWidth)); 811 assertThat(dockedSizes.widthDp, lessThan(fullscreenSizes.widthDp)); 812 assertThat(dockedSizes.metricsWidth, lessThan(fullscreenSizes.metricsWidth)); 813 } 814 } 815 816 /** 817 * Throws an AssertionError if sizes are different. 818 */ 819 private static void assertSizesAreSame(SizeInfo firstSize, SizeInfo secondSize) { 820 assertEquals(firstSize.widthDp, secondSize.widthDp); 821 assertEquals(firstSize.heightDp, secondSize.heightDp); 822 assertEquals(firstSize.displayWidth, secondSize.displayWidth); 823 assertEquals(firstSize.displayHeight, secondSize.displayHeight); 824 assertEquals(firstSize.metricsWidth, secondSize.metricsWidth); 825 assertEquals(firstSize.metricsHeight, secondSize.metricsHeight); 826 assertEquals(firstSize.smallestWidthDp, secondSize.smallestWidthDp); 827 } 828 829 private SizeInfo getActivityDisplaySize(ComponentName activityName) { 830 mAmWmState.computeState(false /* compareTaskAndStackBounds */, 831 new WaitForValidActivityState(activityName)); 832 final SizeInfo details = getLastReportedSizesForActivity(activityName); 833 assertNotNull(details); 834 return details; 835 } 836 837 private Rect getDisplayRect(ComponentName activityName) { 838 final String windowName = getWindowName(activityName); 839 840 mAmWmState.computeState(activityName); 841 mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName); 842 843 final List<WindowManagerState.WindowState> windowList = 844 mAmWmState.getWmState().getMatchingVisibleWindowState(windowName); 845 846 assertEquals("Should have exactly one window state for the activity.", 1, 847 windowList.size()); 848 849 WindowManagerState.WindowState windowState = windowList.get(0); 850 assertNotNull("Should have a valid window", windowState); 851 852 WindowManagerState.Display display = mAmWmState.getWmState() 853 .getDisplay(windowState.getDisplayId()); 854 assertNotNull("Should be on a display", display); 855 856 return display.getDisplayRect(); 857 } 858 859 private void waitForBroadcastActivityReady(int orientation) { 860 mAmWmState.waitForActivityOrientation(BROADCAST_RECEIVER_ACTIVITY, orientation); 861 mAmWmState.waitForActivityState(BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED); 862 } 863 864 /** 865 * Test launching an activity which requests specific UI mode during creation. 866 */ 867 @Test 868 public void testLaunchWithUiModeChange() { 869 // Launch activity that changes UI mode and handles this configuration change. 870 launchActivity(NIGHT_MODE_ACTIVITY); 871 mAmWmState.waitForActivityState(NIGHT_MODE_ACTIVITY, STATE_RESUMED); 872 873 // Check if activity is launched successfully. 874 mAmWmState.assertVisibility(NIGHT_MODE_ACTIVITY, true /* visible */); 875 mAmWmState.assertFocusedActivity("Launched activity should be focused", 876 NIGHT_MODE_ACTIVITY); 877 mAmWmState.assertResumedActivity("Launched activity must be resumed", NIGHT_MODE_ACTIVITY); 878 } 879 } 880