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