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