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