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