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