• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package android.server.wm;
18 
19 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
23 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
24 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
25 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
26 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
27 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
28 import static android.server.wm.CliIntentExtra.extraBool;
29 import static android.server.wm.CliIntentExtra.extraString;
30 import static android.server.wm.ComponentNameUtils.getActivityName;
31 import static android.server.wm.ComponentNameUtils.getWindowName;
32 import static android.server.wm.UiDeviceUtils.pressBackButton;
33 import static android.server.wm.UiDeviceUtils.pressWindowButton;
34 import static android.server.wm.WindowManagerState.STATE_PAUSED;
35 import static android.server.wm.WindowManagerState.STATE_RESUMED;
36 import static android.server.wm.WindowManagerState.STATE_STOPPED;
37 import static android.server.wm.WindowManagerState.dpToPx;
38 import static android.server.wm.app.Components.ALWAYS_FOCUSABLE_PIP_ACTIVITY;
39 import static android.server.wm.app.Components.LAUNCH_ENTER_PIP_ACTIVITY;
40 import static android.server.wm.app.Components.LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY;
41 import static android.server.wm.app.Components.LAUNCH_INTO_PIP_CONTAINER_ACTIVITY;
42 import static android.server.wm.app.Components.LAUNCH_INTO_PIP_HOST_ACTIVITY;
43 import static android.server.wm.app.Components.LAUNCH_PIP_ON_PIP_ACTIVITY;
44 import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
45 import static android.server.wm.app.Components.PIP_ACTIVITY;
46 import static android.server.wm.app.Components.PIP_ACTIVITY2;
47 import static android.server.wm.app.Components.PIP_ACTIVITY_WITH_MINIMAL_SIZE;
48 import static android.server.wm.app.Components.PIP_ACTIVITY_WITH_SAME_AFFINITY;
49 import static android.server.wm.app.Components.PIP_ACTIVITY_WITH_TINY_MINIMAL_SIZE;
50 import static android.server.wm.app.Components.PIP_ON_STOP_ACTIVITY;
51 import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP;
52 import static android.server.wm.app.Components.PipActivity.ACTION_FINISH;
53 import static android.server.wm.app.Components.PipActivity.ACTION_FINISH_LAUNCH_INTO_PIP_HOST;
54 import static android.server.wm.app.Components.PipActivity.ACTION_LAUNCH_TRANSLUCENT_ACTIVITY;
55 import static android.server.wm.app.Components.PipActivity.ACTION_MOVE_TO_BACK;
56 import static android.server.wm.app.Components.PipActivity.ACTION_ON_PIP_REQUESTED;
57 import static android.server.wm.app.Components.PipActivity.ACTION_START_LAUNCH_INTO_PIP_CONTAINER;
58 import static android.server.wm.app.Components.PipActivity.EXTRA_ALLOW_AUTO_PIP;
59 import static android.server.wm.app.Components.PipActivity.EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP;
60 import static android.server.wm.app.Components.PipActivity.EXTRA_CLOSE_ACTION;
61 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
62 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
63 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR;
64 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_BACK_PRESSED;
65 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PAUSE;
66 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PIP_REQUESTED;
67 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT;
68 import static android.server.wm.app.Components.PipActivity.EXTRA_EXPANDED_PIP_ASPECT_RATIO_DENOMINATOR;
69 import static android.server.wm.app.Components.PipActivity.EXTRA_EXPANDED_PIP_ASPECT_RATIO_NUMERATOR;
70 import static android.server.wm.app.Components.PipActivity.EXTRA_FINISH_SELF_ON_RESUME;
71 import static android.server.wm.app.Components.PipActivity.EXTRA_IS_SEAMLESS_RESIZE_ENABLED;
72 import static android.server.wm.app.Components.PipActivity.EXTRA_NUMBER_OF_CUSTOM_ACTIONS;
73 import static android.server.wm.app.Components.PipActivity.EXTRA_ON_PAUSE_DELAY;
74 import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION;
75 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_DENOMINATOR;
76 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_NUMERATOR;
77 import static android.server.wm.app.Components.PipActivity.EXTRA_START_ACTIVITY;
78 import static android.server.wm.app.Components.PipActivity.EXTRA_SUBTITLE;
79 import static android.server.wm.app.Components.PipActivity.EXTRA_TAP_TO_FINISH;
80 import static android.server.wm.app.Components.PipActivity.EXTRA_TITLE;
81 import static android.server.wm.app.Components.PipActivity.UI_STATE_STASHED_RESULT;
82 import static android.server.wm.app.Components.RESUME_WHILE_PAUSING_ACTIVITY;
83 import static android.server.wm.app.Components.TEST_ACTIVITY;
84 import static android.server.wm.app.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY;
85 import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
86 import static android.server.wm.app.Components.TestActivity.EXTRA_CONFIGURATION;
87 import static android.server.wm.app.Components.TestActivity.EXTRA_FIXED_ORIENTATION;
88 import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
89 import static android.server.wm.app27.Components.SDK_27_LAUNCH_ENTER_PIP_ACTIVITY;
90 import static android.server.wm.app27.Components.SDK_27_PIP_ACTIVITY;
91 import static android.view.Display.DEFAULT_DISPLAY;
92 
93 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
94 
95 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
96 import static org.hamcrest.Matchers.lessThan;
97 import static org.hamcrest.Matchers.lessThanOrEqualTo;
98 import static org.junit.Assert.assertEquals;
99 import static org.junit.Assert.assertFalse;
100 import static org.junit.Assert.assertNotEquals;
101 import static org.junit.Assert.assertNotNull;
102 import static org.junit.Assert.assertThat;
103 import static org.junit.Assert.assertTrue;
104 import static org.junit.Assert.fail;
105 import static org.junit.Assume.assumeFalse;
106 import static org.junit.Assume.assumeTrue;
107 
108 import android.app.Activity;
109 import android.app.ActivityTaskManager;
110 import android.app.PictureInPictureParams;
111 import android.app.TaskInfo;
112 import android.content.ComponentName;
113 import android.content.Context;
114 import android.content.Intent;
115 import android.content.pm.ActivityInfo;
116 import android.content.pm.PackageManager;
117 import android.content.res.Configuration;
118 import android.database.ContentObserver;
119 import android.graphics.Rect;
120 import android.os.Bundle;
121 import android.os.Handler;
122 import android.os.Looper;
123 import android.os.RemoteCallback;
124 import android.platform.test.annotations.AsbSecurityTest;
125 import android.platform.test.annotations.Presubmit;
126 import android.provider.Settings;
127 import android.server.wm.CommandSession.ActivityCallback;
128 import android.server.wm.CommandSession.SizeInfo;
129 import android.server.wm.TestJournalProvider.TestJournalContainer;
130 import android.server.wm.WindowManagerState.Task;
131 import android.server.wm.settings.SettingsSession;
132 import android.util.Log;
133 import android.util.Size;
134 
135 import com.android.compatibility.common.util.AppOpsUtils;
136 import com.android.compatibility.common.util.SystemUtil;
137 
138 import com.google.common.truth.Truth;
139 
140 import org.junit.Before;
141 import org.junit.Ignore;
142 import org.junit.Test;
143 
144 import java.io.IOException;
145 import java.util.concurrent.CompletableFuture;
146 import java.util.concurrent.CountDownLatch;
147 import java.util.concurrent.TimeUnit;
148 
149 /**
150  * Build/Install/Run:
151  * atest CtsWindowManagerDeviceTestCases:PinnedStackTests
152  */
153 @Presubmit
154 @android.server.wm.annotation.Group2
155 public class PinnedStackTests extends ActivityManagerTestBase {
156     private static final String TAG = PinnedStackTests.class.getSimpleName();
157 
158     private static final String APP_OPS_OP_ENTER_PICTURE_IN_PICTURE = "PICTURE_IN_PICTURE";
159     private static final int APP_OPS_MODE_IGNORED = 1;
160 
161     private static final int ROTATION_0 = 0;
162     private static final int ROTATION_90 = 1;
163     private static final int ROTATION_180 = 2;
164     private static final int ROTATION_270 = 3;
165 
166     private static final float FLOAT_COMPARE_EPSILON = 0.005f;
167 
168     // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio
169     private static final int MIN_ASPECT_RATIO_NUMERATOR = 100;
170     private static final int MIN_ASPECT_RATIO_DENOMINATOR = 239;
171     private static final int BELOW_MIN_ASPECT_RATIO_DENOMINATOR = MIN_ASPECT_RATIO_DENOMINATOR + 1;
172     // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio
173     private static final int MAX_ASPECT_RATIO_NUMERATOR = 239;
174     private static final int MAX_ASPECT_RATIO_DENOMINATOR = 100;
175     private static final int ABOVE_MAX_ASPECT_RATIO_NUMERATOR = MAX_ASPECT_RATIO_NUMERATOR + 1;
176     // Corresponds to com.android.internal.R.dimen.overridable_minimal_size_pip_resizable_task
177     private static final int OVERRIDABLE_MINIMAL_SIZE_PIP_RESIZABLE_TASK = 48;
178 
179     @Before
180     @Override
setUp()181     public void setUp() throws Exception {
182         super.setUp();
183         assumeTrue(supportsPip());
184     }
185 
186     @Test
testMinimumDeviceSize()187     public void testMinimumDeviceSize() {
188         mWmState.assertDeviceDefaultDisplaySizeForMultiWindow(
189                 "Devices supporting picture-in-picture must be larger than the default minimum"
190                         + " task size");
191     }
192 
193     @Test
testEnterPictureInPictureMode()194     public void testEnterPictureInPictureMode() {
195         pinnedStackTester(getAmStartCmd(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true")),
196                 PIP_ACTIVITY, PIP_ACTIVITY, false /* isFocusable */);
197     }
198 
199     // This test is black-listed in cts-known-failures.xml (b/35314835).
200     @Ignore
201     @Test
testAlwaysFocusablePipActivity()202     public void testAlwaysFocusablePipActivity() {
203         pinnedStackTester(getAmStartCmd(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
204                 ALWAYS_FOCUSABLE_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
205                 true /* isFocusable */);
206     }
207 
208     // This test is black-listed in cts-known-failures.xml (b/35314835).
209     @Ignore
210     @Test
testLaunchIntoPinnedStack()211     public void testLaunchIntoPinnedStack() {
212         pinnedStackTester(getAmStartCmd(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY),
213                 LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
214                 true /* isFocusable */);
215     }
216 
217     @Test
testNonTappablePipActivity()218     public void testNonTappablePipActivity() {
219         // Launch the tap-to-finish activity at a specific place
220         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"),
221                 extraString(EXTRA_TAP_TO_FINISH, "true"));
222         // Wait for animation complete since we are tapping on specific bounds
223         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
224         assertPinnedStackExists();
225 
226         // Tap the screen at a known location in the pinned stack bounds, and ensure that it is
227         // not passed down to the top task
228         tapToFinishPip();
229         mWmState.computeState(
230                 new WaitForValidActivityState(PIP_ACTIVITY));
231         mWmState.assertVisibility(PIP_ACTIVITY, true);
232     }
233 
234     @Test
testPinnedStackInBoundsAfterRotation()235     public void testPinnedStackInBoundsAfterRotation() {
236         assumeTrue("Skipping test: no rotation support", supportsRotation());
237 
238         // Launch an activity that is not fixed-orientation so that the display can rotate
239         launchActivity(TEST_ACTIVITY);
240         // Launch an activity into the pinned stack
241         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"),
242                 extraString(EXTRA_TAP_TO_FINISH, "true"));
243         // Wait for animation complete since we are comparing bounds
244         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
245 
246         // Ensure that the PIP stack is fully visible in each orientation
247         final RotationSession rotationSession = createManagedRotationSession();
248         rotationSession.set(ROTATION_0);
249         assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
250         rotationSession.set(ROTATION_90);
251         assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
252         rotationSession.set(ROTATION_180);
253         assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
254         rotationSession.set(ROTATION_270);
255         assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
256     }
257 
258     @Test
testEnterPipToOtherOrientation()259     public void testEnterPipToOtherOrientation() {
260         // Launch a portrait only app on the fullscreen stack
261         launchActivity(TEST_ACTIVITY,
262                 extraString(EXTRA_FIXED_ORIENTATION, String.valueOf(SCREEN_ORIENTATION_PORTRAIT)));
263         // Launch the PiP activity fixed as landscape
264         launchActivity(PIP_ACTIVITY,
265                 extraString(EXTRA_PIP_ORIENTATION, String.valueOf(SCREEN_ORIENTATION_LANDSCAPE)));
266         // Enter PiP, and assert that the PiP is within bounds now that the device is back in
267         // portrait
268         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
269         // Wait for animation complete since we are comparing bounds
270         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
271         assertPinnedStackExists();
272         assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
273     }
274 
275     @Test
testEnterPipWithMinimalSize()276     public void testEnterPipWithMinimalSize() throws Exception {
277         // Launch a PiP activity with minimal size specified
278         launchActivity(PIP_ACTIVITY_WITH_MINIMAL_SIZE, extraString(EXTRA_ENTER_PIP, "true"));
279         // Wait for animation complete since we are comparing size
280         waitForEnterPipAnimationComplete(PIP_ACTIVITY_WITH_MINIMAL_SIZE);
281         assertPinnedStackExists();
282 
283         // query the minimal size
284         final PackageManager pm = getInstrumentation().getTargetContext().getPackageManager();
285         final ActivityInfo info = pm.getActivityInfo(
286                 PIP_ACTIVITY_WITH_MINIMAL_SIZE, 0 /* flags */);
287         final Size minSize = new Size(info.windowLayout.minWidth, info.windowLayout.minHeight);
288 
289         // compare the bounds with minimal size
290         final Rect pipBounds = getPinnedStackBounds();
291         assertTrue("Pinned task bounds " + pipBounds + " isn't smaller than minimal " + minSize,
292                 (pipBounds.width() == minSize.getWidth()
293                         && pipBounds.height() >= minSize.getHeight())
294                         || (pipBounds.height() == minSize.getHeight()
295                         && pipBounds.width() >= minSize.getWidth()));
296     }
297 
298     @Test
299     @AsbSecurityTest(cveBugId = 174302616)
testEnterPipWithTinyMinimalSize()300     public void testEnterPipWithTinyMinimalSize() {
301         // Launch a PiP activity with minimal size specified and smaller than allowed minimum
302         launchActivity(PIP_ACTIVITY_WITH_TINY_MINIMAL_SIZE, extraString(EXTRA_ENTER_PIP, "true"));
303         // Wait for animation complete since we are comparing size
304         waitForEnterPipAnimationComplete(PIP_ACTIVITY_WITH_TINY_MINIMAL_SIZE);
305         assertPinnedStackExists();
306 
307         final WindowManagerState.WindowState windowState = mWmState.getWindowState(
308                 PIP_ACTIVITY_WITH_TINY_MINIMAL_SIZE);
309         final WindowManagerState.DisplayContent display = mWmState.getDisplay(
310                 windowState.getDisplayId());
311         final int overridableMinSize = dpToPx(
312                 OVERRIDABLE_MINIMAL_SIZE_PIP_RESIZABLE_TASK, display.getDpi());
313 
314         // compare the bounds to verify that it's no smaller than allowed minimum on both dimensions
315         final Rect pipBounds = getPinnedStackBounds();
316         assertTrue("Pinned task bounds " + pipBounds + " isn't smaller than minimal "
317                         + overridableMinSize + " on both dimensions",
318                 pipBounds.width() >= overridableMinSize
319                         && pipBounds.height() >= overridableMinSize);
320     }
321 
322     @Test
testEnterPipAspectRatioMin()323     public void testEnterPipAspectRatioMin() {
324         testEnterPipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
325     }
326 
327     @Test
testEnterPipAspectRatioMax()328     public void testEnterPipAspectRatioMax() {
329         testEnterPipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
330     }
331 
332     @Test
testEnterPipOnBackPressed()333     public void testEnterPipOnBackPressed() {
334         // Launch a PiP activity that calls enterPictureInPictureMode when it receives
335         // onBackPressed callback.
336         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_BACK_PRESSED, "true"));
337 
338         assertEnterPipOnBackPressed(PIP_ACTIVITY);
339     }
340 
341     @Test
testEnterPipOnBackPressedWithAutoPipEnabled()342     public void testEnterPipOnBackPressedWithAutoPipEnabled() {
343         // Launch the PIP activity that calls enterPictureInPictureMode when it receives
344         // onBackPressed callback and set its pip params to allow auto-pip.
345         launchActivity(PIP_ACTIVITY,
346                 extraString(EXTRA_ALLOW_AUTO_PIP, "true"),
347                 extraString(EXTRA_ENTER_PIP_ON_BACK_PRESSED, "true"));
348 
349         assertEnterPipOnBackPressed(PIP_ACTIVITY);
350     }
351 
assertEnterPipOnBackPressed(ComponentName componentName)352     private void assertEnterPipOnBackPressed(ComponentName componentName) {
353         // Press the back button.
354         pressBackButton();
355         // Assert that we have entered PiP.
356         waitForEnterPipAnimationComplete(componentName);
357         assertPinnedStackExists();
358     }
359 
360     @Test
testEnterExpandedPipAspectRatio()361     public void testEnterExpandedPipAspectRatio() {
362         assumeTrue(supportsExpandedPip());
363         launchActivity(PIP_ACTIVITY,
364                 extraString(EXTRA_ENTER_PIP, "true"),
365                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(2)),
366                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(1)),
367                 extraString(EXTRA_EXPANDED_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(1)),
368                 extraString(EXTRA_EXPANDED_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(4)));
369         // Wait for animation complete since we are comparing aspect ratio
370         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
371         assertPinnedStackExists();
372         // Assert that we have entered PIP and that the aspect ratio is correct
373         final Rect bounds = getPinnedStackBounds();
374         assertFloatEquals((float) bounds.width() / bounds.height(), (float) 1.0f / 4.0f);
375     }
376 
377     @Test
testEnterExpandedPipAspectRatioMaxHeight()378     public void testEnterExpandedPipAspectRatioMaxHeight() {
379         assumeTrue(supportsExpandedPip());
380         launchActivity(PIP_ACTIVITY,
381                 extraString(EXTRA_ENTER_PIP, "true"),
382                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(2)),
383                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(1)),
384                 extraString(EXTRA_EXPANDED_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(1)),
385                 extraString(EXTRA_EXPANDED_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(1000)));
386         // Wait for animation complete since we are comparing aspect ratio
387         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
388         assertPinnedStackExists();
389         // Assert that we have entered PIP and that the aspect ratio is correct
390         final Rect bounds = getPinnedStackBounds();
391         final int displayHeight = mWmState.getDisplay(DEFAULT_DISPLAY).getDisplayRect().height();
392         assertTrue(bounds.height() <= displayHeight);
393     }
394 
395     @Test
testEnterExpandedPipAspectRatioMaxWidth()396     public void testEnterExpandedPipAspectRatioMaxWidth() {
397         assumeTrue(supportsExpandedPip());
398         launchActivity(PIP_ACTIVITY,
399                 extraString(EXTRA_ENTER_PIP, "true"),
400                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(2)),
401                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(1)),
402                 extraString(EXTRA_EXPANDED_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(1000)),
403                 extraString(EXTRA_EXPANDED_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(1)));
404         // Wait for animation complete since we are comparing aspect ratio
405         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
406         assertPinnedStackExists();
407         // Assert that we have entered PIP and that the aspect ratio is correct
408         final Rect bounds = getPinnedStackBounds();
409         final int displayWidth = mWmState.getDisplay(DEFAULT_DISPLAY).getDisplayRect().width();
410         assertTrue(bounds.width() <= displayWidth);
411     }
412 
413     @Test
testEnterExpandedPipWithNormalAspectRatio()414     public void testEnterExpandedPipWithNormalAspectRatio() {
415         assumeTrue(supportsExpandedPip());
416         launchActivity(PIP_ACTIVITY,
417                 extraString(EXTRA_ENTER_PIP, "true"),
418                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(2)),
419                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(1)),
420                 extraString(EXTRA_EXPANDED_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(1)),
421                 extraString(EXTRA_EXPANDED_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(2)));
422         assertPinnedStackDoesNotExist();
423 
424         launchActivity(PIP_ACTIVITY,
425                 extraString(EXTRA_ENTER_PIP, "true"),
426                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(2)),
427                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(1)),
428                 extraString(EXTRA_EXPANDED_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(2)),
429                 extraString(EXTRA_EXPANDED_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(1)));
430         assertPinnedStackDoesNotExist();
431     }
432 
433     @Test
testChangeAspectRationWhenInPipMode()434     public void testChangeAspectRationWhenInPipMode() {
435         // Enter PiP mode with a 2:1 aspect ratio
436         testEnterPipAspectRatio(2, 1);
437 
438         // Change the aspect ratio to 1:2
439         final int newNumerator = 1;
440         final int newDenominator = 2;
441         mBroadcastActionTrigger.changeAspectRatio(newNumerator, newDenominator);
442 
443         waitForValidAspectRatio(newNumerator, newDenominator);
444     }
445 
testEnterPipAspectRatio(int num, int denom)446     private void testEnterPipAspectRatio(int num, int denom) {
447         // Launch a test activity so that we're not over home
448         launchActivity(TEST_ACTIVITY);
449 
450         launchActivity(PIP_ACTIVITY,
451                 extraString(EXTRA_ENTER_PIP, "true"),
452                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num)),
453                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom)));
454         // Wait for animation complete since we are comparing aspect ratio
455         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
456         assertPinnedStackExists();
457 
458         // Assert that we have entered PIP and that the aspect ratio is correct
459         Rect pinnedStackBounds = getPinnedStackBounds();
460         assertFloatEquals((float) pinnedStackBounds.width() / pinnedStackBounds.height(),
461                 (float) num / denom);
462     }
463 
464     @Test
testResizePipAspectRatioMin()465     public void testResizePipAspectRatioMin() {
466         testResizePipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
467     }
468 
469     @Test
testResizePipAspectRatioMax()470     public void testResizePipAspectRatioMax() {
471         testResizePipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
472     }
473 
testResizePipAspectRatio(int num, int denom)474     private void testResizePipAspectRatio(int num, int denom) {
475         // Launch a test activity so that we're not over home
476         launchActivity(TEST_ACTIVITY);
477 
478         launchActivity(PIP_ACTIVITY,
479                 extraString(EXTRA_ENTER_PIP, "true"),
480                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num)),
481                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom)));
482         // Wait for animation complete since we are comparing aspect ratio
483         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
484         assertPinnedStackExists();
485         waitForValidAspectRatio(num, denom);
486         Rect bounds = getPinnedStackBounds();
487         assertFloatEquals((float) bounds.width() / bounds.height(), (float) num / denom);
488     }
489 
490     @Test
testEnterPipExtremeAspectRatioMin()491     public void testEnterPipExtremeAspectRatioMin() {
492         testEnterPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
493                 BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
494     }
495 
496     @Test
testEnterPipExtremeAspectRatioMax()497     public void testEnterPipExtremeAspectRatioMax() {
498         testEnterPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
499                 MAX_ASPECT_RATIO_DENOMINATOR);
500     }
501 
testEnterPipExtremeAspectRatio(int num, int denom)502     private void testEnterPipExtremeAspectRatio(int num, int denom) {
503         // Launch a test activity so that we're not over home
504         launchActivity(TEST_ACTIVITY);
505 
506         // Assert that we could not create a pinned stack with an extreme aspect ratio
507         launchActivity(PIP_ACTIVITY,
508                 extraString(EXTRA_ENTER_PIP, "true"),
509                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num)),
510                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom)));
511         assertPinnedStackDoesNotExist();
512     }
513 
514     @Test
testSetPipExtremeAspectRatioMin()515     public void testSetPipExtremeAspectRatioMin() {
516         testSetPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
517                 BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
518     }
519 
520     @Test
testSetPipExtremeAspectRatioMax()521     public void testSetPipExtremeAspectRatioMax() {
522         testSetPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
523                 MAX_ASPECT_RATIO_DENOMINATOR);
524     }
525 
testSetPipExtremeAspectRatio(int num, int denom)526     private void testSetPipExtremeAspectRatio(int num, int denom) {
527         // Launch a test activity so that we're not over home
528         launchActivity(TEST_ACTIVITY);
529 
530         // Try to resize the a normal pinned stack to an extreme aspect ratio and ensure that
531         // fails (the aspect ratio remains the same)
532         launchActivity(PIP_ACTIVITY,
533                 extraString(EXTRA_ENTER_PIP, "true"),
534                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
535                         Integer.toString(MAX_ASPECT_RATIO_NUMERATOR)),
536                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR,
537                         Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR)),
538                 extraString(EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num)),
539                 extraString(EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom)));
540         // Wait for animation complete since we are comparing aspect ratio
541         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
542         assertPinnedStackExists();
543         Rect pinnedStackBounds = getPinnedStackBounds();
544         assertFloatEquals((float) pinnedStackBounds.width() / pinnedStackBounds.height(),
545                 (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
546     }
547 
548     @Test
testShouldDockBigOverlaysWithExpandedPip()549     public void testShouldDockBigOverlaysWithExpandedPip() {
550         testShouldDockBigOverlaysWithExpandedPip(true);
551     }
552 
553     @Test
testShouldNotDockBigOverlaysWithExpandedPip()554     public void testShouldNotDockBigOverlaysWithExpandedPip() {
555         testShouldDockBigOverlaysWithExpandedPip(false);
556     }
557 
testShouldDockBigOverlaysWithExpandedPip(boolean shouldDock)558     private void testShouldDockBigOverlaysWithExpandedPip(boolean shouldDock) {
559         assumeTrue(supportsExpandedPip());
560         TestActivitySession<TestActivity> testSession = createManagedTestActivitySession();
561         final Intent intent = new Intent(mContext, TestActivity.class);
562         testSession.launchTestActivityOnDisplaySync(null, intent, DEFAULT_DISPLAY);
563         final TestActivity activity = testSession.getActivity();
564         mWmState.assertResumedActivity("Activity must be resumed", activity.getComponentName());
565 
566         launchActivity(PIP_ACTIVITY,
567                 extraString(EXTRA_ENTER_PIP, "true"),
568                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(2)),
569                 extraString(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(1)),
570                 extraString(EXTRA_EXPANDED_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(1)),
571                 extraString(EXTRA_EXPANDED_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(4)));
572         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
573         assertPinnedStackExists();
574 
575         testSession.runOnMainSyncAndWait(() -> activity.setShouldDockBigOverlays(shouldDock));
576 
577         mWmState.assertResumedActivity("Activity must be resumed", activity.getComponentName());
578         assertPinnedStackExists();
579         runWithShellPermission(() -> {
580             final Task task = mWmState.getTaskByActivity(activity.getComponentName());
581             final TaskInfo info = mTaskOrganizer.getTaskInfo(task.getTaskId());
582 
583             assertEquals(shouldDock, info.shouldDockBigOverlays());
584         });
585 
586         final boolean[] actual = new boolean[] {!shouldDock};
587         testSession.runOnMainSyncAndWait(() -> {
588             actual[0] = activity.shouldDockBigOverlays();
589         });
590 
591         assertEquals(shouldDock, actual[0]);
592     }
593 
594     @Test
testDisallowPipLaunchFromStoppedActivity()595     public void testDisallowPipLaunchFromStoppedActivity() {
596         // Launch the bottom pip activity which will launch a new activity on top and attempt to
597         // enter pip when it is stopped
598         launchActivityNoWait(PIP_ON_STOP_ACTIVITY);
599 
600         // Wait for the bottom pip activity to be stopped
601         mWmState.waitForActivityState(PIP_ON_STOP_ACTIVITY, STATE_STOPPED);
602 
603         // Assert that there is no pinned stack (that enterPictureInPicture() failed)
604         assertPinnedStackDoesNotExist();
605     }
606 
607     @Test
testLaunchIntoPip()608     public void testLaunchIntoPip() {
609         // Launch a Host activity for launch-into-pip
610         launchActivity(LAUNCH_INTO_PIP_HOST_ACTIVITY);
611 
612         // Send broadcast to Host activity to start a launch-into-pip container activity
613         mBroadcastActionTrigger.doAction(ACTION_START_LAUNCH_INTO_PIP_CONTAINER);
614 
615         // Verify the launch-into-pip container activity enters PiP
616         waitForEnterPipAnimationComplete(LAUNCH_INTO_PIP_CONTAINER_ACTIVITY);
617         assertPinnedStackExists();
618     }
619 
620     @Test
testRemoveLaunchIntoPipHostActivity()621     public void testRemoveLaunchIntoPipHostActivity() {
622         // Launch a Host activity for launch-into-pip
623         launchActivity(LAUNCH_INTO_PIP_HOST_ACTIVITY);
624 
625         // Send broadcast to Host activity to start a launch-into-pip container activity
626         mBroadcastActionTrigger.doAction(ACTION_START_LAUNCH_INTO_PIP_CONTAINER);
627 
628         // Remove the Host activity / task by finishing the host activity
629         waitForEnterPipAnimationComplete(LAUNCH_INTO_PIP_CONTAINER_ACTIVITY);
630         assertPinnedStackExists();
631         mBroadcastActionTrigger.doAction(ACTION_FINISH_LAUNCH_INTO_PIP_HOST);
632 
633         // Verify the launch-into-pip container activity finishes
634         waitForPinnedStackRemoved();
635         assertPinnedStackDoesNotExist();
636     }
637 
638     @Test
testAutoEnterPictureInPicture()639     public void testAutoEnterPictureInPicture() {
640         // Launch a test activity so that we're not over home
641         launchActivity(TEST_ACTIVITY);
642 
643         // Launch the PIP activity on pause
644         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"));
645         assertPinnedStackDoesNotExist();
646 
647         // Go home and ensure that there is a pinned stack
648         launchHomeActivity();
649         waitForEnterPip(PIP_ACTIVITY);
650         assertPinnedStackExists();
651     }
652 
653     @Test
testAutoEnterPictureInPictureOnUserLeaveHintWhenPipRequestedNotOverridden()654     public void testAutoEnterPictureInPictureOnUserLeaveHintWhenPipRequestedNotOverridden()
655             {
656         // Launch a test activity so that we're not over home
657         launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
658 
659         // Launch the PIP activity that enters PIP on user leave hint, not on PIP requested
660         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT, "true"));
661         assertPinnedStackDoesNotExist();
662 
663         // Go home and ensure that there is a pinned stack
664         separateTestJournal();
665         launchHomeActivity();
666         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
667         assertPinnedStackExists();
668 
669         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
670         // Check the order of the callbacks accounting for a task overlay activity that might show.
671         // The PIP request (with a user leave hint) should come before the pip mode change.
672         final int firstUserLeaveIndex =
673                 lifecycleCounts.getFirstIndex(ActivityCallback.ON_USER_LEAVE_HINT);
674         final int firstPipRequestedIndex =
675                 lifecycleCounts.getFirstIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_REQUESTED);
676         final int firstPipModeChangedIndex =
677                 lifecycleCounts.getFirstIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
678         assertTrue("missing request", firstPipRequestedIndex != -1);
679         assertTrue("missing user leave", firstUserLeaveIndex != -1);
680         assertTrue("missing pip mode changed", firstPipModeChangedIndex != -1);
681         assertTrue("pip requested not before pause",
682                 firstPipRequestedIndex < firstUserLeaveIndex);
683         assertTrue("unexpected user leave hint",
684                 firstUserLeaveIndex < firstPipModeChangedIndex);
685     }
686 
687     @Test
testAutoEnterPictureInPictureOnPictureInPictureRequested()688     public void testAutoEnterPictureInPictureOnPictureInPictureRequested() {
689         // Launch a test activity so that we're not over home
690         launchActivity(TEST_ACTIVITY);
691 
692         // Launch the PIP activity on pip requested
693         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_PIP_REQUESTED, "true"));
694         assertPinnedStackDoesNotExist();
695 
696         // Call onPictureInPictureRequested and verify activity enters pip
697         separateTestJournal();
698         mBroadcastActionTrigger.doAction(ACTION_ON_PIP_REQUESTED);
699         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
700         assertPinnedStackExists();
701 
702         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
703         // Check the order of the callbacks accounting for a task overlay activity that might show.
704         // The PIP request (without a user leave hint) should come before the pip mode change.
705         final int firstUserLeaveIndex =
706                 lifecycleCounts.getFirstIndex(ActivityCallback.ON_USER_LEAVE_HINT);
707         final int firstPipRequestedIndex =
708                 lifecycleCounts.getFirstIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_REQUESTED);
709         final int firstPipModeChangedIndex =
710                 lifecycleCounts.getFirstIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
711         assertTrue("missing request", firstPipRequestedIndex != -1);
712         assertTrue("missing pip mode changed", firstPipModeChangedIndex != -1);
713         assertTrue("pip requested not before pause",
714                 firstPipRequestedIndex < firstPipModeChangedIndex);
715         assertTrue("unexpected user leave hint",
716                 firstUserLeaveIndex == -1 || firstUserLeaveIndex > firstPipModeChangedIndex);
717     }
718 
719     @Test
testAutoEnterPictureInPictureLaunchActivity()720     public void testAutoEnterPictureInPictureLaunchActivity() {
721         // Launch a test activity so that we're not over home
722         launchActivity(TEST_ACTIVITY);
723 
724         // Launch the PIP activity on pause, and have it start another activity on
725         // top of itself.  Wait for the new activity to be visible and ensure that the pinned stack
726         // was not created in the process
727         launchActivityNoWait(PIP_ACTIVITY,
728                 extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"),
729                 extraString(EXTRA_START_ACTIVITY, getActivityName(NON_RESIZEABLE_ACTIVITY)));
730         mWmState.computeState(
731                 new WaitForValidActivityState(NON_RESIZEABLE_ACTIVITY));
732         assertPinnedStackDoesNotExist();
733 
734         // Go home while the pip activity is open and ensure the previous activity is not PIPed
735         launchHomeActivity();
736         assertPinnedStackDoesNotExist();
737     }
738 
739     @Test
testAutoEnterPictureInPictureFinish()740     public void testAutoEnterPictureInPictureFinish() {
741         // Launch a test activity so that we're not over home
742         launchActivity(TEST_ACTIVITY);
743 
744         // Launch the PIP activity on pause, and set it to finish itself after
745         // some period.  Wait for the previous activity to be visible, and ensure that the pinned
746         // stack was not created in the process
747         launchActivityNoWait(PIP_ACTIVITY,
748                 extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"),
749                 extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
750         mWmState.computeState(
751                 new WaitForValidActivityState(TEST_ACTIVITY));
752         assertPinnedStackDoesNotExist();
753     }
754 
755     @Test
testAutoEnterPictureInPictureAspectRatio()756     public void testAutoEnterPictureInPictureAspectRatio() {
757         // Launch the PIP activity on pause, and set the aspect ratio
758         launchActivity(PIP_ACTIVITY,
759                 extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"),
760                 extraString(EXTRA_SET_ASPECT_RATIO_NUMERATOR,
761                         Integer.toString(MAX_ASPECT_RATIO_NUMERATOR)),
762                 extraString(EXTRA_SET_ASPECT_RATIO_DENOMINATOR,
763                         Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR)));
764 
765         // Go home while the pip activity is open to trigger auto-PIP
766         launchHomeActivity();
767         // Wait for animation complete since we are comparing aspect ratio
768         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
769         assertPinnedStackExists();
770 
771         waitForValidAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
772         Rect bounds = getPinnedStackBounds();
773         assertFloatEquals((float) bounds.width() / bounds.height(),
774                 (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
775     }
776 
777     @Test
testAutoEnterPictureInPictureOverPip()778     public void testAutoEnterPictureInPictureOverPip() {
779         // Launch another PIP activity
780         launchActivity(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
781         waitForEnterPip(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
782         assertPinnedStackExists();
783 
784         // Launch the PIP activity on pause
785         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"));
786 
787         // Go home while the PIP activity is open to try to trigger auto-enter PIP
788         launchHomeActivity();
789         assertPinnedStackExists();
790 
791         // Ensure that auto-enter pip failed and that the resumed activity in the pinned stack is
792         // still the first activity
793         final Task pinnedStack = getPinnedStack();
794         assertEquals(getActivityName(ALWAYS_FOCUSABLE_PIP_ACTIVITY), pinnedStack.mRealActivity);
795     }
796 
797     @Test
testDismissPipWhenLaunchNewOne()798     public void testDismissPipWhenLaunchNewOne() {
799         // Launch another PIP activity
800         launchActivity(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
801         waitForEnterPip(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
802         assertPinnedStackExists();
803         final Task pinnedStack = getPinnedStack();
804 
805         launchActivityInNewTask(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
806         waitForEnterPip(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
807 
808         assertEquals(1, mWmState.countRootTasks(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD));
809     }
810 
811     @Test
testDisallowMultipleTasksInPinnedStack()812     public void testDisallowMultipleTasksInPinnedStack() {
813         // Launch a test activity so that we have multiple fullscreen tasks
814         launchActivity(TEST_ACTIVITY);
815 
816         // Launch first PIP activity
817         launchActivity(PIP_ACTIVITY);
818         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
819         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
820         int defaultDisplayWindowingMode = getDefaultDisplayWindowingMode(PIP_ACTIVITY);
821 
822         // Launch second PIP activity
823         launchActivity(PIP_ACTIVITY2, extraString(EXTRA_ENTER_PIP, "true"));
824         waitForEnterPipAnimationComplete(PIP_ACTIVITY2);
825 
826         final Task pinnedStack = getPinnedStack();
827         assertEquals(0, pinnedStack.getTasks().size());
828         assertTrue(mWmState.containsActivityInWindowingMode(
829                 PIP_ACTIVITY2, WINDOWING_MODE_PINNED));
830         assertTrue(mWmState.containsActivityInWindowingMode(
831                 PIP_ACTIVITY, defaultDisplayWindowingMode));
832     }
833 
834     @Test
testPipUnPipOverHome()835     public void testPipUnPipOverHome() {
836         // Launch a task behind home to assert that the next fullscreen task isn't visible when
837         // leaving PiP.
838         launchActivity(TEST_ACTIVITY);
839         // Go home
840         launchHomeActivity();
841         // Launch an auto pip activity
842         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
843         waitForEnterPip(PIP_ACTIVITY);
844         assertPinnedStackExists();
845 
846         // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
847         launchActivity(PIP_ACTIVITY);
848         waitForExitPipToFullscreen(PIP_ACTIVITY);
849         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
850         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
851         mWmState.assertVisibility(TEST_ACTIVITY, false);
852         mWmState.assertHomeActivityVisible(true);
853     }
854 
855     @Test
testPipUnPipOverApp()856     public void testPipUnPipOverApp() {
857         // Launch a test activity so that we're not over home
858         launchActivity(TEST_ACTIVITY);
859 
860         // Launch an auto pip activity
861         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
862         waitForEnterPip(PIP_ACTIVITY);
863         assertPinnedStackExists();
864 
865         // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
866         launchActivity(PIP_ACTIVITY);
867         waitForExitPipToFullscreen(PIP_ACTIVITY);
868         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
869         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
870         mWmState.assertVisibility(TEST_ACTIVITY, true);
871     }
872 
873     @Test
testRemovePipWithNoFullscreenOrFreeformStack()874     public void testRemovePipWithNoFullscreenOrFreeformStack() {
875         // Launch a pip activity
876         launchActivity(PIP_ACTIVITY);
877         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
878         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
879 
880         // Remove the stack and ensure that the task is now in the fullscreen/freeform stack (when
881         // no fullscreen/freeform stack existed before)
882         removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
883         assertPinnedStackStateOnMoveToBackStack(PIP_ACTIVITY,
884                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, windowingMode);
885     }
886 
887     @Test
testRemovePipWithVisibleFullscreenOrFreeformStack()888     public void testRemovePipWithVisibleFullscreenOrFreeformStack() {
889         // Launch a fullscreen/freeform activity, and a pip activity over that
890         launchActivity(TEST_ACTIVITY);
891         launchActivity(PIP_ACTIVITY);
892         int testAppWindowingMode = mWmState.getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
893         int pipWindowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
894         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
895 
896         // Remove the stack and ensure that the task is placed in the fullscreen/freeform stack,
897         // behind the top fullscreen/freeform activity
898         removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
899         assertPinnedStackStateOnMoveToBackStack(PIP_ACTIVITY,
900                 testAppWindowingMode, ACTIVITY_TYPE_STANDARD, pipWindowingMode);
901     }
902 
903     @Test
testRemovePipWithHiddenFullscreenOrFreeformStack()904     public void testRemovePipWithHiddenFullscreenOrFreeformStack() {
905         // Launch a fullscreen/freeform activity, return home and while the fullscreen/freeform
906         // stack is hidden, launch a pip activity over home
907         launchActivity(TEST_ACTIVITY);
908         launchHomeActivity();
909         launchActivity(PIP_ACTIVITY);
910         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
911         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
912 
913         // Remove the stack and ensure that the task is placed on top of the hidden
914         // fullscreen/freeform stack, but that the home stack is still focused
915         removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
916         assertPinnedStackStateOnMoveToBackStack(PIP_ACTIVITY,
917                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, windowingMode);
918     }
919 
920     @Test
testMovePipToBackWithNoFullscreenOrFreeformStack()921     public void testMovePipToBackWithNoFullscreenOrFreeformStack() {
922         // Start with a clean slate, remove all the stacks but home
923         removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
924 
925         // Launch a pip activity
926         launchActivity(PIP_ACTIVITY);
927         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
928         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
929 
930         // Remove the stack and ensure that the task is now in the fullscreen/freeform stack (when
931         // no fullscreen/freeform stack existed before)
932         mBroadcastActionTrigger.doAction(ACTION_MOVE_TO_BACK);
933         assertPinnedStackStateOnMoveToBackStack(PIP_ACTIVITY,
934                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, windowingMode);
935     }
936 
937     @Test
testMovePipToBackWithVisibleFullscreenOrFreeformStack()938     public void testMovePipToBackWithVisibleFullscreenOrFreeformStack() {
939         // Launch a fullscreen/freeform activity, and a pip activity over that
940         launchActivity(TEST_ACTIVITY);
941         launchActivity(PIP_ACTIVITY);
942         int testAppWindowingMode = mWmState.getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
943         int pipWindowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
944         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
945 
946         // Remove the stack and ensure that the task is placed in the fullscreen/freeform stack,
947         // behind the top fullscreen/freeform activity
948         mBroadcastActionTrigger.doAction(ACTION_MOVE_TO_BACK);
949         assertPinnedStackStateOnMoveToBackStack(PIP_ACTIVITY,
950                 testAppWindowingMode, ACTIVITY_TYPE_STANDARD, pipWindowingMode);
951     }
952 
953     @Test
testMovePipToBackWithHiddenFullscreenOrFreeformStack()954     public void testMovePipToBackWithHiddenFullscreenOrFreeformStack() {
955         // Launch a fullscreen/freeform activity, return home and while the fullscreen/freeform
956         // stack is hidden, launch a pip activity over home
957         launchActivity(TEST_ACTIVITY);
958         launchHomeActivity();
959         launchActivity(PIP_ACTIVITY);
960         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
961         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
962 
963         // Remove the stack and ensure that the task is placed on top of the hidden
964         // fullscreen/freeform stack, but that the home stack is still focused
965         mBroadcastActionTrigger.doAction(ACTION_MOVE_TO_BACK);
966         assertPinnedStackStateOnMoveToBackStack(
967                 PIP_ACTIVITY, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, windowingMode);
968     }
969 
970     @Test
testPinnedStackAlwaysOnTop()971     public void testPinnedStackAlwaysOnTop() {
972         // Launch activity into pinned stack and assert it's on top.
973         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
974         waitForEnterPip(PIP_ACTIVITY);
975         assertPinnedStackExists();
976         assertPinnedStackIsOnTop();
977 
978         // Launch another activity in fullscreen stack and check that pinned stack is still on top.
979         launchActivity(TEST_ACTIVITY);
980         assertPinnedStackExists();
981         assertPinnedStackIsOnTop();
982 
983         // Launch home and check that pinned stack is still on top.
984         launchHomeActivity();
985         assertPinnedStackExists();
986         assertPinnedStackIsOnTop();
987     }
988 
989     @Test
testAppOpsDenyPipOnPause()990     public void testAppOpsDenyPipOnPause() {
991         try (final AppOpsSession appOpsSession = new AppOpsSession(PIP_ACTIVITY)) {
992             // Disable enter-pip and try to enter pip
993             appOpsSession.setOpToMode(APP_OPS_OP_ENTER_PICTURE_IN_PICTURE, APP_OPS_MODE_IGNORED);
994 
995             // Launch the PIP activity on pause
996             launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
997             assertPinnedStackDoesNotExist();
998 
999             // Go home and ensure that there is no pinned stack
1000             launchHomeActivity();
1001             assertPinnedStackDoesNotExist();
1002         }
1003     }
1004 
1005     @Test
testEnterPipFromTaskWithMultipleActivities()1006     public void testEnterPipFromTaskWithMultipleActivities() {
1007         // Try to enter picture-in-picture from an activity that has more than one activity in the
1008         // task and ensure that it works
1009         launchActivity(LAUNCH_ENTER_PIP_ACTIVITY);
1010         waitForEnterPip(PIP_ACTIVITY);
1011 
1012         final Task task = mWmState.getTaskByActivity(LAUNCH_ENTER_PIP_ACTIVITY);
1013         assertEquals(1, task.mActivities.size());
1014         assertPinnedStackExists();
1015     }
1016 
1017     @Test
testPipFromTaskWithMultipleActivitiesAndExpandPip()1018     public void testPipFromTaskWithMultipleActivitiesAndExpandPip() {
1019         // Try to enter picture-in-picture from an activity that has more than one activity in the
1020         // task and ensure pinned task can go back to its original task when expand to fullscreen
1021         launchActivity(LAUNCH_ENTER_PIP_ACTIVITY);
1022         waitForEnterPip(PIP_ACTIVITY);
1023 
1024         mBroadcastActionTrigger.expandPip();
1025         waitForExitPipToFullscreen(PIP_ACTIVITY);
1026 
1027         final Task task = mWmState.getTaskByActivity(LAUNCH_ENTER_PIP_ACTIVITY);
1028         assertEquals(2, task.mActivities.size());
1029     }
1030 
1031     @Test
testPipFromTaskWithMultipleActivitiesAndDismissPip()1032     public void testPipFromTaskWithMultipleActivitiesAndDismissPip() {
1033         // Try to enter picture-in-picture from an activity that has more than one activity in the
1034         // task and ensure flags on original task get reset after dismissing pip
1035         launchActivity(LAUNCH_ENTER_PIP_ACTIVITY);
1036         waitForEnterPip(PIP_ACTIVITY);
1037 
1038         mBroadcastActionTrigger.doAction(ACTION_FINISH);
1039         waitForPinnedStackRemoved();
1040 
1041         final Task task = mWmState.getTaskByActivity(LAUNCH_ENTER_PIP_ACTIVITY);
1042         assertFalse(task.mHasChildPipActivity);
1043     }
1044 
1045     /**
1046      * When the activity entering PIP is in a Task with another finishing activity, the Task should
1047      * enter PIP instead of reparenting the activity to a new PIP Task.
1048      */
1049     @Test
testPipFromTaskWithAnotherFinishingActivity()1050     public void testPipFromTaskWithAnotherFinishingActivity() {
1051         launchActivityNoWait(LAUNCH_ENTER_PIP_ACTIVITY,
1052                 extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
1053 
1054         waitForEnterPip(PIP_ACTIVITY);
1055         mWmState.waitForActivityRemoved(LAUNCH_ENTER_PIP_ACTIVITY);
1056 
1057         mWmState.assertNotExist(LAUNCH_ENTER_PIP_ACTIVITY);
1058         assertPinnedStackExists();
1059         final Task pipTask = mWmState.getTaskByActivity(PIP_ACTIVITY);
1060         assertEquals(WINDOWING_MODE_PINNED, pipTask.getWindowingMode());
1061         assertEquals(1, pipTask.getActivityCount());
1062     }
1063 
1064     @Test
testPipFromTaskWithMultipleActivitiesAndRemoveOriginalTask()1065     public void testPipFromTaskWithMultipleActivitiesAndRemoveOriginalTask() {
1066         // Try to enter picture-in-picture from an activity that has more than one activity in the
1067         // task and ensure pinned task is removed when the original task vanishes
1068         launchActivity(LAUNCH_ENTER_PIP_ACTIVITY);
1069         waitForEnterPip(PIP_ACTIVITY);
1070 
1071         final int originalTaskId = mWmState.getTaskByActivity(LAUNCH_ENTER_PIP_ACTIVITY).mTaskId;
1072         removeRootTask(originalTaskId);
1073         waitForPinnedStackRemoved();
1074 
1075         assertPinnedStackDoesNotExist();
1076     }
1077 
1078     @Test
testLaunchStoppedActivityWithPiPInSameProcessPreQ()1079     public void testLaunchStoppedActivityWithPiPInSameProcessPreQ() {
1080         // Try to enter picture-in-picture from an activity that has more than one activity in the
1081         // task and ensure that it works, for pre-Q app
1082         launchActivity(SDK_27_LAUNCH_ENTER_PIP_ACTIVITY,
1083                 extraString(EXTRA_ENTER_PIP, "true"));
1084         waitForEnterPip(SDK_27_PIP_ACTIVITY);
1085         assertPinnedStackExists();
1086 
1087         // Puts the host activity to stopped state
1088         launchHomeActivity();
1089         mWmState.assertHomeActivityVisible(true);
1090         waitAndAssertActivityState(SDK_27_LAUNCH_ENTER_PIP_ACTIVITY, STATE_STOPPED,
1091                 "Activity should become STOPPED");
1092         mWmState.assertVisibility(SDK_27_LAUNCH_ENTER_PIP_ACTIVITY, false);
1093 
1094         // Host activity should be visible after re-launch and PiP window still exists
1095         launchActivity(SDK_27_LAUNCH_ENTER_PIP_ACTIVITY);
1096         waitAndAssertActivityState(SDK_27_LAUNCH_ENTER_PIP_ACTIVITY, STATE_RESUMED,
1097                 "Activity should become RESUMED");
1098         mWmState.assertVisibility(SDK_27_LAUNCH_ENTER_PIP_ACTIVITY, true);
1099         assertPinnedStackExists();
1100     }
1101 
1102     @Test
testEnterPipWithResumeWhilePausingActivityNoStop()1103     public void testEnterPipWithResumeWhilePausingActivityNoStop() {
1104         /*
1105          * Launch the resumeWhilePausing activity and ensure that the PiP activity did not get
1106          * stopped and actually went into the pinned stack.
1107          *
1108          * Note that this is a workaround because to trigger the path that we want to happen in
1109          * activity manager, we need to add the leaving activity to the stopping state, which only
1110          * happens when a hidden stack is brought forward. Normally, this happens when you go home,
1111          * but since we can't launch into the home stack directly, we have a workaround.
1112          *
1113          * 1) Launch an activity in a new dynamic stack
1114          * 2) Start the PiP activity that will enter picture-in-picture when paused in the
1115          *    fullscreen stack
1116          * 3) Bring the activity in the dynamic stack forward to trigger PiP
1117          */
1118         launchActivity(RESUME_WHILE_PAUSING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
1119         // Launch an activity that will enter PiP when it is paused with a delay that is long enough
1120         // for the next resumeWhilePausing activity to finish resuming, but slow enough to not
1121         // trigger the current system pause timeout (currently 500ms)
1122         launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
1123                 extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"),
1124                 extraString(EXTRA_ON_PAUSE_DELAY, "350"),
1125                 extraString(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true"));
1126         launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
1127         // if the activity is not launched in same TDA, pip is not triggered.
1128         assumeTrue("Should launch in same tda",
1129                 mWmState.getTaskDisplayArea(RESUME_WHILE_PAUSING_ACTIVITY)
1130                         == mWmState.getTaskDisplayArea(PIP_ACTIVITY)
1131         );
1132         assertPinnedStackExists();
1133     }
1134 
1135     @Test
testDisallowEnterPipActivityLocked()1136     public void testDisallowEnterPipActivityLocked() {
1137         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"));
1138         Task task = mWmState.getRootTaskByActivity(PIP_ACTIVITY);
1139 
1140         // Lock the task and ensure that we can't enter picture-in-picture both explicitly and
1141         // when paused
1142         SystemUtil.runWithShellPermissionIdentity(() -> {
1143             try {
1144                 mAtm.startSystemLockTaskMode(task.mTaskId);
1145                 waitForOrFail("Task in lock mode", () -> {
1146                     return mAm.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
1147                 });
1148                 mBroadcastActionTrigger.enterPipAndWait();
1149                 assertPinnedStackDoesNotExist();
1150                 launchHomeActivityNoWait();
1151                 mWmState.computeState();
1152                 assertPinnedStackDoesNotExist();
1153             } finally {
1154                 mAtm.stopSystemLockTaskMode();
1155             }
1156         });
1157     }
1158 
1159     @Test
testConfigurationChangeOrderDuringTransition()1160     public void testConfigurationChangeOrderDuringTransition() {
1161         // Launch a PiP activity and ensure configuration change only happened once, and that the
1162         // configuration change happened after the picture-in-picture and multi-window callbacks
1163         launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
1164         separateTestJournal();
1165         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
1166         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1167         waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
1168         assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, windowingMode);
1169 
1170         // Trigger it to go back to original mode and ensure that only triggered one configuration
1171         // change as well
1172         separateTestJournal();
1173         launchActivity(PIP_ACTIVITY);
1174         waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
1175         assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY, windowingMode);
1176     }
1177 
1178     /** Helper class to save, set, and restore transition_animation_scale preferences. */
1179     private static class TransitionAnimationScaleSession extends SettingsSession<Float> {
TransitionAnimationScaleSession()1180         TransitionAnimationScaleSession() {
1181             super(Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE),
1182                     Settings.Global::getFloat,
1183                     Settings.Global::putFloat);
1184         }
1185 
1186         @Override
close()1187         public void close() {
1188             // Wait for the restored setting to apply before we continue on with the next test
1189             final CountDownLatch waitLock = new CountDownLatch(1);
1190             final Context context = getInstrumentation().getTargetContext();
1191             context.getContentResolver().registerContentObserver(mUri, false,
1192                     new ContentObserver(new Handler(Looper.getMainLooper())) {
1193                         @Override
1194                         public void onChange(boolean selfChange) {
1195                             waitLock.countDown();
1196                         }
1197                     });
1198             super.close();
1199             try {
1200                 if (!waitLock.await(2, TimeUnit.SECONDS)) {
1201                     Log.i(TAG, "TransitionAnimationScaleSession value not restored");
1202                 }
1203             } catch (InterruptedException impossible) {}
1204         }
1205     }
1206 
1207     @Ignore("b/149946388")
1208     @Test
testEnterPipInterruptedCallbacks()1209     public void testEnterPipInterruptedCallbacks() {
1210         final TransitionAnimationScaleSession transitionAnimationScaleSession =
1211                 mObjectTracker.manage(new TransitionAnimationScaleSession());
1212         // Slow down the transition animations for this test
1213         transitionAnimationScaleSession.set(20f);
1214 
1215         // Launch a PiP activity
1216         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
1217         // Wait until the PiP activity has moved into the pinned stack (happens before the
1218         // transition has started)
1219         waitForEnterPip(PIP_ACTIVITY);
1220         assertPinnedStackExists();
1221 
1222         // Relaunch the PiP activity back into fullscreen
1223         separateTestJournal();
1224         launchActivity(PIP_ACTIVITY);
1225         // Wait until the PiP activity is reparented into the fullscreen stack (happens after
1226         // the transition has finished)
1227         waitForExitPipToFullscreen(PIP_ACTIVITY);
1228 
1229         // Ensure that we get the callbacks indicating that PiP/MW mode was cancelled, but no
1230         // configuration change (since none was sent)
1231         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
1232         assertEquals("onConfigurationChanged", 0,
1233                 lifecycleCounts.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED));
1234         assertEquals("onPictureInPictureModeChanged", 1,
1235                 lifecycleCounts.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
1236         assertEquals("onMultiWindowModeChanged", 1,
1237                 lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
1238     }
1239 
1240     @Test
testStopBeforeMultiWindowCallbacksOnDismiss()1241     public void testStopBeforeMultiWindowCallbacksOnDismiss() {
1242         // Launch a PiP activity
1243         launchActivity(PIP_ACTIVITY);
1244         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
1245 
1246         // Skip the test if it's freeform, since freeform <-> PIP does not trigger any multi-window
1247         // calbacks.
1248         assumeFalse(windowingMode == WINDOWING_MODE_FREEFORM);
1249 
1250         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
1251         // Wait for animation complete so that system has reported pip mode change event to
1252         // client and the last reported pip mode has updated.
1253         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
1254         assertPinnedStackExists();
1255 
1256         // Dismiss it
1257         separateTestJournal();
1258         removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
1259         waitForExitPipToFullscreen(PIP_ACTIVITY);
1260         waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
1261 
1262         // Confirm that we get stop before the multi-window and picture-in-picture mode change
1263         // callbacks
1264         final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(PIP_ACTIVITY);
1265         assertEquals("onStop", 1, lifecycles.getCount(ActivityCallback.ON_STOP));
1266         assertEquals("onPictureInPictureModeChanged", 1,
1267                 lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
1268         assertEquals("onMultiWindowModeChanged", 1,
1269                 lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
1270         final int lastStopIndex = lifecycles.getLastIndex(ActivityCallback.ON_STOP);
1271         final int lastPipIndex = lifecycles.getLastIndex(
1272                 ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
1273         final int lastMwIndex = lifecycles.getLastIndex(
1274                 ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED);
1275         assertThat("onStop should be before onPictureInPictureModeChanged",
1276                 lastStopIndex, lessThan(lastPipIndex));
1277         assertThat("onPictureInPictureModeChanged should be before onMultiWindowModeChanged",
1278                 lastPipIndex, lessThan(lastMwIndex));
1279     }
1280 
1281     @Test
testPreventSetAspectRatioWhileExpanding()1282     public void testPreventSetAspectRatioWhileExpanding() {
1283         // Launch the PiP activity
1284         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
1285         waitForEnterPip(PIP_ACTIVITY);
1286 
1287         // Trigger it to go back to fullscreen and try to set the aspect ratio, and ensure that the
1288         // call to set the aspect ratio did not prevent the PiP from returning to fullscreen
1289         mBroadcastActionTrigger.expandPipWithAspectRatio("123456789", "100000000");
1290         waitForExitPipToFullscreen(PIP_ACTIVITY);
1291         assertPinnedStackDoesNotExist();
1292     }
1293 
1294     @Test
testSetRequestedOrientationWhilePinned()1295     public void testSetRequestedOrientationWhilePinned() {
1296         assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());
1297         // Launch the PiP activity fixed as portrait, and enter picture-in-picture
1298         launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
1299                 extraString(EXTRA_PIP_ORIENTATION, String.valueOf(SCREEN_ORIENTATION_PORTRAIT)),
1300                 extraString(EXTRA_ENTER_PIP, "true"));
1301         waitForEnterPip(PIP_ACTIVITY);
1302         assertPinnedStackExists();
1303 
1304         // Request that the orientation is set to landscape
1305         mBroadcastActionTrigger.requestOrientationForPip(SCREEN_ORIENTATION_LANDSCAPE);
1306 
1307         // Launch the activity back into fullscreen and ensure that it is now in landscape
1308         launchActivity(PIP_ACTIVITY);
1309         waitForExitPipToFullscreen(PIP_ACTIVITY);
1310         assertPinnedStackDoesNotExist();
1311         assertTrue("The PiP activity in fullscreen must be landscape",
1312                 mWmState.waitForActivityOrientation(
1313                         PIP_ACTIVITY, Configuration.ORIENTATION_LANDSCAPE));
1314     }
1315 
1316     @Test
testWindowButtonEntersPip()1317     public void testWindowButtonEntersPip() {
1318         assumeTrue(!mWmState.isHomeRecentsComponent());
1319 
1320         // Launch the PiP activity trigger the window button, ensure that we have entered PiP
1321         launchActivity(PIP_ACTIVITY);
1322         pressWindowButton();
1323         waitForEnterPip(PIP_ACTIVITY);
1324         assertPinnedStackExists();
1325     }
1326 
1327     @Test
testFinishPipActivityWithTaskOverlay()1328     public void testFinishPipActivityWithTaskOverlay() {
1329         // Launch PiP activity
1330         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
1331         waitForEnterPip(PIP_ACTIVITY);
1332         assertPinnedStackExists();
1333         int taskId = mWmState.getStandardRootTaskByWindowingMode(WINDOWING_MODE_PINNED).getTopTask()
1334                 .mTaskId;
1335 
1336         // Ensure that we don't any any other overlays as a result of launching into PIP
1337         launchHomeActivity();
1338 
1339         // Launch task overlay activity into PiP activity task
1340         launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
1341 
1342         // Finish the PiP activity and ensure that there is no pinned stack
1343         mBroadcastActionTrigger.doAction(ACTION_FINISH);
1344         waitForPinnedStackRemoved();
1345         assertPinnedStackDoesNotExist();
1346     }
1347 
1348     @Test
testNoResumeAfterTaskOverlayFinishes()1349     public void testNoResumeAfterTaskOverlayFinishes() {
1350         // Launch PiP activity
1351         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
1352         waitForEnterPip(PIP_ACTIVITY);
1353         assertPinnedStackExists();
1354         Task task = mWmState.getStandardRootTaskByWindowingMode(WINDOWING_MODE_PINNED);
1355         int taskId = task.getTopTask().mTaskId;
1356 
1357         // Launch task overlay activity into PiP activity task
1358         launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
1359 
1360         // Finish the task overlay activity and ensure that the PiP activity never got resumed.
1361         separateTestJournal();
1362         mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
1363         mWmState.waitFor((amState) ->
1364                         !amState.containsActivity(TRANSLUCENT_TEST_ACTIVITY),
1365                 "Waiting for test activity to finish...");
1366         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
1367         assertEquals("onResume", 0, lifecycleCounts.getCount(ActivityCallback.ON_RESUME));
1368         assertEquals("onPause", 0, lifecycleCounts.getCount(ActivityCallback.ON_PAUSE));
1369     }
1370 
1371     @Test
testTranslucentActivityOnTopOfPinnedTask()1372     public void testTranslucentActivityOnTopOfPinnedTask() {
1373         launchActivity(LAUNCH_PIP_ON_PIP_ACTIVITY);
1374         // NOTE: moving to pinned stack will trigger the pip-on-pip activity to launch the
1375         // translucent activity.
1376         enterPipAndAssertPinnedTaskExists(LAUNCH_PIP_ON_PIP_ACTIVITY);
1377         mWmState.waitForValidState(
1378                 new WaitForValidActivityState.Builder(ALWAYS_FOCUSABLE_PIP_ACTIVITY)
1379                         .setWindowingMode(WINDOWING_MODE_PINNED)
1380                         .build());
1381 
1382         assertPinnedStackIsOnTop();
1383         mWmState.assertVisibility(LAUNCH_PIP_ON_PIP_ACTIVITY, true);
1384         mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
1385     }
1386 
1387     @Test
testLaunchTaskByComponentMatchMultipleTasks()1388     public void testLaunchTaskByComponentMatchMultipleTasks() {
1389         // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
1390         // affinity
1391         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
1392         launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
1393         assertPinnedStackExists();
1394 
1395         // Launch the root activity again...
1396         int rootActivityTaskId = mWmState.getTaskByActivity(
1397                 TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId;
1398         launchHomeActivity();
1399         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
1400 
1401         // ...and ensure that the root activity task is found and reused, and that the pinned stack
1402         // is unaffected
1403         assertPinnedStackExists();
1404         mWmState.assertFocusedActivity("Expected root activity focused",
1405                 TEST_ACTIVITY_WITH_SAME_AFFINITY);
1406         assertEquals(rootActivityTaskId, mWmState.getTaskByActivity(
1407                 TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId);
1408     }
1409 
1410     @Test
testLaunchTaskByAffinityMatchMultipleTasks()1411     public void testLaunchTaskByAffinityMatchMultipleTasks() {
1412         // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
1413         // affinity, and also launch another activity in the same task, while finishing itself. As
1414         // a result, the task will not have a component matching the same activity as what it was
1415         // started with
1416         launchActivityNoWait(TEST_ACTIVITY_WITH_SAME_AFFINITY,
1417                 extraString(EXTRA_START_ACTIVITY, getActivityName(TEST_ACTIVITY)),
1418                 extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
1419         mWmState.waitForValidState(new WaitForValidActivityState.Builder(TEST_ACTIVITY)
1420                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
1421                 .setActivityType(ACTIVITY_TYPE_STANDARD)
1422                 .build());
1423         launchActivityNoWait(PIP_ACTIVITY_WITH_SAME_AFFINITY);
1424         waitForEnterPip(PIP_ACTIVITY_WITH_SAME_AFFINITY);
1425         assertPinnedStackExists();
1426 
1427         // Launch the root activity again...
1428         int rootActivityTaskId = mWmState.getTaskByActivity(
1429                 TEST_ACTIVITY).mTaskId;
1430         launchHomeActivity();
1431         launchActivityNoWait(TEST_ACTIVITY_WITH_SAME_AFFINITY);
1432         mWmState.computeState();
1433 
1434         // ...and ensure that even while matching purely by task affinity, the root activity task is
1435         // found and reused, and that the pinned stack is unaffected
1436         assertPinnedStackExists();
1437         mWmState.assertFocusedActivity("Expected root activity focused", TEST_ACTIVITY);
1438         assertEquals(rootActivityTaskId, mWmState.getTaskByActivity(
1439                 TEST_ACTIVITY).mTaskId);
1440     }
1441 
1442     @Test
testLaunchTaskByAffinityMatchSingleTask()1443     public void testLaunchTaskByAffinityMatchSingleTask() {
1444         // Launch an activity into the pinned stack with a fixed affinity
1445         launchActivityNoWait(TEST_ACTIVITY_WITH_SAME_AFFINITY,
1446                 extraString(EXTRA_ENTER_PIP, "true"),
1447                 extraString(EXTRA_START_ACTIVITY, getActivityName(PIP_ACTIVITY)),
1448                 extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
1449         waitForEnterPip(PIP_ACTIVITY);
1450         assertPinnedStackExists();
1451 
1452         // Launch the root activity again, of the matching task and ensure that we expand to
1453         // fullscreen
1454         int activityTaskId = mWmState.getTaskByActivity(PIP_ACTIVITY).mTaskId;
1455         launchHomeActivity();
1456         launchActivityNoWait(TEST_ACTIVITY_WITH_SAME_AFFINITY);
1457         waitForExitPipToFullscreen(PIP_ACTIVITY);
1458         assertPinnedStackDoesNotExist();
1459         assertEquals(activityTaskId, mWmState.getTaskByActivity(
1460                 PIP_ACTIVITY).mTaskId);
1461     }
1462 
1463     /** Test that reported display size corresponds to fullscreen after exiting PiP. */
1464     @Test
testDisplayMetricsPinUnpin()1465     public void testDisplayMetricsPinUnpin() {
1466         separateTestJournal();
1467         launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
1468         launchActivity(PIP_ACTIVITY);
1469         int defaultWindowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
1470         final SizeInfo initialSizes = getLastReportedSizesForActivity(PIP_ACTIVITY);
1471         final Rect initialAppBounds = getAppBounds(PIP_ACTIVITY);
1472         assertNotNull("Must report display dimensions", initialSizes);
1473         assertNotNull("Must report app bounds", initialAppBounds);
1474 
1475         separateTestJournal();
1476         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1477         // Wait for animation complete since we are comparing bounds
1478         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
1479         final SizeInfo pinnedSizes = getLastReportedSizesForActivity(PIP_ACTIVITY);
1480         final Rect pinnedAppBounds = getAppBounds(PIP_ACTIVITY);
1481         assertNotEquals("Reported display size when pinned must be different from default",
1482                 initialSizes, pinnedSizes);
1483         final Size initialAppSize = new Size(initialAppBounds.width(), initialAppBounds.height());
1484         final Size pinnedAppSize = new Size(pinnedAppBounds.width(), pinnedAppBounds.height());
1485         assertNotEquals("Reported app size when pinned must be different from default",
1486                 initialAppSize, pinnedAppSize);
1487 
1488         separateTestJournal();
1489         launchActivity(PIP_ACTIVITY, defaultWindowingMode);
1490         final SizeInfo finalSizes = getLastReportedSizesForActivity(PIP_ACTIVITY);
1491         final Rect finalAppBounds = getAppBounds(PIP_ACTIVITY);
1492         final Size finalAppSize = new Size(finalAppBounds.width(), finalAppBounds.height());
1493         assertEquals("Must report default size after exiting PiP", initialSizes, finalSizes);
1494         assertEquals("Must report default app size after exiting PiP", initialAppSize,
1495                 finalAppSize);
1496     }
1497 
1498     @Test
testAutoPipAllowedBypassesExplicitEnterPip()1499     public void testAutoPipAllowedBypassesExplicitEnterPip() {
1500         // Launch a test activity so that we're not over home.
1501         launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
1502 
1503         // Launch the PIP activity and set its pip params to allow auto-pip.
1504         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ALLOW_AUTO_PIP, "true"));
1505         assertPinnedStackDoesNotExist();
1506 
1507         // Launch a new activity and ensure that there is a pinned stack.
1508         launchActivity(RESUME_WHILE_PAUSING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
1509         waitForEnterPip(PIP_ACTIVITY);
1510         assertPinnedStackExists();
1511         waitAndAssertActivityState(PIP_ACTIVITY, STATE_PAUSED, "activity must be paused");
1512     }
1513 
1514     @Test
testAutoPipOnLaunchingRegularActivity()1515     public void testAutoPipOnLaunchingRegularActivity() {
1516         // Launch the PIP activity and set its pip params to allow auto-pip.
1517         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ALLOW_AUTO_PIP, "true"));
1518         assertPinnedStackDoesNotExist();
1519 
1520         // Launch another and ensure that there is a pinned stack.
1521         launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
1522         // if the activities do not launch in same TDA, pip is not triggered.
1523         assumeTrue("Should launch in same tda",
1524                 mWmState.getTaskDisplayArea(PIP_ACTIVITY)
1525                         == mWmState.getTaskDisplayArea(TEST_ACTIVITY)
1526         );
1527         waitForEnterPip(PIP_ACTIVITY);
1528         assertPinnedStackExists();
1529         waitAndAssertActivityState(PIP_ACTIVITY, STATE_PAUSED, "activity must be paused");
1530     }
1531 
1532     @Test
testAutoPipOnLaunchingTranslucentActivity()1533     public void testAutoPipOnLaunchingTranslucentActivity() {
1534         // Launch the PIP activity and set its pip params to allow auto-pip.
1535         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ALLOW_AUTO_PIP, "true"));
1536         assertPinnedStackDoesNotExist();
1537 
1538         // Launch a translucent activity from PipActivity itself and
1539         // ensure that there is no pinned stack.
1540         mBroadcastActionTrigger.doAction(ACTION_LAUNCH_TRANSLUCENT_ACTIVITY);
1541         assertPinnedStackDoesNotExist();
1542     }
1543 
1544     @Test
testAutoPipOnLaunchingActivityWithNoUserAction()1545     public void testAutoPipOnLaunchingActivityWithNoUserAction() {
1546         // Launch the PIP activity and set its pip params to allow auto-pip.
1547         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ALLOW_AUTO_PIP, "true"));
1548         assertPinnedStackDoesNotExist();
1549 
1550         int windowingMode = mWmState.getTaskByActivity(PIP_ACTIVITY).getWindowingMode();
1551         // Skip the test if freeform, since desktops may manually request PIP immediately after
1552         // the test activity launch.
1553         assumeFalse(windowingMode == WINDOWING_MODE_FREEFORM);
1554 
1555         // Launch a regular activity with FLAG_ACTIVITY_NO_USER_ACTION and
1556         // ensure that there is no pinned stack.
1557         launchActivityWithNoUserAction(TEST_ACTIVITY);
1558         assertPinnedStackDoesNotExist();
1559         waitAndAssertActivityState(PIP_ACTIVITY, STATE_STOPPED, "activity must be stopped");
1560     }
1561 
1562     @Test
testMaxNumberOfActions()1563     public void testMaxNumberOfActions() {
1564         final int maxNumberActions = ActivityTaskManager.getMaxNumPictureInPictureActions(mContext);
1565         assertThat(maxNumberActions, greaterThanOrEqualTo(3));
1566     }
1567 
1568     @Test
testFillMaxAllowedActions()1569     public void testFillMaxAllowedActions() {
1570         final int maxNumberActions = ActivityTaskManager.getMaxNumPictureInPictureActions(mContext);
1571         // Launch the PIP activity with max allowed actions
1572         launchActivity(PIP_ACTIVITY,
1573                 extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(maxNumberActions)));
1574         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1575 
1576         assertNumberOfActions(PIP_ACTIVITY, maxNumberActions);
1577     }
1578 
1579     @Test
testRejectExceededActions()1580     public void testRejectExceededActions() {
1581         final int maxNumberActions = ActivityTaskManager.getMaxNumPictureInPictureActions(mContext);
1582         // Launch the PIP activity with exceeded amount of actions
1583         launchActivity(PIP_ACTIVITY,
1584                 extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(maxNumberActions + 1)));
1585         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1586 
1587         assertNumberOfActions(PIP_ACTIVITY, maxNumberActions);
1588     }
1589 
1590     @Test
testCloseActionIsSet()1591     public void testCloseActionIsSet() {
1592         launchActivity(PIP_ACTIVITY, extraBool(EXTRA_CLOSE_ACTION, true));
1593         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1594 
1595         runWithShellPermission(() -> {
1596             final Task task = mWmState.getTaskByActivity(PIP_ACTIVITY);
1597             final TaskInfo info = mTaskOrganizer.getTaskInfo(task.getTaskId());
1598             final PictureInPictureParams params = info.getPictureInPictureParams();
1599 
1600             assertNotNull(params.getCloseAction());
1601         });
1602     }
1603 
1604     @Test
testIsSeamlessResizeEnabledDefaultToTrue()1605     public void testIsSeamlessResizeEnabledDefaultToTrue() {
1606         // Launch the PIP activity with some random param without setting isSeamlessResizeEnabled
1607         // so the PictureInPictureParams acquired from TaskInfo is not null
1608         launchActivity(PIP_ACTIVITY,
1609                 extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(1)));
1610         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1611 
1612         // Assert the default value of isSeamlessResizeEnabled is set to true.
1613         assertIsSeamlessResizeEnabled(PIP_ACTIVITY, true);
1614     }
1615 
1616     @Test
testDisableIsSeamlessResizeEnabled()1617     public void testDisableIsSeamlessResizeEnabled() {
1618         // Launch the PIP activity with overridden isSeamlessResizeEnabled param
1619         launchActivity(PIP_ACTIVITY, extraBool(EXTRA_IS_SEAMLESS_RESIZE_ENABLED, false));
1620         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1621 
1622         // Assert the value of isSeamlessResizeEnabled is overridden.
1623         assertIsSeamlessResizeEnabled(PIP_ACTIVITY, false);
1624     }
1625 
1626     @Test
testPictureInPictureUiStateChangedCallback()1627     public void testPictureInPictureUiStateChangedCallback() throws Exception {
1628         launchActivity(PIP_ACTIVITY);
1629         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1630         waitForEnterPip(PIP_ACTIVITY);
1631 
1632         final CompletableFuture<Boolean> callbackReturn = new CompletableFuture<>();
1633         RemoteCallback cb = new RemoteCallback((Bundle result) ->
1634                 callbackReturn.complete(result.getBoolean(UI_STATE_STASHED_RESULT)));
1635         mBroadcastActionTrigger.sendPipStateUpdate(cb, true);
1636         Truth.assertThat(callbackReturn.get(5000, TimeUnit.MILLISECONDS)).isEqualTo(true);
1637 
1638         final CompletableFuture<Boolean> callbackReturnNotStashed = new CompletableFuture<>();
1639         RemoteCallback cbStashed = new RemoteCallback((Bundle result) ->
1640                 callbackReturnNotStashed.complete(result.getBoolean(UI_STATE_STASHED_RESULT)));
1641         mBroadcastActionTrigger.sendPipStateUpdate(cbStashed, false);
1642         Truth.assertThat(callbackReturnNotStashed.get(5000, TimeUnit.MILLISECONDS))
1643                 .isEqualTo(false);
1644     }
1645 
assertIsSeamlessResizeEnabled(ComponentName componentName, boolean expected)1646     private void assertIsSeamlessResizeEnabled(ComponentName componentName, boolean expected) {
1647         runWithShellPermission(() -> {
1648             final Task task = mWmState.getTaskByActivity(componentName);
1649             final TaskInfo info = mTaskOrganizer.getTaskInfo(task.getTaskId());
1650             final PictureInPictureParams params = info.getPictureInPictureParams();
1651 
1652             assertEquals(expected, params.isSeamlessResizeEnabled());
1653         });
1654     }
1655 
1656     @Test
testTitleIsSet()1657     public void testTitleIsSet() {
1658         // Launch the PIP activity with given title
1659         String title = "PipTitle";
1660         launchActivity(PIP_ACTIVITY, extraString(EXTRA_TITLE, title));
1661         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1662 
1663         // Assert the title was set.
1664         runWithShellPermission(() -> {
1665             final Task task = mWmState.getTaskByActivity(PIP_ACTIVITY);
1666             final TaskInfo info = mTaskOrganizer.getTaskInfo(task.getTaskId());
1667             final PictureInPictureParams params = info.getPictureInPictureParams();
1668 
1669             assertEquals(title, params.getTitle().toString());
1670         });
1671     }
1672 
1673     @Test
testSubtitleIsSet()1674     public void testSubtitleIsSet() {
1675         // Launch the PIP activity with given subtitle
1676         String subtitle = "PipSubtitle";
1677         launchActivity(PIP_ACTIVITY, extraString(EXTRA_SUBTITLE, subtitle));
1678         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1679 
1680         // Assert the subtitle was set.
1681         runWithShellPermission(() -> {
1682             final Task task = mWmState.getTaskByActivity(PIP_ACTIVITY);
1683             final TaskInfo info = mTaskOrganizer.getTaskInfo(task.getTaskId());
1684             final PictureInPictureParams params = info.getPictureInPictureParams();
1685 
1686             assertEquals(subtitle, params.getSubtitle().toString());
1687         });
1688     }
1689 
assertNumberOfActions(ComponentName componentName, int numberOfActions)1690     private void assertNumberOfActions(ComponentName componentName, int numberOfActions) {
1691         runWithShellPermission(() -> {
1692             final Task task = mWmState.getTaskByActivity(componentName);
1693             final TaskInfo info = mTaskOrganizer.getTaskInfo(task.getTaskId());
1694             final PictureInPictureParams params = info.getPictureInPictureParams();
1695 
1696             assertNotNull(params);
1697             assertNotNull(params.getActions());
1698             assertEquals(params.getActions().size(), numberOfActions);
1699         });
1700     }
1701 
enterPipAndAssertPinnedTaskExists(ComponentName activityName)1702     private void enterPipAndAssertPinnedTaskExists(ComponentName activityName) {
1703         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
1704         waitForEnterPip(activityName);
1705         assertPinnedStackExists();
1706     }
1707 
1708     /** Get app bounds in last applied configuration. */
getAppBounds(ComponentName activityName)1709     private Rect getAppBounds(ComponentName activityName) {
1710         final Configuration config = TestJournalContainer.get(activityName).extras
1711                 .getParcelable(EXTRA_CONFIGURATION);
1712         if (config != null) {
1713             return config.windowConfiguration.getAppBounds();
1714         }
1715         return null;
1716     }
1717 
1718     /**
1719      * Called after the given {@param activityName} has been moved to the back stack, which follows
1720      * the activity's previous windowing mode. Ensures that the stack matching the
1721      * {@param windowingMode} and {@param activityType} is focused, and checks PIP activity is now
1722      * properly stopped and now belongs to a stack of {@param previousWindowingMode}.
1723      */
assertPinnedStackStateOnMoveToBackStack(ComponentName activityName, int windowingMode, int activityType, int previousWindowingMode)1724     private void assertPinnedStackStateOnMoveToBackStack(ComponentName activityName,
1725             int windowingMode, int activityType, int previousWindowingMode) {
1726         mWmState.waitForFocusedStack(windowingMode, activityType);
1727         mWmState.assertFocusedRootTask("Wrong focused stack", windowingMode, activityType);
1728         waitAndAssertActivityState(activityName, STATE_STOPPED,
1729                 "Activity should go to STOPPED");
1730         assertTrue(mWmState.containsActivityInWindowingMode(
1731                 activityName, previousWindowingMode));
1732         assertPinnedStackDoesNotExist();
1733     }
1734 
1735     /**
1736      * Asserts that the pinned stack bounds is contained in the display bounds.
1737      */
assertPinnedStackActivityIsInDisplayBounds(ComponentName activityName)1738     private void assertPinnedStackActivityIsInDisplayBounds(ComponentName activityName) {
1739         final WindowManagerState.WindowState windowState = mWmState.getWindowState(activityName);
1740         final WindowManagerState.DisplayContent display = mWmState.getDisplay(
1741                 windowState.getDisplayId());
1742         final Rect displayRect = display.getDisplayRect();
1743         final Rect pinnedStackBounds = getPinnedStackBounds();
1744         assertTrue(displayRect.contains(pinnedStackBounds));
1745     }
1746 
getDefaultDisplayWindowingMode(ComponentName activityName)1747     private int getDefaultDisplayWindowingMode(ComponentName activityName) {
1748         Task task = mWmState.getTaskByActivity(activityName);
1749         return mWmState.getDisplay(task.mDisplayId)
1750                 .getWindowingMode();
1751     }
1752 
1753     /**
1754      * Asserts that the pinned stack exists.
1755      */
assertPinnedStackExists()1756     private void assertPinnedStackExists() {
1757         mWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
1758                 ACTIVITY_TYPE_STANDARD);
1759     }
1760 
1761     /**
1762      * Asserts that the pinned stack does not exist.
1763      */
assertPinnedStackDoesNotExist()1764     private void assertPinnedStackDoesNotExist() {
1765         mWmState.assertDoesNotContainStack("Must not contain pinned stack.",
1766                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1767     }
1768 
1769     /**
1770      * Asserts that the pinned stack is the front stack.
1771      */
assertPinnedStackIsOnTop()1772     private void assertPinnedStackIsOnTop() {
1773         mWmState.assertFrontStack("Pinned stack must always be on top.",
1774                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1775     }
1776 
1777     /**
1778      * Asserts that the activity received exactly one of each of the callbacks when entering and
1779      * exiting picture-in-picture.
1780      */
assertValidPictureInPictureCallbackOrder(ComponentName activityName, int windowingMode)1781     private void assertValidPictureInPictureCallbackOrder(ComponentName activityName,
1782             int windowingMode) {
1783         final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
1784         // There might be one additional config change caused by smallest screen width change when
1785         // there are cutout areas on the left & right edges of the display.
1786         assertThat(getActivityName(activityName) +
1787                         " onConfigurationChanged() shouldn't be triggered more than 2 times",
1788                 lifecycles.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED),
1789                 lessThanOrEqualTo(2));
1790         assertEquals(getActivityName(activityName) + " onMultiWindowModeChanged",
1791                 windowingMode == WINDOWING_MODE_FULLSCREEN ? 1 : 0,
1792                 lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
1793         assertEquals(getActivityName(activityName) + " onPictureInPictureModeChanged()",
1794                 1, lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
1795         final int lastPipIndex = lifecycles
1796                 .getLastIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
1797         final int lastConfigIndex = lifecycles
1798                 .getLastIndex(ActivityCallback.ON_CONFIGURATION_CHANGED);
1799         // In the case of Freeform, there's no onMultiWindowModeChange callback, so we will only
1800         // check for that callback for Fullscreen
1801         if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
1802             final int lastMwIndex = lifecycles
1803                     .getLastIndex(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED);
1804             assertThat("onPictureInPictureModeChanged should be before onMultiWindowModeChanged",
1805                     lastPipIndex, lessThan(lastMwIndex));
1806             assertThat("onMultiWindowModeChanged should be before onConfigurationChanged",
1807                     lastMwIndex, lessThan(lastConfigIndex));
1808         } else {
1809             assertThat("onPictureInPictureModeChanged should be before onConfigurationChanged",
1810                     lastPipIndex, lessThan(lastConfigIndex));
1811         }
1812     }
1813 
1814     /**
1815      * Waits until the given activity has entered picture-in-picture mode (allowing for the
1816      * subsequent animation to start).
1817      */
waitForEnterPip(ComponentName activityName)1818     private void waitForEnterPip(ComponentName activityName) {
1819         mWmState.waitForWithAmState(wmState -> {
1820             Task task = wmState.getTaskByActivity(activityName);
1821             return task != null && task.getWindowingMode() == WINDOWING_MODE_PINNED;
1822         }, "checking task windowing mode");
1823     }
1824 
1825     /**
1826      * Waits until the picture-in-picture animation has finished.
1827      */
waitForEnterPipAnimationComplete(ComponentName activityName)1828     private void waitForEnterPipAnimationComplete(ComponentName activityName) {
1829         waitForEnterPip(activityName);
1830         mWmState.waitForWithAmState(wmState -> {
1831             Task task = wmState.getTaskByActivity(activityName);
1832             if (task == null) {
1833                 return false;
1834             }
1835             WindowManagerState.Activity activity = task.getActivity(activityName);
1836             return activity.getWindowingMode() == WINDOWING_MODE_PINNED
1837                     && activity.getState().equals(STATE_PAUSED);
1838         }, "checking activity windowing mode");
1839         if (ENABLE_SHELL_TRANSITIONS) {
1840             mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
1841         }
1842     }
1843 
1844     /**
1845      * Waits until the pinned stack has been removed.
1846      */
waitForPinnedStackRemoved()1847     private void waitForPinnedStackRemoved() {
1848         mWmState.waitFor((amState) ->
1849                 !amState.containsRootTasks(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD),
1850                 "pinned stack to be removed");
1851     }
1852 
1853     /**
1854      * Waits until the picture-in-picture animation to fullscreen has finished.
1855      */
waitForExitPipToFullscreen(ComponentName activityName)1856     private void waitForExitPipToFullscreen(ComponentName activityName) {
1857         mWmState.waitForWithAmState(wmState -> {
1858             final Task task = wmState.getTaskByActivity(activityName);
1859             if (task == null) {
1860                 return false;
1861             }
1862             final WindowManagerState.Activity activity = task.getActivity(activityName);
1863             return activity.getWindowingMode() != WINDOWING_MODE_PINNED;
1864         }, "checking activity windowing mode");
1865         mWmState.waitForWithAmState(wmState -> {
1866             final Task task = wmState.getTaskByActivity(activityName);
1867             return task != null && task.getWindowingMode() != WINDOWING_MODE_PINNED;
1868         }, "checking task windowing mode");
1869     }
1870 
1871     /**
1872      * Waits until the expected picture-in-picture callbacks have been made.
1873      */
waitForValidPictureInPictureCallbacks(ComponentName activityName)1874     private void waitForValidPictureInPictureCallbacks(ComponentName activityName) {
1875         mWmState.waitFor((amState) -> {
1876             final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
1877             return lifecycles.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED) == 1
1878                     && lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED) == 1
1879                     && lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED) == 1;
1880         }, "picture-in-picture activity callbacks...");
1881     }
1882 
waitForValidAspectRatio(int num, int denom)1883     private void waitForValidAspectRatio(int num, int denom) {
1884         // Hacky, but we need to wait for the auto-enter picture-in-picture animation to complete
1885         // and before we can check the pinned stack bounds
1886         mWmState.waitForWithAmState((state) -> {
1887             Rect bounds = state.getStandardRootTaskByWindowingMode(WINDOWING_MODE_PINNED)
1888                     .getBounds();
1889             return floatEquals((float) bounds.width() / bounds.height(), (float) num / denom);
1890         }, "valid aspect ratio");
1891     }
1892 
1893     /**
1894      * @return the current pinned stack.
1895      */
getPinnedStack()1896     private Task getPinnedStack() {
1897         return mWmState.getStandardRootTaskByWindowingMode(WINDOWING_MODE_PINNED);
1898     }
1899 
1900     /**
1901      * @return the current pinned stack bounds.
1902      */
getPinnedStackBounds()1903     private Rect getPinnedStackBounds() {
1904         return getPinnedStack().getBounds();
1905     }
1906 
1907     /**
1908      * Compares two floats with a common epsilon.
1909      */
assertFloatEquals(float actual, float expected)1910     private void assertFloatEquals(float actual, float expected) {
1911         if (!floatEquals(actual, expected)) {
1912             fail(expected + " not equal to " + actual);
1913         }
1914     }
1915 
floatEquals(float a, float b)1916     private boolean floatEquals(float a, float b) {
1917         return Math.abs(a - b) < FLOAT_COMPARE_EPSILON;
1918     }
1919 
1920     /**
1921      * Triggers a tap over the pinned stack bounds to trigger the PIP to close.
1922      */
tapToFinishPip()1923     private void tapToFinishPip() {
1924         Rect pinnedStackBounds = getPinnedStackBounds();
1925         int tapX = pinnedStackBounds.left + pinnedStackBounds.width() - 100;
1926         int tapY = pinnedStackBounds.top + pinnedStackBounds.height() - 100;
1927         tapOnDisplaySync(tapX, tapY, DEFAULT_DISPLAY);
1928     }
1929 
1930     /**
1931      * Launches the given {@param activityName} into the {@param taskId} as a task overlay.
1932      */
launchPinnedActivityAsTaskOverlay(ComponentName activityName, int taskId)1933     private void launchPinnedActivityAsTaskOverlay(ComponentName activityName, int taskId) {
1934         executeShellCommand(getAmStartCmd(activityName) + " --task " + taskId + " --task-overlay");
1935 
1936         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
1937                 .setWindowingMode(WINDOWING_MODE_PINNED)
1938                 .setActivityType(ACTIVITY_TYPE_STANDARD)
1939                 .build());
1940     }
1941 
1942     private static class AppOpsSession implements AutoCloseable {
1943 
1944         private final String mPackageName;
1945 
AppOpsSession(ComponentName activityName)1946         AppOpsSession(ComponentName activityName) {
1947             mPackageName = activityName.getPackageName();
1948         }
1949 
1950         /**
1951          * Sets an app-ops op for a given package to a given mode.
1952          */
setOpToMode(String op, int mode)1953         void setOpToMode(String op, int mode) {
1954             try {
1955                 AppOpsUtils.setOpMode(mPackageName, op, mode);
1956             } catch (Exception e) {
1957                 e.printStackTrace();
1958             }
1959         }
1960 
1961         @Override
close()1962         public void close() {
1963             try {
1964                 AppOpsUtils.reset(mPackageName);
1965             } catch (IOException e) {
1966                 e.printStackTrace();
1967             }
1968         }
1969     }
1970 
1971     /**
1972      * TODO: Improve tests check to actually check that apps are not interactive instead of checking
1973      *       if the stack is focused.
1974      */
pinnedStackTester(String startActivityCmd, ComponentName startActivity, ComponentName topActivityName, boolean isFocusable)1975     private void pinnedStackTester(String startActivityCmd, ComponentName startActivity,
1976             ComponentName topActivityName, boolean isFocusable) {
1977         executeShellCommand(startActivityCmd);
1978         mWmState.waitForValidState(startActivity);
1979 
1980         mWmState.waitForValidState(new WaitForValidActivityState.Builder(topActivityName)
1981                 .setWindowingMode(WINDOWING_MODE_PINNED)
1982                 .setActivityType(ACTIVITY_TYPE_STANDARD)
1983                 .build());
1984         mWmState.computeState();
1985 
1986         if (supportsPip()) {
1987             final String windowName = getWindowName(topActivityName);
1988             assertPinnedStackExists();
1989             mWmState.assertFrontStack("Pinned stack must be the front stack.",
1990                     WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1991             mWmState.assertVisibility(topActivityName, true);
1992 
1993             if (isFocusable) {
1994                 mWmState.assertFocusedRootTask("Pinned stack must be the focused stack.",
1995                         WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1996                 mWmState.assertFocusedActivity(
1997                         "Pinned activity must be focused activity.", topActivityName);
1998                 mWmState.assertFocusedWindow(
1999                         "Pinned window must be focused window.", windowName);
2000                 // Not checking for resumed state here because PiP overlay can be launched on top
2001                 // in different task by SystemUI.
2002             } else {
2003                 // Don't assert that the stack is not focused as a focusable PiP overlay can be
2004                 // launched on top as a task overlay by SystemUI.
2005                 mWmState.assertNotFocusedActivity(
2006                         "Pinned activity can't be the focused activity.", topActivityName);
2007                 mWmState.assertNotResumedActivity(
2008                         "Pinned activity can't be the resumed activity.", topActivityName);
2009                 mWmState.assertNotFocusedWindow(
2010                         "Pinned window can't be focused window.", windowName);
2011             }
2012         } else {
2013             mWmState.assertDoesNotContainStack("Must not contain pinned stack.",
2014                     WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
2015         }
2016     }
2017 
2018     public static class TestActivity extends Activity { }
2019 }
2020