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.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 22 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 23 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 24 import static android.server.wm.SplitActivityLifecycleTest.SplitTestActivity.EXTRA_SET_RESULT_AND_FINISH; 25 import static android.server.wm.SplitActivityLifecycleTest.SplitTestActivity.EXTRA_SHOW_WHEN_LOCKED; 26 import static android.server.wm.WindowManagerState.STATE_STARTED; 27 import static android.server.wm.WindowManagerState.STATE_STOPPED; 28 import static android.view.Display.DEFAULT_DISPLAY; 29 import static android.view.Surface.ROTATION_0; 30 import static android.view.Surface.ROTATION_90; 31 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE; 32 import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN; 33 34 import static com.google.common.truth.Truth.assertThat; 35 import static com.google.common.truth.Truth.assertWithMessage; 36 37 import static org.junit.Assume.assumeTrue; 38 39 import android.app.Activity; 40 import android.content.ComponentName; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.res.Configuration; 44 import android.graphics.Rect; 45 import android.os.Bundle; 46 import android.os.IBinder; 47 import android.os.SystemProperties; 48 import android.platform.test.annotations.Presubmit; 49 import android.server.wm.WindowManagerState.TaskFragment; 50 import android.view.WindowManager; 51 import android.window.TaskFragmentCreationParams; 52 import android.window.TaskFragmentInfo; 53 import android.window.WindowContainerToken; 54 import android.window.WindowContainerTransaction; 55 56 import androidx.annotation.NonNull; 57 58 import org.junit.Test; 59 60 /** 61 * Tests that verify the behavior of split Activity. 62 * <p> 63 * At the beginning of test, two Activities are launched side-by-side in two adjacent TaskFragments. 64 * Then another Activity will be launched with different scenarios. The purpose of this test is to 65 * verify the CUJ of split Activity. 66 * </p> 67 * 68 * Build/Install/Run: 69 * atest CtsWindowManagerDeviceTestCases:SplitActivityLifecycleTest 70 */ 71 @Presubmit 72 @android.server.wm.annotation.Group2 73 public class SplitActivityLifecycleTest extends TaskFragmentOrganizerTestBase { 74 /** The bounds should only be updated through {@link #updateSplitBounds(Rect)}. */ 75 private final Rect mPrimaryBounds = new Rect(); 76 private final Rect mPrimaryRelativeBounds = new Rect(); 77 private final Rect mSideBounds = new Rect(); 78 private final Rect mSideRelativeBounds = new Rect(); 79 80 private TaskFragmentRecord mTaskFragA; 81 private TaskFragmentRecord mTaskFragB; 82 private final ComponentName mActivityA = new ComponentName(mContext, ActivityA.class); 83 private final ComponentName mActivityB = new ComponentName(mContext, ActivityB.class); 84 private final ComponentName mActivityC = new ComponentName(mContext, ActivityC.class); 85 private final Intent mIntent = new Intent().setComponent(mActivityC); 86 87 @Override setUp()88 public void setUp() throws Exception { 89 super.setUp(); 90 } 91 92 @Override setUpOwnerActivity()93 Activity setUpOwnerActivity() { 94 // Launch activities in fullscreen, otherwise, some tests fail on devices which use freeform 95 // as the default windowing mode, because tests' prerequisite are that activity A, B, and C 96 // need to overlay completely, but they can be partially overlay as freeform windows. 97 return startActivityInWindowingModeFullScreen(ActivityA.class); 98 } 99 100 /** Launch two Activities in two adjacent TaskFragments side-by-side. */ initializeSplitActivities()101 private void initializeSplitActivities() { 102 initializeSplitActivities(false /* showWhenLocked */); 103 } 104 105 /** 106 * Launch two Activities in two adjacent TaskFragments side-by-side and support to set the 107 * showWhenLocked attribute to Activity B. 108 */ initializeSplitActivities(boolean showWhenLocked)109 private void initializeSplitActivities(boolean showWhenLocked) { 110 final Rect activityBounds = mOwnerActivity.getWindowManager().getCurrentWindowMetrics() 111 .getBounds(); 112 updateSplitBounds(activityBounds); 113 114 final TaskFragmentCreationParams paramsA = generatePrimaryTaskFragParams(); 115 final TaskFragmentCreationParams paramsB = generateSideTaskFragParams(); 116 IBinder taskFragTokenA = paramsA.getFragmentToken(); 117 IBinder taskFragTokenB = paramsB.getFragmentToken(); 118 119 final WindowContainerTransaction wct = new WindowContainerTransaction() 120 .createTaskFragment(paramsA) 121 .reparentActivityToTaskFragment(taskFragTokenA, mOwnerToken) 122 .createTaskFragment(paramsB) 123 .setAdjacentTaskFragments(taskFragTokenA, taskFragTokenB, null /* params */); 124 125 final Intent intent = new Intent().setComponent(mActivityB); 126 if (showWhenLocked) { 127 intent.putExtra(EXTRA_SHOW_WHEN_LOCKED, true); 128 } 129 wct.startActivityInTaskFragment(taskFragTokenB, mOwnerToken, intent, 130 null /* activityOptions */); 131 132 mTaskFragmentOrganizer.setAppearedCount(2); 133 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, 134 false /* shouldApplyIndependently */); 135 mTaskFragmentOrganizer.waitForTaskFragmentCreated(); 136 137 final TaskFragmentInfo infoA = mTaskFragmentOrganizer.getTaskFragmentInfo( 138 taskFragTokenA); 139 final TaskFragmentInfo infoB = mTaskFragmentOrganizer.getTaskFragmentInfo( 140 taskFragTokenB); 141 142 assertNotEmptyTaskFragment(infoA, taskFragTokenA, mOwnerToken); 143 assertNotEmptyTaskFragment(infoB, taskFragTokenB); 144 145 mTaskFragA = new TaskFragmentRecord(infoA); 146 mTaskFragB = new TaskFragmentRecord(infoB); 147 148 waitAndAssertResumedActivity(mActivityA, "Activity A must still be resumed."); 149 waitAndAssertResumedActivity(mActivityB, "Activity B must still be resumed."); 150 151 mTaskFragmentOrganizer.resetLatch(); 152 } 153 154 /** 155 * Splits the {@code parentBounds} vertically to {@link #mPrimaryBounds} and 156 * {@link #mSideBounds}. {@link #mPrimaryRelativeBounds} and {@link #mSideRelativeBounds} will 157 * also be updated to the corresponding relative bounds in parent coordinate. 158 */ updateSplitBounds(@onNull Rect parentBounds)159 private void updateSplitBounds(@NonNull Rect parentBounds) { 160 parentBounds.splitVertically(mPrimaryBounds, mSideBounds); 161 mPrimaryRelativeBounds.set(mPrimaryBounds); 162 mPrimaryRelativeBounds.offsetTo(0, 0); 163 mSideRelativeBounds.set(mSideBounds); 164 mSideRelativeBounds.offsetTo(mSideBounds.left - mPrimaryBounds.left, 165 mSideBounds.top - mPrimaryBounds.top); 166 } 167 168 /** 169 * Verifies the behavior to launch Activity in the same TaskFragment as the owner Activity. 170 * <p> 171 * For example, given that Activity A and B are showed side-by-side, this test verifies 172 * the behavior to launch Activity C in the same TaskFragment as Activity A: 173 * <pre class="prettyprint"> 174 * |A|B| -> |C|B| 175 * </pre></p> 176 */ 177 @Test testActivityLaunchInSameSplitTaskFragment()178 public void testActivityLaunchInSameSplitTaskFragment() { 179 // Initialize test environment by launching Activity A and B side-by-side. 180 initializeSplitActivities(); 181 182 final IBinder taskFragTokenA = mTaskFragA.getTaskFragToken(); 183 final WindowContainerTransaction wct = new WindowContainerTransaction() 184 .startActivityInTaskFragment(taskFragTokenA, mOwnerToken, mIntent, 185 null /* activityOptions */); 186 187 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, 188 false /* shouldApplyIndependently */); 189 190 final TaskFragmentInfo infoA = mTaskFragmentOrganizer.waitForAndGetTaskFragmentInfo( 191 taskFragTokenA, info -> info.getActivities().size() == 2, 192 "getActivities from TaskFragment A must contain 2 activities"); 193 194 assertNotEmptyTaskFragment(infoA, taskFragTokenA, mOwnerToken); 195 196 waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed."); 197 waitAndAssertActivityState(mActivityA, STATE_STOPPED, 198 "Activity A is occluded by Activity C, so it must be stopped."); 199 waitAndAssertResumedActivity(mActivityB, "Activity B must be resumed."); 200 201 final TaskFragment taskFragmentA = mWmState.getTaskFragmentByActivity(mActivityA); 202 assertWithMessage("TaskFragmentA must contain Activity A and C") 203 .that(taskFragmentA.mActivities).containsExactly(mWmState.getActivity(mActivityA), 204 mWmState.getActivity(mActivityC)); 205 } 206 207 /** 208 * Verifies the behavior to launch Activity in the adjacent TaskFragment. 209 * <p> 210 * For example, given that Activity A and B are showed side-by-side, this test verifies 211 * the behavior to launch Activity C in the same TaskFragment as Activity B: 212 * <pre class="prettyprint"> 213 * |A|B| -> |A|C| 214 * </pre></p> 215 */ 216 @Test testActivityLaunchInAdjacentSplitTaskFragment()217 public void testActivityLaunchInAdjacentSplitTaskFragment() { 218 // Initialize test environment by launching Activity A and B side-by-side. 219 initializeSplitActivities(); 220 221 final IBinder taskFragTokenB = mTaskFragB.getTaskFragToken(); 222 final WindowContainerTransaction wct = new WindowContainerTransaction() 223 .startActivityInTaskFragment(taskFragTokenB, mOwnerToken, mIntent, 224 null /* activityOptions */); 225 226 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, 227 false /* shouldApplyIndependently */); 228 229 final TaskFragmentInfo infoB = mTaskFragmentOrganizer.waitForAndGetTaskFragmentInfo( 230 taskFragTokenB, info -> info.getActivities().size() == 2, 231 "getActivities from TaskFragment A must contain 2 activities"); 232 233 assertNotEmptyTaskFragment(infoB, taskFragTokenB); 234 235 waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed."); 236 waitAndAssertResumedActivity(mActivityA, "Activity A must be resumed."); 237 waitAndAssertActivityState(mActivityB, STATE_STOPPED, 238 "Activity B is occluded by Activity C, so it must be stopped."); 239 240 final TaskFragment taskFragmentB = mWmState.getTaskFragmentByActivity(mActivityB); 241 assertWithMessage("TaskFragmentB must contain Activity B and C") 242 .that(taskFragmentB.mActivities).containsExactly(mWmState.getActivity(mActivityB), 243 mWmState.getActivity(mActivityC)); 244 } 245 246 /** 247 * Verifies the behavior that the Activity instance in bottom TaskFragment calls 248 * {@link Context#startActivity(Intent)} to launch another Activity. 249 * <p> 250 * For example, given that Activity A and B are showed side-by-side, Activity A calls 251 * {@link Context#startActivity(Intent)} to launch Activity C. The expected behavior is that 252 * Activity C will be launch on top of Activity B as below: 253 * <pre class="prettyprint"> 254 * |A|B| -> |A|C| 255 * </pre> 256 * The reason is that TaskFragment B has higher z-order than TaskFragment A because we create 257 * TaskFragment B later than TaskFragment A. 258 * </p> 259 */ 260 @Test testActivityLaunchFromBottomTaskFragment()261 public void testActivityLaunchFromBottomTaskFragment() { 262 // Initialize test environment by launching Activity A and B side-by-side. 263 initializeSplitActivities(); 264 265 mOwnerActivity.startActivity(mIntent); 266 267 final IBinder taskFragTokenB = mTaskFragB.getTaskFragToken(); 268 final TaskFragmentInfo infoB = mTaskFragmentOrganizer.waitForAndGetTaskFragmentInfo( 269 taskFragTokenB, info -> info.getActivities().size() == 2, 270 "getActivities from TaskFragment A must contain 2 activities"); 271 272 assertNotEmptyTaskFragment(infoB, taskFragTokenB); 273 274 waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed."); 275 waitAndAssertResumedActivity(mActivityA, "Activity A must be resumed."); 276 waitAndAssertActivityState(mActivityB, STATE_STOPPED, 277 "Activity B is occluded by Activity C, so it must be stopped."); 278 279 final TaskFragment taskFragmentB = mWmState.getTaskFragmentByActivity(mActivityB); 280 assertWithMessage("TaskFragmentB must contain Activity B and C") 281 .that(taskFragmentB.mActivities).containsExactly(mWmState.getActivity(mActivityB), 282 mWmState.getActivity(mActivityC)); 283 } 284 285 /** 286 * Verifies the behavior of the activities in a TaskFragment that is sandwiched in adjacent 287 * TaskFragments. 288 */ 289 @Test testSandwichTaskFragmentInAdjacent()290 public void testSandwichTaskFragmentInAdjacent() { 291 // Initialize test environment by launching Activity A and B side-by-side. 292 initializeSplitActivities(); 293 294 final IBinder taskFragTokenA = mTaskFragA.getTaskFragToken(); 295 final TaskFragmentCreationParams paramsC = generateSideTaskFragParams(); 296 final IBinder taskFragTokenC = paramsC.getFragmentToken(); 297 final WindowContainerTransaction wct = new WindowContainerTransaction() 298 // Create the side TaskFragment for C and launch 299 .createTaskFragment(paramsC) 300 .startActivityInTaskFragment(taskFragTokenC, mOwnerToken, mIntent, 301 null /* activityOptions */) 302 .setAdjacentTaskFragments(taskFragTokenA, taskFragTokenC, null /* options */); 303 304 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, 305 false /* shouldApplyIndependently */); 306 // Wait for the TaskFragment of Activity C to be created. 307 mTaskFragmentOrganizer.waitForTaskFragmentCreated(); 308 309 waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed."); 310 waitAndAssertActivityState(mActivityB, STATE_STOPPED, 311 "Activity B is occluded by Activity C, so it must be stopped."); 312 waitAndAssertResumedActivity(mActivityA, "Activity A must be resumed."); 313 } 314 315 /** 316 * Verifies the behavior of the activities in a TaskFragment that is sandwiched in adjacent 317 * TaskFragments. It should be hidden even if part of it is not cover by the adjacent 318 * TaskFragment above. 319 */ 320 @Test testSandwichTaskFragmentInAdjacent_partialOccluding()321 public void testSandwichTaskFragmentInAdjacent_partialOccluding() { 322 // Initialize test environment by launching Activity A and B side-by-side. 323 initializeSplitActivities(); 324 325 final IBinder taskFragTokenA = mTaskFragA.getTaskFragToken(); 326 // TaskFragment C is not fully occluding TaskFragment B. 327 final Rect partialOccludingRelativeSideBounds = new Rect(mSideRelativeBounds); 328 partialOccludingRelativeSideBounds.left += 50; 329 final TaskFragmentCreationParams paramsC = mTaskFragmentOrganizer.generateTaskFragParams( 330 mOwnerToken, partialOccludingRelativeSideBounds, WINDOWING_MODE_MULTI_WINDOW); 331 final IBinder taskFragTokenC = paramsC.getFragmentToken(); 332 final WindowContainerTransaction wct = new WindowContainerTransaction() 333 // Create the side TaskFragment for C and launch 334 .createTaskFragment(paramsC) 335 .startActivityInTaskFragment(taskFragTokenC, mOwnerToken, mIntent, 336 null /* activityOptions */) 337 .setAdjacentTaskFragments(taskFragTokenA, taskFragTokenC, null /* options */); 338 339 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, 340 false /* shouldApplyIndependently */); 341 // Wait for the TaskFragment of Activity C to be created. 342 mTaskFragmentOrganizer.waitForTaskFragmentCreated(); 343 344 waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed."); 345 waitAndAssertActivityState(mActivityB, STATE_STOPPED, 346 "Activity B is occluded by Activity C, so it must be stopped."); 347 waitAndAssertResumedActivity(mActivityA, "Activity A must be resumed."); 348 } 349 350 /** 351 * Verifies the behavior to launch adjacent Activity to the adjacent TaskFragment. 352 * <p> 353 * For example, given that Activity A and B are showed side-by-side, this test verifies 354 * the behavior to launch the Activity C to the adjacent TaskFragment of the secondary 355 * TaskFragment, which Activity B is attached to. Then the secondary TaskFragment is shifted to 356 * occlude the primary TaskFragment, which Activity A is attached to, and the adjacent 357 * TaskFragment, which Activity C is attached to, is occupied the region where the secondary 358 * TaskFragment is located. This test is to verify the "shopping mode" scenario. 359 * <pre class="prettyprint"> 360 * |A|B| -> |B|C| 361 * </pre></p> 362 */ 363 @Test testAdjacentActivityLaunchFromSecondarySplitTaskFragment()364 public void testAdjacentActivityLaunchFromSecondarySplitTaskFragment() { 365 // Initialize test environment by launching Activity A and B side-by-side. 366 initializeSplitActivities(); 367 368 final IBinder taskFragTokenB = mTaskFragB.getTaskFragToken(); 369 final TaskFragmentCreationParams paramsC = generateSideTaskFragParams(); 370 final IBinder taskFragTokenC = paramsC.getFragmentToken(); 371 final WindowContainerTransaction wct = new WindowContainerTransaction() 372 // Move TaskFragment B to the primaryBounds 373 .setRelativeBounds(mTaskFragB.getToken(), mPrimaryRelativeBounds) 374 // Create the side TaskFragment for C and launch 375 .createTaskFragment(paramsC) 376 .startActivityInTaskFragment(taskFragTokenC, mOwnerToken, mIntent, 377 null /* activityOptions */) 378 .setAdjacentTaskFragments(taskFragTokenB, taskFragTokenC, null /* options */); 379 380 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, 381 false /* shouldApplyIndependently */); 382 // Wait for the TaskFragment of Activity C to be created. 383 mTaskFragmentOrganizer.waitForTaskFragmentCreated(); 384 // Wait for the TaskFragment of Activity B to be changed. 385 mTaskFragmentOrganizer.waitForTaskFragmentInfoChanged(); 386 387 final TaskFragmentInfo infoB = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragTokenB); 388 final TaskFragmentInfo infoC = mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragTokenC); 389 390 assertNotEmptyTaskFragment(infoB, taskFragTokenB); 391 assertNotEmptyTaskFragment(infoC, taskFragTokenC); 392 393 mTaskFragB = new TaskFragmentRecord(infoB); 394 final TaskFragmentRecord taskFragC = new TaskFragmentRecord(infoC); 395 396 assertThat(mTaskFragB.getBounds()).isEqualTo(mPrimaryBounds); 397 assertThat(taskFragC.getBounds()).isEqualTo(mSideBounds); 398 399 waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed."); 400 waitAndAssertActivityState(mActivityA, STATE_STOPPED, 401 "Activity A is occluded by Activity C, so it must be stopped."); 402 waitAndAssertResumedActivity(mActivityB, "Activity B must be resumed."); 403 } 404 405 /** 406 * Verifies the behavior to launch Activity in expanded TaskFragment. 407 * <p> 408 * For example, given that Activity A and B are showed side-by-side, this test verifies 409 * the behavior to launch Activity C in the TaskFragment which fills the Task bounds of owner 410 * Activity: 411 * <pre class="prettyprint"> 412 * |A|B| -> |C| 413 * </pre></p> 414 */ 415 @Test testActivityLaunchInExpandedTaskFragment()416 public void testActivityLaunchInExpandedTaskFragment() { 417 // Initialize test environment by launching Activity A and B side-by-side. 418 initializeSplitActivities(); 419 420 testActivityLaunchInExpandedTaskFragmentInternal(); 421 } 422 testActivityLaunchInExpandedTaskFragmentInternal()423 private void testActivityLaunchInExpandedTaskFragmentInternal() { 424 425 final TaskFragmentCreationParams fullScreenParamsC = mTaskFragmentOrganizer 426 .generateTaskFragParams(mOwnerToken, new Rect(), WINDOWING_MODE_FULLSCREEN); 427 final IBinder taskFragTokenC = fullScreenParamsC.getFragmentToken(); 428 final WindowContainerTransaction wct = new WindowContainerTransaction() 429 .createTaskFragment(fullScreenParamsC) 430 .startActivityInTaskFragment(taskFragTokenC, mOwnerToken, mIntent, 431 null /* activityOptions */); 432 433 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_OPEN, 434 false /* shouldApplyIndependently */); 435 436 mTaskFragmentOrganizer.waitForTaskFragmentCreated(); 437 438 assertNotEmptyTaskFragment(mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragTokenC), 439 taskFragTokenC); 440 441 waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed."); 442 waitAndAssertActivityState(mActivityA, STATE_STOPPED, 443 "Activity A is occluded by Activity C, so it must be stopped."); 444 waitAndAssertActivityState(mActivityB, STATE_STOPPED, 445 "Activity B is occluded by Activity C, so it must be stopped."); 446 } 447 448 /** 449 * Verifies the show-when-locked behavior while launch embedded activities. Don't show the 450 * embedded activities even if one of Activity has showWhenLocked flag. 451 */ 452 @Test testLaunchEmbeddedActivityWithShowWhenLocked()453 public void testLaunchEmbeddedActivityWithShowWhenLocked() { 454 assumeTrue(supportsLockScreen()); 455 456 final LockScreenSession lockScreenSession = createManagedLockScreenSession(); 457 // Initialize test environment by launching Activity A and B (with showWhenLocked) 458 // side-by-side. 459 initializeSplitActivities(true /* showWhenLocked */); 460 461 lockScreenSession.sleepDevice(); 462 lockScreenSession.wakeUpDevice(); 463 464 waitAndAssertActivityState(mActivityA, STATE_STOPPED,"Activity A must be stopped"); 465 waitAndAssertActivityState(mActivityB, STATE_STOPPED,"Activity B must be stopped"); 466 } 467 468 /** 469 * Verifies the show-when-locked behavior while launch embedded activities. Don't show the 470 * embedded activities if the activities don't have showWhenLocked flag. 471 */ 472 @Test testLaunchEmbeddedActivitiesWithoutShowWhenLocked()473 public void testLaunchEmbeddedActivitiesWithoutShowWhenLocked() { 474 assumeTrue(supportsLockScreen()); 475 476 final LockScreenSession lockScreenSession = createManagedLockScreenSession(); 477 // Initialize test environment by launching Activity A and B side-by-side. 478 initializeSplitActivities(); 479 480 lockScreenSession.sleepDevice(); 481 lockScreenSession.wakeUpDevice(); 482 483 waitAndAssertActivityState(mActivityA, STATE_STOPPED,"Activity A must be stopped"); 484 waitAndAssertActivityState(mActivityB, STATE_STOPPED,"Activity B must be stopped"); 485 } 486 487 /** 488 * Verifies the show-when-locked behavior while launch embedded activities. The embedded 489 * activities should be shown on top of the lock screen since they have the showWhenLocked flag. 490 * Don't show the embedded activities even if one of Activity has showWhenLocked flag. 491 */ 492 @Test testLaunchEmbeddedActivitiesWithShowWhenLocked()493 public void testLaunchEmbeddedActivitiesWithShowWhenLocked() { 494 assumeTrue(supportsLockScreen()); 495 496 final LockScreenSession lockScreenSession = createManagedLockScreenSession(); 497 // Initialize test environment by launching Activity A and B side-by-side. 498 mOwnerActivity.setShowWhenLocked(true); 499 initializeSplitActivities(true /* showWhenLocked */); 500 501 lockScreenSession.sleepDevice(); 502 lockScreenSession.wakeUpDevice(); 503 504 waitAndAssertResumedActivity(mActivityA, "Activity A must be resumed."); 505 waitAndAssertResumedActivity(mActivityB, "Activity B must be resumed."); 506 507 // Launch Activity C without show-when-lock and verifies that both activities are stopped. 508 mOwnerActivity.startActivity(mIntent); 509 waitAndAssertActivityState(mActivityA, STATE_STOPPED, "Activity A must be stopped"); 510 waitAndAssertActivityState(mActivityC, STATE_STOPPED, "Activity C must be stopped"); 511 } 512 513 /** 514 * Verifies the Activity in primary TaskFragment is no longer focused after clear adjacent 515 * TaskFragments. 516 */ 517 @Test testResetFocusedAppAfterClearAdjacentTaskFragment()518 public void testResetFocusedAppAfterClearAdjacentTaskFragment() { 519 // Initialize test environment by launching Activity A and B side-by-side. 520 initializeSplitActivities(); 521 522 // Request the focus on the primary TaskFragment 523 WindowContainerTransaction wct = new WindowContainerTransaction() 524 .requestFocusOnTaskFragment(mTaskFragA.getTaskFragToken()); 525 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, 526 false /* shouldApplyIndependently */); 527 waitForActivityFocused(5000, mActivityA); 528 assertThat(mWmState.getFocusedApp()).isEqualTo(mActivityA.flattenToShortString()); 529 530 // Expand top TaskFragment and clear the adjacent TaskFragments to have the two 531 // TaskFragment stacked. 532 wct = new WindowContainerTransaction() 533 .setRelativeBounds(mTaskFragB.getToken(), new Rect()) 534 .setWindowingMode(mTaskFragB.getToken(), WINDOWING_MODE_UNDEFINED) 535 .clearAdjacentTaskFragments(mTaskFragA.getTaskFragToken()); 536 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, 537 false /* shouldApplyIndependently */); 538 539 // Ensure the Activity on primary TaskFragment is stopped and no longer focused. 540 waitAndAssertActivityState(mActivityA, STATE_STOPPED, "Activity A must be stopped"); 541 assertThat(mWmState.getFocusedApp()).isNotEqualTo(mActivityA.flattenToShortString()); 542 assertThat(mWmState.getFocusedWindow()).isEqualTo(mActivityB.flattenToShortString()); 543 } 544 545 /** 546 * Verifies an Activity below adjacent translucent TaskFragments is visible. 547 */ 548 @Test testTranslucentAdjacentTaskFragment()549 public void testTranslucentAdjacentTaskFragment() { 550 // Create ActivityB on top of ActivityA. 551 // Make sure ActivityB is launched into the same task as ActivityA so that we can reparent 552 // it to TaskFragment in the same task later. 553 Activity activityB = startActivity(ActivityB.class, DEFAULT_DISPLAY, true /* hasFocus */, 554 WINDOWING_MODE_FULLSCREEN, mOwnerActivity.getTaskId()); 555 waitAndAssertResumedActivity(mActivityB, "Activity B must be resumed."); 556 waitAndAssertActivityState(mActivityA, STATE_STOPPED, 557 "Activity A is occluded by Activity B, so it must be stopped."); 558 559 // Create two adjacent TaskFragments, making ActivityB and TranslucentActivity 560 // displayed side-by-side (ActivityB|TranslucentActivity). 561 updateSplitBounds(mOwnerActivity.getWindowManager().getCurrentWindowMetrics().getBounds()); 562 final TaskFragmentCreationParams primaryParams = generatePrimaryTaskFragParams(); 563 final TaskFragmentCreationParams secondaryParams = generateSideTaskFragParams(); 564 IBinder primaryToken = primaryParams.getFragmentToken(); 565 IBinder secondaryToken = secondaryParams.getFragmentToken(); 566 567 final ComponentName translucentActivity = new ComponentName(mContext, 568 TranslucentActivity.class); 569 final Intent intent = new Intent().setComponent(translucentActivity); 570 WindowContainerTransaction wct = new WindowContainerTransaction() 571 .createTaskFragment(primaryParams) 572 .reparentActivityToTaskFragment(primaryToken, getActivityToken(activityB)) 573 .createTaskFragment(secondaryParams) 574 .setAdjacentTaskFragments(primaryToken, secondaryToken, null /* params */) 575 .startActivityInTaskFragment(secondaryToken, mOwnerToken, intent, 576 null /* activityOptions */); 577 mTaskFragmentOrganizer.applyTransaction(wct, TASK_FRAGMENT_TRANSIT_CHANGE, 578 false /* shouldApplyIndependently */); 579 580 waitAndAssertResumedActivity(translucentActivity, "TranslucentActivity must be resumed."); 581 waitAndAssertResumedActivity(mActivityB, "Activity B must be resumed."); 582 waitAndAssertActivityState(mActivityA, STATE_STARTED, 583 "Activity A is not fully occluded and must be visible and started"); 584 } 585 586 @Test testIgnoreOrientationRequestForActivityEmbeddingSplits()587 public void testIgnoreOrientationRequestForActivityEmbeddingSplits() { 588 // Skip the test on devices without WM extensions. 589 assumeTrue(SystemProperties.getBoolean("persist.wm.extensions.enabled", false)); 590 591 // Skip the test if this is not a large screen device 592 assumeTrue(getDisplayConfiguration().smallestScreenWidthDp 593 >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP); 594 595 // Rotate the device to landscape 596 final RotationSession rotationSession = createManagedRotationSession(); 597 final int[] rotations = { ROTATION_0, ROTATION_90 }; 598 for (final int rotation : rotations) { 599 if (getDisplayConfiguration().orientation == ORIENTATION_LANDSCAPE) { 600 break; 601 } 602 rotationSession.set(rotation); 603 } 604 assumeTrue(getDisplayConfiguration().orientation == ORIENTATION_LANDSCAPE); 605 606 // Launch a fixed-portrait activity 607 Activity activity = startActivityInWindowingModeFullScreen(PortraitActivity.class); 608 609 // The activity should be displayed in portrait while the display is remained in landscape. 610 assertWithMessage("The activity should be displayed in portrait") 611 .that(activity.getResources().getConfiguration().orientation) 612 .isEqualTo(ORIENTATION_PORTRAIT); 613 assertWithMessage("The display should be remained in landscape") 614 .that(getDisplayConfiguration().orientation) 615 .isEqualTo(ORIENTATION_LANDSCAPE); 616 } 617 getDisplayConfiguration()618 private Configuration getDisplayConfiguration() { 619 mWmState.computeState(); 620 WindowManagerState.DisplayContent display = mWmState.getDisplay(DEFAULT_DISPLAY); 621 return display.mFullConfiguration; 622 } 623 624 /** 625 * Verifies starting an Activity on the adjacent TaskFragment and able to get the result. 626 */ 627 @Test testStartActivityForResultInAdjacentTaskFragment()628 public void testStartActivityForResultInAdjacentTaskFragment() { 629 // Initialize test environment by launching Activity A and B side-by-side. 630 initializeSplitActivities(); 631 632 // Start an Activity on the adjacent TaskFragment for result. 633 final Intent intent = new Intent(); 634 intent.setComponent(mActivityC); 635 intent.putExtra(EXTRA_SET_RESULT_AND_FINISH, true); 636 mOwnerActivity.startActivityForResult(intent, 1 /* requestCode */); 637 638 // Waits for the result 639 waitForOrFail("Wait for the result", 640 () -> ((SplitTestActivity) mOwnerActivity).getResultCode() == 100); 641 } 642 generatePrimaryTaskFragParams()643 private TaskFragmentCreationParams generatePrimaryTaskFragParams() { 644 return mTaskFragmentOrganizer.generateTaskFragParams(mOwnerToken, mPrimaryRelativeBounds, 645 WINDOWING_MODE_MULTI_WINDOW); 646 } 647 generateSideTaskFragParams()648 private TaskFragmentCreationParams generateSideTaskFragParams() { 649 return mTaskFragmentOrganizer.generateTaskFragParams(mOwnerToken, mSideRelativeBounds, 650 WINDOWING_MODE_MULTI_WINDOW); 651 } 652 653 private static class TaskFragmentRecord { 654 private final IBinder mTaskFragToken; 655 private final Rect mBounds = new Rect(); 656 private final WindowContainerToken mContainerToken; 657 TaskFragmentRecord(TaskFragmentInfo info)658 private TaskFragmentRecord(TaskFragmentInfo info) { 659 mTaskFragToken = info.getFragmentToken(); 660 mBounds.set(info.getConfiguration().windowConfiguration.getBounds()); 661 mContainerToken = info.getToken(); 662 } 663 getTaskFragToken()664 private IBinder getTaskFragToken() { 665 return mTaskFragToken; 666 } 667 getBounds()668 private Rect getBounds() { 669 return mBounds; 670 } 671 getToken()672 private WindowContainerToken getToken() { 673 return mContainerToken; 674 } 675 } 676 677 public static class ActivityA extends SplitTestActivity {} 678 public static class ActivityB extends SplitTestActivity {} 679 public static class ActivityC extends SplitTestActivity {} 680 public static class PortraitActivity extends SplitTestActivity {} 681 public static class TranslucentActivity extends SplitTestActivity {} 682 public static class SplitTestActivity extends FocusableActivity { 683 public static final String EXTRA_SHOW_WHEN_LOCKED = "showWhenLocked"; 684 public static final String EXTRA_SET_RESULT_AND_FINISH = "setResultAndFinish"; 685 686 private int mResultCode = -1; 687 @Override onCreate(Bundle icicle)688 protected void onCreate(Bundle icicle) { 689 super.onCreate(icicle); 690 if (getIntent().getBooleanExtra(EXTRA_SHOW_WHEN_LOCKED, false)) { 691 setShowWhenLocked(true); 692 } 693 } 694 695 @Override onResume()696 protected void onResume() { 697 super.onResume(); 698 if (getIntent().getBooleanExtra(EXTRA_SET_RESULT_AND_FINISH, false)) { 699 setResult(100); 700 finish(); 701 } 702 } 703 704 @Override onActivityResult(int requestCode, int resultCode, Intent data)705 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 706 super.onActivityResult(requestCode, resultCode, data); 707 mResultCode = resultCode; 708 } 709 getResultCode()710 public int getResultCode() { 711 return mResultCode; 712 } 713 } 714 } 715