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.Task;
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 Task 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 Task pinnedStack = getPinnedStack();
601 
602         launchActivityInNewTask(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
603         waitForEnterPip(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
604 
605         assertEquals(1, mWmState.countRootTasks(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 Task 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 Task 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 Task 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 Task 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         Task task = mWmState.getRootTaskByActivity(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.waitForActivityOrientation(PIP_ACTIVITY, ORIENTATION_LANDSCAPE);
1099 
1100         final Task pipActivityTask = mWmState.getTaskByActivity(PIP_ACTIVITY);
1101         assertEquals(ORIENTATION_LANDSCAPE, pipActivityTask.mOverrideConfiguration.orientation);
1102     }
1103 
1104     @Test
testWindowButtonEntersPip()1105     public void testWindowButtonEntersPip() {
1106         assumeTrue(!mWmState.isHomeRecentsComponent());
1107 
1108         // Launch the PiP activity trigger the window button, ensure that we have entered PiP
1109         launchActivity(PIP_ACTIVITY);
1110         pressWindowButton();
1111         waitForEnterPip(PIP_ACTIVITY);
1112         assertPinnedStackExists();
1113     }
1114 
1115     @Test
testFinishPipActivityWithTaskOverlay()1116     public void testFinishPipActivityWithTaskOverlay() {
1117         // Launch PiP activity
1118         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
1119         waitForEnterPip(PIP_ACTIVITY);
1120         assertPinnedStackExists();
1121         int taskId = mWmState.getStandardRootTaskByWindowingMode(WINDOWING_MODE_PINNED).getTopTask()
1122                 .mTaskId;
1123 
1124         // Ensure that we don't any any other overlays as a result of launching into PIP
1125         launchHomeActivity();
1126 
1127         // Launch task overlay activity into PiP activity task
1128         launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
1129 
1130         // Finish the PiP activity and ensure that there is no pinned stack
1131         mBroadcastActionTrigger.doAction(ACTION_FINISH);
1132         waitForPinnedStackRemoved();
1133         assertPinnedStackDoesNotExist();
1134     }
1135 
1136     @Test
testNoResumeAfterTaskOverlayFinishes()1137     public void testNoResumeAfterTaskOverlayFinishes() {
1138         // Launch PiP activity
1139         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
1140         waitForEnterPip(PIP_ACTIVITY);
1141         assertPinnedStackExists();
1142         Task task = mWmState.getStandardRootTaskByWindowingMode(WINDOWING_MODE_PINNED);
1143         int taskId = task.getTopTask().mTaskId;
1144 
1145         // Launch task overlay activity into PiP activity task
1146         launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
1147 
1148         // Finish the task overlay activity and ensure that the PiP activity never got resumed.
1149         separateTestJournal();
1150         mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
1151         mWmState.waitFor((amState) ->
1152                         !amState.containsActivity(TRANSLUCENT_TEST_ACTIVITY),
1153                 "Waiting for test activity to finish...");
1154         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
1155         assertEquals("onResume", 0, lifecycleCounts.getCount(ActivityCallback.ON_RESUME));
1156         assertEquals("onPause", 0, lifecycleCounts.getCount(ActivityCallback.ON_PAUSE));
1157     }
1158 
1159     @Test
testTranslucentActivityOnTopOfPinnedTask()1160     public void testTranslucentActivityOnTopOfPinnedTask() {
1161         launchActivity(LAUNCH_PIP_ON_PIP_ACTIVITY);
1162         // NOTE: moving to pinned stack will trigger the pip-on-pip activity to launch the
1163         // translucent activity.
1164         enterPipAndAssertPinnedTaskExists(LAUNCH_PIP_ON_PIP_ACTIVITY);
1165         mWmState.waitForValidState(
1166                 new WaitForValidActivityState.Builder(ALWAYS_FOCUSABLE_PIP_ACTIVITY)
1167                         .setWindowingMode(WINDOWING_MODE_PINNED)
1168                         .build());
1169 
1170         assertPinnedStackIsOnTop();
1171         mWmState.assertVisibility(LAUNCH_PIP_ON_PIP_ACTIVITY, true);
1172         mWmState.assertVisibility(ALWAYS_FOCUSABLE_PIP_ACTIVITY, true);
1173     }
1174 
1175     @Test
testLaunchTaskByComponentMatchMultipleTasks()1176     public void testLaunchTaskByComponentMatchMultipleTasks() {
1177         // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
1178         // affinity
1179         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
1180         launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
1181         assertPinnedStackExists();
1182 
1183         // Launch the root activity again...
1184         int rootActivityTaskId = mWmState.getTaskByActivity(
1185                 TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId;
1186         launchHomeActivity();
1187         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
1188 
1189         // ...and ensure that the root activity task is found and reused, and that the pinned stack
1190         // is unaffected
1191         assertPinnedStackExists();
1192         mWmState.assertFocusedActivity("Expected root activity focused",
1193                 TEST_ACTIVITY_WITH_SAME_AFFINITY);
1194         assertEquals(rootActivityTaskId, mWmState.getTaskByActivity(
1195                 TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId);
1196     }
1197 
1198     @Test
testLaunchTaskByAffinityMatchMultipleTasks()1199     public void testLaunchTaskByAffinityMatchMultipleTasks() {
1200         // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
1201         // affinity, and also launch another activity in the same task, while finishing itself. As
1202         // a result, the task will not have a component matching the same activity as what it was
1203         // started with
1204         launchActivityNoWait(TEST_ACTIVITY_WITH_SAME_AFFINITY,
1205                 extraString(EXTRA_START_ACTIVITY, getActivityName(TEST_ACTIVITY)),
1206                 extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
1207         mWmState.waitForValidState(new WaitForValidActivityState.Builder(TEST_ACTIVITY)
1208                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
1209                 .setActivityType(ACTIVITY_TYPE_STANDARD)
1210                 .build());
1211         launchActivityNoWait(PIP_ACTIVITY_WITH_SAME_AFFINITY);
1212         waitForEnterPip(PIP_ACTIVITY_WITH_SAME_AFFINITY);
1213         assertPinnedStackExists();
1214 
1215         // Launch the root activity again...
1216         int rootActivityTaskId = mWmState.getTaskByActivity(
1217                 TEST_ACTIVITY).mTaskId;
1218         launchHomeActivity();
1219         launchActivityNoWait(TEST_ACTIVITY_WITH_SAME_AFFINITY);
1220         mWmState.computeState();
1221 
1222         // ...and ensure that even while matching purely by task affinity, the root activity task is
1223         // found and reused, and that the pinned stack is unaffected
1224         assertPinnedStackExists();
1225         mWmState.assertFocusedActivity("Expected root activity focused", TEST_ACTIVITY);
1226         assertEquals(rootActivityTaskId, mWmState.getTaskByActivity(
1227                 TEST_ACTIVITY).mTaskId);
1228     }
1229 
1230     @Test
testLaunchTaskByAffinityMatchSingleTask()1231     public void testLaunchTaskByAffinityMatchSingleTask() {
1232         // Launch an activity into the pinned stack with a fixed affinity
1233         launchActivityNoWait(TEST_ACTIVITY_WITH_SAME_AFFINITY,
1234                 extraString(EXTRA_ENTER_PIP, "true"),
1235                 extraString(EXTRA_START_ACTIVITY, getActivityName(PIP_ACTIVITY)),
1236                 extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
1237         waitForEnterPip(PIP_ACTIVITY);
1238         assertPinnedStackExists();
1239 
1240         // Launch the root activity again, of the matching task and ensure that we expand to
1241         // fullscreen
1242         int activityTaskId = mWmState.getTaskByActivity(PIP_ACTIVITY).mTaskId;
1243         launchHomeActivity();
1244         launchActivityNoWait(TEST_ACTIVITY_WITH_SAME_AFFINITY);
1245         waitForExitPipToFullscreen(PIP_ACTIVITY);
1246         assertPinnedStackDoesNotExist();
1247         assertEquals(activityTaskId, mWmState.getTaskByActivity(
1248                 PIP_ACTIVITY).mTaskId);
1249     }
1250 
1251     /** Test that reported display size corresponds to fullscreen after exiting PiP. */
1252     @Test
testDisplayMetricsPinUnpin()1253     public void testDisplayMetricsPinUnpin() {
1254         separateTestJournal();
1255         launchActivity(TEST_ACTIVITY);
1256         final int defaultWindowingMode = mWmState
1257                 .getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
1258         final SizeInfo initialSizes = getLastReportedSizesForActivity(TEST_ACTIVITY);
1259         final Rect initialAppBounds = getAppBounds(TEST_ACTIVITY);
1260         assertNotNull("Must report display dimensions", initialSizes);
1261         assertNotNull("Must report app bounds", initialAppBounds);
1262 
1263         separateTestJournal();
1264         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP, "true"));
1265         // Wait for animation complete since we are comparing bounds
1266         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
1267         final SizeInfo pinnedSizes = getLastReportedSizesForActivity(PIP_ACTIVITY);
1268         final Rect pinnedAppBounds = getAppBounds(PIP_ACTIVITY);
1269         assertNotEquals("Reported display size when pinned must be different from default",
1270                 initialSizes, pinnedSizes);
1271         final Size initialAppSize = new Size(initialAppBounds.width(), initialAppBounds.height());
1272         final Size pinnedAppSize = new Size(pinnedAppBounds.width(), pinnedAppBounds.height());
1273         assertNotEquals("Reported app size when pinned must be different from default",
1274                 initialAppSize, pinnedAppSize);
1275 
1276         separateTestJournal();
1277         launchActivity(PIP_ACTIVITY, defaultWindowingMode);
1278         final SizeInfo finalSizes = getLastReportedSizesForActivity(PIP_ACTIVITY);
1279         final Rect finalAppBounds = getAppBounds(PIP_ACTIVITY);
1280         final Size finalAppSize = new Size(finalAppBounds.width(), finalAppBounds.height());
1281         assertEquals("Must report default size after exiting PiP", initialSizes, finalSizes);
1282         assertEquals("Must report default app size after exiting PiP", initialAppSize,
1283                 finalAppSize);
1284     }
1285 
1286     @Test
testAutoPipAllowedBypassesExplicitEnterPip()1287     public void testAutoPipAllowedBypassesExplicitEnterPip() {
1288         // Launch a test activity so that we're not over home.
1289         launchActivity(TEST_ACTIVITY);
1290 
1291         // Launch the PIP activity and set its pip params to allow auto-pip.
1292         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ALLOW_AUTO_PIP, "true"));
1293         assertPinnedStackDoesNotExist();
1294 
1295         // Go home and ensure that there is a pinned stack.
1296         launchHomeActivity();
1297         waitForEnterPip(PIP_ACTIVITY);
1298         assertPinnedStackExists();
1299         waitAndAssertActivityState(PIP_ACTIVITY, STATE_PAUSED, "activity must be paused");
1300     }
1301 
1302     @Test
testAutoPipOnLaunchingAnotherActivity()1303     public void testAutoPipOnLaunchingAnotherActivity() {
1304         // Launch the PIP activity and set its pip params to allow auto-pip.
1305         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ALLOW_AUTO_PIP, "true"));
1306         assertPinnedStackDoesNotExist();
1307 
1308         // Launch another and ensure that there is a pinned stack.
1309         launchActivity(TEST_ACTIVITY);
1310         waitForEnterPip(PIP_ACTIVITY);
1311         assertPinnedStackExists();
1312         waitAndAssertActivityState(PIP_ACTIVITY, STATE_PAUSED, "activity must be paused");
1313     }
1314 
1315     @Test
testMaxNumberOfActions()1316     public void testMaxNumberOfActions() {
1317         final int maxNumberActions = ActivityTaskManager.getMaxNumPictureInPictureActions(mContext);
1318         assertThat(maxNumberActions, greaterThanOrEqualTo(3));
1319     }
1320 
1321     @Test
testFillMaxAllowedActions()1322     public void testFillMaxAllowedActions() {
1323         final int maxNumberActions = ActivityTaskManager.getMaxNumPictureInPictureActions(mContext);
1324         // Launch the PIP activity with max allowed actions
1325         launchActivity(PIP_ACTIVITY,
1326                 extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(maxNumberActions)));
1327         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1328 
1329         assertNumberOfActions(PIP_ACTIVITY, maxNumberActions);
1330     }
1331 
1332     @Test
testRejectExceededActions()1333     public void testRejectExceededActions() {
1334         final int maxNumberActions = ActivityTaskManager.getMaxNumPictureInPictureActions(mContext);
1335         // Launch the PIP activity with exceeded amount of actions
1336         launchActivity(PIP_ACTIVITY,
1337                 extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(maxNumberActions + 1)));
1338         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1339 
1340         assertNumberOfActions(PIP_ACTIVITY, maxNumberActions);
1341     }
1342 
1343     @Test
testIsSeamlessResizeEnabledDefaultToTrue()1344     public void testIsSeamlessResizeEnabledDefaultToTrue() {
1345         // Launch the PIP activity with some random param without setting isSeamlessResizeEnabled
1346         // so the PictureInPictureParams acquired from TaskInfo is not null
1347         launchActivity(PIP_ACTIVITY,
1348                 extraString(EXTRA_NUMBER_OF_CUSTOM_ACTIONS, String.valueOf(1)));
1349         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1350 
1351         // Assert the default value of isSeamlessResizeEnabled is set to true.
1352         assertIsSeamlessResizeEnabled(PIP_ACTIVITY, true);
1353     }
1354 
1355     @Test
testDisableIsSeamlessResizeEnabled()1356     public void testDisableIsSeamlessResizeEnabled() {
1357         // Launch the PIP activity with overridden isSeamlessResizeEnabled param
1358         launchActivity(PIP_ACTIVITY, extraBool(EXTRA_IS_SEAMLESS_RESIZE_ENABLED, false));
1359         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1360 
1361         // Assert the value of isSeamlessResizeEnabled is overridden.
1362         assertIsSeamlessResizeEnabled(PIP_ACTIVITY, false);
1363     }
1364 
1365     @Test
testPictureInPictureStateChangeCallback()1366     public void testPictureInPictureStateChangeCallback() throws Exception {
1367         launchActivity(PIP_ACTIVITY);
1368         enterPipAndAssertPinnedTaskExists(PIP_ACTIVITY);
1369         waitForEnterPip(PIP_ACTIVITY);
1370 
1371         final CompletableFuture<Boolean> callbackReturn = new CompletableFuture<>();
1372         RemoteCallback cb = new RemoteCallback((Bundle result) ->
1373                 callbackReturn.complete(result.getBoolean(PIP_CALLBACK_RESULT_KEY)));
1374         mBroadcastActionTrigger.sendPipStateUpdate(cb, true);
1375         Truth.assertThat(callbackReturn.get(5000, TimeUnit.MILLISECONDS)).isEqualTo(true);
1376 
1377         final CompletableFuture<Boolean> callbackReturnNotStashed = new CompletableFuture<>();
1378         RemoteCallback cbStashed = new RemoteCallback((Bundle result) ->
1379                 callbackReturnNotStashed.complete(result.getBoolean(PIP_CALLBACK_RESULT_KEY)));
1380         mBroadcastActionTrigger.sendPipStateUpdate(cbStashed, false);
1381         Truth.assertThat(callbackReturnNotStashed.get(5000, TimeUnit.MILLISECONDS))
1382                 .isEqualTo(false);
1383     }
1384 
assertIsSeamlessResizeEnabled(ComponentName componentName, boolean expected)1385     private void assertIsSeamlessResizeEnabled(ComponentName componentName, boolean expected) {
1386         runWithShellPermission(() -> {
1387             final Task task = mWmState.getTaskByActivity(componentName);
1388             final TaskInfo info = mTaskOrganizer.getTaskInfo(task.getTaskId());
1389             final PictureInPictureParams params = info.getPictureInPictureParams();
1390 
1391             assertEquals(expected, params.isSeamlessResizeEnabled());
1392         });
1393     }
1394 
assertNumberOfActions(ComponentName componentName, int numberOfActions)1395     private void assertNumberOfActions(ComponentName componentName, int numberOfActions) {
1396         runWithShellPermission(() -> {
1397             final Task task = mWmState.getTaskByActivity(componentName);
1398             final TaskInfo info = mTaskOrganizer.getTaskInfo(task.getTaskId());
1399             final PictureInPictureParams params = info.getPictureInPictureParams();
1400 
1401             assertNotNull(params);
1402             assertNotNull(params.getActions());
1403             assertEquals(params.getActions().size(), numberOfActions);
1404         });
1405     }
1406 
enterPipAndAssertPinnedTaskExists(ComponentName activityName)1407     private void enterPipAndAssertPinnedTaskExists(ComponentName activityName) {
1408         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
1409         waitForEnterPip(activityName);
1410         assertPinnedStackExists();
1411     }
1412 
1413     /** Get app bounds in last applied configuration. */
getAppBounds(ComponentName activityName)1414     private Rect getAppBounds(ComponentName activityName) {
1415         final Configuration config = TestJournalContainer.get(activityName).extras
1416                 .getParcelable(EXTRA_CONFIGURATION);
1417         if (config != null) {
1418             return config.windowConfiguration.getAppBounds();
1419         }
1420         return null;
1421     }
1422 
1423     /**
1424      * Called after the given {@param activityName} has been moved to the back stack, which follows
1425      * the activity's previous windowing mode. Ensures that the stack matching the
1426      * {@param windowingMode} and {@param activityType} is focused, and checks PIP activity is now
1427      * properly stopped and now belongs to a stack of {@param previousWindowingMode}.
1428      */
assertPinnedStackStateOnMoveToBackStack(ComponentName activityName, int windowingMode, int activityType, int previousWindowingMode)1429     private void assertPinnedStackStateOnMoveToBackStack(ComponentName activityName,
1430             int windowingMode, int activityType, int previousWindowingMode) {
1431         mWmState.waitForFocusedStack(windowingMode, activityType);
1432         mWmState.assertFocusedRootTask("Wrong focused stack", windowingMode, activityType);
1433         waitAndAssertActivityState(activityName, STATE_STOPPED,
1434                 "Activity should go to STOPPED");
1435         assertTrue(mWmState.containsActivityInWindowingMode(
1436                 activityName, previousWindowingMode));
1437         assertPinnedStackDoesNotExist();
1438     }
1439 
1440     /**
1441      * Asserts that the pinned stack bounds is contained in the display bounds.
1442      */
assertPinnedStackActivityIsInDisplayBounds(ComponentName activityName)1443     private void assertPinnedStackActivityIsInDisplayBounds(ComponentName activityName) {
1444         final WindowManagerState.WindowState windowState = getWindowState(activityName);
1445         final WindowManagerState.DisplayContent display = mWmState.getDisplay(
1446                 windowState.getDisplayId());
1447         final Rect displayRect = display.getDisplayRect();
1448         final Rect pinnedStackBounds = getPinnedStackBounds();
1449         assertTrue(displayRect.contains(pinnedStackBounds));
1450     }
1451 
getDefaultDisplayWindowingMode(ComponentName activityName)1452     private int getDefaultDisplayWindowingMode(ComponentName activityName) {
1453         Task task = mWmState.getTaskByActivity(activityName);
1454         return mWmState.getDisplay(task.mDisplayId)
1455                 .getWindowingMode();
1456     }
1457 
1458     /**
1459      * Asserts that the pinned stack exists.
1460      */
assertPinnedStackExists()1461     private void assertPinnedStackExists() {
1462         mWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
1463                 ACTIVITY_TYPE_STANDARD);
1464     }
1465 
1466     /**
1467      * Asserts that the pinned stack does not exist.
1468      */
assertPinnedStackDoesNotExist()1469     private void assertPinnedStackDoesNotExist() {
1470         mWmState.assertDoesNotContainStack("Must not contain pinned stack.",
1471                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1472     }
1473 
1474     /**
1475      * Asserts that the pinned stack is the front stack.
1476      */
assertPinnedStackIsOnTop()1477     private void assertPinnedStackIsOnTop() {
1478         mWmState.assertFrontStack("Pinned stack must always be on top.",
1479                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1480     }
1481 
1482     /**
1483      * Asserts that the activity received exactly one of each of the callbacks when entering and
1484      * exiting picture-in-picture.
1485      */
assertValidPictureInPictureCallbackOrder(ComponentName activityName, int windowingMode)1486     private void assertValidPictureInPictureCallbackOrder(ComponentName activityName,
1487             int windowingMode) {
1488         final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
1489         // There might be one additional config change caused by smallest screen width change when
1490         // there are cutout areas on the left & right edges of the display.
1491         assertThat(getActivityName(activityName) +
1492                         " onConfigurationChanged() shouldn't be triggered more than 2 times",
1493                 lifecycles.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED),
1494                 lessThanOrEqualTo(2));
1495         assertEquals(getActivityName(activityName) + " onMultiWindowModeChanged",
1496                 windowingMode == WINDOWING_MODE_FULLSCREEN ? 1 : 0,
1497                 lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
1498         assertEquals(getActivityName(activityName) + " onPictureInPictureModeChanged()",
1499                 1, lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
1500         final int lastPipIndex = lifecycles
1501                 .getLastIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
1502         final int lastConfigIndex = lifecycles
1503                 .getLastIndex(ActivityCallback.ON_CONFIGURATION_CHANGED);
1504         // In the case of Freeform, there's no onMultiWindowModeChange callback, so we will only
1505         // check for that callback for Fullscreen
1506         if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
1507             final int lastMwIndex = lifecycles
1508                     .getLastIndex(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED);
1509             assertThat("onPictureInPictureModeChanged should be before onMultiWindowModeChanged",
1510                     lastPipIndex, lessThan(lastMwIndex));
1511             assertThat("onMultiWindowModeChanged should be before onConfigurationChanged",
1512                     lastMwIndex, lessThan(lastConfigIndex));
1513         } else {
1514             assertThat("onPictureInPictureModeChanged should be before onConfigurationChanged",
1515                     lastPipIndex, lessThan(lastConfigIndex));
1516         }
1517     }
1518 
1519     /**
1520      * Waits until the given activity has entered picture-in-picture mode (allowing for the
1521      * subsequent animation to start).
1522      */
waitForEnterPip(ComponentName activityName)1523     private void waitForEnterPip(ComponentName activityName) {
1524         mWmState.waitForWithAmState(wmState -> {
1525             Task task = wmState.getTaskByActivity(activityName);
1526             return task != null && task.getWindowingMode() == WINDOWING_MODE_PINNED;
1527         }, "checking task windowing mode");
1528     }
1529 
1530     /**
1531      * Waits until the picture-in-picture animation has finished.
1532      */
waitForEnterPipAnimationComplete(ComponentName activityName)1533     private void waitForEnterPipAnimationComplete(ComponentName activityName) {
1534         waitForEnterPip(activityName);
1535         mWmState.waitForWithAmState(wmState -> {
1536             Task task = wmState.getTaskByActivity(activityName);
1537             if (task == null) {
1538                 return false;
1539             }
1540             WindowManagerState.Activity activity = task.getActivity(activityName);
1541             return activity.getWindowingMode() == WINDOWING_MODE_PINNED
1542                     && activity.getState().equals(STATE_PAUSED);
1543         }, "checking activity windowing mode");
1544     }
1545 
1546     /**
1547      * Waits until the pinned stack has been removed.
1548      */
waitForPinnedStackRemoved()1549     private void waitForPinnedStackRemoved() {
1550         mWmState.waitFor((amState) ->
1551                 !amState.containsRootTasks(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD),
1552                 "pinned stack to be removed");
1553     }
1554 
1555     /**
1556      * Waits until the picture-in-picture animation to fullscreen has finished.
1557      */
waitForExitPipToFullscreen(ComponentName activityName)1558     private void waitForExitPipToFullscreen(ComponentName activityName) {
1559         mWmState.waitForWithAmState(wmState -> {
1560             final Task task = wmState.getTaskByActivity(activityName);
1561             if (task == null) {
1562                 return false;
1563             }
1564             final WindowManagerState.Activity activity = task.getActivity(activityName);
1565             return activity.getWindowingMode() != WINDOWING_MODE_PINNED;
1566         }, "checking activity windowing mode");
1567         mWmState.waitForWithAmState(wmState -> {
1568             final Task task = wmState.getTaskByActivity(activityName);
1569             return task != null && task.getWindowingMode() != WINDOWING_MODE_PINNED;
1570         }, "checking task windowing mode");
1571     }
1572 
1573     /**
1574      * Waits until the expected picture-in-picture callbacks have been made.
1575      */
waitForValidPictureInPictureCallbacks(ComponentName activityName)1576     private void waitForValidPictureInPictureCallbacks(ComponentName activityName) {
1577         mWmState.waitFor((amState) -> {
1578             final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
1579             return lifecycles.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED) == 1
1580                     && lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED) == 1
1581                     && lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED) == 1;
1582         }, "picture-in-picture activity callbacks...");
1583     }
1584 
waitForValidAspectRatio(int num, int denom)1585     private void waitForValidAspectRatio(int num, int denom) {
1586         // Hacky, but we need to wait for the auto-enter picture-in-picture animation to complete
1587         // and before we can check the pinned stack bounds
1588         mWmState.waitForWithAmState((state) -> {
1589             Rect bounds = state.getStandardRootTaskByWindowingMode(WINDOWING_MODE_PINNED)
1590                     .getBounds();
1591             return floatEquals((float) bounds.width() / bounds.height(), (float) num / denom);
1592         }, "valid aspect ratio");
1593     }
1594 
1595     /**
1596      * @return the window state for the given {@param activityName}'s window.
1597      */
getWindowState(ComponentName activityName)1598     private WindowManagerState.WindowState getWindowState(ComponentName activityName) {
1599         String windowName = getWindowName(activityName);
1600         mWmState.computeState(activityName);
1601         final List<WindowManagerState.WindowState> tempWindowList =
1602                 mWmState.getMatchingVisibleWindowState(windowName);
1603         return tempWindowList.get(0);
1604     }
1605 
1606     /**
1607      * @return the current pinned stack.
1608      */
getPinnedStack()1609     private Task getPinnedStack() {
1610         return mWmState.getStandardRootTaskByWindowingMode(WINDOWING_MODE_PINNED);
1611     }
1612 
1613     /**
1614      * @return the current pinned stack bounds.
1615      */
getPinnedStackBounds()1616     private Rect getPinnedStackBounds() {
1617         return getPinnedStack().getBounds();
1618     }
1619 
1620     /**
1621      * Compares two floats with a common epsilon.
1622      */
assertFloatEquals(float actual, float expected)1623     private void assertFloatEquals(float actual, float expected) {
1624         if (!floatEquals(actual, expected)) {
1625             fail(expected + " not equal to " + actual);
1626         }
1627     }
1628 
floatEquals(float a, float b)1629     private boolean floatEquals(float a, float b) {
1630         return Math.abs(a - b) < FLOAT_COMPARE_EPSILON;
1631     }
1632 
1633     /**
1634      * Triggers a tap over the pinned stack bounds to trigger the PIP to close.
1635      */
tapToFinishPip()1636     private void tapToFinishPip() {
1637         Rect pinnedStackBounds = getPinnedStackBounds();
1638         int tapX = pinnedStackBounds.left + pinnedStackBounds.width() - 100;
1639         int tapY = pinnedStackBounds.top + pinnedStackBounds.height() - 100;
1640         tapOnDisplaySync(tapX, tapY, DEFAULT_DISPLAY);
1641     }
1642 
1643     /**
1644      * Launches the given {@param activityName} into the {@param taskId} as a task overlay.
1645      */
launchPinnedActivityAsTaskOverlay(ComponentName activityName, int taskId)1646     private void launchPinnedActivityAsTaskOverlay(ComponentName activityName, int taskId) {
1647         executeShellCommand(getAmStartCmd(activityName) + " --task " + taskId + " --task-overlay");
1648 
1649         mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
1650                 .setWindowingMode(WINDOWING_MODE_PINNED)
1651                 .setActivityType(ACTIVITY_TYPE_STANDARD)
1652                 .build());
1653     }
1654 
1655     private static class AppOpsSession implements AutoCloseable {
1656 
1657         private final String mPackageName;
1658 
AppOpsSession(ComponentName activityName)1659         AppOpsSession(ComponentName activityName) {
1660             mPackageName = activityName.getPackageName();
1661         }
1662 
1663         /**
1664          * Sets an app-ops op for a given package to a given mode.
1665          */
setOpToMode(String op, int mode)1666         void setOpToMode(String op, int mode) {
1667             try {
1668                 AppOpsUtils.setOpMode(mPackageName, op, mode);
1669             } catch (Exception e) {
1670                 e.printStackTrace();
1671             }
1672         }
1673 
1674         @Override
close()1675         public void close() {
1676             try {
1677                 AppOpsUtils.reset(mPackageName);
1678             } catch (IOException e) {
1679                 e.printStackTrace();
1680             }
1681         }
1682     }
1683 
1684     /**
1685      * TODO: Improve tests check to actually check that apps are not interactive instead of checking
1686      *       if the stack is focused.
1687      */
pinnedStackTester(String startActivityCmd, ComponentName startActivity, ComponentName topActivityName, boolean isFocusable)1688     private void pinnedStackTester(String startActivityCmd, ComponentName startActivity,
1689             ComponentName topActivityName, boolean isFocusable) {
1690         executeShellCommand(startActivityCmd);
1691         mWmState.waitForValidState(startActivity);
1692 
1693         mWmState.waitForValidState(new WaitForValidActivityState.Builder(topActivityName)
1694                 .setWindowingMode(WINDOWING_MODE_PINNED)
1695                 .setActivityType(ACTIVITY_TYPE_STANDARD)
1696                 .build());
1697         mWmState.computeState();
1698 
1699         if (supportsPip()) {
1700             final String windowName = getWindowName(topActivityName);
1701             assertPinnedStackExists();
1702             mWmState.assertFrontStack("Pinned stack must be the front stack.",
1703                     WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1704             mWmState.assertVisibility(topActivityName, true);
1705 
1706             if (isFocusable) {
1707                 mWmState.assertFocusedRootTask("Pinned stack must be the focused stack.",
1708                         WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1709                 mWmState.assertFocusedActivity(
1710                         "Pinned activity must be focused activity.", topActivityName);
1711                 mWmState.assertFocusedWindow(
1712                         "Pinned window must be focused window.", windowName);
1713                 // Not checking for resumed state here because PiP overlay can be launched on top
1714                 // in different task by SystemUI.
1715             } else {
1716                 // Don't assert that the stack is not focused as a focusable PiP overlay can be
1717                 // launched on top as a task overlay by SystemUI.
1718                 mWmState.assertNotFocusedActivity(
1719                         "Pinned activity can't be the focused activity.", topActivityName);
1720                 mWmState.assertNotResumedActivity(
1721                         "Pinned activity can't be the resumed activity.", topActivityName);
1722                 mWmState.assertNotFocusedWindow(
1723                         "Pinned window can't be focused window.", windowName);
1724             }
1725         } else {
1726             mWmState.assertDoesNotContainStack("Must not contain pinned stack.",
1727                     WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
1728         }
1729     }
1730 }
1731