• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.ActivityManager.LOCK_TASK_MODE_NONE;
20 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
23 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
24 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
25 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
26 import static android.server.wm.ComponentNameUtils.getActivityName;
27 import static android.server.wm.ComponentNameUtils.getWindowName;
28 import static android.server.wm.UiDeviceUtils.pressWindowButton;
29 import static android.server.wm.WindowManagerState.STATE_PAUSED;
30 import static android.server.wm.WindowManagerState.STATE_RESUMED;
31 import static android.server.wm.WindowManagerState.STATE_STOPPED;
32 import static android.server.wm.WindowManagerState.dpToPx;
33 import static android.server.wm.app.Components.ALWAYS_FOCUSABLE_PIP_ACTIVITY;
34 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
35 import static android.server.wm.app.Components.LAUNCH_ENTER_PIP_ACTIVITY;
36 import static android.server.wm.app.Components.LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY;
37 import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
38 import static android.server.wm.app.Components.PIP_ACTIVITY;
39 import static android.server.wm.app.Components.PIP_ACTIVITY2;
40 import static android.server.wm.app.Components.PIP_ACTIVITY_WITH_MINIMAL_SIZE;
41 import static android.server.wm.app.Components.PIP_ACTIVITY_WITH_SAME_AFFINITY;
42 import static android.server.wm.app.Components.PIP_ACTIVITY_WITH_TINY_MINIMAL_SIZE;
43 import static android.server.wm.app.Components.PIP_ON_STOP_ACTIVITY;
44 import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP;
45 import static android.server.wm.app.Components.PipActivity.ACTION_FINISH;
46 import static android.server.wm.app.Components.PipActivity.ACTION_MOVE_TO_BACK;
47 import static android.server.wm.app.Components.PipActivity.ACTION_ON_PIP_REQUESTED;
48 import static android.server.wm.app.Components.PipActivity.EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP;
49 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
50 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
51 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR;
52 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PAUSE;
53 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PIP_REQUESTED;
54 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT;
55 import static android.server.wm.app.Components.PipActivity.EXTRA_FINISH_SELF_ON_RESUME;
56 import static android.server.wm.app.Components.PipActivity.EXTRA_ON_PAUSE_DELAY;
57 import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION;
58 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_DENOMINATOR;
59 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_NUMERATOR;
60 import static android.server.wm.app.Components.PipActivity.EXTRA_START_ACTIVITY;
61 import static android.server.wm.app.Components.PipActivity.EXTRA_TAP_TO_FINISH;
62 import static android.server.wm.app.Components.RESUME_WHILE_PAUSING_ACTIVITY;
63 import static android.server.wm.app.Components.TEST_ACTIVITY;
64 import static android.server.wm.app.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY;
65 import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
66 import static android.server.wm.app.Components.TestActivity.EXTRA_CONFIGURATION;
67 import static android.server.wm.app.Components.TestActivity.EXTRA_FIXED_ORIENTATION;
68 import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
69 import static android.server.wm.app27.Components.SDK_27_LAUNCH_ENTER_PIP_ACTIVITY;
70 import static android.server.wm.app27.Components.SDK_27_PIP_ACTIVITY;
71 import static android.view.Display.DEFAULT_DISPLAY;
72 
73 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
74 
75 import static org.hamcrest.Matchers.lessThan;
76 import static org.junit.Assert.assertEquals;
77 import static org.junit.Assert.assertNotEquals;
78 import static org.junit.Assert.assertNotNull;
79 import static org.junit.Assert.assertThat;
80 import static org.junit.Assert.assertTrue;
81 import static org.junit.Assert.fail;
82 import static org.junit.Assume.assumeTrue;
83 
84 import android.content.ComponentName;
85 import android.content.Context;
86 import android.content.pm.ActivityInfo;
87 import android.content.pm.PackageManager;
88 import android.content.res.Configuration;
89 import android.database.ContentObserver;
90 import android.graphics.Rect;
91 import android.os.Handler;
92 import android.os.Looper;
93 import android.platform.test.annotations.Presubmit;
94 import android.platform.test.annotations.SecurityTest;
95 import android.provider.Settings;
96 import android.server.wm.CommandSession.ActivityCallback;
97 import android.server.wm.CommandSession.SizeInfo;
98 import android.server.wm.TestJournalProvider.TestJournalContainer;
99 import android.server.wm.WindowManagerState.ActivityTask;
100 import android.server.wm.settings.SettingsSession;
101 import android.util.Log;
102 import android.util.Size;
103 
104 import androidx.test.filters.FlakyTest;
105 
106 import com.android.compatibility.common.util.AppOpsUtils;
107 import com.android.compatibility.common.util.SystemUtil;
108 
109 import org.junit.Before;
110 import org.junit.Ignore;
111 import org.junit.Test;
112 
113 import java.io.IOException;
114 import java.util.List;
115 import java.util.concurrent.CountDownLatch;
116 import java.util.concurrent.TimeUnit;
117 
118 /**
119  * Build/Install/Run:
120  * atest CtsWindowManagerDeviceTestCases:PinnedStackTests
121  */
122 @Presubmit
123 @android.server.wm.annotation.Group2
124 public class PinnedStackTests extends ActivityManagerTestBase {
125     private static final String TAG = PinnedStackTests.class.getSimpleName();
126 
127     private static final String APP_OPS_OP_ENTER_PICTURE_IN_PICTURE = "PICTURE_IN_PICTURE";
128     private static final int APP_OPS_MODE_IGNORED = 1;
129 
130     private static final int ROTATION_0 = 0;
131     private static final int ROTATION_90 = 1;
132     private static final int ROTATION_180 = 2;
133     private static final int ROTATION_270 = 3;
134 
135     // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
136     private static final int ORIENTATION_LANDSCAPE = 0;
137     // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
138     private static final int ORIENTATION_PORTRAIT = 1;
139 
140     private static final float FLOAT_COMPARE_EPSILON = 0.005f;
141 
142     // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio
143     private static final int MIN_ASPECT_RATIO_NUMERATOR = 100;
144     private static final int MIN_ASPECT_RATIO_DENOMINATOR = 239;
145     private static final int BELOW_MIN_ASPECT_RATIO_DENOMINATOR = MIN_ASPECT_RATIO_DENOMINATOR + 1;
146     // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio
147     private static final int MAX_ASPECT_RATIO_NUMERATOR = 239;
148     private static final int MAX_ASPECT_RATIO_DENOMINATOR = 100;
149     private static final int ABOVE_MAX_ASPECT_RATIO_NUMERATOR = MAX_ASPECT_RATIO_NUMERATOR + 1;
150     // Corresponds to com.android.internal.R.dimen.overridable_minimal_size_pip_resizable_task
151     private static final int OVERRIDABLE_MINIMAL_SIZE_PIP_RESIZABLE_TASK = 48;
152 
153     @Before
154     @Override
setUp()155     public void setUp() throws Exception {
156         super.setUp();
157         assumeTrue(supportsPip());
158     }
159 
160     @Test
testMinimumDeviceSize()161     public void testMinimumDeviceSize() throws Exception {
162         mWmState.assertDeviceDefaultDisplaySize(
163                 "Devices supporting picture-in-picture must be larger than the default minimum"
164                         + " task size");
165     }
166 
167     @Test
testEnterPictureInPictureMode()168     public void testEnterPictureInPictureMode() throws Exception {
169         pinnedStackTester(getAmStartCmd(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true"),
170                 PIP_ACTIVITY, PIP_ACTIVITY, false /* moveTopToPinnedStack */,
171                 false /* isFocusable */);
172     }
173 
174     @Test
testMoveTopActivityToPinnedStack()175     public void testMoveTopActivityToPinnedStack() throws Exception {
176         pinnedStackTester(getAmStartCmd(PIP_ACTIVITY), PIP_ACTIVITY, PIP_ACTIVITY,
177                 true /* moveTopToPinnedStack */, false /* isFocusable */);
178     }
179 
180     // This test is black-listed in cts-known-failures.xml (b/35314835).
181     @Ignore
182     @Test
testAlwaysFocusablePipActivity()183     public void testAlwaysFocusablePipActivity() throws Exception {
184         pinnedStackTester(getAmStartCmd(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
185                 ALWAYS_FOCUSABLE_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
186                 false /* moveTopToPinnedStack */, true /* isFocusable */);
187     }
188 
189     // This test is black-listed in cts-known-failures.xml (b/35314835).
190     @Ignore
191     @Test
testLaunchIntoPinnedStack()192     public void testLaunchIntoPinnedStack() throws Exception {
193         pinnedStackTester(getAmStartCmd(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY),
194                 LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
195                 false /* moveTopToPinnedStack */, true /* isFocusable */);
196     }
197 
198     @Test
testNonTappablePipActivity()199     public void testNonTappablePipActivity() throws Exception {
200         // Launch the tap-to-finish activity at a specific place
201         launchActivity(PIP_ACTIVITY,
202                 EXTRA_ENTER_PIP, "true",
203                 EXTRA_TAP_TO_FINISH, "true");
204         // Wait for animation complete since we are tapping on specific bounds
205         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
206         assertPinnedStackExists();
207 
208         // Tap the screen at a known location in the pinned stack bounds, and ensure that it is
209         // not passed down to the top task
210         tapToFinishPip();
211         mWmState.computeState(
212                 new WaitForValidActivityState(PIP_ACTIVITY));
213         mWmState.assertVisibility(PIP_ACTIVITY, true);
214     }
215 
216     @Test
testPinnedStackInBoundsAfterRotation()217     public void testPinnedStackInBoundsAfterRotation() {
218         assumeTrue("Skipping test: no rotation support", supportsRotation());
219 
220         // Launch an activity that is not fixed-orientation so that the display can rotate
221         launchActivity(TEST_ACTIVITY);
222         // Launch an activity into the pinned stack
223         launchActivity(PIP_ACTIVITY,
224                 EXTRA_ENTER_PIP, "true",
225                 EXTRA_TAP_TO_FINISH, "true");
226         // Wait for animation complete since we are comparing bounds
227         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
228 
229         // Ensure that the PIP stack is fully visible in each orientation
230         final RotationSession rotationSession = createManagedRotationSession();
231         rotationSession.set(ROTATION_0);
232         assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
233         rotationSession.set(ROTATION_90);
234         assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
235         rotationSession.set(ROTATION_180);
236         assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
237         rotationSession.set(ROTATION_270);
238         assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
239     }
240 
241     @Test
testEnterPipToOtherOrientation()242     public void testEnterPipToOtherOrientation() throws Exception {
243         // Launch a portrait only app on the fullscreen stack
244         launchActivity(TEST_ACTIVITY,
245                 EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT));
246         // Launch the PiP activity fixed as landscape
247         launchActivity(PIP_ACTIVITY,
248                 EXTRA_PIP_ORIENTATION, String.valueOf(ORIENTATION_LANDSCAPE));
249         // Enter PiP, and assert that the PiP is within bounds now that the device is back in
250         // portrait
251         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
252         // Wait for animation complete since we are comparing bounds
253         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
254         assertPinnedStackExists();
255         assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
256     }
257 
258     @Test
testEnterPipWithMinimalSize()259     public void testEnterPipWithMinimalSize() throws Exception {
260         // Launch a PiP activity with minimal size specified
261         launchActivity(PIP_ACTIVITY_WITH_MINIMAL_SIZE, EXTRA_ENTER_PIP, "true");
262         // Wait for animation complete since we are comparing size
263         waitForEnterPipAnimationComplete(PIP_ACTIVITY_WITH_MINIMAL_SIZE);
264         assertPinnedStackExists();
265 
266         // query the minimal size
267         final PackageManager pm = getInstrumentation().getTargetContext().getPackageManager();
268         final ActivityInfo info = pm.getActivityInfo(
269                 PIP_ACTIVITY_WITH_MINIMAL_SIZE, 0 /* flags */);
270         final Size minSize = new Size(info.windowLayout.minWidth, info.windowLayout.minHeight);
271 
272         // compare the bounds with minimal size
273         final Rect pipBounds = getPinnedStackBounds();
274         assertTrue("Pinned stack bounds is no smaller than minimal",
275                 (pipBounds.width() == minSize.getWidth()
276                         && pipBounds.height() >= minSize.getHeight())
277                         || (pipBounds.height() == minSize.getHeight()
278                         && pipBounds.width() >= minSize.getWidth()));
279     }
280 
281     @Test
282     @SecurityTest(minPatchLevel="2021-03")
testEnterPipWithTinyMinimalSize()283     public void testEnterPipWithTinyMinimalSize() throws Exception {
284         // Launch a PiP activity with minimal size specified and smaller than allowed minimum
285         launchActivity(PIP_ACTIVITY_WITH_TINY_MINIMAL_SIZE, EXTRA_ENTER_PIP, "true");
286         // Wait for animation complete since we are comparing size
287         waitForEnterPipAnimationComplete(PIP_ACTIVITY_WITH_TINY_MINIMAL_SIZE);
288         assertPinnedStackExists();
289 
290         final WindowManagerState.WindowState windowState = getWindowState(
291                 PIP_ACTIVITY_WITH_TINY_MINIMAL_SIZE);
292         final WindowManagerState.DisplayContent display = mWmState.getDisplay(
293                 windowState.getDisplayId());
294         final int overridableMinSize = dpToPx(
295                 OVERRIDABLE_MINIMAL_SIZE_PIP_RESIZABLE_TASK, display.getDpi());
296 
297         // compare the bounds to verify that it's no smaller than allowed minimum on both dimensions
298         final Rect pipBounds = getPinnedStackBounds();
299         assertTrue("Pinned task bounds " + pipBounds + " isn't smaller than minimal "
300                         + overridableMinSize + " on both dimensions",
301                 pipBounds.width() >= overridableMinSize
302                         && pipBounds.height() >= overridableMinSize);
303     }
304 
305     @Test
testEnterPipAspectRatioMin()306     public void testEnterPipAspectRatioMin() throws Exception {
307         testEnterPipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
308     }
309 
310     @Test
testEnterPipAspectRatioMax()311     public void testEnterPipAspectRatioMax() throws Exception {
312         testEnterPipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
313     }
314 
testEnterPipAspectRatio(int num, int denom)315     private void testEnterPipAspectRatio(int num, int denom) throws Exception {
316         // Launch a test activity so that we're not over home
317         launchActivity(TEST_ACTIVITY);
318 
319         launchActivity(PIP_ACTIVITY,
320                 EXTRA_ENTER_PIP, "true",
321                 EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
322                 EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
323         // Wait for animation complete since we are comparing aspect ratio
324         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
325         assertPinnedStackExists();
326 
327         // Assert that we have entered PIP and that the aspect ratio is correct
328         Rect pinnedStackBounds = getPinnedStackBounds();
329         assertFloatEquals((float) pinnedStackBounds.width() / pinnedStackBounds.height(),
330                 (float) num / denom);
331     }
332 
333     @Test
testResizePipAspectRatioMin()334     public void testResizePipAspectRatioMin() throws Exception {
335         testResizePipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
336     }
337 
338     @Test
testResizePipAspectRatioMax()339     public void testResizePipAspectRatioMax() throws Exception {
340         testResizePipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
341     }
342 
testResizePipAspectRatio(int num, int denom)343     private void testResizePipAspectRatio(int num, int denom) throws Exception {
344         // Launch a test activity so that we're not over home
345         launchActivity(TEST_ACTIVITY);
346 
347         launchActivity(PIP_ACTIVITY,
348                 EXTRA_ENTER_PIP, "true",
349                 EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
350                 EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
351         // Wait for animation complete since we are comparing aspect ratio
352         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
353         assertPinnedStackExists();
354         waitForValidAspectRatio(num, denom);
355         Rect bounds = getPinnedStackBounds();
356         assertFloatEquals((float) bounds.width() / bounds.height(), (float) num / denom);
357     }
358 
359     @Test
testEnterPipExtremeAspectRatioMin()360     public void testEnterPipExtremeAspectRatioMin() throws Exception {
361         testEnterPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
362                 BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
363     }
364 
365     @Test
testEnterPipExtremeAspectRatioMax()366     public void testEnterPipExtremeAspectRatioMax() throws Exception {
367         testEnterPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
368                 MAX_ASPECT_RATIO_DENOMINATOR);
369     }
370 
testEnterPipExtremeAspectRatio(int num, int denom)371     private void testEnterPipExtremeAspectRatio(int num, int denom) throws Exception {
372         // Launch a test activity so that we're not over home
373         launchActivity(TEST_ACTIVITY);
374 
375         // Assert that we could not create a pinned stack with an extreme aspect ratio
376         launchActivity(PIP_ACTIVITY,
377                 EXTRA_ENTER_PIP, "true",
378                 EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
379                 EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
380         assertPinnedStackDoesNotExist();
381     }
382 
383     @Test
testSetPipExtremeAspectRatioMin()384     public void testSetPipExtremeAspectRatioMin() throws Exception {
385         testSetPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
386                 BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
387     }
388 
389     @Test
testSetPipExtremeAspectRatioMax()390     public void testSetPipExtremeAspectRatioMax() throws Exception {
391         testSetPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
392                 MAX_ASPECT_RATIO_DENOMINATOR);
393     }
394 
testSetPipExtremeAspectRatio(int num, int denom)395     private void testSetPipExtremeAspectRatio(int num, int denom) throws Exception {
396         // Launch a test activity so that we're not over home
397         launchActivity(TEST_ACTIVITY);
398 
399         // Try to resize the a normal pinned stack to an extreme aspect ratio and ensure that
400         // fails (the aspect ratio remains the same)
401         launchActivity(PIP_ACTIVITY,
402                 EXTRA_ENTER_PIP, "true",
403                 EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
404                         Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
405                 EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR,
406                         Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR),
407                 EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
408                 EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
409         // Wait for animation complete since we are comparing aspect ratio
410         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
411         assertPinnedStackExists();
412         Rect pinnedStackBounds = getPinnedStackBounds();
413         assertFloatEquals((float) pinnedStackBounds.width() / pinnedStackBounds.height(),
414                 (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
415     }
416 
417     @Test
testDisallowPipLaunchFromStoppedActivity()418     public void testDisallowPipLaunchFromStoppedActivity() throws Exception {
419         // Launch the bottom pip activity which will launch a new activity on top and attempt to
420         // enter pip when it is stopped
421         launchActivity(PIP_ON_STOP_ACTIVITY);
422 
423         // Wait for the bottom pip activity to be stopped
424         mWmState.waitForActivityState(PIP_ON_STOP_ACTIVITY, STATE_STOPPED);
425 
426         // Assert that there is no pinned stack (that enterPictureInPicture() failed)
427         assertPinnedStackDoesNotExist();
428     }
429 
430     @Test
testAutoEnterPictureInPicture()431     public void testAutoEnterPictureInPicture() throws Exception {
432         // Launch a test activity so that we're not over home
433         launchActivity(TEST_ACTIVITY);
434 
435         // Launch the PIP activity on pause
436         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
437         assertPinnedStackDoesNotExist();
438 
439         // Go home and ensure that there is a pinned stack
440         launchHomeActivity();
441         waitForEnterPip(PIP_ACTIVITY);
442         assertPinnedStackExists();
443     }
444 
445     @Test
testAutoEnterPictureInPictureOnUserLeaveHintWhenPipRequestedNotOverridden()446     public void testAutoEnterPictureInPictureOnUserLeaveHintWhenPipRequestedNotOverridden()
447             throws Exception {
448         // Launch a test activity so that we're not over home
449         launchActivity(TEST_ACTIVITY);
450 
451         // Launch the PIP activity that enters PIP on user leave hint, not on PIP requested
452         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT, "true");
453         assertPinnedStackDoesNotExist();
454 
455         // Go home and ensure that there is a pinned stack
456         separateTestJournal();
457         launchHomeActivity();
458         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
459         assertPinnedStackExists();
460 
461         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
462         // Check the order of the callbacks accounting for a task overlay activity that might show.
463         // The PIP request (with a user leave hint) should come before the pip mode change.
464         final int firstUserLeaveIndex =
465                 lifecycleCounts.getFirstIndex(ActivityCallback.ON_USER_LEAVE_HINT);
466         final int firstPipRequestedIndex =
467                 lifecycleCounts.getFirstIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_REQUESTED);
468         final int firstPipModeChangedIndex =
469                 lifecycleCounts.getFirstIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
470         assertTrue("missing request", firstPipRequestedIndex != -1);
471         assertTrue("missing user leave", firstUserLeaveIndex != -1);
472         assertTrue("missing pip mode changed", firstPipModeChangedIndex != -1);
473         assertTrue("pip requested not before pause",
474                 firstPipRequestedIndex < firstUserLeaveIndex);
475         assertTrue("unexpected user leave hint",
476                 firstUserLeaveIndex < firstPipModeChangedIndex);
477     }
478 
479     @Test
testAutoEnterPictureInPictureOnPictureInPictureRequested()480     public void testAutoEnterPictureInPictureOnPictureInPictureRequested() throws Exception {
481         // Launch a test activity so that we're not over home
482         launchActivity(TEST_ACTIVITY);
483 
484         // Launch the PIP activity on pip requested
485         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PIP_REQUESTED, "true");
486         assertPinnedStackDoesNotExist();
487 
488         // Call onPictureInPictureRequested and verify activity enters pip
489         separateTestJournal();
490         mBroadcastActionTrigger.doAction(ACTION_ON_PIP_REQUESTED);
491         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
492         assertPinnedStackExists();
493 
494         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
495         // Check the order of the callbacks accounting for a task overlay activity that might show.
496         // The PIP request (without a user leave hint) should come before the pip mode change.
497         final int firstUserLeaveIndex =
498                 lifecycleCounts.getFirstIndex(ActivityCallback.ON_USER_LEAVE_HINT);
499         final int firstPipRequestedIndex =
500                 lifecycleCounts.getFirstIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_REQUESTED);
501         final int firstPipModeChangedIndex =
502                 lifecycleCounts.getFirstIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
503         assertTrue("missing request", firstPipRequestedIndex != -1);
504         assertTrue("missing pip mode changed", firstPipModeChangedIndex != -1);
505         assertTrue("pip requested not before pause",
506                 firstPipRequestedIndex < firstPipModeChangedIndex);
507         assertTrue("unexpected user leave hint",
508                 firstUserLeaveIndex == -1 || firstUserLeaveIndex > firstPipModeChangedIndex);
509     }
510 
511     @Test
testAutoEnterPictureInPictureLaunchActivity()512     public void testAutoEnterPictureInPictureLaunchActivity() throws Exception {
513         // Launch a test activity so that we're not over home
514         launchActivity(TEST_ACTIVITY);
515 
516         // Launch the PIP activity on pause, and have it start another activity on
517         // top of itself.  Wait for the new activity to be visible and ensure that the pinned stack
518         // was not created in the process
519         launchActivity(PIP_ACTIVITY,
520                 EXTRA_ENTER_PIP_ON_PAUSE, "true",
521                 EXTRA_START_ACTIVITY, getActivityName(NON_RESIZEABLE_ACTIVITY));
522         mWmState.computeState(
523                 new WaitForValidActivityState(NON_RESIZEABLE_ACTIVITY));
524         assertPinnedStackDoesNotExist();
525 
526         // Go home while the pip activity is open and ensure the previous activity is not PIPed
527         launchHomeActivity();
528         assertPinnedStackDoesNotExist();
529     }
530 
531     @Test
testAutoEnterPictureInPictureFinish()532     public void testAutoEnterPictureInPictureFinish() throws Exception {
533         // Launch a test activity so that we're not over home
534         launchActivity(TEST_ACTIVITY);
535 
536         // Launch the PIP activity on pause, and set it to finish itself after
537         // some period.  Wait for the previous activity to be visible, and ensure that the pinned
538         // stack was not created in the process
539         launchActivity(PIP_ACTIVITY,
540                 EXTRA_ENTER_PIP_ON_PAUSE, "true",
541                 EXTRA_FINISH_SELF_ON_RESUME, "true");
542         assertPinnedStackDoesNotExist();
543     }
544 
545     @Test
testAutoEnterPictureInPictureAspectRatio()546     public void testAutoEnterPictureInPictureAspectRatio() throws Exception {
547         // Launch the PIP activity on pause, and set the aspect ratio
548         launchActivity(PIP_ACTIVITY,
549                 EXTRA_ENTER_PIP_ON_PAUSE, "true",
550                 EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
551                 EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR));
552 
553         // Go home while the pip activity is open to trigger auto-PIP
554         launchHomeActivity();
555         // Wait for animation complete since we are comparing aspect ratio
556         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
557         assertPinnedStackExists();
558 
559         waitForValidAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
560         Rect bounds = getPinnedStackBounds();
561         assertFloatEquals((float) bounds.width() / bounds.height(),
562                 (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
563     }
564 
565     @Test
testAutoEnterPictureInPictureOverPip()566     public void testAutoEnterPictureInPictureOverPip() throws Exception {
567         // Launch another PIP activity
568         launchActivity(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
569         waitForEnterPip(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
570         assertPinnedStackExists();
571 
572         // Launch the PIP activity on pause
573         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
574 
575         // Go home while the PIP activity is open to try to trigger auto-enter PIP
576         launchHomeActivity();
577         assertPinnedStackExists();
578 
579         // Ensure that auto-enter pip failed and that the resumed activity in the pinned stack is
580         // still the first activity
581         final ActivityTask pinnedStack = getPinnedStack();
582         assertEquals(getActivityName(ALWAYS_FOCUSABLE_PIP_ACTIVITY), pinnedStack.mRealActivity);
583     }
584 
585     @Test
testDismissPipWhenLaunchNewOne()586     public void testDismissPipWhenLaunchNewOne() throws Exception {
587         // Launch another PIP activity
588         launchActivity(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
589         waitForEnterPip(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
590         assertPinnedStackExists();
591         final ActivityTask pinnedStack = getPinnedStack();
592 
593         launchActivityInNewTask(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
594         waitForEnterPip(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
595 
596         assertEquals(1, mWmState.countStacks(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD));
597     }
598 
599     @Test
testDisallowMultipleTasksInPinnedStack()600     public void testDisallowMultipleTasksInPinnedStack() throws Exception {
601         // Launch a test activity so that we have multiple fullscreen tasks
602         launchActivity(TEST_ACTIVITY);
603 
604         // Launch first PIP activity
605         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
606         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
607 
608         // Launch second PIP activity
609         launchActivity(PIP_ACTIVITY2, EXTRA_ENTER_PIP, "true");
610 
611         final ActivityTask pinnedStack = getPinnedStack();
612         assertEquals(0, pinnedStack.getTasks().size());
613         assertTrue(mWmState.containsActivityInWindowingMode(
614                 PIP_ACTIVITY2, WINDOWING_MODE_PINNED));
615         assertTrue(mWmState.containsActivityInWindowingMode(
616                 PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN));
617     }
618 
619     @Test
testPipUnPipOverHome()620     public void testPipUnPipOverHome() throws Exception {
621         // Go home
622         launchHomeActivity();
623         // Launch an auto pip activity
624         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
625         waitForEnterPip(PIP_ACTIVITY);
626         assertPinnedStackExists();
627 
628         // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
629         launchActivity(PIP_ACTIVITY);
630         waitForExitPipToFullscreen(PIP_ACTIVITY);
631         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
632         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
633         mWmState.assertHomeActivityVisible(true);
634     }
635 
636     @Test
testPipUnPipOverApp()637     public void testPipUnPipOverApp() throws Exception {
638         // Launch a test activity so that we're not over home
639         launchActivity(TEST_ACTIVITY);
640 
641         // Launch an auto pip activity
642         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
643         waitForEnterPip(PIP_ACTIVITY);
644         assertPinnedStackExists();
645 
646         // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
647         launchActivity(PIP_ACTIVITY);
648         waitForExitPipToFullscreen(PIP_ACTIVITY);
649         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
650         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
651         mWmState.assertVisibility(TEST_ACTIVITY, true);
652     }
653 
654     @Test
testRemovePipWithNoFullscreenStack()655     public void testRemovePipWithNoFullscreenStack() throws Exception {
656         // Launch a pip activity
657         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
658         waitForEnterPip(PIP_ACTIVITY);
659         assertPinnedStackExists();
660 
661         // Remove the stack and ensure that the task is now in the fullscreen stack (when no
662         // fullscreen stack existed before)
663         removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
664         assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
665                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
666     }
667 
668     @Test
testRemovePipWithVisibleFullscreenStack()669     public void testRemovePipWithVisibleFullscreenStack() throws Exception {
670         // Launch a fullscreen activity, and a pip activity over that
671         launchActivity(TEST_ACTIVITY);
672         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
673         waitForEnterPip(PIP_ACTIVITY);
674         assertPinnedStackExists();
675 
676         // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
677         // top fullscreen activity
678         removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
679         assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
680                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
681     }
682 
683     @Test
testRemovePipWithHiddenFullscreenStack()684     public void testRemovePipWithHiddenFullscreenStack() throws Exception {
685         // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
686         // launch a pip activity over home
687         launchActivity(TEST_ACTIVITY);
688         launchHomeActivity();
689         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
690         waitForEnterPip(PIP_ACTIVITY);
691         assertPinnedStackExists();
692 
693         // Remove the stack and ensure that the task is placed on top of the hidden fullscreen
694         // stack, but that the home stack is still focused
695         removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
696         assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
697                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
698     }
699 
700     @Test
testMovePipToBackWithNoFullscreenStack()701     public void testMovePipToBackWithNoFullscreenStack() throws Exception {
702         // Start with a clean slate, remove all the stacks but home
703         removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
704 
705         // Launch a pip activity
706         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
707         waitForEnterPip(PIP_ACTIVITY);
708         assertPinnedStackExists();
709 
710         // Remove the stack and ensure that the task is now in the fullscreen stack (when no
711         // fullscreen stack existed before)
712         mBroadcastActionTrigger.doAction(ACTION_MOVE_TO_BACK);
713         assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
714                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
715     }
716 
717     @Test
testMovePipToBackWithVisibleFullscreenStack()718     public void testMovePipToBackWithVisibleFullscreenStack() throws Exception {
719         // Launch a fullscreen activity, and a pip activity over that
720         launchActivity(TEST_ACTIVITY);
721         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
722         waitForEnterPip(PIP_ACTIVITY);
723         assertPinnedStackExists();
724 
725         // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
726         // top fullscreen activity
727         mBroadcastActionTrigger.doAction(ACTION_MOVE_TO_BACK);
728         assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
729                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
730     }
731 
732     @Test
testMovePipToBackWithHiddenFullscreenStack()733     public void testMovePipToBackWithHiddenFullscreenStack() throws Exception {
734         // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
735         // launch a pip activity over home
736         launchActivity(TEST_ACTIVITY);
737         launchHomeActivity();
738         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
739         waitForEnterPip(PIP_ACTIVITY);
740         assertPinnedStackExists();
741 
742         // Remove the stack and ensure that the task is placed on top of the hidden fullscreen
743         // stack, but that the home stack is still focused
744         mBroadcastActionTrigger.doAction(ACTION_MOVE_TO_BACK);
745         assertPinnedStackStateOnMoveToFullscreen(
746                 PIP_ACTIVITY, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
747     }
748 
749     @Test
testPinnedStackAlwaysOnTop()750     public void testPinnedStackAlwaysOnTop() throws Exception {
751         // Launch activity into pinned stack and assert it's on top.
752         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
753         waitForEnterPip(PIP_ACTIVITY);
754         assertPinnedStackExists();
755         assertPinnedStackIsOnTop();
756 
757         // Launch another activity in fullscreen stack and check that pinned stack is still on top.
758         launchActivity(TEST_ACTIVITY);
759         assertPinnedStackExists();
760         assertPinnedStackIsOnTop();
761 
762         // Launch home and check that pinned stack is still on top.
763         launchHomeActivity();
764         assertPinnedStackExists();
765         assertPinnedStackIsOnTop();
766     }
767 
768     @Test
testAppOpsDenyPipOnPause()769     public void testAppOpsDenyPipOnPause() throws Exception {
770         try (final AppOpsSession appOpsSession = new AppOpsSession(PIP_ACTIVITY)) {
771             // Disable enter-pip and try to enter pip
772             appOpsSession.setOpToMode(APP_OPS_OP_ENTER_PICTURE_IN_PICTURE, APP_OPS_MODE_IGNORED);
773 
774             // Launch the PIP activity on pause
775             launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
776             assertPinnedStackDoesNotExist();
777 
778             // Go home and ensure that there is no pinned stack
779             launchHomeActivity();
780             assertPinnedStackDoesNotExist();
781         }
782     }
783 
784     @Test
testEnterPipFromTaskWithMultipleActivities()785     public void testEnterPipFromTaskWithMultipleActivities() throws Exception {
786         // Try to enter picture-in-picture from an activity that has more than one activity in the
787         // task and ensure that it works
788         launchActivity(LAUNCH_ENTER_PIP_ACTIVITY);
789         waitForEnterPip(PIP_ACTIVITY);
790         assertPinnedStackExists();
791     }
792 
793     @Test
testLaunchStoppedActivityWithPiPInSameProcessPreQ()794     public void testLaunchStoppedActivityWithPiPInSameProcessPreQ() {
795         // Try to enter picture-in-picture from an activity that has more than one activity in the
796         // task and ensure that it works, for pre-Q app
797         launchActivity(SDK_27_LAUNCH_ENTER_PIP_ACTIVITY,
798                 EXTRA_ENTER_PIP, "true");
799         waitForEnterPip(SDK_27_PIP_ACTIVITY);
800         assertPinnedStackExists();
801 
802         // Puts the host activity to stopped state
803         launchHomeActivity();
804         mWmState.assertHomeActivityVisible(true);
805         waitAndAssertActivityState(SDK_27_LAUNCH_ENTER_PIP_ACTIVITY, STATE_STOPPED,
806                 "Activity should become STOPPED");
807         mWmState.assertVisibility(SDK_27_LAUNCH_ENTER_PIP_ACTIVITY, false);
808 
809         // Host activity should be visible after re-launch and PiP window still exists
810         launchActivity(SDK_27_LAUNCH_ENTER_PIP_ACTIVITY);
811         waitAndAssertActivityState(SDK_27_LAUNCH_ENTER_PIP_ACTIVITY, STATE_RESUMED,
812                 "Activity should become RESUMED");
813         mWmState.assertVisibility(SDK_27_LAUNCH_ENTER_PIP_ACTIVITY, true);
814         assertPinnedStackExists();
815     }
816 
817     @Test
testEnterPipWithResumeWhilePausingActivityNoStop()818     public void testEnterPipWithResumeWhilePausingActivityNoStop() throws Exception {
819         /*
820          * Launch the resumeWhilePausing activity and ensure that the PiP activity did not get
821          * stopped and actually went into the pinned stack.
822          *
823          * Note that this is a workaround because to trigger the path that we want to happen in
824          * activity manager, we need to add the leaving activity to the stopping state, which only
825          * happens when a hidden stack is brought forward. Normally, this happens when you go home,
826          * but since we can't launch into the home stack directly, we have a workaround.
827          *
828          * 1) Launch an activity in a new dynamic stack
829          * 2) Start the PiP activity that will enter picture-in-picture when paused in the
830          *    fullscreen stack
831          * 3) Bring the activity in the dynamic stack forward to trigger PiP
832          */
833         launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
834         // Launch an activity that will enter PiP when it is paused with a delay that is long enough
835         // for the next resumeWhilePausing activity to finish resuming, but slow enough to not
836         // trigger the current system pause timeout (currently 500ms)
837         launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
838                 EXTRA_ENTER_PIP_ON_PAUSE, "true",
839                 EXTRA_ON_PAUSE_DELAY, "350",
840                 EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
841         launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
842         assertPinnedStackExists();
843     }
844 
845     @Test
testDisallowEnterPipActivityLocked()846     public void testDisallowEnterPipActivityLocked() throws Exception {
847         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
848         ActivityTask task = mWmState.getStandardStackByWindowingMode(
849                 WINDOWING_MODE_FULLSCREEN).getTopTask();
850 
851         // Lock the task and ensure that we can't enter picture-in-picture both explicitly and
852         // when paused
853         SystemUtil.runWithShellPermissionIdentity(() -> {
854             try {
855                 mAtm.startSystemLockTaskMode(task.mTaskId);
856                 waitForOrFail("Task in lock mode", () -> {
857                     return mAm.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
858                 });
859                 mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
860                 waitForEnterPip(PIP_ACTIVITY);
861                 assertPinnedStackDoesNotExist();
862                 launchHomeActivity();
863                 assertPinnedStackDoesNotExist();
864             } finally {
865                 mAtm.stopSystemLockTaskMode();
866             }
867         });
868     }
869 
870     @Test
testConfigurationChangeOrderDuringTransition()871     public void testConfigurationChangeOrderDuringTransition() throws Exception {
872         // Launch a PiP activity and ensure configuration change only happened once, and that the
873         // configuration change happened after the picture-in-picture and multi-window callbacks
874         launchActivity(PIP_ACTIVITY);
875         separateTestJournal();
876         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
877         waitForEnterPip(PIP_ACTIVITY);
878         assertPinnedStackExists();
879         waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
880         assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY);
881 
882         // Trigger it to go back to fullscreen and ensure that only triggered one configuration
883         // change as well
884         separateTestJournal();
885         launchActivity(PIP_ACTIVITY);
886         waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
887         assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY);
888     }
889 
890     /** Helper class to save, set, and restore transition_animation_scale preferences. */
891     private static class TransitionAnimationScaleSession extends SettingsSession<Float> {
TransitionAnimationScaleSession()892         TransitionAnimationScaleSession() {
893             super(Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE),
894                     Settings.Global::getFloat,
895                     Settings.Global::putFloat);
896         }
897 
898         @Override
close()899         public void close() {
900             // Wait for the restored setting to apply before we continue on with the next test
901             final CountDownLatch waitLock = new CountDownLatch(1);
902             final Context context = getInstrumentation().getTargetContext();
903             context.getContentResolver().registerContentObserver(mUri, false,
904                     new ContentObserver(new Handler(Looper.getMainLooper())) {
905                         @Override
906                         public void onChange(boolean selfChange) {
907                             waitLock.countDown();
908                         }
909                     });
910             super.close();
911             try {
912                 if (!waitLock.await(2, TimeUnit.SECONDS)) {
913                     Log.i(TAG, "TransitionAnimationScaleSession value not restored");
914                 }
915             } catch (InterruptedException impossible) {}
916         }
917     }
918 
919     @Ignore("b/149946388")
920     @Test
testEnterPipInterruptedCallbacks()921     public void testEnterPipInterruptedCallbacks() {
922         final TransitionAnimationScaleSession transitionAnimationScaleSession =
923                 mObjectTracker.manage(new TransitionAnimationScaleSession());
924         // Slow down the transition animations for this test
925         transitionAnimationScaleSession.set(20f);
926 
927         // Launch a PiP activity
928         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
929         // Wait until the PiP activity has moved into the pinned stack (happens before the
930         // transition has started)
931         waitForEnterPip(PIP_ACTIVITY);
932         assertPinnedStackExists();
933 
934         // Relaunch the PiP activity back into fullscreen
935         separateTestJournal();
936         launchActivity(PIP_ACTIVITY);
937         // Wait until the PiP activity is reparented into the fullscreen stack (happens after
938         // the transition has finished)
939         waitForExitPipToFullscreen(PIP_ACTIVITY);
940 
941         // Ensure that we get the callbacks indicating that PiP/MW mode was cancelled, but no
942         // configuration change (since none was sent)
943         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
944         assertEquals("onConfigurationChanged", 0,
945                 lifecycleCounts.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED));
946         assertEquals("onPictureInPictureModeChanged", 1,
947                 lifecycleCounts.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
948         assertEquals("onMultiWindowModeChanged", 1,
949                 lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
950     }
951 
952     @Test
testStopBeforeMultiWindowCallbacksOnDismiss()953     public void testStopBeforeMultiWindowCallbacksOnDismiss() throws Exception {
954         // Launch a PiP activity
955         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
956         // Wait for animation complete so that system has reported pip mode change event to
957         // client and the last reported pip mode has updated.
958         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
959         assertPinnedStackExists();
960 
961         // Dismiss it
962         separateTestJournal();
963         removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
964         waitForExitPipToFullscreen(PIP_ACTIVITY);
965         waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
966 
967         // Confirm that we get stop before the multi-window and picture-in-picture mode change
968         // callbacks
969         final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(PIP_ACTIVITY);
970         assertEquals("onStop", 1, lifecycles.getCount(ActivityCallback.ON_STOP));
971         assertEquals("onPictureInPictureModeChanged", 1,
972                 lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
973         assertEquals("onMultiWindowModeChanged", 1,
974                 lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
975         final int lastStopIndex = lifecycles.getLastIndex(ActivityCallback.ON_STOP);
976         final int lastPipIndex = lifecycles.getLastIndex(
977                 ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
978         final int lastMwIndex = lifecycles.getLastIndex(
979                 ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED);
980         assertThat("onStop should be before onPictureInPictureModeChanged",
981                 lastStopIndex, lessThan(lastPipIndex));
982         assertThat("onPictureInPictureModeChanged should be before onMultiWindowModeChanged",
983                 lastPipIndex, lessThan(lastMwIndex));
984     }
985 
986     @Test
testPreventSetAspectRatioWhileExpanding()987     public void testPreventSetAspectRatioWhileExpanding() throws Exception {
988         // Launch the PiP activity
989         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
990         waitForEnterPip(PIP_ACTIVITY);
991 
992         // Trigger it to go back to fullscreen and try to set the aspect ratio, and ensure that the
993         // call to set the aspect ratio did not prevent the PiP from returning to fullscreen
994         mBroadcastActionTrigger.expandPipWithAspectRatio("123456789", "100000000");
995         waitForExitPipToFullscreen(PIP_ACTIVITY);
996         assertPinnedStackDoesNotExist();
997     }
998 
999     @Test
testSetRequestedOrientationWhilePinned()1000     public void testSetRequestedOrientationWhilePinned() throws Exception {
1001         // Launch the PiP activity fixed as portrait, and enter picture-in-picture
1002         launchActivity(PIP_ACTIVITY,
1003                 EXTRA_PIP_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT),
1004                 EXTRA_ENTER_PIP, "true");
1005         waitForEnterPip(PIP_ACTIVITY);
1006         assertPinnedStackExists();
1007 
1008         // Request that the orientation is set to landscape
1009         mBroadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE);
1010 
1011         // Launch the activity back into fullscreen and ensure that it is now in landscape
1012         launchActivity(PIP_ACTIVITY);
1013         waitForExitPipToFullscreen(PIP_ACTIVITY);
1014         assertPinnedStackDoesNotExist();
1015         mWmState.waitForLastOrientation(ORIENTATION_LANDSCAPE);
1016         assertEquals(ORIENTATION_LANDSCAPE, mWmState.getLastOrientation());
1017     }
1018 
1019     @Test
testWindowButtonEntersPip()1020     public void testWindowButtonEntersPip() throws Exception {
1021         assumeTrue(!mWmState.isHomeRecentsComponent());
1022 
1023         // Launch the PiP activity trigger the window button, ensure that we have entered PiP
1024         launchActivity(PIP_ACTIVITY);
1025         pressWindowButton();
1026         waitForEnterPip(PIP_ACTIVITY);
1027         assertPinnedStackExists();
1028     }
1029 
1030     @Test
1031     @FlakyTest(bugId=156314330)
testFinishPipActivityWithTaskOverlay()1032     public void testFinishPipActivityWithTaskOverlay() throws Exception {
1033         // Trigger PiP menu activity to properly lose focuse when going home
1034         launchActivity(TEST_ACTIVITY);
1035         // Launch PiP activity
1036         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
1037         waitForEnterPip(PIP_ACTIVITY);
1038         assertPinnedStackExists();
1039         int taskId = mWmState.getStandardStackByWindowingMode(
1040                 WINDOWING_MODE_PINNED).getTopTask().mTaskId;
1041 
1042         // Ensure that we don't any any other overlays as a result of launching into PIP
1043         launchHomeActivity();
1044 
1045         // Launch task overlay activity into PiP activity task
1046         launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
1047 
1048         // Finish the PiP activity and ensure that there is no pinned stack
1049         mBroadcastActionTrigger.doAction(ACTION_FINISH);
1050         waitForPinnedStackRemoved();
1051         assertPinnedStackDoesNotExist();
1052     }
1053 
1054     @Test
testNoResumeAfterTaskOverlayFinishes()1055     public void testNoResumeAfterTaskOverlayFinishes() throws Exception {
1056         // Launch PiP activity
1057         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
1058         waitForEnterPip(PIP_ACTIVITY);
1059         assertPinnedStackExists();
1060         ActivityTask stack = mWmState.getStandardStackByWindowingMode(WINDOWING_MODE_PINNED);
1061         int taskId = stack.getTopTask().mTaskId;
1062 
1063         // Launch task overlay activity into PiP activity task
1064         launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
1065 
1066         // Finish the task overlay activity and ensure that the PiP activity never got resumed.
1067         separateTestJournal();
1068         mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
1069         mWmState.waitFor((amState) ->
1070                         !amState.containsActivity(TRANSLUCENT_TEST_ACTIVITY),
1071                 "Waiting for test activity to finish...");
1072         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
1073         assertEquals("onResume", 0, lifecycleCounts.getCount(ActivityCallback.ON_RESUME));
1074         assertEquals("onPause", 0, lifecycleCounts.getCount(ActivityCallback.ON_PAUSE));
1075     }
1076 
1077     @Test
testPinnedStackWithDockedStack()1078     public void testPinnedStackWithDockedStack() throws Exception {
1079         assumeTrue(supportsSplitScreenMultiWindow());
1080 
1081         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
1082         waitForEnterPip(PIP_ACTIVITY);
1083         launchActivitiesInSplitScreen(
1084                 getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
1085                 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
1086                         .setRandomData(true)
1087                         .setMultipleTask(false)
1088         );
1089         mWmState.assertVisibility(PIP_ACTIVITY, true);
1090         mWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
1091         mWmState.assertVisibility(TEST_ACTIVITY, true);
1092 
1093         // Launch the activities again to take focus and make sure nothing is hidden
1094         launchActivitiesInSplitScreen(
1095                 getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
1096                 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
1097                         .setRandomData(true)
1098                         .setMultipleTask(false)
1099         );
1100         mWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
1101         mWmState.assertVisibility(TEST_ACTIVITY, true);
1102 
1103         // Go to recents to make sure that fullscreen stack is invisible
1104         // Some devices do not support recents or implement it differently (instead of using a
1105         // separate stack id or as an activity), for those cases the visibility asserts will be
1106         // ignored
1107         pressAppSwitchButtonAndWaitForRecents();
1108         mWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
1109         mWmState.assertVisibility(TEST_ACTIVITY, false);
1110     }
1111 
1112     @Test
testLaunchTaskByComponentMatchMultipleTasks()1113     public void testLaunchTaskByComponentMatchMultipleTasks() throws Exception {
1114         // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
1115         // affinity
1116         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
1117         launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
1118         assertPinnedStackExists();
1119 
1120         // Launch the root activity again...
1121         int rootActivityTaskId = mWmState.getTaskByActivity(
1122                 TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId;
1123         launchHomeActivity();
1124         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
1125 
1126         // ...and ensure that the root activity task is found and reused, and that the pinned stack
1127         // is unaffected
1128         assertPinnedStackExists();
1129         mWmState.assertFocusedActivity("Expected root activity focused",
1130                 TEST_ACTIVITY_WITH_SAME_AFFINITY);
1131         assertEquals(rootActivityTaskId, mWmState.getTaskByActivity(
1132                 TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId);
1133     }
1134 
1135     @Test
testLaunchTaskByAffinityMatchMultipleTasks()1136     public void testLaunchTaskByAffinityMatchMultipleTasks() throws Exception {
1137         // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
1138         // affinity, and also launch another activity in the same task, while finishing itself. As
1139         // a result, the task will not have a component matching the same activity as what it was
1140         // started with
1141         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
1142                 EXTRA_START_ACTIVITY, getActivityName(TEST_ACTIVITY),
1143                 EXTRA_FINISH_SELF_ON_RESUME, "true");
1144         mWmState.waitForValidState(new WaitForValidActivityState.Builder(TEST_ACTIVITY)
1145                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
1146                 .setActivityType(ACTIVITY_TYPE_STANDARD)
1147                 .build());
1148         launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
1149         waitForEnterPip(PIP_ACTIVITY_WITH_SAME_AFFINITY);
1150         assertPinnedStackExists();
1151 
1152         // Launch the root activity again...
1153         int rootActivityTaskId = mWmState.getTaskByActivity(
1154                 TEST_ACTIVITY).mTaskId;
1155         launchHomeActivity();
1156         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
1157 
1158         // ...and ensure that even while matching purely by task affinity, the root activity task is
1159         // found and reused, and that the pinned stack is unaffected
1160         assertPinnedStackExists();
1161         mWmState.assertFocusedActivity("Expected root activity focused", TEST_ACTIVITY);
1162         assertEquals(rootActivityTaskId, mWmState.getTaskByActivity(
1163                 TEST_ACTIVITY).mTaskId);
1164     }
1165 
1166     @Test
testLaunchTaskByAffinityMatchSingleTask()1167     public void testLaunchTaskByAffinityMatchSingleTask() throws Exception {
1168         // Launch an activity into the pinned stack with a fixed affinity
1169         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
1170                 EXTRA_ENTER_PIP, "true",
1171                 EXTRA_START_ACTIVITY, getActivityName(PIP_ACTIVITY),
1172                 EXTRA_FINISH_SELF_ON_RESUME, "true");
1173         waitForEnterPip(PIP_ACTIVITY);
1174         assertPinnedStackExists();
1175 
1176         // Launch the root activity again, of the matching task and ensure that we expand to
1177         // fullscreen
1178         int activityTaskId = mWmState.getTaskByActivity(PIP_ACTIVITY).mTaskId;
1179         launchHomeActivity();
1180         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
1181         waitForExitPipToFullscreen(PIP_ACTIVITY);
1182         assertPinnedStackDoesNotExist();
1183         assertEquals(activityTaskId, mWmState.getTaskByActivity(
1184                 PIP_ACTIVITY).mTaskId);
1185     }
1186 
1187     /** Test that reported display size corresponds to fullscreen after exiting PiP. */
1188     @Test
testDisplayMetricsPinUnpin()1189     public void testDisplayMetricsPinUnpin() throws Exception {
1190         separateTestJournal();
1191         launchActivity(TEST_ACTIVITY);
1192         final int defaultWindowingMode = mWmState
1193                 .getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
1194         final SizeInfo initialSizes = getLastReportedSizesForActivity(TEST_ACTIVITY);
1195         final Rect initialAppBounds = getAppBounds(TEST_ACTIVITY);
1196         assertNotNull("Must report display dimensions", initialSizes);
1197         assertNotNull("Must report app bounds", initialAppBounds);
1198 
1199         separateTestJournal();
1200         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
1201         // Wait for animation complete since we are comparing bounds
1202         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
1203         final SizeInfo pinnedSizes = getLastReportedSizesForActivity(PIP_ACTIVITY);
1204         final Rect pinnedAppBounds = getAppBounds(PIP_ACTIVITY);
1205         assertNotEquals("Reported display size when pinned must be different from default",
1206                 initialSizes, pinnedSizes);
1207         final Size initialAppSize = new Size(initialAppBounds.width(), initialAppBounds.height());
1208         final Size pinnedAppSize = new Size(pinnedAppBounds.width(), pinnedAppBounds.height());
1209         assertNotEquals("Reported app size when pinned must be different from default",
1210                 initialAppSize, pinnedAppSize);
1211 
1212         separateTestJournal();
1213         launchActivity(PIP_ACTIVITY, defaultWindowingMode);
1214         final SizeInfo finalSizes = getLastReportedSizesForActivity(PIP_ACTIVITY);
1215         final Rect finalAppBounds = getAppBounds(PIP_ACTIVITY);
1216         final Size finalAppSize = new Size(finalAppBounds.width(), finalAppBounds.height());
1217         assertEquals("Must report default size after exiting PiP", initialSizes, finalSizes);
1218         assertEquals("Must report default app size after exiting PiP", initialAppSize,
1219                 finalAppSize);
1220     }
1221 
1222     /** Get app bounds in last applied configuration. */
getAppBounds(ComponentName activityName)1223     private Rect getAppBounds(ComponentName activityName) {
1224         final Configuration config = TestJournalContainer.get(activityName).extras
1225                 .getParcelable(EXTRA_CONFIGURATION);
1226         if (config != null) {
1227             return config.windowConfiguration.getAppBounds();
1228         }
1229         return null;
1230     }
1231 
1232     /**
1233      * Called after the given {@param activityName} has been moved to the fullscreen stack. Ensures
1234      * that the stack matching the {@param windowingMode} and {@param activityType} is focused, and
1235      * checks the top and/or bottom tasks in the fullscreen stack if
1236      * {@param expectTopTaskHasActivity} or {@param expectBottomTaskHasActivity} are set
1237      * respectively.
1238      */
assertPinnedStackStateOnMoveToFullscreen(ComponentName activityName, int windowingMode, int activityType)1239     private void assertPinnedStackStateOnMoveToFullscreen(ComponentName activityName,
1240             int windowingMode, int activityType) {
1241         mWmState.waitForFocusedStack(windowingMode, activityType);
1242         mWmState.assertFocusedStack("Wrong focused stack", windowingMode, activityType);
1243         waitAndAssertActivityState(activityName, STATE_STOPPED,
1244                 "Activity should go to STOPPED");
1245         assertTrue(mWmState.containsActivityInWindowingMode(
1246                 activityName, WINDOWING_MODE_FULLSCREEN));
1247         assertPinnedStackDoesNotExist();
1248     }
1249 
1250     /**
1251      * Asserts that the pinned stack bounds is contained in the display bounds.
1252      */
assertPinnedStackActivityIsInDisplayBounds(ComponentName activityName)1253     private void assertPinnedStackActivityIsInDisplayBounds(ComponentName activityName) {
1254         final WindowManagerState.WindowState windowState = getWindowState(activityName);
1255         final WindowManagerState.DisplayContent display = mWmState.getDisplay(
1256                 windowState.getDisplayId());
1257         final Rect displayRect = display.getDisplayRect();
1258         final Rect pinnedStackBounds = getPinnedStackBounds();
1259         assertTrue(displayRect.contains(pinnedStackBounds));
1260     }
1261 
1262     /**
1263      * Asserts that the pinned stack exists.
1264      */
assertPinnedStackExists()1265     private void assertPinnedStackExists() {
1266         mWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
1267                 ACTIVITY_TYPE_STANDARD);
1268     }
1269 
1270     /**
1271      * Asserts that the pinned stack does not exist.
1272      */
assertPinnedStackDoesNotExist()1273     private void assertPinnedStackDoesNotExist() {
1274         mWmState.assertDoesNotContainStack("Must not contain pinned stack.",
1275                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1276     }
1277 
1278     /**
1279      * Asserts that the pinned stack is the front stack.
1280      */
assertPinnedStackIsOnTop()1281     private void assertPinnedStackIsOnTop() {
1282         mWmState.assertFrontStack("Pinned stack must always be on top.",
1283                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1284     }
1285 
1286     /**
1287      * Asserts that the activity received exactly one of each of the callbacks when entering and
1288      * exiting picture-in-picture.
1289      */
assertValidPictureInPictureCallbackOrder(ComponentName activityName)1290     private void assertValidPictureInPictureCallbackOrder(ComponentName activityName) {
1291         final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
1292 
1293         assertEquals(getActivityName(activityName) + " onConfigurationChanged()",
1294                 1, lifecycles.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED));
1295         assertEquals(getActivityName(activityName) + " onPictureInPictureModeChanged()",
1296                 1, lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
1297         assertEquals(getActivityName(activityName) + " onMultiWindowModeChanged",
1298                 1, lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
1299         final int lastPipIndex = lifecycles
1300                 .getLastIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
1301         final int lastMwIndex = lifecycles
1302                 .getLastIndex(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED);
1303         final int lastConfigIndex = lifecycles
1304                 .getLastIndex(ActivityCallback.ON_CONFIGURATION_CHANGED);
1305         assertThat("onPictureInPictureModeChanged should be before onMultiWindowModeChanged",
1306                 lastPipIndex, lessThan(lastMwIndex));
1307         assertThat("onMultiWindowModeChanged should be before onConfigurationChanged",
1308                 lastMwIndex, lessThan(lastConfigIndex));
1309     }
1310 
1311     /**
1312      * Waits until the given activity has entered picture-in-picture mode (allowing for the
1313      * subsequent animation to start).
1314      */
waitForEnterPip(ComponentName activityName)1315     private void waitForEnterPip(ComponentName activityName) {
1316         mWmState.waitForWithAmState(wmState -> {
1317             ActivityTask task = wmState.getTaskByActivity(activityName);
1318             return task != null && task.getWindowingMode() == WINDOWING_MODE_PINNED;
1319         }, "checking task windowing mode");
1320     }
1321 
1322     /**
1323      * Waits until the picture-in-picture animation has finished.
1324      */
waitForEnterPipAnimationComplete(ComponentName activityName)1325     private void waitForEnterPipAnimationComplete(ComponentName activityName) {
1326         waitForEnterPip(activityName);
1327         mWmState.waitForWithAmState(wmState -> {
1328             ActivityTask task = wmState.getTaskByActivity(activityName);
1329             if (task == null) {
1330                 return false;
1331             }
1332             WindowManagerState.Activity activity = task.getActivity(activityName);
1333             return activity.getWindowingMode() == WINDOWING_MODE_PINNED
1334                     && activity.getState().equals(STATE_PAUSED);
1335         }, "checking activity windowing mode");
1336     }
1337 
1338     /**
1339      * Waits until the pinned stack has been removed.
1340      */
waitForPinnedStackRemoved()1341     private void waitForPinnedStackRemoved() {
1342         mWmState.waitFor((amState) ->
1343                 !amState.containsStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD),
1344                 "pinned stack to be removed");
1345     }
1346 
1347     /**
1348      * Waits until the picture-in-picture animation to fullscreen has finished.
1349      */
waitForExitPipToFullscreen(ComponentName activityName)1350     private void waitForExitPipToFullscreen(ComponentName activityName) {
1351         mWmState.waitForWithAmState(wmState -> {
1352             final ActivityTask task = wmState.getTaskByActivity(activityName);
1353             if (task == null) {
1354                 return false;
1355             }
1356             final WindowManagerState.Activity activity = task.getActivity(activityName);
1357             return activity.getWindowingMode() != WINDOWING_MODE_PINNED;
1358         }, "checking activity windowing mode");
1359         mWmState.waitForWithAmState(wmState -> {
1360             final ActivityTask task = wmState.getTaskByActivity(activityName);
1361             return task != null && task.getWindowingMode() != WINDOWING_MODE_PINNED;
1362         }, "checking task windowing mode");
1363     }
1364 
1365     /**
1366      * Waits until the expected picture-in-picture callbacks have been made.
1367      */
waitForValidPictureInPictureCallbacks(ComponentName activityName)1368     private void waitForValidPictureInPictureCallbacks(ComponentName activityName) {
1369         mWmState.waitFor((amState) -> {
1370             final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
1371             return lifecycles.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED) == 1
1372                     && lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED) == 1
1373                     && lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED) == 1;
1374         }, "picture-in-picture activity callbacks...");
1375     }
1376 
waitForValidAspectRatio(int num, int denom)1377     private void waitForValidAspectRatio(int num, int denom) {
1378         // Hacky, but we need to wait for the auto-enter picture-in-picture animation to complete
1379         // and before we can check the pinned stack bounds
1380         mWmState.waitForWithAmState((state) -> {
1381             Rect bounds = state.getStandardStackByWindowingMode(WINDOWING_MODE_PINNED).getBounds();
1382             return floatEquals((float) bounds.width() / bounds.height(), (float) num / denom);
1383         }, "valid aspect ratio");
1384     }
1385 
1386     /**
1387      * @return the window state for the given {@param activityName}'s window.
1388      */
getWindowState(ComponentName activityName)1389     private WindowManagerState.WindowState getWindowState(ComponentName activityName) {
1390         String windowName = getWindowName(activityName);
1391         mWmState.computeState(activityName);
1392         final List<WindowManagerState.WindowState> tempWindowList =
1393                 mWmState.getMatchingVisibleWindowState(windowName);
1394         return tempWindowList.get(0);
1395     }
1396 
1397     /**
1398      * @return the current pinned stack.
1399      */
getPinnedStack()1400     private ActivityTask getPinnedStack() {
1401         return mWmState.getStandardStackByWindowingMode(WINDOWING_MODE_PINNED);
1402     }
1403 
1404     /**
1405      * @return the current pinned stack bounds.
1406      */
getPinnedStackBounds()1407     private Rect getPinnedStackBounds() {
1408         return getPinnedStack().getBounds();
1409     }
1410 
1411     /**
1412      * Compares two floats with a common epsilon.
1413      */
assertFloatEquals(float actual, float expected)1414     private void assertFloatEquals(float actual, float expected) {
1415         if (!floatEquals(actual, expected)) {
1416             fail(expected + " not equal to " + actual);
1417         }
1418     }
1419 
floatEquals(float a, float b)1420     private boolean floatEquals(float a, float b) {
1421         return Math.abs(a - b) < FLOAT_COMPARE_EPSILON;
1422     }
1423 
1424     /**
1425      * Triggers a tap over the pinned stack bounds to trigger the PIP to close.
1426      */
tapToFinishPip()1427     private void tapToFinishPip() {
1428         Rect pinnedStackBounds = getPinnedStackBounds();
1429         int tapX = pinnedStackBounds.left + pinnedStackBounds.width() - 100;
1430         int tapY = pinnedStackBounds.top + pinnedStackBounds.height() - 100;
1431         tapOnDisplaySync(tapX, tapY, DEFAULT_DISPLAY);
1432     }
1433 
1434     /**
1435      * Launches the given {@param activityName} into the {@param taskId} as a task overlay.
1436      */
launchPinnedActivityAsTaskOverlay(ComponentName activityName, int taskId)1437     private void launchPinnedActivityAsTaskOverlay(ComponentName activityName, int taskId) {
1438         executeShellCommand(getAmStartCmd(activityName) + " --task " + taskId + " --task-overlay");
1439 
1440         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
1441                 .setWindowingMode(WINDOWING_MODE_PINNED)
1442                 .setActivityType(ACTIVITY_TYPE_STANDARD)
1443                 .build());
1444     }
1445 
1446     private static class AppOpsSession implements AutoCloseable {
1447 
1448         private final String mPackageName;
1449 
AppOpsSession(ComponentName activityName)1450         AppOpsSession(ComponentName activityName) {
1451             mPackageName = activityName.getPackageName();
1452         }
1453 
1454         /**
1455          * Sets an app-ops op for a given package to a given mode.
1456          */
setOpToMode(String op, int mode)1457         void setOpToMode(String op, int mode) {
1458             try {
1459                 AppOpsUtils.setOpMode(mPackageName, op, mode);
1460             } catch (Exception e) {
1461                 e.printStackTrace();
1462             }
1463         }
1464 
1465         @Override
close()1466         public void close() {
1467             try {
1468                 AppOpsUtils.reset(mPackageName);
1469             } catch (IOException e) {
1470                 e.printStackTrace();
1471             }
1472         }
1473     }
1474 
1475     /**
1476      * TODO: Improve tests check to actually check that apps are not interactive instead of checking
1477      *       if the stack is focused.
1478      */
pinnedStackTester(String startActivityCmd, ComponentName startActivity, ComponentName topActivityName, boolean moveTopToPinnedStack, boolean isFocusable)1479     private void pinnedStackTester(String startActivityCmd, ComponentName startActivity,
1480             ComponentName topActivityName, boolean moveTopToPinnedStack, boolean isFocusable) {
1481         executeShellCommand(startActivityCmd);
1482         mWmState.waitForValidState(startActivity);
1483 
1484         if (moveTopToPinnedStack) {
1485             final int stackId = mWmState.getStackIdByActivity(topActivityName);
1486 
1487             assertNotEquals(stackId, INVALID_STACK_ID);
1488             moveTopActivityToPinnedStack(stackId);
1489         }
1490 
1491         mWmState.waitForValidState(new WaitForValidActivityState.Builder(topActivityName)
1492                 .setWindowingMode(WINDOWING_MODE_PINNED)
1493                 .setActivityType(ACTIVITY_TYPE_STANDARD)
1494                 .build());
1495         mWmState.computeState();
1496 
1497         if (supportsPip()) {
1498             final String windowName = getWindowName(topActivityName);
1499             assertPinnedStackExists();
1500             mWmState.assertFrontStack("Pinned stack must be the front stack.",
1501                     WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1502             mWmState.assertVisibility(topActivityName, true);
1503 
1504             if (isFocusable) {
1505                 mWmState.assertFocusedStack("Pinned stack must be the focused stack.",
1506                         WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1507                 mWmState.assertFocusedActivity(
1508                         "Pinned activity must be focused activity.", topActivityName);
1509                 mWmState.assertFocusedWindow(
1510                         "Pinned window must be focused window.", windowName);
1511                 // Not checking for resumed state here because PiP overlay can be launched on top
1512                 // in different task by SystemUI.
1513             } else {
1514                 // Don't assert that the stack is not focused as a focusable PiP overlay can be
1515                 // launched on top as a task overlay by SystemUI.
1516                 mWmState.assertNotFocusedActivity(
1517                         "Pinned activity can't be the focused activity.", topActivityName);
1518                 mWmState.assertNotResumedActivity(
1519                         "Pinned activity can't be the resumed activity.", topActivityName);
1520                 mWmState.assertNotFocusedWindow(
1521                         "Pinned window can't be focused window.", windowName);
1522             }
1523         } else {
1524             mWmState.assertDoesNotContainStack("Must not contain pinned stack.",
1525                     WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1526         }
1527     }
1528 }
1529