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