1 /* 2 * Copyright (C) 2022 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 androidx.window.extensions.embedding; 18 19 import static android.app.ActivityManager.START_CANCELED; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 22 import static android.view.Display.DEFAULT_DISPLAY; 23 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT; 24 import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; 25 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; 26 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR; 27 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED; 28 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED; 29 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; 30 31 import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY; 32 import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY; 33 import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES; 34 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; 35 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; 36 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG; 37 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityBuilder; 38 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions; 39 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; 40 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder; 41 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder; 42 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; 43 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer; 44 import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; 45 import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS; 46 47 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; 48 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 49 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 50 51 import static com.google.common.truth.Truth.assertWithMessage; 52 53 import static org.junit.Assert.assertEquals; 54 import static org.junit.Assert.assertFalse; 55 import static org.junit.Assert.assertNotEquals; 56 import static org.junit.Assert.assertNotNull; 57 import static org.junit.Assert.assertNull; 58 import static org.junit.Assert.assertThrows; 59 import static org.junit.Assert.assertTrue; 60 import static org.mockito.ArgumentMatchers.any; 61 import static org.mockito.ArgumentMatchers.anyBoolean; 62 import static org.mockito.ArgumentMatchers.anyInt; 63 import static org.mockito.ArgumentMatchers.eq; 64 import static org.mockito.ArgumentMatchers.isNull; 65 import static org.mockito.Mockito.clearInvocations; 66 import static org.mockito.Mockito.doNothing; 67 import static org.mockito.Mockito.doReturn; 68 import static org.mockito.Mockito.mock; 69 import static org.mockito.Mockito.never; 70 import static org.mockito.Mockito.times; 71 72 import android.annotation.NonNull; 73 import android.app.Activity; 74 import android.app.ActivityOptions; 75 import android.content.ComponentName; 76 import android.content.Intent; 77 import android.content.pm.ActivityInfo; 78 import android.content.res.Configuration; 79 import android.content.res.Resources; 80 import android.graphics.Rect; 81 import android.os.Binder; 82 import android.os.Bundle; 83 import android.os.Handler; 84 import android.os.IBinder; 85 import android.platform.test.annotations.Presubmit; 86 import android.util.ArraySet; 87 import android.view.WindowInsets; 88 import android.view.WindowMetrics; 89 import android.window.TaskFragmentInfo; 90 import android.window.TaskFragmentOrganizer; 91 import android.window.TaskFragmentParentInfo; 92 import android.window.TaskFragmentTransaction; 93 import android.window.WindowContainerTransaction; 94 95 import androidx.test.core.app.ApplicationProvider; 96 import androidx.test.ext.junit.runners.AndroidJUnit4; 97 import androidx.test.filters.SmallTest; 98 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; 99 import androidx.window.extensions.layout.WindowLayoutComponentImpl; 100 import androidx.window.extensions.layout.WindowLayoutInfo; 101 102 import org.junit.Before; 103 import org.junit.Test; 104 import org.junit.runner.RunWith; 105 import org.mockito.ArgumentCaptor; 106 import org.mockito.Mock; 107 import org.mockito.MockitoAnnotations; 108 109 import java.util.ArrayList; 110 import java.util.Collections; 111 import java.util.List; 112 import java.util.Set; 113 import java.util.function.Consumer; 114 115 /** 116 * Test class for {@link SplitController}. 117 * 118 * Build/Install/Run: 119 * atest WMJetpackUnitTests:SplitControllerTest 120 */ 121 // Suppress GuardedBy warning on unit tests 122 @SuppressWarnings("GuardedBy") 123 @Presubmit 124 @SmallTest 125 @RunWith(AndroidJUnit4.class) 126 public class SplitControllerTest { 127 private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent( 128 new ComponentName("test", "placeholder")); 129 130 private Activity mActivity; 131 @Mock 132 private Resources mActivityResources; 133 @Mock 134 private TaskFragmentInfo mInfo; 135 @Mock 136 private WindowContainerTransaction mTransaction; 137 @Mock 138 private Handler mHandler; 139 @Mock 140 private WindowLayoutComponentImpl mWindowLayoutComponent; 141 142 private SplitController mSplitController; 143 private SplitPresenter mSplitPresenter; 144 private Consumer<List<SplitInfo>> mEmbeddingCallback; 145 private List<SplitInfo> mSplitInfos; 146 private TransactionManager mTransactionManager; 147 148 @Before setUp()149 public void setUp() { 150 MockitoAnnotations.initMocks(this); 151 doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent) 152 .getCurrentWindowLayoutInfo(anyInt(), any()); 153 DeviceStateManagerFoldingFeatureProducer producer = 154 mock(DeviceStateManagerFoldingFeatureProducer.class); 155 mSplitController = new SplitController(mWindowLayoutComponent, producer); 156 mSplitPresenter = mSplitController.mPresenter; 157 mSplitInfos = new ArrayList<>(); 158 mEmbeddingCallback = splitInfos -> { 159 mSplitInfos.clear(); 160 mSplitInfos.addAll(splitInfos); 161 }; 162 mSplitController.setSplitInfoCallback(mEmbeddingCallback); 163 mTransactionManager = mSplitController.mTransactionManager; 164 spyOn(mSplitController); 165 spyOn(mSplitPresenter); 166 spyOn(mEmbeddingCallback); 167 spyOn(mTransactionManager); 168 doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean()); 169 final Configuration activityConfig = new Configuration(); 170 activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); 171 activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS); 172 doReturn(activityConfig).when(mActivityResources).getConfiguration(); 173 doReturn(mHandler).when(mSplitController).getHandler(); 174 mActivity = createMockActivity(); 175 } 176 177 @Test testGetTopActiveContainer()178 public void testGetTopActiveContainer() { 179 final TaskContainer taskContainer = createTestTaskContainer(); 180 // tf1 has no running activity so is not active. 181 final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, 182 new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */); 183 // tf2 has running activity so is active. 184 final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class); 185 doReturn(1).when(tf2).getRunningActivityCount(); 186 taskContainer.addTaskFragmentContainer(tf2); 187 // tf3 is finished so is not active. 188 final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class); 189 doReturn(true).when(tf3).isFinished(); 190 doReturn(false).when(tf3).isWaitingActivityAppear(); 191 taskContainer.addTaskFragmentContainer(tf3); 192 mSplitController.mTaskContainers.put(TASK_ID, taskContainer); 193 194 assertWithMessage("Must return tf2 because tf3 is not active.") 195 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2); 196 197 taskContainer.removeTaskFragmentContainer(tf3); 198 199 assertWithMessage("Must return tf2 because tf2 has running activity.") 200 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2); 201 202 taskContainer.removeTaskFragmentContainer(tf2); 203 204 assertWithMessage("Must return tf because we are waiting for tf1 to appear.") 205 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1); 206 207 final TaskFragmentInfo info = mock(TaskFragmentInfo.class); 208 doReturn(new ArrayList<>()).when(info).getActivities(); 209 doReturn(true).when(info).isEmpty(); 210 tf1.setInfo(mTransaction, info); 211 212 assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after" 213 + " creation.") 214 .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1); 215 216 doReturn(false).when(info).isEmpty(); 217 tf1.setInfo(mTransaction, info); 218 219 assertWithMessage("Must return null because tf1 becomes empty.") 220 .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull(); 221 } 222 223 @Test testOnTaskFragmentVanished()224 public void testOnTaskFragmentVanished() { 225 final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); 226 doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken(); 227 228 // The TaskFragment has been removed in the server, we only need to cleanup the reference. 229 mSplitController.onTaskFragmentVanished(mTransaction, mInfo); 230 231 verify(mSplitPresenter, never()).deleteTaskFragment(any(), any()); 232 verify(mSplitController).removeContainer(tf); 233 verify(mTransaction, never()).finishActivity(any()); 234 } 235 236 @Test testOnTaskFragmentAppearEmptyTimeout()237 public void testOnTaskFragmentAppearEmptyTimeout() { 238 // Setup to make sure a transaction record is started. 239 mTransactionManager.startNewTransaction(); 240 final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); 241 doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any()); 242 mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf); 243 244 verify(mSplitPresenter).cleanupContainer(mTransaction, tf, 245 false /* shouldFinishDependent */); 246 } 247 248 @Test testOnActivityDestroyed()249 public void testOnActivityDestroyed() { 250 doReturn(new Binder()).when(mActivity).getActivityToken(); 251 final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); 252 253 assertTrue(tf.hasActivity(mActivity.getActivityToken())); 254 255 // When the activity is not finishing, do not clear the record. 256 doReturn(false).when(mActivity).isFinishing(); 257 mSplitController.onActivityDestroyed(mActivity); 258 259 assertTrue(tf.hasActivity(mActivity.getActivityToken())); 260 261 // Clear the record when the activity is finishing and destroyed. 262 doReturn(true).when(mActivity).isFinishing(); 263 mSplitController.onActivityDestroyed(mActivity); 264 265 assertFalse(tf.hasActivity(mActivity.getActivityToken())); 266 } 267 268 @Test testNewContainer()269 public void testNewContainer() { 270 // Must pass in a valid activity. 271 assertThrows(IllegalArgumentException.class, () -> 272 mSplitController.newContainer(null /* activity */, TASK_ID)); 273 assertThrows(IllegalArgumentException.class, () -> 274 mSplitController.newContainer(mActivity, null /* launchingActivity */, TASK_ID)); 275 276 final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, mActivity, 277 TASK_ID); 278 final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); 279 280 assertNotNull(tf); 281 assertNotNull(taskContainer); 282 assertEquals(TASK_BOUNDS, taskContainer.getTaskProperties().getConfiguration() 283 .windowConfiguration.getBounds()); 284 } 285 286 @Test testUpdateContainer()287 public void testUpdateContainer() { 288 // Make SplitController#launchPlaceholderIfNecessary(TaskFragmentContainer) return true 289 // and verify if shouldContainerBeExpanded() not called. 290 final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); 291 spyOn(tf); 292 doReturn(mActivity).when(tf).getTopNonFinishingActivity(); 293 doReturn(true).when(tf).isEmpty(); 294 doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mTransaction, 295 mActivity, false /* isOnCreated */); 296 doNothing().when(mSplitPresenter).updateSplitContainer(any(), any()); 297 298 mSplitController.updateContainer(mTransaction, tf); 299 300 verify(mSplitController, never()).shouldContainerBeExpanded(any()); 301 302 // Verify if tf should be expanded, getTopActiveContainer() won't be called 303 doReturn(null).when(tf).getTopNonFinishingActivity(); 304 doReturn(true).when(mSplitController).shouldContainerBeExpanded(tf); 305 306 mSplitController.updateContainer(mTransaction, tf); 307 308 verify(mSplitController, never()).getTopActiveContainer(TASK_ID); 309 310 // Verify if tf is not in split, dismissPlaceholderIfNecessary won't be called. 311 doReturn(false).when(mSplitController).shouldContainerBeExpanded(tf); 312 313 mSplitController.updateContainer(mTransaction, tf); 314 315 verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any()); 316 317 // Verify if tf is not in the top splitContainer, 318 final SplitContainer splitContainer = mock(SplitContainer.class); 319 doReturn(tf).when(splitContainer).getPrimaryContainer(); 320 doReturn(tf).when(splitContainer).getSecondaryContainer(); 321 doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer(); 322 doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule(); 323 final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); 324 taskContainer.addSplitContainer(splitContainer); 325 // Add a mock SplitContainer on top of splitContainer 326 final SplitContainer splitContainer2 = mock(SplitContainer.class); 327 taskContainer.addSplitContainer(splitContainer2); 328 329 mSplitController.updateContainer(mTransaction, tf); 330 331 verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any()); 332 333 // Verify if one or both containers in the top SplitContainer are finished, 334 // dismissPlaceholder() won't be called. 335 final ArrayList<SplitContainer> splitContainersToRemove = new ArrayList<>(); 336 splitContainersToRemove.add(splitContainer2); 337 taskContainer.removeSplitContainers(splitContainersToRemove); 338 doReturn(true).when(tf).isFinished(); 339 340 mSplitController.updateContainer(mTransaction, tf); 341 342 verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any()); 343 344 // Verify if placeholder should be dismissed, updateSplitContainer() won't be called. 345 doReturn(false).when(tf).isFinished(); 346 doReturn(true).when(mSplitController) 347 .dismissPlaceholderIfNecessary(mTransaction, splitContainer); 348 349 mSplitController.updateContainer(mTransaction, tf); 350 351 verify(mSplitPresenter, never()).updateSplitContainer(any(), any()); 352 353 // Verify if the top active split is updated if both of its containers are not finished. 354 doReturn(false).when(mSplitController) 355 .dismissPlaceholderIfNecessary(mTransaction, splitContainer); 356 357 mSplitController.updateContainer(mTransaction, tf); 358 359 verify(mSplitPresenter).updateSplitContainer(splitContainer, mTransaction); 360 } 361 362 @Test testUpdateContainer_skipIfTaskIsInvisible()363 public void testUpdateContainer_skipIfTaskIsInvisible() { 364 final Activity r0 = createMockActivity(); 365 final Activity r1 = createMockActivity(); 366 addSplitTaskFragments(r0, r1); 367 final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); 368 final TaskFragmentContainer taskFragmentContainer = 369 taskContainer.getTaskFragmentContainers().get(0); 370 spyOn(taskContainer); 371 372 // No update when the Task is invisible. 373 clearInvocations(mSplitPresenter); 374 doReturn(false).when(taskContainer).isVisible(); 375 mSplitController.updateContainer(mTransaction, taskFragmentContainer); 376 377 verify(mSplitPresenter, never()).updateSplitContainer(any(), any()); 378 379 // Update the split when the Task is visible. 380 doReturn(true).when(taskContainer).isVisible(); 381 mSplitController.updateContainer(mTransaction, taskFragmentContainer); 382 383 verify(mSplitPresenter).updateSplitContainer(taskContainer.getSplitContainers().get(0), 384 mTransaction); 385 } 386 387 @Test testOnStartActivityResultError()388 public void testOnStartActivityResultError() { 389 final Intent intent = new Intent(); 390 final TaskContainer taskContainer = createTestTaskContainer(); 391 final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, 392 intent, taskContainer, mSplitController, null /* pairedPrimaryContainer */); 393 final SplitController.ActivityStartMonitor monitor = 394 mSplitController.getActivityStartMonitor(); 395 396 container.setPendingAppearedIntent(intent); 397 final Bundle bundle = new Bundle(); 398 bundle.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, 399 container.getTaskFragmentToken()); 400 monitor.mCurrentIntent = intent; 401 doReturn(container).when(mSplitController).getContainer(any()); 402 403 monitor.onStartActivityResult(START_CANCELED, bundle); 404 assertNull(container.getPendingAppearedIntent()); 405 } 406 407 @Test testOnActivityCreated()408 public void testOnActivityCreated() { 409 mSplitController.onActivityCreated(mTransaction, mActivity); 410 411 // Disallow to split as primary because we want the new launch to be always on top. 412 verify(mSplitController).resolveActivityToContainer(mTransaction, mActivity, 413 false /* isOnReparent */); 414 } 415 416 @Test testOnActivityReparentedToTask_sameProcess()417 public void testOnActivityReparentedToTask_sameProcess() { 418 mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, new Intent(), 419 mActivity.getActivityToken()); 420 421 // Treated as on activity created, but allow to split as primary. 422 verify(mSplitController).resolveActivityToContainer(mTransaction, 423 mActivity, true /* isOnReparent */); 424 // Try to place the activity to the top TaskFragment when there is no matched rule. 425 verify(mSplitController).placeActivityInTopContainer(mTransaction, mActivity); 426 } 427 428 @Test testOnActivityReparentedToTask_diffProcess()429 public void testOnActivityReparentedToTask_diffProcess() { 430 // Create an empty TaskFragment to initialize for the Task. 431 mSplitController.newContainer(new Intent(), mActivity, TASK_ID); 432 final IBinder activityToken = new Binder(); 433 final Intent intent = new Intent(); 434 435 mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken); 436 437 // Treated as starting new intent 438 verify(mSplitController, never()).resolveActivityToContainer(any(), any(), anyBoolean()); 439 verify(mSplitController).resolveStartActivityIntent(any(), eq(TASK_ID), eq(intent), 440 isNull()); 441 } 442 443 @Test testResolveStartActivityIntent_withoutLaunchingActivity()444 public void testResolveStartActivityIntent_withoutLaunchingActivity() { 445 final Intent intent = new Intent(); 446 final ActivityRule expandRule = createActivityBuilder(r -> false, i -> i == intent) 447 .setShouldAlwaysExpand(true) 448 .build(); 449 mSplitController.setEmbeddingRules(Collections.singleton(expandRule)); 450 451 // No other activity available in the Task. 452 TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(mTransaction, 453 TASK_ID, intent, null /* launchingActivity */); 454 assertNull(container); 455 456 // Task contains another activity that can be used as owner activity. 457 createMockTaskFragmentContainer(mActivity); 458 container = mSplitController.resolveStartActivityIntent(mTransaction, 459 TASK_ID, intent, null /* launchingActivity */); 460 assertNotNull(container); 461 } 462 463 @Test testResolveStartActivityIntent_shouldExpand()464 public void testResolveStartActivityIntent_shouldExpand() { 465 final Intent intent = new Intent(); 466 setupExpandRule(intent); 467 final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( 468 mTransaction, TASK_ID, intent, mActivity); 469 470 assertNotNull(container); 471 assertTrue(container.areLastRequestedBoundsEqual(null)); 472 assertTrue(container.isLastRequestedWindowingModeEqual(WINDOWING_MODE_UNDEFINED)); 473 assertFalse(container.hasActivity(mActivity.getActivityToken())); 474 verify(mSplitPresenter).createTaskFragment(mTransaction, container.getTaskFragmentToken(), 475 mActivity.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED); 476 } 477 478 @Test testResolveStartActivityIntent_shouldSplitWithLaunchingActivity()479 public void testResolveStartActivityIntent_shouldSplitWithLaunchingActivity() { 480 final Intent intent = new Intent(); 481 setupSplitRule(mActivity, intent); 482 483 final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( 484 mTransaction, TASK_ID, intent, mActivity); 485 final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( 486 mActivity); 487 488 assertSplitPair(primaryContainer, container); 489 } 490 491 @Test testResolveStartActivityIntent_shouldSplitWithTopExpandActivity()492 public void testResolveStartActivityIntent_shouldSplitWithTopExpandActivity() { 493 final Intent intent = new Intent(); 494 setupSplitRule(mActivity, intent); 495 createMockTaskFragmentContainer(mActivity); 496 497 final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( 498 mTransaction, TASK_ID, intent, null /* launchingActivity */); 499 final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( 500 mActivity); 501 502 assertSplitPair(primaryContainer, container); 503 } 504 505 @Test testResolveStartActivityIntent_shouldSplitWithTopSecondaryActivity()506 public void testResolveStartActivityIntent_shouldSplitWithTopSecondaryActivity() { 507 final Intent intent = new Intent(); 508 setupSplitRule(mActivity, intent); 509 final Activity primaryActivity = createMockActivity(); 510 addSplitTaskFragments(primaryActivity, mActivity); 511 512 final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( 513 mTransaction, TASK_ID, intent, null /* launchingActivity */); 514 final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( 515 mActivity); 516 517 assertSplitPair(primaryContainer, container); 518 } 519 520 @Test testResolveStartActivityIntent_shouldSplitWithTopPrimaryActivity()521 public void testResolveStartActivityIntent_shouldSplitWithTopPrimaryActivity() { 522 final Intent intent = new Intent(); 523 setupSplitRule(mActivity, intent); 524 final Activity secondaryActivity = createMockActivity(); 525 addSplitTaskFragments(mActivity, secondaryActivity); 526 527 final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( 528 mTransaction, TASK_ID, intent, null /* launchingActivity */); 529 final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( 530 mActivity); 531 532 assertSplitPair(primaryContainer, container); 533 } 534 535 @Test testResolveStartActivityIntent_shouldLaunchInFullscreen()536 public void testResolveStartActivityIntent_shouldLaunchInFullscreen() { 537 final Intent intent = new Intent().setComponent( 538 new ComponentName(ApplicationProvider.getApplicationContext(), 539 MinimumDimensionActivity.class)); 540 setupSplitRule(mActivity, intent); 541 final Activity primaryActivity = createMockActivity(); 542 addSplitTaskFragments(primaryActivity, mActivity); 543 544 final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( 545 mTransaction, TASK_ID, intent, null /* launchingActivity */); 546 final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( 547 mActivity); 548 549 assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container)); 550 assertTrue(primaryContainer.areLastRequestedBoundsEqual(null)); 551 assertTrue(container.areLastRequestedBoundsEqual(null)); 552 } 553 554 @Test testResolveStartActivityIntent_shouldExpandSplitContainer()555 public void testResolveStartActivityIntent_shouldExpandSplitContainer() { 556 final Intent intent = new Intent().setComponent( 557 new ComponentName(ApplicationProvider.getApplicationContext(), 558 MinimumDimensionActivity.class)); 559 setupSplitRule(mActivity, intent, false /* clearTop */); 560 final Activity secondaryActivity = createMockActivity(); 561 addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */); 562 563 final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( 564 mTransaction, TASK_ID, intent, mActivity); 565 final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( 566 mActivity); 567 568 assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container)); 569 assertTrue(primaryContainer.areLastRequestedBoundsEqual(null)); 570 assertTrue(container.areLastRequestedBoundsEqual(null)); 571 } 572 573 @Test testResolveStartActivityIntent_noInfo_shouldCreateSplitContainer()574 public void testResolveStartActivityIntent_noInfo_shouldCreateSplitContainer() { 575 final Intent intent = new Intent().setComponent( 576 new ComponentName(ApplicationProvider.getApplicationContext(), 577 MinimumDimensionActivity.class)); 578 setupSplitRule(mActivity, intent, false /* clearTop */); 579 final Activity secondaryActivity = createMockActivity(); 580 addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */); 581 582 final TaskFragmentContainer secondaryContainer = mSplitController 583 .getContainerWithActivity(secondaryActivity); 584 secondaryContainer.mInfo = null; 585 586 final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( 587 mTransaction, TASK_ID, intent, mActivity); 588 final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( 589 mActivity); 590 591 assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container)); 592 assertTrue(primaryContainer.areLastRequestedBoundsEqual(null)); 593 assertTrue(container.areLastRequestedBoundsEqual(null)); 594 assertNotEquals(container, secondaryContainer); 595 } 596 597 @Test testResolveStartActivityIntent_skipIfPinned()598 public void testResolveStartActivityIntent_skipIfPinned() { 599 final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity); 600 final TaskContainer taskContainer = container.getTaskContainer(); 601 spyOn(taskContainer); 602 final Intent intent = new Intent(); 603 setupSplitRule(mActivity, intent); 604 doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container); 605 assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent, 606 mActivity)); 607 } 608 609 @Test testPlaceActivityInTopContainer()610 public void testPlaceActivityInTopContainer() { 611 mSplitController.placeActivityInTopContainer(mTransaction, mActivity); 612 613 verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any()); 614 615 // Place in the top container if there is no other rule matched. 616 final TaskFragmentContainer topContainer = mSplitController 617 .newContainer(new Intent(), mActivity, TASK_ID); 618 mSplitController.placeActivityInTopContainer(mTransaction, mActivity); 619 620 verify(mTransaction).reparentActivityToTaskFragment(topContainer.getTaskFragmentToken(), 621 mActivity.getActivityToken()); 622 623 // Not reparent if activity is in a TaskFragment. 624 clearInvocations(mTransaction); 625 mSplitController.newContainer(mActivity, TASK_ID); 626 mSplitController.placeActivityInTopContainer(mTransaction, mActivity); 627 628 verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any()); 629 } 630 631 @Test testResolveActivityToContainer_noRuleMatched()632 public void testResolveActivityToContainer_noRuleMatched() { 633 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 634 false /* isOnReparent */); 635 636 assertFalse(result); 637 verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any()); 638 } 639 640 @Test testResolveActivityToContainer_expandRule_notInTaskFragment()641 public void testResolveActivityToContainer_expandRule_notInTaskFragment() { 642 setupExpandRule(mActivity); 643 644 // When the activity is not in any TaskFragment, create a new expanded TaskFragment for it. 645 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 646 false /* isOnReparent */); 647 final TaskFragmentContainer container = mSplitController.getContainerWithActivity( 648 mActivity); 649 650 assertTrue(result); 651 assertNotNull(container); 652 verify(mSplitController).newContainer(mActivity, TASK_ID); 653 verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(), 654 mActivity); 655 } 656 657 @Test testResolveActivityToContainer_expandRule_inSingleTaskFragment()658 public void testResolveActivityToContainer_expandRule_inSingleTaskFragment() { 659 setupExpandRule(mActivity); 660 661 // When the activity is not in any TaskFragment, create a new expanded TaskFragment for it. 662 final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID); 663 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 664 false /* isOnReparent */); 665 666 assertTrue(result); 667 verify(mSplitPresenter).expandTaskFragment(mTransaction, container.getTaskFragmentToken()); 668 } 669 670 @Test testResolveActivityToContainer_expandRule_inSplitTaskFragment()671 public void testResolveActivityToContainer_expandRule_inSplitTaskFragment() { 672 setupExpandRule(mActivity); 673 674 // When the activity is not in any TaskFragment, create a new expanded TaskFragment for it. 675 final Activity activity = createMockActivity(); 676 addSplitTaskFragments(activity, mActivity); 677 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 678 false /* isOnReparent */); 679 final TaskFragmentContainer container = mSplitController.getContainerWithActivity( 680 mActivity); 681 682 assertTrue(result); 683 assertNotNull(container); 684 verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(), 685 mActivity); 686 } 687 688 @Test testResolveActivityToContainer_placeholderRule_notInTaskFragment()689 public void testResolveActivityToContainer_placeholderRule_notInTaskFragment() { 690 // Setup to make sure a transaction record is started. 691 mTransactionManager.startNewTransaction(); 692 setupPlaceholderRule(mActivity); 693 final SplitPlaceholderRule placeholderRule = 694 (SplitPlaceholderRule) mSplitController.getSplitRules().get(0); 695 696 // Launch placeholder if the activity is not in any TaskFragment. 697 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 698 false /* isOnReparent */); 699 700 assertTrue(result); 701 verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, 702 mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), 703 placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */); 704 } 705 706 @Test testResolveActivityToContainer_placeholderRule_inOccludedTaskFragment()707 public void testResolveActivityToContainer_placeholderRule_inOccludedTaskFragment() { 708 setupPlaceholderRule(mActivity); 709 710 // Don't launch placeholder if the activity is not in the topmost active TaskFragment. 711 final Activity activity = createMockActivity(); 712 mSplitController.newContainer(mActivity, TASK_ID); 713 mSplitController.newContainer(activity, TASK_ID); 714 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 715 false /* isOnReparent */); 716 717 assertTrue(result); 718 verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(), 719 any(), anyBoolean()); 720 } 721 722 @Test testResolveActivityToContainer_placeholderRule_inTopMostTaskFragment()723 public void testResolveActivityToContainer_placeholderRule_inTopMostTaskFragment() { 724 // Setup to make sure a transaction record is started. 725 mTransactionManager.startNewTransaction(); 726 setupPlaceholderRule(mActivity); 727 final SplitPlaceholderRule placeholderRule = 728 (SplitPlaceholderRule) mSplitController.getSplitRules().get(0); 729 730 // Launch placeholder if the activity is in the topmost expanded TaskFragment. 731 mSplitController.newContainer(mActivity, TASK_ID); 732 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 733 false /* isOnReparent */); 734 735 assertTrue(result); 736 verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, 737 mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), 738 placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */); 739 } 740 741 @Test testResolveActivityToContainer_placeholderRule_inPrimarySplit()742 public void testResolveActivityToContainer_placeholderRule_inPrimarySplit() { 743 setupPlaceholderRule(mActivity); 744 745 // Don't launch placeholder if the activity is in primary split. 746 final Activity secondaryActivity = createMockActivity(); 747 addSplitTaskFragments(mActivity, secondaryActivity); 748 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 749 false /* isOnReparent */); 750 751 assertTrue(result); 752 verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(), 753 any(), anyBoolean()); 754 } 755 756 @Test testResolveActivityToContainer_placeholderRule_inSecondarySplit()757 public void testResolveActivityToContainer_placeholderRule_inSecondarySplit() { 758 // Setup to make sure a transaction record is started. 759 mTransactionManager.startNewTransaction(); 760 setupPlaceholderRule(mActivity); 761 final SplitPlaceholderRule placeholderRule = 762 (SplitPlaceholderRule) mSplitController.getSplitRules().get(0); 763 764 // Launch placeholder if the activity is in secondary split. 765 final Activity primaryActivity = createMockActivity(); 766 addSplitTaskFragments(primaryActivity, mActivity); 767 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 768 false /* isOnReparent */); 769 770 assertTrue(result); 771 verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, 772 mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), 773 placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */); 774 } 775 776 @Test testResolveActivityToContainer_splitRule_inPrimarySplitWithRuleMatched()777 public void testResolveActivityToContainer_splitRule_inPrimarySplitWithRuleMatched() { 778 final Intent secondaryIntent = new Intent(); 779 setupSplitRule(mActivity, secondaryIntent); 780 final SplitPairRule splitRule = (SplitPairRule) mSplitController.getSplitRules().get(0); 781 782 // Activity is already in primary split, no need to create new split. 783 final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity, 784 TASK_ID); 785 final TaskFragmentContainer secondaryContainer = mSplitController.newContainer( 786 secondaryIntent, mActivity, TASK_ID); 787 mSplitController.registerSplit( 788 mTransaction, 789 primaryContainer, 790 mActivity, 791 secondaryContainer, 792 splitRule, 793 SPLIT_ATTRIBUTES); 794 clearInvocations(mSplitController); 795 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 796 false /* isOnReparent */); 797 798 assertTrue(result); 799 verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any()); 800 verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); 801 } 802 803 @Test testResolveActivityToContainer_splitRule_inPrimarySplitWithNoRuleMatched()804 public void testResolveActivityToContainer_splitRule_inPrimarySplitWithNoRuleMatched() { 805 final Intent secondaryIntent = new Intent(); 806 setupSplitRule(mActivity, secondaryIntent); 807 final SplitPairRule splitRule = (SplitPairRule) mSplitController.getSplitRules().get(0); 808 809 // The new launched activity is in primary split, but there is no rule for it to split with 810 // the secondary, so return false. 811 final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity, 812 TASK_ID); 813 final TaskFragmentContainer secondaryContainer = mSplitController.newContainer( 814 secondaryIntent, mActivity, TASK_ID); 815 mSplitController.registerSplit( 816 mTransaction, 817 primaryContainer, 818 mActivity, 819 secondaryContainer, 820 splitRule, 821 SPLIT_ATTRIBUTES); 822 final Activity launchedActivity = createMockActivity(); 823 primaryContainer.addPendingAppearedActivity(launchedActivity); 824 825 assertTrue(mSplitController.resolveActivityToContainer(mTransaction, launchedActivity, 826 false /* isOnReparent */)); 827 } 828 829 @Test testResolveActivityToContainer_splitRule_inSecondarySplitWithRuleMatched()830 public void testResolveActivityToContainer_splitRule_inSecondarySplitWithRuleMatched() { 831 final Activity primaryActivity = createMockActivity(); 832 setupSplitRule(primaryActivity, mActivity); 833 834 // Activity is already in secondary split, no need to create new split. 835 addSplitTaskFragments(primaryActivity, mActivity); 836 clearInvocations(mSplitController); 837 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 838 false /* isOnReparent */); 839 840 assertTrue(result); 841 verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any()); 842 verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); 843 } 844 845 @Test testResolveActivityToContainer_splitRule_inSecondarySplitWithNoRuleMatched()846 public void testResolveActivityToContainer_splitRule_inSecondarySplitWithNoRuleMatched() { 847 final Activity primaryActivity = createMockActivity(); 848 final Activity secondaryActivity = createMockActivity(); 849 setupSplitRule(primaryActivity, secondaryActivity); 850 851 // Activity is in secondary split, but there is no rule to split it with primary. 852 addSplitTaskFragments(primaryActivity, secondaryActivity); 853 mSplitController.getContainerWithActivity(secondaryActivity) 854 .addPendingAppearedActivity(mActivity); 855 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 856 false /* isOnReparent */); 857 858 assertFalse(result); 859 } 860 861 @Test testResolveActivityToContainer_placeholderRule_isPlaceholderWithRuleMatched()862 public void testResolveActivityToContainer_placeholderRule_isPlaceholderWithRuleMatched() { 863 final Activity primaryActivity = createMockActivity(); 864 setupPlaceholderRule(primaryActivity); 865 final SplitPlaceholderRule placeholderRule = 866 (SplitPlaceholderRule) mSplitController.getSplitRules().get(0); 867 doReturn(PLACEHOLDER_INTENT).when(mActivity).getIntent(); 868 869 // Activity is a placeholder. 870 final TaskFragmentContainer primaryContainer = mSplitController.newContainer( 871 primaryActivity, TASK_ID); 872 final TaskFragmentContainer secondaryContainer = mSplitController.newContainer(mActivity, 873 TASK_ID); 874 mSplitController.registerSplit( 875 mTransaction, 876 primaryContainer, 877 mActivity, 878 secondaryContainer, 879 placeholderRule, 880 SPLIT_ATTRIBUTES); 881 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 882 false /* isOnReparent */); 883 884 assertTrue(result); 885 } 886 887 @Test testResolveActivityToContainer_splitRule_splitWithActivityBelowAsSecondary()888 public void testResolveActivityToContainer_splitRule_splitWithActivityBelowAsSecondary() { 889 final Activity activityBelow = createMockActivity(); 890 setupSplitRule(activityBelow, mActivity); 891 892 final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, 893 TASK_ID); 894 container.addPendingAppearedActivity(mActivity); 895 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 896 false /* isOnReparent */); 897 898 assertTrue(result); 899 assertSplitPair(activityBelow, mActivity); 900 } 901 902 @Test testResolveActivityToContainer_splitRule_splitWithActivityBelowAsPrimary()903 public void testResolveActivityToContainer_splitRule_splitWithActivityBelowAsPrimary() { 904 final Activity activityBelow = createMockActivity(); 905 setupSplitRule(mActivity, activityBelow); 906 907 // Disallow to split as primary. 908 final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, 909 TASK_ID); 910 container.addPendingAppearedActivity(mActivity); 911 boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 912 false /* isOnReparent */); 913 914 assertFalse(result); 915 assertEquals(container, mSplitController.getContainerWithActivity(mActivity)); 916 917 // Allow to split as primary. 918 result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 919 true /* isOnReparent */); 920 921 assertTrue(result); 922 assertSplitPair(mActivity, activityBelow); 923 } 924 925 @Test testResolveActivityToContainer_splitRule_splitWithCurrentPrimaryAsSecondary()926 public void testResolveActivityToContainer_splitRule_splitWithCurrentPrimaryAsSecondary() { 927 final Activity primaryActivity = createMockActivity(); 928 setupSplitRule(primaryActivity, mActivity); 929 930 final Activity activityBelow = createMockActivity(); 931 addSplitTaskFragments(primaryActivity, activityBelow); 932 final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( 933 primaryActivity); 934 final TaskFragmentContainer secondaryContainer = mSplitController.getContainerWithActivity( 935 activityBelow); 936 secondaryContainer.addPendingAppearedActivity(mActivity); 937 final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 938 false /* isOnReparent */); 939 final TaskFragmentContainer container = mSplitController.getContainerWithActivity( 940 mActivity); 941 942 assertTrue(result); 943 // TODO(b/231845476) we should always respect clearTop. 944 // assertNotEquals(secondaryContainer, container); 945 assertSplitPair(primaryContainer, container); 946 } 947 948 @Test testResolveActivityToContainer_splitRule_splitWithCurrentPrimaryAsPrimary()949 public void testResolveActivityToContainer_splitRule_splitWithCurrentPrimaryAsPrimary() { 950 final Activity primaryActivity = createMockActivity(); 951 setupSplitRule(mActivity, primaryActivity); 952 953 final Activity activityBelow = createMockActivity(); 954 addSplitTaskFragments(primaryActivity, activityBelow); 955 final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( 956 primaryActivity); 957 primaryContainer.addPendingAppearedActivity(mActivity); 958 boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 959 false /* isOnReparent */); 960 961 assertTrue(result); 962 assertEquals(primaryContainer, mSplitController.getContainerWithActivity(mActivity)); 963 964 965 result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 966 true /* isOnReparent */); 967 968 assertTrue(result); 969 assertSplitPair(mActivity, primaryActivity); 970 } 971 972 @Test testResolveActivityToContainer_primaryActivityMinDimensionsNotSatisfied()973 public void testResolveActivityToContainer_primaryActivityMinDimensionsNotSatisfied() { 974 final Activity activityBelow = createMockActivity(); 975 setupSplitRule(mActivity, activityBelow); 976 977 doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo(); 978 979 final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, 980 TASK_ID); 981 container.addPendingAppearedActivity(mActivity); 982 983 // Allow to split as primary. 984 boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 985 true /* isOnReparent */); 986 987 assertTrue(result); 988 assertSplitPair(mActivity, activityBelow, true /* matchParentBounds */); 989 } 990 991 @Test testResolveActivityToContainer_secondaryActivityMinDimensionsNotSatisfied()992 public void testResolveActivityToContainer_secondaryActivityMinDimensionsNotSatisfied() { 993 final Activity activityBelow = createMockActivity(); 994 setupSplitRule(activityBelow, mActivity); 995 996 doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo(); 997 998 final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, 999 TASK_ID); 1000 container.addPendingAppearedActivity(mActivity); 1001 1002 boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 1003 false /* isOnReparent */); 1004 1005 assertTrue(result); 1006 assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */); 1007 } 1008 1009 @Test testResolveActivityToContainer_minDimensions_shouldExpandSplitContainer()1010 public void testResolveActivityToContainer_minDimensions_shouldExpandSplitContainer() { 1011 final Activity primaryActivity = createMockActivity(); 1012 final Activity secondaryActivity = createMockActivity(); 1013 addSplitTaskFragments(primaryActivity, secondaryActivity, false /* clearTop */); 1014 1015 setupSplitRule(primaryActivity, mActivity, false /* clearTop */); 1016 doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo(); 1017 doReturn(secondaryActivity).when(mSplitController).findActivityBelow(eq(mActivity)); 1018 1019 clearInvocations(mSplitPresenter); 1020 boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, 1021 false /* isOnReparent */); 1022 1023 assertTrue(result); 1024 assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */); 1025 assertTrue(mSplitController.getContainerWithActivity(mActivity) 1026 .areLastRequestedBoundsEqual(new Rect())); 1027 } 1028 1029 @Test testFindActivityBelow()1030 public void testFindActivityBelow() { 1031 // Create a container with two activities 1032 final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity); 1033 final Activity pendingAppearedActivity = createMockActivity(); 1034 container.addPendingAppearedActivity(pendingAppearedActivity); 1035 1036 // Ensure the activity below matches 1037 assertEquals(mActivity, 1038 mSplitController.findActivityBelow(pendingAppearedActivity)); 1039 1040 // Ensure that the activity look up won't search for the in-process activities and should 1041 // IPC to WM core to get the activity below. It should be `null` for this mock test. 1042 spyOn(container); 1043 doReturn(true).when(container).hasCrossProcessActivities(); 1044 assertNotEquals(mActivity, 1045 mSplitController.findActivityBelow(pendingAppearedActivity)); 1046 } 1047 1048 @Test testResolveActivityToContainer_inUnknownTaskFragment()1049 public void testResolveActivityToContainer_inUnknownTaskFragment() { 1050 doReturn(new Binder()).when(mSplitController) 1051 .getTaskFragmentTokenFromActivityClientRecord(mActivity); 1052 1053 // No need to handle when the new launched activity is in an unknown TaskFragment. 1054 assertTrue(mSplitController.resolveActivityToContainer(mTransaction, mActivity, 1055 false /* isOnReparent */)); 1056 } 1057 1058 @Test testResolveActivityToContainer_skipIfNonTopOrPinned()1059 public void testResolveActivityToContainer_skipIfNonTopOrPinned() { 1060 final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity); 1061 final Activity pinnedActivity = createMockActivity(); 1062 final TaskFragmentContainer topContainer = mSplitController.newContainer(pinnedActivity, 1063 TASK_ID); 1064 final TaskContainer taskContainer = container.getTaskContainer(); 1065 spyOn(taskContainer); 1066 doReturn(container).when(taskContainer).getTopNonFinishingTaskFragmentContainer(false); 1067 doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(topContainer); 1068 1069 // No need to handle when the new launched activity is in a pinned TaskFragment. 1070 assertTrue(mSplitController.resolveActivityToContainer(mTransaction, pinnedActivity, 1071 false /* isOnReparent */)); 1072 verify(mSplitController, never()).shouldExpand(any(), any()); 1073 1074 // Should proceed to resolve if the new launched activity is in the next top TaskFragment 1075 // (e.g. the top-most TaskFragment is pinned) 1076 mSplitController.resolveActivityToContainer(mTransaction, mActivity, 1077 false /* isOnReparent */); 1078 verify(mSplitController).shouldExpand(any(), any()); 1079 } 1080 1081 @Test testGetPlaceholderOptions()1082 public void testGetPlaceholderOptions() { 1083 // Setup to make sure a transaction record is started. 1084 mTransactionManager.startNewTransaction(); 1085 doReturn(true).when(mActivity).isResumed(); 1086 1087 assertNull(mSplitController.getPlaceholderOptions(mActivity, false /* isOnCreated */)); 1088 1089 doReturn(false).when(mActivity).isResumed(); 1090 1091 assertNull(mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */)); 1092 1093 // Launch placeholder without moving the Task to front if the Task is now in background (not 1094 // resumed or onCreated). 1095 final Bundle options = mSplitController.getPlaceholderOptions(mActivity, 1096 false /* isOnCreated */); 1097 1098 assertNotNull(options); 1099 final ActivityOptions activityOptions = new ActivityOptions(options); 1100 assertTrue(activityOptions.getAvoidMoveToFront()); 1101 } 1102 1103 @Test testFinishTwoSplitThatShouldFinishTogether()1104 public void testFinishTwoSplitThatShouldFinishTogether() { 1105 // Setup two split pairs that should finish each other when finishing one. 1106 final Activity secondaryActivity0 = createMockActivity(); 1107 final Activity secondaryActivity1 = createMockActivity(); 1108 final TaskFragmentContainer primaryContainer = createMockTaskFragmentContainer(mActivity); 1109 final TaskFragmentContainer secondaryContainer0 = createMockTaskFragmentContainer( 1110 secondaryActivity0); 1111 final TaskFragmentContainer secondaryContainer1 = createMockTaskFragmentContainer( 1112 secondaryActivity1); 1113 final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); 1114 final SplitRule rule0 = createSplitRule(mActivity, secondaryActivity0, FINISH_ALWAYS, 1115 FINISH_ALWAYS, false /* clearTop */); 1116 final SplitRule rule1 = createSplitRule(mActivity, secondaryActivity1, FINISH_ALWAYS, 1117 FINISH_ALWAYS, false /* clearTop */); 1118 registerSplitPair(primaryContainer, secondaryContainer0, rule0); 1119 registerSplitPair(primaryContainer, secondaryContainer1, rule1); 1120 1121 primaryContainer.finish(true /* shouldFinishDependent */, mSplitPresenter, 1122 mTransaction, mSplitController); 1123 1124 // All containers and activities should be finished based on the FINISH_ALWAYS behavior. 1125 assertTrue(primaryContainer.isFinished()); 1126 assertTrue(secondaryContainer0.isFinished()); 1127 assertTrue(secondaryContainer1.isFinished()); 1128 verify(mTransaction).finishActivity(mActivity.getActivityToken()); 1129 verify(mTransaction).finishActivity(secondaryActivity0.getActivityToken()); 1130 verify(mTransaction).finishActivity(secondaryActivity1.getActivityToken()); 1131 assertTrue(taskContainer.getTaskFragmentContainers().isEmpty()); 1132 assertTrue(taskContainer.getSplitContainers().isEmpty()); 1133 } 1134 1135 @Test testOnTransactionReady_taskFragmentAppeared()1136 public void testOnTransactionReady_taskFragmentAppeared() { 1137 final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); 1138 final TaskFragmentInfo info = mock(TaskFragmentInfo.class); 1139 transaction.addChange(new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_APPEARED) 1140 .setTaskId(TASK_ID) 1141 .setTaskFragmentToken(new Binder()) 1142 .setTaskFragmentInfo(info)); 1143 mSplitController.onTransactionReady(transaction); 1144 1145 verify(mSplitController).onTaskFragmentAppeared(any(), eq(info)); 1146 verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), 1147 anyInt(), anyBoolean()); 1148 } 1149 1150 @Test testOnTransactionReady_taskFragmentInfoChanged()1151 public void testOnTransactionReady_taskFragmentInfoChanged() { 1152 final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); 1153 final TaskFragmentInfo info = mock(TaskFragmentInfo.class); 1154 transaction.addChange(new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_INFO_CHANGED) 1155 .setTaskId(TASK_ID) 1156 .setTaskFragmentToken(new Binder()) 1157 .setTaskFragmentInfo(info)); 1158 mSplitController.onTransactionReady(transaction); 1159 1160 verify(mSplitController).onTaskFragmentInfoChanged(any(), eq(info)); 1161 verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), 1162 anyInt(), anyBoolean()); 1163 } 1164 1165 @Test testOnTransactionReady_taskFragmentVanished()1166 public void testOnTransactionReady_taskFragmentVanished() { 1167 final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); 1168 final TaskFragmentInfo info = mock(TaskFragmentInfo.class); 1169 transaction.addChange(new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_VANISHED) 1170 .setTaskId(TASK_ID) 1171 .setTaskFragmentToken(new Binder()) 1172 .setTaskFragmentInfo(info)); 1173 mSplitController.onTransactionReady(transaction); 1174 1175 verify(mSplitController).onTaskFragmentVanished(any(), eq(info)); 1176 verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), 1177 anyInt(), anyBoolean()); 1178 } 1179 1180 @Test testOnTransactionReady_taskFragmentParentInfoChanged()1181 public void testOnTransactionReady_taskFragmentParentInfoChanged() { 1182 final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); 1183 final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY, 1184 DEFAULT_DISPLAY, true); 1185 transaction.addChange(new TaskFragmentTransaction.Change( 1186 TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED) 1187 .setTaskId(TASK_ID) 1188 .setTaskFragmentParentInfo(parentInfo)); 1189 mSplitController.onTransactionReady(transaction); 1190 1191 verify(mSplitController).onTaskFragmentParentInfoChanged(any(), eq(TASK_ID), 1192 eq(parentInfo)); 1193 verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), 1194 anyInt(), anyBoolean()); 1195 } 1196 1197 @Test testOnTransactionReady_taskFragmentParentError()1198 public void testOnTransactionReady_taskFragmentParentError() { 1199 final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); 1200 final IBinder errorToken = new Binder(); 1201 final TaskFragmentInfo info = mock(TaskFragmentInfo.class); 1202 final int opType = OP_TYPE_CREATE_TASK_FRAGMENT; 1203 final Exception exception = new SecurityException("test"); 1204 final Bundle errorBundle = TaskFragmentOrganizer.putErrorInfoInBundle(exception, info, 1205 opType); 1206 transaction.addChange(new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_ERROR) 1207 .setErrorCallbackToken(errorToken) 1208 .setErrorBundle(errorBundle)); 1209 mSplitController.onTransactionReady(transaction); 1210 1211 verify(mSplitController).onTaskFragmentError(any(), eq(errorToken), eq(info), eq(opType), 1212 eq(exception)); 1213 verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), 1214 anyInt(), anyBoolean()); 1215 } 1216 1217 @Test testOnTransactionReady_activityReparentedToTask()1218 public void testOnTransactionReady_activityReparentedToTask() { 1219 final TaskFragmentTransaction transaction = new TaskFragmentTransaction(); 1220 final Intent intent = mock(Intent.class); 1221 final IBinder activityToken = new Binder(); 1222 transaction.addChange(new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK) 1223 .setTaskId(TASK_ID) 1224 .setActivityIntent(intent) 1225 .setActivityToken(activityToken)); 1226 mSplitController.onTransactionReady(transaction); 1227 1228 verify(mSplitController).onActivityReparentedToTask(any(), eq(TASK_ID), eq(intent), 1229 eq(activityToken)); 1230 verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), 1231 anyInt(), anyBoolean()); 1232 } 1233 1234 @Test testHasSamePresentation()1235 public void testHasSamePresentation() { 1236 SplitPairRule splitRule1 = createSplitPairRuleBuilder( 1237 activityPair -> true, 1238 activityIntentPair -> true, 1239 windowMetrics -> true) 1240 .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) 1241 .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) 1242 .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) 1243 .build(); 1244 SplitPairRule splitRule2 = createSplitPairRuleBuilder( 1245 activityPair -> true, 1246 activityIntentPair -> true, 1247 windowMetrics -> true) 1248 .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) 1249 .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) 1250 .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) 1251 .build(); 1252 1253 assertTrue("Rules must have same presentation if tags are null and has same properties.", 1254 SplitController.areRulesSamePresentation(splitRule1, splitRule2, 1255 new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED))); 1256 1257 splitRule2 = createSplitPairRuleBuilder( 1258 activityPair -> true, 1259 activityIntentPair -> true, 1260 windowMetrics -> true) 1261 .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) 1262 .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) 1263 .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) 1264 .setTag(TEST_TAG) 1265 .build(); 1266 1267 assertFalse("Rules must have different presentations if tags are not equal regardless" 1268 + "of other properties", 1269 SplitController.areRulesSamePresentation(splitRule1, splitRule2, 1270 new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED))); 1271 } 1272 1273 @Test testSplitInfoCallback_reportSplit()1274 public void testSplitInfoCallback_reportSplit() { 1275 final Activity r0 = createMockActivity(); 1276 final Activity r1 = createMockActivity(); 1277 addSplitTaskFragments(r0, r1); 1278 1279 mSplitController.updateCallbackIfNecessary(); 1280 assertEquals(1, mSplitInfos.size()); 1281 final SplitInfo splitInfo = mSplitInfos.get(0); 1282 assertEquals(1, splitInfo.getPrimaryActivityStack().getActivities().size()); 1283 assertEquals(1, splitInfo.getSecondaryActivityStack().getActivities().size()); 1284 assertEquals(r0, splitInfo.getPrimaryActivityStack().getActivities().get(0)); 1285 assertEquals(r1, splitInfo.getSecondaryActivityStack().getActivities().get(0)); 1286 } 1287 1288 @Test testSplitInfoCallback_reportSplitInMultipleTasks()1289 public void testSplitInfoCallback_reportSplitInMultipleTasks() { 1290 final int taskId0 = 1; 1291 final int taskId1 = 2; 1292 final Activity r0 = createMockActivity(taskId0); 1293 final Activity r1 = createMockActivity(taskId0); 1294 final Activity r2 = createMockActivity(taskId1); 1295 final Activity r3 = createMockActivity(taskId1); 1296 addSplitTaskFragments(r0, r1); 1297 addSplitTaskFragments(r2, r3); 1298 1299 mSplitController.updateCallbackIfNecessary(); 1300 assertEquals(2, mSplitInfos.size()); 1301 } 1302 1303 @Test testSplitInfoCallback_doNotReportIfInIntermediateState()1304 public void testSplitInfoCallback_doNotReportIfInIntermediateState() { 1305 final Activity r0 = createMockActivity(); 1306 final Activity r1 = createMockActivity(); 1307 addSplitTaskFragments(r0, r1); 1308 final TaskFragmentContainer tf0 = mSplitController.getContainerWithActivity(r0); 1309 final TaskFragmentContainer tf1 = mSplitController.getContainerWithActivity(r1); 1310 spyOn(tf0); 1311 spyOn(tf1); 1312 1313 // Do not report if activity has not appeared in the TaskFragmentContainer in split. 1314 doReturn(true).when(tf0).isInIntermediateState(); 1315 mSplitController.updateCallbackIfNecessary(); 1316 verify(mEmbeddingCallback, never()).accept(any()); 1317 1318 doReturn(false).when(tf0).isInIntermediateState(); 1319 mSplitController.updateCallbackIfNecessary(); 1320 verify(mEmbeddingCallback).accept(any()); 1321 } 1322 1323 @Test testLaunchPlaceholderIfNecessary_nonEmbeddedActivity()1324 public void testLaunchPlaceholderIfNecessary_nonEmbeddedActivity() { 1325 // Launch placeholder for non embedded activity. 1326 setupPlaceholderRule(mActivity); 1327 mTransactionManager.startNewTransaction(); 1328 mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, 1329 true /* isOnCreated */); 1330 1331 verify(mTransaction).startActivityInTaskFragment(any(), any(), eq(PLACEHOLDER_INTENT), 1332 any()); 1333 } 1334 1335 @Test testLaunchPlaceholderIfNecessary_embeddedInTopTaskFragment()1336 public void testLaunchPlaceholderIfNecessary_embeddedInTopTaskFragment() { 1337 // Launch placeholder for activity in top TaskFragment. 1338 setupPlaceholderRule(mActivity); 1339 mTransactionManager.startNewTransaction(); 1340 final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID); 1341 mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, 1342 true /* isOnCreated */); 1343 1344 assertTrue(container.hasActivity(mActivity.getActivityToken())); 1345 verify(mTransaction).startActivityInTaskFragment(any(), any(), eq(PLACEHOLDER_INTENT), 1346 any()); 1347 } 1348 1349 @Test testLaunchPlaceholderIfNecessary_embeddedBelowTaskFragment()1350 public void testLaunchPlaceholderIfNecessary_embeddedBelowTaskFragment() { 1351 // Do not launch placeholder for invisible activity below the top TaskFragment. 1352 setupPlaceholderRule(mActivity); 1353 mTransactionManager.startNewTransaction(); 1354 final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); 1355 final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity, 1356 TASK_ID); 1357 bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity, 1358 false /* isVisible */)); 1359 topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); 1360 assertFalse(bottomTf.isVisible()); 1361 mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, 1362 true /* isOnCreated */); 1363 1364 verify(mTransaction, never()).startActivityInTaskFragment(any(), any(), any(), any()); 1365 } 1366 1367 @Test testLaunchPlaceholderIfNecessary_embeddedBelowTransparentTaskFragment()1368 public void testLaunchPlaceholderIfNecessary_embeddedBelowTransparentTaskFragment() { 1369 // Launch placeholder for visible activity below the top TaskFragment. 1370 setupPlaceholderRule(mActivity); 1371 mTransactionManager.startNewTransaction(); 1372 final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); 1373 final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity, 1374 TASK_ID); 1375 bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity, 1376 true /* isVisible */)); 1377 topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); 1378 assertTrue(bottomTf.isVisible()); 1379 mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, 1380 true /* isOnCreated */); 1381 1382 verify(mTransaction).startActivityInTaskFragment(any(), any(), any(), any()); 1383 } 1384 1385 @Test testFinishActivityStacks_emptySet_earlyReturn()1386 public void testFinishActivityStacks_emptySet_earlyReturn() { 1387 mSplitController.finishActivityStacks(Collections.emptySet()); 1388 1389 verify(mSplitController, never()).updateContainersInTaskIfVisible(any(), anyInt()); 1390 } 1391 1392 @Test testFinishActivityStacks_invalidStacks_earlyReturn()1393 public void testFinishActivityStacks_invalidStacks_earlyReturn() { 1394 mSplitController.finishActivityStacks(Collections.singleton(new Binder())); 1395 1396 verify(mSplitController, never()).updateContainersInTaskIfVisible(any(), anyInt()); 1397 } 1398 1399 @Test testFinishActivityStacks_finishSingleActivityStack()1400 public void testFinishActivityStacks_finishSingleActivityStack() { 1401 TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); 1402 tf.setInfo(mTransaction, createMockTaskFragmentInfo(tf, mActivity)); 1403 1404 final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID); 1405 assertEquals(taskContainer.getTaskFragmentContainers().get(0), tf); 1406 1407 mSplitController.finishActivityStacks(Collections.singleton(tf.getTaskFragmentToken())); 1408 1409 verify(mSplitPresenter).deleteTaskFragment(any(), eq(tf.getTaskFragmentToken())); 1410 assertTrue(taskContainer.getTaskFragmentContainers().isEmpty()); 1411 } 1412 1413 @Test testFinishActivityStacks_finishActivityStacksInOrder()1414 public void testFinishActivityStacks_finishActivityStacksInOrder() { 1415 TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); 1416 TaskFragmentContainer topTf = mSplitController.newContainer(mActivity, TASK_ID); 1417 bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity)); 1418 topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); 1419 1420 final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID); 1421 assertEquals(taskContainer.getTaskFragmentContainers().size(), 2); 1422 1423 Set<IBinder> activityStackTokens = new ArraySet<>(new IBinder[]{ 1424 topTf.getTaskFragmentToken(), bottomTf.getTaskFragmentToken()}); 1425 1426 mSplitController.finishActivityStacks(activityStackTokens); 1427 1428 ArgumentCaptor<IBinder> argumentCaptor = ArgumentCaptor.forClass(IBinder.class); 1429 1430 verify(mSplitPresenter, times(2)).deleteTaskFragment(any(), argumentCaptor.capture()); 1431 1432 List<IBinder> fragmentTokens = argumentCaptor.getAllValues(); 1433 assertEquals("The ActivityStack must be deleted from the lowest z-order " 1434 + "regardless of the order in ActivityStack set", 1435 bottomTf.getTaskFragmentToken(), fragmentTokens.get(0)); 1436 assertEquals("The ActivityStack must be deleted from the lowest z-order " 1437 + "regardless of the order in ActivityStack set", 1438 topTf.getTaskFragmentToken(), fragmentTokens.get(1)); 1439 1440 assertTrue(taskContainer.getTaskFragmentContainers().isEmpty()); 1441 } 1442 1443 @Test testUpdateSplitAttributes_invalidSplitContainerToken_earlyReturn()1444 public void testUpdateSplitAttributes_invalidSplitContainerToken_earlyReturn() { 1445 mSplitController.updateSplitAttributes(new Binder(), SPLIT_ATTRIBUTES); 1446 1447 verify(mTransactionManager, never()).startNewTransaction(); 1448 } 1449 1450 @Test testUpdateSplitAttributes_nullParams_throwException()1451 public void testUpdateSplitAttributes_nullParams_throwException() { 1452 assertThrows(NullPointerException.class, 1453 () -> mSplitController.updateSplitAttributes(null, SPLIT_ATTRIBUTES)); 1454 1455 final SplitContainer splitContainer = mock(SplitContainer.class); 1456 final IBinder token = new Binder(); 1457 doReturn(token).when(splitContainer).getToken(); 1458 doReturn(splitContainer).when(mSplitController).getSplitContainer(eq(token)); 1459 1460 assertThrows(NullPointerException.class, 1461 () -> mSplitController.updateSplitAttributes(token, null)); 1462 } 1463 1464 @Test testUpdateSplitAttributes_doNotNeedToUpdateSplitContainer_doNotApplyTransaction()1465 public void testUpdateSplitAttributes_doNotNeedToUpdateSplitContainer_doNotApplyTransaction() { 1466 final SplitContainer splitContainer = mock(SplitContainer.class); 1467 final IBinder token = new Binder(); 1468 doReturn(token).when(splitContainer).getToken(); 1469 doReturn(splitContainer).when(mSplitController).getSplitContainer(eq(token)); 1470 doReturn(false).when(mSplitController).updateSplitContainerIfNeeded( 1471 eq(splitContainer), any(), eq(SPLIT_ATTRIBUTES)); 1472 TransactionManager.TransactionRecord testRecord = 1473 mock(TransactionManager.TransactionRecord.class); 1474 doReturn(testRecord).when(mTransactionManager).startNewTransaction(); 1475 1476 mSplitController.updateSplitAttributes(token, SPLIT_ATTRIBUTES); 1477 1478 verify(splitContainer).updateDefaultSplitAttributes(eq(SPLIT_ATTRIBUTES)); 1479 verify(testRecord).abort(); 1480 } 1481 1482 @Test testUpdateSplitAttributes_splitContainerUpdated_updateAttrs()1483 public void testUpdateSplitAttributes_splitContainerUpdated_updateAttrs() { 1484 final SplitContainer splitContainer = mock(SplitContainer.class); 1485 final IBinder token = new Binder(); 1486 doReturn(token).when(splitContainer).getToken(); 1487 doReturn(splitContainer).when(mSplitController).getSplitContainer(eq(token)); 1488 doReturn(true).when(mSplitController).updateSplitContainerIfNeeded( 1489 eq(splitContainer), any(), eq(SPLIT_ATTRIBUTES)); 1490 TransactionManager.TransactionRecord testRecord = 1491 mock(TransactionManager.TransactionRecord.class); 1492 doReturn(testRecord).when(mTransactionManager).startNewTransaction(); 1493 1494 mSplitController.updateSplitAttributes(token, SPLIT_ATTRIBUTES); 1495 1496 verify(splitContainer).updateDefaultSplitAttributes(eq(SPLIT_ATTRIBUTES)); 1497 verify(testRecord).apply(eq(false)); 1498 } 1499 1500 @Test testPinTopActivityStack()1501 public void testPinTopActivityStack() { 1502 // Create two activities. 1503 final Activity primaryActivity = createMockActivity(); 1504 final Activity secondaryActivity = createMockActivity(); 1505 1506 // Unable to pin if not being embedded. 1507 SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(), 1508 parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build(); 1509 assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule)); 1510 1511 // Split the two activities. 1512 addSplitTaskFragments(primaryActivity, secondaryActivity); 1513 final TaskFragmentContainer primaryContainer = 1514 mSplitController.getContainerWithActivity(primaryActivity); 1515 spyOn(primaryContainer); 1516 1517 // Unable to pin if no valid TaskFragment. 1518 doReturn(true).when(primaryContainer).isFinished(); 1519 assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule)); 1520 1521 // Otherwise, should pin successfully. 1522 doReturn(false).when(primaryContainer).isFinished(); 1523 assertTrue(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule)); 1524 1525 // Unable to pin if there is already a pinned TaskFragment 1526 assertFalse(mSplitController.pinTopActivityStack(TASK_ID, splitPinRule)); 1527 1528 // Unable to pin on an unknown Task. 1529 assertFalse(mSplitController.pinTopActivityStack(TASK_ID + 1, splitPinRule)); 1530 1531 // Gets the current size of all the SplitContainers. 1532 final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); 1533 final int splitContainerCount = taskContainer.getSplitContainers().size(); 1534 1535 // Create another activity and split with primary activity. 1536 final Activity thirdActivity = createMockActivity(); 1537 addSplitTaskFragments(primaryActivity, thirdActivity); 1538 1539 // Ensure another SplitContainer is added and the pinned TaskFragment still on top 1540 assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1); 1541 assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity() 1542 == secondaryActivity); 1543 } 1544 1545 /** Creates a mock activity in the organizer process. */ createMockActivity()1546 private Activity createMockActivity() { 1547 return createMockActivity(TASK_ID); 1548 } 1549 1550 /** Creates a mock activity in the organizer process. */ createMockActivity(int taskId)1551 private Activity createMockActivity(int taskId) { 1552 final Activity activity = mock(Activity.class); 1553 doReturn(mActivityResources).when(activity).getResources(); 1554 final IBinder activityToken = new Binder(); 1555 doReturn(activityToken).when(activity).getActivityToken(); 1556 doReturn(activity).when(mSplitController).getActivity(activityToken); 1557 doReturn(taskId).when(activity).getTaskId(); 1558 doReturn(new ActivityInfo()).when(activity).getActivityInfo(); 1559 doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId(); 1560 return activity; 1561 } 1562 1563 /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */ createMockTaskFragmentContainer(@onNull Activity activity)1564 private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) { 1565 final TaskFragmentContainer container = mSplitController.newContainer(activity, 1566 activity.getTaskId()); 1567 setupTaskFragmentInfo(container, activity); 1568 return container; 1569 } 1570 1571 /** Setups the given TaskFragment as it has appeared in the server. */ setupTaskFragmentInfo(@onNull TaskFragmentContainer container, @NonNull Activity activity)1572 private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container, 1573 @NonNull Activity activity) { 1574 final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity); 1575 container.setInfo(mTransaction, info); 1576 mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info); 1577 } 1578 1579 /** Setups a rule to always expand the given intent. */ setupExpandRule(@onNull Intent expandIntent)1580 private void setupExpandRule(@NonNull Intent expandIntent) { 1581 final ActivityRule expandRule = createActivityBuilder(r -> false, expandIntent::equals) 1582 .setShouldAlwaysExpand(true) 1583 .build(); 1584 mSplitController.setEmbeddingRules(Collections.singleton(expandRule)); 1585 } 1586 1587 /** Setups a rule to always expand the given activity. */ setupExpandRule(@onNull Activity expandActivity)1588 private void setupExpandRule(@NonNull Activity expandActivity) { 1589 final ActivityRule expandRule = createActivityBuilder(expandActivity::equals, i -> false) 1590 .setShouldAlwaysExpand(true) 1591 .build(); 1592 mSplitController.setEmbeddingRules(Collections.singleton(expandRule)); 1593 } 1594 1595 /** Setups a rule to launch placeholder for the given activity. */ setupPlaceholderRule(@onNull Activity primaryActivity)1596 private void setupPlaceholderRule(@NonNull Activity primaryActivity) { 1597 final SplitRule placeholderRule = createSplitPlaceholderRuleBuilder(PLACEHOLDER_INTENT, 1598 primaryActivity::equals, i -> false, w -> true) 1599 .setDefaultSplitAttributes(SPLIT_ATTRIBUTES) 1600 .build(); 1601 mSplitController.setEmbeddingRules(Collections.singleton(placeholderRule)); 1602 } 1603 1604 /** Setups a rule to always split the given activities. */ setupSplitRule(@onNull Activity primaryActivity, @NonNull Intent secondaryIntent)1605 private void setupSplitRule(@NonNull Activity primaryActivity, 1606 @NonNull Intent secondaryIntent) { 1607 setupSplitRule(primaryActivity, secondaryIntent, true /* clearTop */); 1608 } 1609 1610 /** Setups a rule to always split the given activities. */ setupSplitRule(@onNull Activity primaryActivity, @NonNull Intent secondaryIntent, boolean clearTop)1611 private void setupSplitRule(@NonNull Activity primaryActivity, 1612 @NonNull Intent secondaryIntent, boolean clearTop) { 1613 final SplitRule splitRule = createSplitRule(primaryActivity, secondaryIntent, clearTop); 1614 mSplitController.setEmbeddingRules(Collections.singleton(splitRule)); 1615 } 1616 1617 /** Setups a rule to always split the given activities. */ setupSplitRule(@onNull Activity primaryActivity, @NonNull Activity secondaryActivity)1618 private void setupSplitRule(@NonNull Activity primaryActivity, 1619 @NonNull Activity secondaryActivity) { 1620 setupSplitRule(primaryActivity, secondaryActivity, true /* clearTop */); 1621 } 1622 1623 /** Setups a rule to always split the given activities. */ setupSplitRule(@onNull Activity primaryActivity, @NonNull Activity secondaryActivity, boolean clearTop)1624 private void setupSplitRule(@NonNull Activity primaryActivity, 1625 @NonNull Activity secondaryActivity, boolean clearTop) { 1626 final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity, clearTop); 1627 mSplitController.setEmbeddingRules(Collections.singleton(splitRule)); 1628 } 1629 1630 /** Adds a pair of TaskFragments as split for the given activities. */ addSplitTaskFragments(@onNull Activity primaryActivity, @NonNull Activity secondaryActivity)1631 private void addSplitTaskFragments(@NonNull Activity primaryActivity, 1632 @NonNull Activity secondaryActivity) { 1633 addSplitTaskFragments(primaryActivity, secondaryActivity, true /* clearTop */); 1634 } 1635 1636 /** Adds a pair of TaskFragments as split for the given activities. */ addSplitTaskFragments(@onNull Activity primaryActivity, @NonNull Activity secondaryActivity, boolean clearTop)1637 private void addSplitTaskFragments(@NonNull Activity primaryActivity, 1638 @NonNull Activity secondaryActivity, boolean clearTop) { 1639 registerSplitPair(createMockTaskFragmentContainer(primaryActivity), 1640 createMockTaskFragmentContainer(secondaryActivity), 1641 createSplitRule(primaryActivity, secondaryActivity, clearTop)); 1642 } 1643 1644 /** Registers the two given TaskFragments as split pair. */ registerSplitPair(@onNull TaskFragmentContainer primaryContainer, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule rule)1645 private void registerSplitPair(@NonNull TaskFragmentContainer primaryContainer, 1646 @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule rule) { 1647 mSplitController.registerSplit( 1648 mock(WindowContainerTransaction.class), 1649 primaryContainer, 1650 primaryContainer.getTopNonFinishingActivity(), 1651 secondaryContainer, 1652 rule, 1653 SPLIT_ATTRIBUTES); 1654 1655 // We need to set those in case we are not respecting clear top. 1656 // TODO(b/231845476) we should always respect clearTop. 1657 final int windowingMode = mSplitController.getTaskContainer(primaryContainer.getTaskId()) 1658 .getWindowingModeForSplitTaskFragment(TASK_BOUNDS); 1659 primaryContainer.setLastRequestedWindowingMode(windowingMode); 1660 secondaryContainer.setLastRequestedWindowingMode(windowingMode); 1661 primaryContainer.setLastRequestedBounds(getSplitBounds(true /* isPrimary */)); 1662 secondaryContainer.setLastRequestedBounds(getSplitBounds(false /* isPrimary */)); 1663 } 1664 1665 /** Asserts that the two given activities are in split. */ assertSplitPair(@onNull Activity primaryActivity, @NonNull Activity secondaryActivity)1666 private void assertSplitPair(@NonNull Activity primaryActivity, 1667 @NonNull Activity secondaryActivity) { 1668 assertSplitPair(primaryActivity, secondaryActivity, false /* matchParentBounds */); 1669 } 1670 1671 /** Asserts that the two given activities are in split. */ assertSplitPair(@onNull Activity primaryActivity, @NonNull Activity secondaryActivity, boolean matchParentBounds)1672 private void assertSplitPair(@NonNull Activity primaryActivity, 1673 @NonNull Activity secondaryActivity, boolean matchParentBounds) { 1674 assertSplitPair(mSplitController.getContainerWithActivity(primaryActivity), 1675 mSplitController.getContainerWithActivity(secondaryActivity), matchParentBounds); 1676 } 1677 assertSplitPair(@onNull TaskFragmentContainer primaryContainer, @NonNull TaskFragmentContainer secondaryContainer)1678 private void assertSplitPair(@NonNull TaskFragmentContainer primaryContainer, 1679 @NonNull TaskFragmentContainer secondaryContainer) { 1680 assertSplitPair(primaryContainer, secondaryContainer, false /* matchParentBounds*/); 1681 } 1682 1683 /** Asserts that the two given TaskFragments are in split. */ assertSplitPair(@onNull TaskFragmentContainer primaryContainer, @NonNull TaskFragmentContainer secondaryContainer, boolean matchParentBounds)1684 private void assertSplitPair(@NonNull TaskFragmentContainer primaryContainer, 1685 @NonNull TaskFragmentContainer secondaryContainer, boolean matchParentBounds) { 1686 assertNotNull(primaryContainer); 1687 assertNotNull(secondaryContainer); 1688 assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, 1689 secondaryContainer)); 1690 if (primaryContainer.mInfo != null) { 1691 final Rect primaryBounds = matchParentBounds ? new Rect() 1692 : getSplitBounds(true /* isPrimary */); 1693 final int windowingMode = matchParentBounds ? WINDOWING_MODE_UNDEFINED 1694 : WINDOWING_MODE_MULTI_WINDOW; 1695 assertTrue(primaryContainer.areLastRequestedBoundsEqual(primaryBounds)); 1696 assertTrue(primaryContainer.isLastRequestedWindowingModeEqual(windowingMode)); 1697 } 1698 if (secondaryContainer.mInfo != null) { 1699 final Rect secondaryBounds = matchParentBounds ? new Rect() 1700 : getSplitBounds(false /* isPrimary */); 1701 final int windowingMode = matchParentBounds ? WINDOWING_MODE_UNDEFINED 1702 : WINDOWING_MODE_MULTI_WINDOW; 1703 assertTrue(secondaryContainer.areLastRequestedBoundsEqual(secondaryBounds)); 1704 assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual(windowingMode)); 1705 } 1706 } 1707 } 1708