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