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