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