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