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