Home | History | Annotate | Download | only in wm
      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.ActivityTaskManager.INVALID_STACK_ID;
     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_FULLSCREEN;
     23 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
     24 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
     25 import static android.server.wm.ActivityManagerState.STATE_RESUMED;
     26 import static android.server.wm.ActivityManagerState.STATE_STOPPED;
     27 import static android.server.wm.ComponentNameUtils.getActivityName;
     28 import static android.server.wm.ComponentNameUtils.getWindowName;
     29 import static android.server.wm.UiDeviceUtils.pressWindowButton;
     30 import static android.server.wm.app.Components.ALWAYS_FOCUSABLE_PIP_ACTIVITY;
     31 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
     32 import static android.server.wm.app.Components.LAUNCH_ENTER_PIP_ACTIVITY;
     33 import static android.server.wm.app.Components.LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY;
     34 import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
     35 import static android.server.wm.app.Components.NO_RELAUNCH_ACTIVITY;
     36 import static android.server.wm.app.Components.PIP_ACTIVITY;
     37 import static android.server.wm.app.Components.PIP_ACTIVITY2;
     38 import static android.server.wm.app.Components.PIP_ACTIVITY_WITH_SAME_AFFINITY;
     39 import static android.server.wm.app.Components.PIP_ON_STOP_ACTIVITY;
     40 import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP;
     41 import static android.server.wm.app.Components.PipActivity.ACTION_EXPAND_PIP;
     42 import static android.server.wm.app.Components.PipActivity.ACTION_FINISH;
     43 import static android.server.wm.app.Components.PipActivity.ACTION_MOVE_TO_BACK;
     44 import static android.server.wm.app.Components.PipActivity.EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP;
     45 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
     46 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
     47 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR;
     48 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PAUSE;
     49 import static android.server.wm.app.Components.PipActivity.EXTRA_FINISH_SELF_ON_RESUME;
     50 import static android.server.wm.app.Components.PipActivity.EXTRA_ON_PAUSE_DELAY;
     51 import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION;
     52 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_DENOMINATOR;
     53 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_NUMERATOR;
     54 import static android.server.wm.app.Components.PipActivity.EXTRA_START_ACTIVITY;
     55 import static android.server.wm.app.Components.PipActivity.EXTRA_TAP_TO_FINISH;
     56 import static android.server.wm.app.Components.RESUME_WHILE_PAUSING_ACTIVITY;
     57 import static android.server.wm.app.Components.TEST_ACTIVITY;
     58 import static android.server.wm.app.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY;
     59 import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
     60 import static android.server.wm.app.Components.TestActivity.EXTRA_CONFIGURATION;
     61 import static android.server.wm.app.Components.TestActivity.EXTRA_FIXED_ORIENTATION;
     62 import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
     63 import static android.view.Display.DEFAULT_DISPLAY;
     64 
     65 import static androidx.test.InstrumentationRegistry.getInstrumentation;
     66 
     67 import static org.hamcrest.Matchers.lessThan;
     68 import static org.junit.Assert.assertEquals;
     69 import static org.junit.Assert.assertNotEquals;
     70 import static org.junit.Assert.assertNotNull;
     71 import static org.junit.Assert.assertThat;
     72 import static org.junit.Assert.assertTrue;
     73 import static org.junit.Assert.fail;
     74 import static org.junit.Assume.assumeTrue;
     75 
     76 import android.content.ComponentName;
     77 import android.content.Context;
     78 import android.content.res.Configuration;
     79 import android.database.ContentObserver;
     80 import android.graphics.Rect;
     81 import android.os.Handler;
     82 import android.os.Looper;
     83 import android.platform.test.annotations.Presubmit;
     84 import android.provider.Settings;
     85 import android.server.wm.ActivityManagerState.ActivityStack;
     86 import android.server.wm.ActivityManagerState.ActivityTask;
     87 import android.server.wm.CommandSession.ActivityCallback;
     88 import android.server.wm.CommandSession.SizeInfo;
     89 import android.server.wm.TestJournalProvider.TestJournalContainer;
     90 import android.server.wm.WindowManagerState.WindowStack;
     91 import android.server.wm.settings.SettingsSession;
     92 import android.util.Log;
     93 import android.util.Size;
     94 
     95 import androidx.test.filters.FlakyTest;
     96 
     97 import com.android.compatibility.common.util.AppOpsUtils;
     98 import com.android.compatibility.common.util.SystemUtil;
     99 
    100 import org.junit.Before;
    101 import org.junit.Ignore;
    102 import org.junit.Test;
    103 
    104 import java.io.IOException;
    105 import java.util.List;
    106 import java.util.concurrent.CountDownLatch;
    107 import java.util.concurrent.TimeUnit;
    108 
    109 /**
    110  * Build/Install/Run:
    111  * atest CtsWindowManagerDeviceTestCases:PinnedStackTests
    112  */
    113 @Presubmit
    114 @FlakyTest(bugId = 71792368)
    115 public class PinnedStackTests extends ActivityManagerTestBase {
    116     private static final String TAG = PinnedStackTests.class.getSimpleName();
    117 
    118     private static final String APP_OPS_OP_ENTER_PICTURE_IN_PICTURE = "PICTURE_IN_PICTURE";
    119     private static final int APP_OPS_MODE_ALLOWED = 0;
    120     private static final int APP_OPS_MODE_IGNORED = 1;
    121     private static final int APP_OPS_MODE_ERRORED = 2;
    122 
    123     private static final int ROTATION_0 = 0;
    124     private static final int ROTATION_90 = 1;
    125     private static final int ROTATION_180 = 2;
    126     private static final int ROTATION_270 = 3;
    127 
    128     // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
    129     private static final int ORIENTATION_LANDSCAPE = 0;
    130     // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
    131     private static final int ORIENTATION_PORTRAIT = 1;
    132 
    133     private static final float FLOAT_COMPARE_EPSILON = 0.005f;
    134 
    135     // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio
    136     private static final int MIN_ASPECT_RATIO_NUMERATOR = 100;
    137     private static final int MIN_ASPECT_RATIO_DENOMINATOR = 239;
    138     private static final int BELOW_MIN_ASPECT_RATIO_DENOMINATOR = MIN_ASPECT_RATIO_DENOMINATOR + 1;
    139     // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio
    140     private static final int MAX_ASPECT_RATIO_NUMERATOR = 239;
    141     private static final int MAX_ASPECT_RATIO_DENOMINATOR = 100;
    142     private static final int ABOVE_MAX_ASPECT_RATIO_NUMERATOR = MAX_ASPECT_RATIO_NUMERATOR + 1;
    143 
    144     @Before
    145     @Override
    146     public void setUp() throws Exception {
    147         super.setUp();
    148         assumeTrue(supportsPip());
    149     }
    150 
    151     @Test
    152     public void testMinimumDeviceSize() throws Exception {
    153         mAmWmState.assertDeviceDefaultDisplaySize(
    154                 "Devices supporting picture-in-picture must be larger than the default minimum"
    155                         + " task size");
    156     }
    157 
    158     @Test
    159     public void testEnterPictureInPictureMode() throws Exception {
    160         pinnedStackTester(getAmStartCmd(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true"),
    161                 PIP_ACTIVITY, PIP_ACTIVITY, false /* moveTopToPinnedStack */,
    162                 false /* isFocusable */);
    163     }
    164 
    165     @Test
    166     public void testMoveTopActivityToPinnedStack() throws Exception {
    167         pinnedStackTester(getAmStartCmd(PIP_ACTIVITY), PIP_ACTIVITY, PIP_ACTIVITY,
    168                 true /* moveTopToPinnedStack */, false /* isFocusable */);
    169     }
    170 
    171     // This test is black-listed in cts-known-failures.xml (b/35314835).
    172     @Ignore
    173     @Test
    174     public void testAlwaysFocusablePipActivity() throws Exception {
    175         pinnedStackTester(getAmStartCmd(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
    176                 ALWAYS_FOCUSABLE_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
    177                 false /* moveTopToPinnedStack */, true /* isFocusable */);
    178     }
    179 
    180     // This test is black-listed in cts-known-failures.xml (b/35314835).
    181     @Ignore
    182     @Test
    183     public void testLaunchIntoPinnedStack() throws Exception {
    184         pinnedStackTester(getAmStartCmd(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY),
    185                 LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY, ALWAYS_FOCUSABLE_PIP_ACTIVITY,
    186                 false /* moveTopToPinnedStack */, true /* isFocusable */);
    187     }
    188 
    189     @Test
    190     public void testNonTappablePipActivity() throws Exception {
    191         // Launch the tap-to-finish activity at a specific place
    192         launchActivity(PIP_ACTIVITY,
    193                 EXTRA_ENTER_PIP, "true",
    194                 EXTRA_TAP_TO_FINISH, "true");
    195         // Wait for animation complete since we are tapping on specific bounds
    196         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    197         assertPinnedStackExists();
    198 
    199         // Tap the screen at a known location in the pinned stack bounds, and ensure that it is
    200         // not passed down to the top task
    201         tapToFinishPip();
    202         mAmWmState.computeState(false /* compareTaskAndStackBounds */,
    203                 new WaitForValidActivityState(PIP_ACTIVITY));
    204         mAmWmState.assertVisibility(PIP_ACTIVITY, true);
    205     }
    206 
    207     @Test
    208     public void testPinnedStackDefaultBounds() throws Exception {
    209         // Launch a PIP activity
    210         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    211         // Wait for animation complete since we are comparing bounds
    212         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    213 
    214         try (final RotationSession rotationSession = new RotationSession()) {
    215             rotationSession.set(ROTATION_0);
    216             mAmWmState.waitForWithWmState((wmState1) -> {
    217                 Rect db = wmState1.getDefaultPinnedStackBounds();
    218                 Rect sb = wmState1.getStableBounds();
    219                 return (db.width() > 0 && db.height() > 0) &&
    220                         (sb.contains(db));
    221             }, "Waiting for valid bounds..");
    222             WindowManagerState wmState = mAmWmState.getWmState();
    223             wmState.computeState();
    224             Rect defaultPipBounds = wmState.getDefaultPinnedStackBounds();
    225             Rect stableBounds = wmState.getStableBounds();
    226             assertTrue(defaultPipBounds.width() > 0 && defaultPipBounds.height() > 0);
    227             assertTrue(stableBounds.contains(defaultPipBounds));
    228 
    229             rotationSession.set(ROTATION_90);
    230             mAmWmState.waitForWithWmState((wmState1) -> {
    231                 Rect db = wmState1.getDefaultPinnedStackBounds();
    232                 Rect sb = wmState1.getStableBounds();
    233                 return (db.width() > 0 && db.height() > 0) &&
    234                         (sb.contains(db));
    235             }, "Waiting for valid bounds...");
    236             wmState = mAmWmState.getWmState();
    237             wmState.computeState();
    238             defaultPipBounds = wmState.getDefaultPinnedStackBounds();
    239             stableBounds = wmState.getStableBounds();
    240             assertTrue(defaultPipBounds.width() > 0 && defaultPipBounds.height() > 0);
    241             assertTrue(stableBounds.contains(defaultPipBounds));
    242         }
    243     }
    244 
    245     @Test
    246     public void testPinnedStackMovementBounds() throws Exception {
    247         // Launch a PIP activity
    248         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    249         // Wait for animation complete since we are comparing bounds
    250         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    251 
    252         try (final RotationSession rotationSession = new RotationSession()) {
    253             rotationSession.set(ROTATION_0);
    254             mAmWmState.waitForWithWmState((wmState1) -> {
    255                 Rect db = wmState1.getPinnedStackMovementBounds();
    256                 Rect sb = wmState1.getStableBounds();
    257                 return (db.width() > 0 && db.height() > 0) &&
    258                         (sb.contains(db));
    259             }, "Waiting for valid bounds...");
    260             WindowManagerState wmState = mAmWmState.getWmState();
    261             wmState.computeState();
    262             Rect pipMovementBounds = wmState.getPinnedStackMovementBounds();
    263             Rect stableBounds = wmState.getStableBounds();
    264             assertTrue(pipMovementBounds.width() > 0 && pipMovementBounds.height() > 0);
    265             assertTrue(stableBounds.contains(pipMovementBounds));
    266 
    267             rotationSession.set(ROTATION_90);
    268             mAmWmState.waitForWithWmState((wmState1) -> {
    269                 Rect db = wmState1.getPinnedStackMovementBounds();
    270                 Rect sb = wmState1.getStableBounds();
    271                 return (db.width() > 0 && db.height() > 0) &&
    272                         (sb.contains(db));
    273             }, "Waiting for valid bounds...");
    274             wmState = mAmWmState.getWmState();
    275             wmState.computeState();
    276             pipMovementBounds = wmState.getPinnedStackMovementBounds();
    277             stableBounds = wmState.getStableBounds();
    278             assertTrue(pipMovementBounds.width() > 0 && pipMovementBounds.height() > 0);
    279             assertTrue(stableBounds.contains(pipMovementBounds));
    280         }
    281     }
    282 
    283     @Test
    284     @FlakyTest // TODO: Reintroduce to presubmit once b/71508234 is resolved.
    285     public void testPinnedStackOutOfBoundsInsetsNonNegative() throws Exception {
    286         final WindowManagerState wmState = mAmWmState.getWmState();
    287 
    288         // Launch an activity into the pinned stack
    289         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true",
    290                 EXTRA_TAP_TO_FINISH, "true");
    291         // Wait for animation complete since we are comparing bounds
    292         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    293 
    294         // Get the display dimensions
    295         WindowManagerState.WindowState windowState = getWindowState(PIP_ACTIVITY);
    296         WindowManagerState.Display display = wmState.getDisplay(windowState.getDisplayId());
    297         Rect displayRect = display.getDisplayRect();
    298 
    299         // Move the pinned stack offscreen
    300         final int stackId = getPinnedStack().mStackId;
    301         final int top = 0;
    302         final int left = displayRect.width() - 200;
    303         resizeStack(stackId, left, top, left + 500, top + 500);
    304 
    305         // Ensure that the surface insets are not negative
    306         windowState = getWindowState(PIP_ACTIVITY);
    307         Rect contentInsets = windowState.getContentInsets();
    308         if (contentInsets != null) {
    309             assertTrue(contentInsets.left >= 0 && contentInsets.top >= 0
    310                     && contentInsets.width() >= 0 && contentInsets.height() >= 0);
    311         }
    312     }
    313 
    314     @Test
    315     public void testPinnedStackInBoundsAfterRotation() throws Exception {
    316         // Launch an activity into the pinned stack
    317         launchActivity(PIP_ACTIVITY,
    318                 EXTRA_ENTER_PIP, "true",
    319                 EXTRA_TAP_TO_FINISH, "true");
    320         // Wait for animation complete since we are comparing bounds
    321         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    322 
    323         // Ensure that the PIP stack is fully visible in each orientation
    324         try (final RotationSession rotationSession = new RotationSession()) {
    325             rotationSession.set(ROTATION_0);
    326             assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
    327             rotationSession.set(ROTATION_90);
    328             assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
    329             rotationSession.set(ROTATION_180);
    330             assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
    331             rotationSession.set(ROTATION_270);
    332             assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
    333         }
    334     }
    335 
    336     @Test
    337     public void testEnterPipToOtherOrientation() throws Exception {
    338         // Launch a portrait only app on the fullscreen stack
    339         launchActivity(TEST_ACTIVITY,
    340                 EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT));
    341         // Launch the PiP activity fixed as landscape
    342         launchActivity(PIP_ACTIVITY,
    343                 EXTRA_PIP_ORIENTATION, String.valueOf(ORIENTATION_LANDSCAPE));
    344         // Enter PiP, and assert that the PiP is within bounds now that the device is back in
    345         // portrait
    346         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
    347         // Wait for animation complete since we are comparing bounds
    348         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    349         assertPinnedStackExists();
    350         assertPinnedStackActivityIsInDisplayBounds(PIP_ACTIVITY);
    351     }
    352 
    353     @Test
    354     public void testEnterPipAspectRatioMin() throws Exception {
    355         testEnterPipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
    356     }
    357 
    358     @Test
    359     public void testEnterPipAspectRatioMax() throws Exception {
    360         testEnterPipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
    361     }
    362 
    363     private void testEnterPipAspectRatio(int num, int denom) throws Exception {
    364         launchActivity(PIP_ACTIVITY,
    365                 EXTRA_ENTER_PIP, "true",
    366                 EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
    367                 EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
    368         // Wait for animation complete since we are comparing aspect ratio
    369         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    370         assertPinnedStackExists();
    371 
    372         // Assert that we have entered PIP and that the aspect ratio is correct
    373         Rect pinnedStackBounds = getPinnedStackBounds();
    374         assertFloatEquals((float) pinnedStackBounds.width() / pinnedStackBounds.height(),
    375                 (float) num / denom);
    376     }
    377 
    378     @Test
    379     public void testResizePipAspectRatioMin() throws Exception {
    380         testResizePipAspectRatio(MIN_ASPECT_RATIO_NUMERATOR, MIN_ASPECT_RATIO_DENOMINATOR);
    381     }
    382 
    383     @Test
    384     public void testResizePipAspectRatioMax() throws Exception {
    385         testResizePipAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
    386     }
    387 
    388     private void testResizePipAspectRatio(int num, int denom) throws Exception {
    389         launchActivity(PIP_ACTIVITY,
    390                 EXTRA_ENTER_PIP, "true",
    391                 EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
    392                 EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
    393         // Wait for animation complete since we are comparing aspect ratio
    394         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    395         assertPinnedStackExists();
    396         waitForValidAspectRatio(num, denom);
    397         Rect bounds = getPinnedStackBounds();
    398         assertFloatEquals((float) bounds.width() / bounds.height(), (float) num / denom);
    399     }
    400 
    401     @Test
    402     public void testEnterPipExtremeAspectRatioMin() throws Exception {
    403         testEnterPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
    404                 BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
    405     }
    406 
    407     @Test
    408     public void testEnterPipExtremeAspectRatioMax() throws Exception {
    409         testEnterPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
    410                 MAX_ASPECT_RATIO_DENOMINATOR);
    411     }
    412 
    413     private void testEnterPipExtremeAspectRatio(int num, int denom) throws Exception {
    414         // Assert that we could not create a pinned stack with an extreme aspect ratio
    415         launchActivity(PIP_ACTIVITY,
    416                 EXTRA_ENTER_PIP, "true",
    417                 EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
    418                 EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
    419         assertPinnedStackDoesNotExist();
    420     }
    421 
    422     @Test
    423     public void testSetPipExtremeAspectRatioMin() throws Exception {
    424         testSetPipExtremeAspectRatio(MIN_ASPECT_RATIO_NUMERATOR,
    425                 BELOW_MIN_ASPECT_RATIO_DENOMINATOR);
    426     }
    427 
    428     @Test
    429     public void testSetPipExtremeAspectRatioMax() throws Exception {
    430         testSetPipExtremeAspectRatio(ABOVE_MAX_ASPECT_RATIO_NUMERATOR,
    431                 MAX_ASPECT_RATIO_DENOMINATOR);
    432     }
    433 
    434     private void testSetPipExtremeAspectRatio(int num, int denom) throws Exception {
    435         // Try to resize the a normal pinned stack to an extreme aspect ratio and ensure that
    436         // fails (the aspect ratio remains the same)
    437         launchActivity(PIP_ACTIVITY,
    438                 EXTRA_ENTER_PIP, "true",
    439                 EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
    440                         Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
    441                 EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR,
    442                         Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR),
    443                 EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(num),
    444                 EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(denom));
    445         // Wait for animation complete since we are comparing aspect ratio
    446         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    447         assertPinnedStackExists();
    448         Rect pinnedStackBounds = getPinnedStackBounds();
    449         assertFloatEquals((float) pinnedStackBounds.width() / pinnedStackBounds.height(),
    450                 (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
    451     }
    452 
    453     @Test
    454     public void testDisallowPipLaunchFromStoppedActivity() throws Exception {
    455         // Launch the bottom pip activity which will launch a new activity on top and attempt to
    456         // enter pip when it is stopped
    457         launchActivity(PIP_ON_STOP_ACTIVITY);
    458 
    459         // Wait for the bottom pip activity to be stopped
    460         mAmWmState.waitForActivityState(PIP_ON_STOP_ACTIVITY, STATE_STOPPED);
    461 
    462         // Assert that there is no pinned stack (that enterPictureInPicture() failed)
    463         assertPinnedStackDoesNotExist();
    464     }
    465 
    466     @Test
    467     public void testAutoEnterPictureInPicture() throws Exception {
    468         // Launch a test activity so that we're not over home
    469         launchActivity(TEST_ACTIVITY);
    470 
    471         // Launch the PIP activity on pause
    472         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
    473         assertPinnedStackDoesNotExist();
    474 
    475         // Go home and ensure that there is a pinned stack
    476         launchHomeActivity();
    477         waitForEnterPip(PIP_ACTIVITY);
    478         assertPinnedStackExists();
    479     }
    480 
    481     @Test
    482     public void testAutoEnterPictureInPictureLaunchActivity() throws Exception {
    483         // Launch a test activity so that we're not over home
    484         launchActivity(TEST_ACTIVITY);
    485 
    486         // Launch the PIP activity on pause, and have it start another activity on
    487         // top of itself.  Wait for the new activity to be visible and ensure that the pinned stack
    488         // was not created in the process
    489         launchActivity(PIP_ACTIVITY,
    490                 EXTRA_ENTER_PIP_ON_PAUSE, "true",
    491                 EXTRA_START_ACTIVITY, getActivityName(NON_RESIZEABLE_ACTIVITY));
    492         mAmWmState.computeState(false /* compareTaskAndStackBounds */,
    493                 new WaitForValidActivityState(NON_RESIZEABLE_ACTIVITY));
    494         assertPinnedStackDoesNotExist();
    495 
    496         // Go home while the pip activity is open and ensure the previous activity is not PIPed
    497         launchHomeActivity();
    498         assertPinnedStackDoesNotExist();
    499     }
    500 
    501     @Test
    502     public void testAutoEnterPictureInPictureFinish() throws Exception {
    503         // Launch a test activity so that we're not over home
    504         launchActivity(TEST_ACTIVITY);
    505 
    506         // Launch the PIP activity on pause, and set it to finish itself after
    507         // some period.  Wait for the previous activity to be visible, and ensure that the pinned
    508         // stack was not created in the process
    509         launchActivity(PIP_ACTIVITY,
    510                 EXTRA_ENTER_PIP_ON_PAUSE, "true",
    511                 EXTRA_FINISH_SELF_ON_RESUME, "true");
    512         assertPinnedStackDoesNotExist();
    513     }
    514 
    515     @Test
    516     public void testAutoEnterPictureInPictureAspectRatio() throws Exception {
    517         // Launch the PIP activity on pause, and set the aspect ratio
    518         launchActivity(PIP_ACTIVITY,
    519                 EXTRA_ENTER_PIP_ON_PAUSE, "true",
    520                 EXTRA_SET_ASPECT_RATIO_NUMERATOR, Integer.toString(MAX_ASPECT_RATIO_NUMERATOR),
    521                 EXTRA_SET_ASPECT_RATIO_DENOMINATOR, Integer.toString(MAX_ASPECT_RATIO_DENOMINATOR));
    522 
    523         // Go home while the pip activity is open to trigger auto-PIP
    524         launchHomeActivity();
    525         // Wait for animation complete since we are comparing aspect ratio
    526         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    527         assertPinnedStackExists();
    528 
    529         waitForValidAspectRatio(MAX_ASPECT_RATIO_NUMERATOR, MAX_ASPECT_RATIO_DENOMINATOR);
    530         Rect bounds = getPinnedStackBounds();
    531         assertFloatEquals((float) bounds.width() / bounds.height(),
    532                 (float) MAX_ASPECT_RATIO_NUMERATOR / MAX_ASPECT_RATIO_DENOMINATOR);
    533     }
    534 
    535     @Test
    536     public void testAutoEnterPictureInPictureOverPip() throws Exception {
    537         // Launch another PIP activity
    538         launchActivity(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
    539         waitForEnterPip(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
    540         assertPinnedStackExists();
    541 
    542         // Launch the PIP activity on pause
    543         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
    544 
    545         // Go home while the PIP activity is open to try to trigger auto-enter PIP
    546         launchHomeActivity();
    547         assertPinnedStackExists();
    548 
    549         // Ensure that auto-enter pip failed and that the resumed activity in the pinned stack is
    550         // still the first activity
    551         final ActivityStack pinnedStack = getPinnedStack();
    552         assertEquals(1, pinnedStack.getTasks().size());
    553         assertEquals(getActivityName(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
    554                 pinnedStack.getTasks().get(0).mRealActivity);
    555     }
    556 
    557     @Test
    558     public void testDisallowMultipleTasksInPinnedStack() throws Exception {
    559         // Launch a test activity so that we have multiple fullscreen tasks
    560         launchActivity(TEST_ACTIVITY);
    561 
    562         // Launch first PIP activity
    563         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    564         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    565 
    566         // Launch second PIP activity
    567         launchActivity(PIP_ACTIVITY2, EXTRA_ENTER_PIP, "true");
    568 
    569         final ActivityStack pinnedStack = getPinnedStack();
    570         assertEquals(1, pinnedStack.getTasks().size());
    571         assertTrue(mAmWmState.getAmState().containsActivityInWindowingMode(
    572                 PIP_ACTIVITY2, WINDOWING_MODE_PINNED));
    573         assertTrue(mAmWmState.getAmState().containsActivityInWindowingMode(
    574                 PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN));
    575     }
    576 
    577     @Test
    578     public void testPipUnPipOverHome() throws Exception {
    579         // Go home
    580         launchHomeActivity();
    581         // Launch an auto pip activity
    582         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    583         waitForEnterPip(PIP_ACTIVITY);
    584         assertPinnedStackExists();
    585 
    586         // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
    587         launchActivity(PIP_ACTIVITY);
    588         waitForExitPipToFullscreen(PIP_ACTIVITY);
    589         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
    590         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    591         mAmWmState.assertHomeActivityVisible(true);
    592     }
    593 
    594     @Test
    595     public void testPipUnPipOverApp() throws Exception {
    596         // Launch a test activity so that we're not over home
    597         launchActivity(TEST_ACTIVITY);
    598 
    599         // Launch an auto pip activity
    600         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    601         waitForEnterPip(PIP_ACTIVITY);
    602         assertPinnedStackExists();
    603 
    604         // Relaunch the activity to fullscreen to trigger the activity to exit and re-enter pip
    605         launchActivity(PIP_ACTIVITY);
    606         waitForExitPipToFullscreen(PIP_ACTIVITY);
    607         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
    608         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    609         mAmWmState.assertVisibility(TEST_ACTIVITY, true);
    610     }
    611 
    612     @Test
    613     public void testRemovePipWithNoFullscreenStack() throws Exception {
    614         // Launch a pip activity
    615         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    616         waitForEnterPip(PIP_ACTIVITY);
    617         assertPinnedStackExists();
    618 
    619         // Remove the stack and ensure that the task is now in the fullscreen stack (when no
    620         // fullscreen stack existed before)
    621         removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
    622         assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
    623                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
    624     }
    625 
    626     @Test
    627     public void testRemovePipWithVisibleFullscreenStack() throws Exception {
    628         // Launch a fullscreen activity, and a pip activity over that
    629         launchActivity(TEST_ACTIVITY);
    630         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    631         waitForEnterPip(PIP_ACTIVITY);
    632         assertPinnedStackExists();
    633 
    634         // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
    635         // top fullscreen activity
    636         removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
    637         assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
    638                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
    639     }
    640 
    641     @FlakyTest(bugId = 70746098)
    642     @Test
    643     public void testRemovePipWithHiddenFullscreenStack() throws Exception {
    644         // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
    645         // launch a pip activity over home
    646         launchActivity(TEST_ACTIVITY);
    647         launchHomeActivity();
    648         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    649         waitForEnterPip(PIP_ACTIVITY);
    650         assertPinnedStackExists();
    651 
    652         // Remove the stack and ensure that the task is placed on top of the hidden fullscreen
    653         // stack, but that the home stack is still focused
    654         removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
    655         assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
    656                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
    657     }
    658 
    659     @Test
    660     public void testMovePipToBackWithNoFullscreenStack() throws Exception {
    661         // Start with a clean slate, remove all the stacks but home
    662         removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
    663 
    664         // Launch a pip activity
    665         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    666         waitForEnterPip(PIP_ACTIVITY);
    667         assertPinnedStackExists();
    668 
    669         // Remove the stack and ensure that the task is now in the fullscreen stack (when no
    670         // fullscreen stack existed before)
    671         mBroadcastActionTrigger.doAction(ACTION_MOVE_TO_BACK);
    672         assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
    673                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
    674     }
    675 
    676     @FlakyTest(bugId = 70906499)
    677     @Test
    678     public void testMovePipToBackWithVisibleFullscreenStack() throws Exception {
    679         // Launch a fullscreen activity, and a pip activity over that
    680         launchActivity(TEST_ACTIVITY);
    681         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    682         waitForEnterPip(PIP_ACTIVITY);
    683         assertPinnedStackExists();
    684 
    685         // Remove the stack and ensure that the task is placed in the fullscreen stack, behind the
    686         // top fullscreen activity
    687         mBroadcastActionTrigger.doAction(ACTION_MOVE_TO_BACK);
    688         assertPinnedStackStateOnMoveToFullscreen(PIP_ACTIVITY,
    689                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
    690     }
    691 
    692     @FlakyTest(bugId = 70906499)
    693     @Test
    694     public void testMovePipToBackWithHiddenFullscreenStack() throws Exception {
    695         // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
    696         // launch a pip activity over home
    697         launchActivity(TEST_ACTIVITY);
    698         launchHomeActivity();
    699         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    700         waitForEnterPip(PIP_ACTIVITY);
    701         assertPinnedStackExists();
    702 
    703         // Remove the stack and ensure that the task is placed on top of the hidden fullscreen
    704         // stack, but that the home stack is still focused
    705         mBroadcastActionTrigger.doAction(ACTION_MOVE_TO_BACK);
    706         assertPinnedStackStateOnMoveToFullscreen(
    707                 PIP_ACTIVITY, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
    708     }
    709 
    710     @Test
    711     public void testPinnedStackAlwaysOnTop() throws Exception {
    712         // Launch activity into pinned stack and assert it's on top.
    713         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    714         waitForEnterPip(PIP_ACTIVITY);
    715         assertPinnedStackExists();
    716         assertPinnedStackIsOnTop();
    717 
    718         // Launch another activity in fullscreen stack and check that pinned stack is still on top.
    719         launchActivity(TEST_ACTIVITY);
    720         assertPinnedStackExists();
    721         assertPinnedStackIsOnTop();
    722 
    723         // Launch home and check that pinned stack is still on top.
    724         launchHomeActivity();
    725         assertPinnedStackExists();
    726         assertPinnedStackIsOnTop();
    727     }
    728 
    729     @Test
    730     public void testAppOpsDenyPipOnPause() throws Exception {
    731         try (final AppOpsSession appOpsSession = new AppOpsSession(PIP_ACTIVITY)) {
    732             // Disable enter-pip and try to enter pip
    733             appOpsSession.setOpToMode(APP_OPS_OP_ENTER_PICTURE_IN_PICTURE, APP_OPS_MODE_IGNORED);
    734 
    735             // Launch the PIP activity on pause
    736             launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    737             assertPinnedStackDoesNotExist();
    738 
    739             // Go home and ensure that there is no pinned stack
    740             launchHomeActivity();
    741             assertPinnedStackDoesNotExist();
    742         }
    743     }
    744 
    745     @Test
    746     public void testEnterPipFromTaskWithMultipleActivities() throws Exception {
    747         // Try to enter picture-in-picture from an activity that has more than one activity in the
    748         // task and ensure that it works
    749         launchActivity(LAUNCH_ENTER_PIP_ACTIVITY);
    750         waitForEnterPip(PIP_ACTIVITY);
    751         assertPinnedStackExists();
    752     }
    753 
    754     @Test
    755     public void testEnterPipWithResumeWhilePausingActivityNoStop() throws Exception {
    756         /*
    757          * Launch the resumeWhilePausing activity and ensure that the PiP activity did not get
    758          * stopped and actually went into the pinned stack.
    759          *
    760          * Note that this is a workaround because to trigger the path that we want to happen in
    761          * activity manager, we need to add the leaving activity to the stopping state, which only
    762          * happens when a hidden stack is brought forward. Normally, this happens when you go home,
    763          * but since we can't launch into the home stack directly, we have a workaround.
    764          *
    765          * 1) Launch an activity in a new dynamic stack
    766          * 2) Start the PiP activity that will enter picture-in-picture when paused in the
    767          *    fullscreen stack
    768          * 3) Bring the activity in the dynamic stack forward to trigger PiP
    769          */
    770         launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
    771         // Launch an activity that will enter PiP when it is paused with a delay that is long enough
    772         // for the next resumeWhilePausing activity to finish resuming, but slow enough to not
    773         // trigger the current system pause timeout (currently 500ms)
    774         launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
    775                 EXTRA_ENTER_PIP_ON_PAUSE, "true",
    776                 EXTRA_ON_PAUSE_DELAY, "350",
    777                 EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
    778         launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
    779         assertPinnedStackExists();
    780     }
    781 
    782     @Test
    783     public void testDisallowEnterPipActivityLocked() throws Exception {
    784         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP_ON_PAUSE, "true");
    785         ActivityTask task = mAmWmState.getAmState().getStandardStackByWindowingMode(
    786                 WINDOWING_MODE_FULLSCREEN).getTopTask();
    787 
    788         // Lock the task and ensure that we can't enter picture-in-picture both explicitly and
    789         // when paused
    790         SystemUtil.runWithShellPermissionIdentity(() -> {
    791             try {
    792                 mAtm.startSystemLockTaskMode(task.mTaskId);
    793                 mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
    794                 waitForEnterPip(PIP_ACTIVITY);
    795                 assertPinnedStackDoesNotExist();
    796                 launchHomeActivity();
    797                 assertPinnedStackDoesNotExist();
    798             } finally {
    799                 mAtm.stopSystemLockTaskMode();
    800             }
    801         });
    802     }
    803 
    804     @FlakyTest(bugId = 70328524)
    805     @Test
    806     public void testConfigurationChangeOrderDuringTransition() throws Exception {
    807         // Launch a PiP activity and ensure configuration change only happened once, and that the
    808         // configuration change happened after the picture-in-picture and multi-window callbacks
    809         launchActivity(PIP_ACTIVITY);
    810         separateTestJournal();
    811         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
    812         waitForEnterPip(PIP_ACTIVITY);
    813         assertPinnedStackExists();
    814         waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
    815         assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY);
    816 
    817         // Trigger it to go back to fullscreen and ensure that only triggered one configuration
    818         // change as well
    819         separateTestJournal();
    820         launchActivity(PIP_ACTIVITY);
    821         waitForValidPictureInPictureCallbacks(PIP_ACTIVITY);
    822         assertValidPictureInPictureCallbackOrder(PIP_ACTIVITY);
    823     }
    824 
    825     /** Helper class to save, set, and restore transition_animation_scale preferences. */
    826     private static class TransitionAnimationScaleSession extends SettingsSession<Float> {
    827         TransitionAnimationScaleSession() {
    828             super(Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE),
    829                     Settings.Global::getFloat,
    830                     Settings.Global::putFloat);
    831         }
    832 
    833         @Override
    834         public void close() throws Exception {
    835             // Wait for the restored setting to apply before we continue on with the next test
    836             final CountDownLatch waitLock = new CountDownLatch(1);
    837             final Context context = getInstrumentation().getTargetContext();
    838             context.getContentResolver().registerContentObserver(mUri, false,
    839                     new ContentObserver(new Handler(Looper.getMainLooper())) {
    840                         @Override
    841                         public void onChange(boolean selfChange) {
    842                             waitLock.countDown();
    843                         }
    844                     });
    845             super.close();
    846             if (!waitLock.await(2, TimeUnit.SECONDS)) {
    847                 Log.i(TAG, "TransitionAnimationScaleSession value not restored");
    848             }
    849         }
    850     }
    851 
    852     @Test
    853     public void testEnterPipInterruptedCallbacks() throws Exception {
    854         try (final TransitionAnimationScaleSession transitionAnimationScaleSession =
    855                 new TransitionAnimationScaleSession()) {
    856             // Slow down the transition animations for this test
    857             transitionAnimationScaleSession.set(20f);
    858 
    859             // Launch a PiP activity
    860             launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    861             // Wait until the PiP activity has moved into the pinned stack (happens before the
    862             // transition has started)
    863             waitForEnterPip(PIP_ACTIVITY);
    864             assertPinnedStackExists();
    865 
    866             // Relaunch the PiP activity back into fullscreen
    867             separateTestJournal();
    868             launchActivity(PIP_ACTIVITY);
    869             // Wait until the PiP activity is reparented into the fullscreen stack (happens after
    870             // the transition has finished)
    871             waitForExitPipToFullscreen(PIP_ACTIVITY);
    872 
    873             // Ensure that we get the callbacks indicating that PiP/MW mode was cancelled, but no
    874             // configuration change (since none was sent)
    875             final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(
    876                     PIP_ACTIVITY);
    877             assertEquals("onConfigurationChanged", 0,
    878                     lifecycleCounts.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED));
    879             assertEquals("onPictureInPictureModeChanged", 1,
    880                     lifecycleCounts.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
    881             assertEquals("onMultiWindowModeChanged", 1,
    882                     lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
    883         }
    884     }
    885 
    886     @Test
    887     public void testStopBeforeMultiWindowCallbacksOnDismiss() throws Exception {
    888         // Launch a PiP activity
    889         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    890         // Wait for animation complete so that system has reported pip mode change event to
    891         // client and the last reported pip mode has updated.
    892         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
    893         assertPinnedStackExists();
    894 
    895         // Dismiss it
    896         separateTestJournal();
    897         removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
    898         waitForExitPipToFullscreen(PIP_ACTIVITY);
    899 
    900         // Confirm that we get stop before the multi-window and picture-in-picture mode change
    901         // callbacks
    902         final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(PIP_ACTIVITY);
    903         assertEquals("onStop", 1, lifecycles.getCount(ActivityCallback.ON_STOP));
    904         assertEquals("onPictureInPictureModeChanged", 1,
    905                 lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
    906         assertEquals("onMultiWindowModeChanged", 1,
    907                 lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
    908         final int lastStopIndex = lifecycles.getLastIndex(ActivityCallback.ON_STOP);
    909         final int lastPipIndex = lifecycles.getLastIndex(
    910                 ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
    911         final int lastMwIndex = lifecycles.getLastIndex(
    912                 ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED);
    913         assertThat("onStop should be before onPictureInPictureModeChanged",
    914                 lastStopIndex, lessThan(lastPipIndex));
    915         assertThat("onPictureInPictureModeChanged should be before onMultiWindowModeChanged",
    916                 lastPipIndex, lessThan(lastMwIndex));
    917     }
    918 
    919     @Test
    920     public void testPreventSetAspectRatioWhileExpanding() throws Exception {
    921         // Launch the PiP activity
    922         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    923         waitForEnterPip(PIP_ACTIVITY);
    924 
    925         // Trigger it to go back to fullscreen and try to set the aspect ratio, and ensure that the
    926         // call to set the aspect ratio did not prevent the PiP from returning to fullscreen
    927         mBroadcastActionTrigger.expandPipWithAspectRatio("123456789", "100000000");
    928         waitForExitPipToFullscreen(PIP_ACTIVITY);
    929         assertPinnedStackDoesNotExist();
    930     }
    931 
    932     @Test
    933     public void testSetRequestedOrientationWhilePinned() throws Exception {
    934         // Launch the PiP activity fixed as portrait, and enter picture-in-picture
    935         launchActivity(PIP_ACTIVITY,
    936                 EXTRA_PIP_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT),
    937                 EXTRA_ENTER_PIP, "true");
    938         waitForEnterPip(PIP_ACTIVITY);
    939         assertPinnedStackExists();
    940 
    941         // Request that the orientation is set to landscape
    942         mBroadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE);
    943 
    944         // Launch the activity back into fullscreen and ensure that it is now in landscape
    945         launchActivity(PIP_ACTIVITY);
    946         waitForExitPipToFullscreen(PIP_ACTIVITY);
    947         assertPinnedStackDoesNotExist();
    948         assertEquals(ORIENTATION_LANDSCAPE, mAmWmState.getWmState().getLastOrientation());
    949     }
    950 
    951     @Test
    952     public void testWindowButtonEntersPip() throws Exception {
    953         assumeTrue(!mAmWmState.getAmState().isHomeRecentsComponent());
    954 
    955         // Launch the PiP activity trigger the window button, ensure that we have entered PiP
    956         launchActivity(PIP_ACTIVITY);
    957         pressWindowButton();
    958         waitForEnterPip(PIP_ACTIVITY);
    959         assertPinnedStackExists();
    960     }
    961 
    962     @Test
    963     public void testFinishPipActivityWithTaskOverlay() throws Exception {
    964         // Launch PiP activity
    965         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    966         waitForEnterPip(PIP_ACTIVITY);
    967         assertPinnedStackExists();
    968         int taskId = mAmWmState.getAmState().getStandardStackByWindowingMode(
    969                 WINDOWING_MODE_PINNED).getTopTask().mTaskId;
    970 
    971         // Ensure that we don't any any other overlays as a result of launching into PIP
    972         launchHomeActivity();
    973 
    974         // Launch task overlay activity into PiP activity task
    975         launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
    976 
    977         // Finish the PiP activity and ensure that there is no pinned stack
    978         mBroadcastActionTrigger.doAction(ACTION_FINISH);
    979         waitForPinnedStackRemoved();
    980         assertPinnedStackDoesNotExist();
    981     }
    982 
    983     @Test
    984     public void testNoResumeAfterTaskOverlayFinishes() throws Exception {
    985         // Launch PiP activity
    986         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
    987         waitForEnterPip(PIP_ACTIVITY);
    988         assertPinnedStackExists();
    989         ActivityStack stack = mAmWmState.getAmState().getStandardStackByWindowingMode(
    990                 WINDOWING_MODE_PINNED);
    991         int stackId = stack.mStackId;
    992         int taskId = stack.getTopTask().mTaskId;
    993 
    994         // Launch task overlay activity into PiP activity task
    995         launchPinnedActivityAsTaskOverlay(TRANSLUCENT_TEST_ACTIVITY, taskId);
    996 
    997         // Finish the task overlay activity while animating and ensure that the PiP activity never
    998         // got resumed.
    999         separateTestJournal();
   1000         SystemUtil.runWithShellPermissionIdentity(
   1001                 () -> mAtm.resizeStack(stackId, new Rect(20, 20, 500, 500), true /* animate */));
   1002         mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
   1003         mAmWmState.waitFor((amState, wmState) ->
   1004                         !amState.containsActivity(TRANSLUCENT_TEST_ACTIVITY),
   1005                 "Waiting for test activity to finish...");
   1006         final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(PIP_ACTIVITY);
   1007         assertEquals("onResume", 0, lifecycleCounts.getCount(ActivityCallback.ON_RESUME));
   1008         assertEquals("onPause", 0, lifecycleCounts.getCount(ActivityCallback.ON_PAUSE));
   1009     }
   1010 
   1011     @Test
   1012     public void testPinnedStackWithDockedStack() throws Exception {
   1013         assumeTrue(supportsSplitScreenMultiWindow());
   1014 
   1015         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
   1016         waitForEnterPip(PIP_ACTIVITY);
   1017         launchActivitiesInSplitScreen(
   1018                 getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
   1019                 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
   1020                         .setRandomData(true)
   1021                         .setMultipleTask(false)
   1022         );
   1023         mAmWmState.assertVisibility(PIP_ACTIVITY, true);
   1024         mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
   1025         mAmWmState.assertVisibility(TEST_ACTIVITY, true);
   1026 
   1027         // Launch the activities again to take focus and make sure nothing is hidden
   1028         launchActivitiesInSplitScreen(
   1029                 getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
   1030                 getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
   1031                         .setRandomData(true)
   1032                         .setMultipleTask(false)
   1033         );
   1034         mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
   1035         mAmWmState.assertVisibility(TEST_ACTIVITY, true);
   1036 
   1037         // Go to recents to make sure that fullscreen stack is invisible
   1038         // Some devices do not support recents or implement it differently (instead of using a
   1039         // separate stack id or as an activity), for those cases the visibility asserts will be
   1040         // ignored
   1041         pressAppSwitchButtonAndWaitForRecents();
   1042         mAmWmState.assertVisibility(LAUNCHING_ACTIVITY, true);
   1043         mAmWmState.assertVisibility(TEST_ACTIVITY, false);
   1044     }
   1045 
   1046     @Test
   1047     public void testLaunchTaskByComponentMatchMultipleTasks() throws Exception {
   1048         // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
   1049         // affinity
   1050         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
   1051         launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
   1052         assertPinnedStackExists();
   1053 
   1054         // Launch the root activity again...
   1055         int rootActivityTaskId = mAmWmState.getAmState().getTaskByActivity(
   1056                 TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId;
   1057         launchHomeActivity();
   1058         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
   1059 
   1060         // ...and ensure that the root activity task is found and reused, and that the pinned stack
   1061         // is unaffected
   1062         assertPinnedStackExists();
   1063         mAmWmState.assertFocusedActivity("Expected root activity focused",
   1064                 TEST_ACTIVITY_WITH_SAME_AFFINITY);
   1065         assertEquals(rootActivityTaskId, mAmWmState.getAmState().getTaskByActivity(
   1066                 TEST_ACTIVITY_WITH_SAME_AFFINITY).mTaskId);
   1067     }
   1068 
   1069     @Test
   1070     public void testLaunchTaskByAffinityMatchMultipleTasks() throws Exception {
   1071         // Launch a fullscreen activity which will launch a PiP activity in a new task with the same
   1072         // affinity, and also launch another activity in the same task, while finishing itself. As
   1073         // a result, the task will not have a component matching the same activity as what it was
   1074         // started with
   1075         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
   1076                 EXTRA_START_ACTIVITY, getActivityName(TEST_ACTIVITY),
   1077                 EXTRA_FINISH_SELF_ON_RESUME, "true");
   1078         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(TEST_ACTIVITY)
   1079                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
   1080                 .setActivityType(ACTIVITY_TYPE_STANDARD)
   1081                 .build());
   1082         launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
   1083         waitForEnterPip(PIP_ACTIVITY_WITH_SAME_AFFINITY);
   1084         assertPinnedStackExists();
   1085 
   1086         // Launch the root activity again...
   1087         int rootActivityTaskId = mAmWmState.getAmState().getTaskByActivity(
   1088                 TEST_ACTIVITY).mTaskId;
   1089         launchHomeActivity();
   1090         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
   1091 
   1092         // ...and ensure that even while matching purely by task affinity, the root activity task is
   1093         // found and reused, and that the pinned stack is unaffected
   1094         assertPinnedStackExists();
   1095         mAmWmState.assertFocusedActivity("Expected root activity focused", TEST_ACTIVITY);
   1096         assertEquals(rootActivityTaskId, mAmWmState.getAmState().getTaskByActivity(
   1097                 TEST_ACTIVITY).mTaskId);
   1098     }
   1099 
   1100     @Test
   1101     public void testLaunchTaskByAffinityMatchSingleTask() throws Exception {
   1102         // Launch an activity into the pinned stack with a fixed affinity
   1103         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY,
   1104                 EXTRA_ENTER_PIP, "true",
   1105                 EXTRA_START_ACTIVITY, getActivityName(PIP_ACTIVITY),
   1106                 EXTRA_FINISH_SELF_ON_RESUME, "true");
   1107         waitForEnterPip(TEST_ACTIVITY_WITH_SAME_AFFINITY);
   1108         assertPinnedStackExists();
   1109 
   1110         // Launch the root activity again, of the matching task and ensure that we expand to
   1111         // fullscreen
   1112         int activityTaskId = mAmWmState.getAmState().getTaskByActivity(PIP_ACTIVITY).mTaskId;
   1113         launchHomeActivity();
   1114         launchActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY);
   1115         waitForExitPipToFullscreen(TEST_ACTIVITY_WITH_SAME_AFFINITY);
   1116         assertPinnedStackDoesNotExist();
   1117         assertEquals(activityTaskId, mAmWmState.getAmState().getTaskByActivity(
   1118                 PIP_ACTIVITY).mTaskId);
   1119     }
   1120 
   1121     /** Test that reported display size corresponds to fullscreen after exiting PiP. */
   1122     @FlakyTest
   1123     @Test
   1124     public void testDisplayMetricsPinUnpin() throws Exception {
   1125         separateTestJournal();
   1126         launchActivity(TEST_ACTIVITY);
   1127         final int defaultWindowingMode = mAmWmState.getAmState()
   1128                 .getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
   1129         final SizeInfo initialSizes = getLastReportedSizesForActivity(TEST_ACTIVITY);
   1130         final Rect initialAppBounds = getAppBounds(TEST_ACTIVITY);
   1131         assertNotNull("Must report display dimensions", initialSizes);
   1132         assertNotNull("Must report app bounds", initialAppBounds);
   1133 
   1134         separateTestJournal();
   1135         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
   1136         // Wait for animation complete since we are comparing bounds
   1137         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
   1138         final SizeInfo pinnedSizes = getLastReportedSizesForActivity(PIP_ACTIVITY);
   1139         final Rect pinnedAppBounds = getAppBounds(PIP_ACTIVITY);
   1140         assertNotEquals("Reported display size when pinned must be different from default",
   1141                 initialSizes, pinnedSizes);
   1142         final Size initialAppSize = new Size(initialAppBounds.width(), initialAppBounds.height());
   1143         final Size pinnedAppSize = new Size(pinnedAppBounds.width(), pinnedAppBounds.height());
   1144         assertNotEquals("Reported app size when pinned must be different from default",
   1145                 initialAppSize, pinnedAppSize);
   1146 
   1147         separateTestJournal();
   1148         launchActivity(PIP_ACTIVITY, defaultWindowingMode);
   1149         final SizeInfo finalSizes = getLastReportedSizesForActivity(PIP_ACTIVITY);
   1150         final Rect finalAppBounds = getAppBounds(PIP_ACTIVITY);
   1151         final Size finalAppSize = new Size(finalAppBounds.width(), finalAppBounds.height());
   1152         assertEquals("Must report default size after exiting PiP", initialSizes, finalSizes);
   1153         assertEquals("Must report default app size after exiting PiP", initialAppSize,
   1154                 finalAppSize);
   1155     }
   1156 
   1157     @Test
   1158     public void testEnterPictureInPictureSavePosition() throws Exception {
   1159         // Ensure we have static shelf offset by running this test over a non-home activity
   1160         launchActivity(NO_RELAUNCH_ACTIVITY);
   1161         mAmWmState.waitForActivityState(mAmWmState.getAmState().getHomeActivityName(),
   1162                 STATE_STOPPED);
   1163 
   1164         // Launch PiP activity with auto-enter PiP, save the default position of the PiP
   1165         // (while the PiP is still animating sleep)
   1166         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
   1167         // Wait for animation complete since we are comparing bounds
   1168         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
   1169         assertPinnedStackExists();
   1170 
   1171         // Move the PiP to a new position on screen
   1172         final Rect initialBounds = new Rect();
   1173         final Rect offsetBounds = new Rect();
   1174         offsetPipWithinMovementBounds(100 /* offsetY */, initialBounds, offsetBounds);
   1175 
   1176         // Expand the PiP back to fullscreen and back into PiP and ensure that it is in the same
   1177         // position as before we expanded (and that the default bounds reflect that)
   1178         mBroadcastActionTrigger.doAction(ACTION_EXPAND_PIP);
   1179         waitForExitPipToFullscreen(PIP_ACTIVITY);
   1180         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
   1181         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
   1182         mAmWmState.computeState(true);
   1183         // Due to rounding in how we save and apply the snap fraction we may be a pixel off, so just
   1184         // account for that in this check
   1185         offsetBounds.inset(-1, -1);
   1186         assertTrue("Expected offsetBounds=" + offsetBounds + " to contain bounds="
   1187                 + getPinnedStackBounds(), offsetBounds.contains(getPinnedStackBounds()));
   1188 
   1189         // Expand the PiP, then launch an activity in a new task, and ensure that the PiP goes back
   1190         // to the default position (and not the saved position) the next time it is launched
   1191         mBroadcastActionTrigger.doAction(ACTION_EXPAND_PIP);
   1192         waitForExitPipToFullscreen(PIP_ACTIVITY);
   1193         launchActivity(TEST_ACTIVITY);
   1194         mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
   1195         mAmWmState.waitForActivityState(PIP_ACTIVITY, STATE_RESUMED);
   1196         mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
   1197         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
   1198         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
   1199         assertPinnedStackExists();
   1200         assertTrue("Expected initialBounds=" + initialBounds + " to equal bounds="
   1201                 + getPinnedStackBounds(), initialBounds.equals(getPinnedStackBounds()));
   1202     }
   1203 
   1204     @Test
   1205     @FlakyTest(bugId = 71792368)
   1206     public void testEnterPictureInPictureDiscardSavedPositionOnFinish() throws Exception {
   1207         // Ensure we have static shelf offset by running this test over a non-home activity
   1208         launchActivity(NO_RELAUNCH_ACTIVITY);
   1209         mAmWmState.waitForActivityState(mAmWmState.getAmState().getHomeActivityName(),
   1210                 STATE_STOPPED);
   1211 
   1212         // Launch PiP activity with auto-enter PiP, save the default position of the PiP
   1213         // (while the PiP is still animating sleep)
   1214         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
   1215         // Wait for animation complete since we are comparing bounds
   1216         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
   1217         assertPinnedStackExists();
   1218 
   1219         // Move the PiP to a new position on screen
   1220         final Rect initialBounds = new Rect();
   1221         final Rect offsetBounds = new Rect();
   1222         offsetPipWithinMovementBounds(100 /* offsetY */, initialBounds, offsetBounds);
   1223 
   1224         // Finish the activity
   1225         mBroadcastActionTrigger.doAction(ACTION_FINISH);
   1226         waitForPinnedStackRemoved();
   1227         assertPinnedStackDoesNotExist();
   1228 
   1229         // Ensure that starting the same PiP activity after it finished will go to the default
   1230         // bounds
   1231         launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
   1232         waitForEnterPipAnimationComplete(PIP_ACTIVITY);
   1233         assertPinnedStackExists();
   1234         assertTrue("Expected initialBounds=" + initialBounds + " to equal bounds="
   1235                 + getPinnedStackBounds(), initialBounds.equals(getPinnedStackBounds()));
   1236     }
   1237 
   1238     /**
   1239      * Offsets the PiP in a direction by {@param offsetY} such that it is still within the movement
   1240      * bounds.
   1241      */
   1242     private void offsetPipWithinMovementBounds(int offsetY, Rect initialBoundsOut,
   1243             Rect offsetBoundsOut) {
   1244         final ActivityStack stack = getPinnedStack();
   1245         final Rect displayRect = mAmWmState.getWmState().getDisplay(stack.mDisplayId)
   1246                 .getDisplayRect();
   1247         initialBoundsOut.set(getPinnedStackBounds());
   1248         offsetBoundsOut.set(initialBoundsOut);
   1249         if (initialBoundsOut.top < displayRect.centerY()) {
   1250             // If the default gravity is top-aligned, offset down instead of up
   1251             offsetBoundsOut.offset(0, offsetY);
   1252         } else {
   1253             offsetBoundsOut.offset(0, -offsetY);
   1254         }
   1255         resizeStack(stack.mStackId, offsetBoundsOut.left, offsetBoundsOut.top,
   1256                 offsetBoundsOut.right, offsetBoundsOut.bottom);
   1257     }
   1258 
   1259     /** Get app bounds in last applied configuration. */
   1260     private Rect getAppBounds(ComponentName activityName) {
   1261         final Configuration config = TestJournalContainer.get(activityName).extras
   1262                 .getParcelable(EXTRA_CONFIGURATION);
   1263         if (config != null) {
   1264             return config.windowConfiguration.getAppBounds();
   1265         }
   1266         return null;
   1267     }
   1268 
   1269     /**
   1270      * Called after the given {@param activityName} has been moved to the fullscreen stack. Ensures
   1271      * that the stack matching the {@param windowingMode} and {@param activityType} is focused, and
   1272      * checks the top and/or bottom tasks in the fullscreen stack if
   1273      * {@param expectTopTaskHasActivity} or {@param expectBottomTaskHasActivity} are set
   1274      * respectively.
   1275      */
   1276     private void assertPinnedStackStateOnMoveToFullscreen(ComponentName activityName,
   1277             int windowingMode, int activityType) {
   1278         mAmWmState.waitForFocusedStack(windowingMode, activityType);
   1279         mAmWmState.assertFocusedStack("Wrong focused stack", windowingMode, activityType);
   1280         waitAndAssertActivityState(activityName, STATE_STOPPED,
   1281                 "Activity should go to STOPPED");
   1282         assertTrue(mAmWmState.getAmState().containsActivityInWindowingMode(
   1283                 activityName, WINDOWING_MODE_FULLSCREEN));
   1284         assertPinnedStackDoesNotExist();
   1285     }
   1286 
   1287     /**
   1288      * Asserts that the pinned stack bounds does not intersect with the IME bounds.
   1289      */
   1290     private void assertPinnedStackDoesNotIntersectIME() {
   1291         // Ensure that the IME is visible
   1292         WindowManagerState wmState = mAmWmState.getWmState();
   1293         wmState.computeState();
   1294         WindowManagerState.WindowState imeWinState = wmState.getInputMethodWindowState();
   1295         assertTrue(imeWinState != null);
   1296 
   1297         // Ensure that the PIP movement is constrained by the display bounds intersecting the
   1298         // non-IME bounds
   1299         Rect imeContentFrame = imeWinState.getContentFrame();
   1300         Rect imeContentInsets = imeWinState.getGivenContentInsets();
   1301         Rect imeBounds = new Rect(imeContentFrame.left + imeContentInsets.left,
   1302                 imeContentFrame.top + imeContentInsets.top,
   1303                 imeContentFrame.right - imeContentInsets.width(),
   1304                 imeContentFrame.bottom - imeContentInsets.height());
   1305         wmState.computeState();
   1306         Rect pipMovementBounds = wmState.getPinnedStackMovementBounds();
   1307         assertTrue(!Rect.intersects(pipMovementBounds, imeBounds));
   1308     }
   1309 
   1310     /**
   1311      * Asserts that the pinned stack bounds is contained in the display bounds.
   1312      */
   1313     private void assertPinnedStackActivityIsInDisplayBounds(ComponentName activityName) {
   1314         final WindowManagerState.WindowState windowState = getWindowState(activityName);
   1315         final WindowManagerState.Display display = mAmWmState.getWmState().getDisplay(
   1316                 windowState.getDisplayId());
   1317         final Rect displayRect = display.getDisplayRect();
   1318         final Rect pinnedStackBounds = getPinnedStackBounds();
   1319         assertTrue(displayRect.contains(pinnedStackBounds));
   1320     }
   1321 
   1322     /**
   1323      * Asserts that the pinned stack exists.
   1324      */
   1325     private void assertPinnedStackExists() {
   1326         mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
   1327                 ACTIVITY_TYPE_STANDARD);
   1328     }
   1329 
   1330     /**
   1331      * Asserts that the pinned stack does not exist.
   1332      */
   1333     private void assertPinnedStackDoesNotExist() {
   1334         mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
   1335                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
   1336     }
   1337 
   1338     /**
   1339      * Asserts that the pinned stack is the front stack.
   1340      */
   1341     private void assertPinnedStackIsOnTop() {
   1342         mAmWmState.assertFrontStack("Pinned stack must always be on top.",
   1343                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
   1344     }
   1345 
   1346     /**
   1347      * Asserts that the activity received exactly one of each of the callbacks when entering and
   1348      * exiting picture-in-picture.
   1349      */
   1350     private void assertValidPictureInPictureCallbackOrder(ComponentName activityName) {
   1351         final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
   1352 
   1353         assertEquals(getActivityName(activityName) + " onConfigurationChanged()",
   1354                 1, lifecycles.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED));
   1355         assertEquals(getActivityName(activityName) + " onPictureInPictureModeChanged()",
   1356                 1, lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED));
   1357         assertEquals(getActivityName(activityName) + " onMultiWindowModeChanged",
   1358                 1, lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
   1359         final int lastPipIndex = lifecycles
   1360                 .getLastIndex(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED);
   1361         final int lastMwIndex = lifecycles
   1362                 .getLastIndex(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED);
   1363         final int lastConfigIndex = lifecycles
   1364                 .getLastIndex(ActivityCallback.ON_CONFIGURATION_CHANGED);
   1365         assertThat("onPictureInPictureModeChanged should be before onMultiWindowModeChanged",
   1366                 lastPipIndex, lessThan(lastMwIndex));
   1367         assertThat("onMultiWindowModeChanged should be before onConfigurationChanged",
   1368                 lastMwIndex, lessThan(lastConfigIndex));
   1369     }
   1370 
   1371     /**
   1372      * Waits until the given activity has entered picture-in-picture mode (allowing for the
   1373      * subsequent animation to start).
   1374      */
   1375     private void waitForEnterPip(ComponentName activityName) {
   1376         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
   1377                 .setWindowingMode(WINDOWING_MODE_PINNED)
   1378                 .setActivityType(ACTIVITY_TYPE_STANDARD)
   1379                 .build());
   1380     }
   1381 
   1382     /**
   1383      * Waits until the picture-in-picture animation has finished.
   1384      */
   1385     private void waitForEnterPipAnimationComplete(ComponentName activityName) {
   1386         waitForEnterPip(activityName);
   1387         mAmWmState.waitFor((amState, wmState) -> {
   1388                 WindowStack stack = wmState.getStandardStackByWindowingMode(WINDOWING_MODE_PINNED);
   1389                 return stack != null && !stack.mAnimatingBounds;
   1390             }, "Waiting for pinned stack bounds animation to finish");
   1391     }
   1392 
   1393     /**
   1394      * Waits until the pinned stack has been removed.
   1395      */
   1396     private void waitForPinnedStackRemoved() {
   1397         mAmWmState.waitFor((amState, wmState) -> {
   1398             return !amState.containsStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD)
   1399                     && !wmState.containsStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
   1400         }, "Waiting for pinned stack to be removed...");
   1401     }
   1402 
   1403     /**
   1404      * Waits until the picture-in-picture animation to fullscreen has finished.
   1405      */
   1406     private void waitForExitPipToFullscreen(ComponentName activityName) {
   1407         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
   1408                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
   1409                 .setActivityType(ACTIVITY_TYPE_STANDARD)
   1410                 .build());
   1411     }
   1412 
   1413     /**
   1414      * Waits until the expected picture-in-picture callbacks have been made.
   1415      */
   1416     private void waitForValidPictureInPictureCallbacks(ComponentName activityName) {
   1417         mAmWmState.waitFor((amState, wmState) -> {
   1418             final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
   1419             return lifecycles.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED) == 1
   1420                     && lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED) == 1
   1421                     && lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED) == 1;
   1422         }, "Waiting for picture-in-picture activity callbacks...");
   1423     }
   1424 
   1425     private void waitForValidAspectRatio(int num, int denom) {
   1426         // Hacky, but we need to wait for the auto-enter picture-in-picture animation to complete
   1427         // and before we can check the pinned stack bounds
   1428         mAmWmState.waitForWithAmState((state) -> {
   1429             Rect bounds = state.getStandardStackByWindowingMode(WINDOWING_MODE_PINNED).getBounds();
   1430             return floatEquals((float) bounds.width() / bounds.height(), (float) num / denom);
   1431         }, "waitForValidAspectRatio");
   1432     }
   1433 
   1434     /**
   1435      * @return the window state for the given {@param activityName}'s window.
   1436      */
   1437     private WindowManagerState.WindowState getWindowState(ComponentName activityName) {
   1438         String windowName = getWindowName(activityName);
   1439         mAmWmState.computeState(activityName);
   1440         final List<WindowManagerState.WindowState> tempWindowList =
   1441                 mAmWmState.getWmState().getMatchingVisibleWindowState(windowName);
   1442         return tempWindowList.get(0);
   1443     }
   1444 
   1445     /**
   1446      * @return the current pinned stack.
   1447      */
   1448     private ActivityStack getPinnedStack() {
   1449         return mAmWmState.getAmState().getStandardStackByWindowingMode(WINDOWING_MODE_PINNED);
   1450     }
   1451 
   1452     /**
   1453      * @return the current pinned stack bounds.
   1454      */
   1455     private Rect getPinnedStackBounds() {
   1456         return getPinnedStack().getBounds();
   1457     }
   1458 
   1459     /**
   1460      * Compares two floats with a common epsilon.
   1461      */
   1462     private void assertFloatEquals(float actual, float expected) {
   1463         if (!floatEquals(actual, expected)) {
   1464             fail(expected + " not equal to " + actual);
   1465         }
   1466     }
   1467 
   1468     private boolean floatEquals(float a, float b) {
   1469         return Math.abs(a - b) < FLOAT_COMPARE_EPSILON;
   1470     }
   1471 
   1472     /**
   1473      * Triggers a tap over the pinned stack bounds to trigger the PIP to close.
   1474      */
   1475     private void tapToFinishPip() {
   1476         Rect pinnedStackBounds = getPinnedStackBounds();
   1477         int tapX = pinnedStackBounds.left + pinnedStackBounds.width() - 100;
   1478         int tapY = pinnedStackBounds.top + pinnedStackBounds.height() - 100;
   1479         tapOnDisplay(tapX, tapY, DEFAULT_DISPLAY);
   1480     }
   1481 
   1482     /**
   1483      * Launches the given {@param activityName} into the {@param taskId} as a task overlay.
   1484      */
   1485     private void launchPinnedActivityAsTaskOverlay(ComponentName activityName, int taskId) {
   1486         executeShellCommand(getAmStartCmd(activityName) + " --task " + taskId + " --task-overlay");
   1487 
   1488         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
   1489                 .setWindowingMode(WINDOWING_MODE_PINNED)
   1490                 .setActivityType(ACTIVITY_TYPE_STANDARD)
   1491                 .build());
   1492     }
   1493 
   1494     private static class AppOpsSession implements AutoCloseable {
   1495 
   1496         private final String mPackageName;
   1497 
   1498         AppOpsSession(ComponentName activityName) {
   1499             mPackageName = activityName.getPackageName();
   1500         }
   1501 
   1502         /**
   1503          * Sets an app-ops op for a given package to a given mode.
   1504          */
   1505         void setOpToMode(String op, int mode) {
   1506             try {
   1507                 AppOpsUtils.setOpMode(mPackageName, op, mode);
   1508             } catch (Exception e) {
   1509                 e.printStackTrace();
   1510             }
   1511         }
   1512 
   1513         @Override
   1514         public void close() {
   1515             try {
   1516                 AppOpsUtils.reset(mPackageName);
   1517             } catch (IOException e) {
   1518                 e.printStackTrace();
   1519             }
   1520         }
   1521     }
   1522 
   1523     /**
   1524      * TODO: Improve tests check to actually check that apps are not interactive instead of checking
   1525      *       if the stack is focused.
   1526      */
   1527     private void pinnedStackTester(String startActivityCmd, ComponentName startActivity,
   1528             ComponentName topActivityName, boolean moveTopToPinnedStack, boolean isFocusable) {
   1529         executeShellCommand(startActivityCmd);
   1530         mAmWmState.waitForValidState(startActivity);
   1531 
   1532         if (moveTopToPinnedStack) {
   1533             final int stackId = mAmWmState.getAmState().getStackIdByActivity(topActivityName);
   1534 
   1535             assertNotEquals(stackId, INVALID_STACK_ID);
   1536             moveTopActivityToPinnedStack(stackId);
   1537         }
   1538 
   1539         mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(topActivityName)
   1540                 .setWindowingMode(WINDOWING_MODE_PINNED)
   1541                 .setActivityType(ACTIVITY_TYPE_STANDARD)
   1542                 .build());
   1543         mAmWmState.computeState(true);
   1544 
   1545         if (supportsPip()) {
   1546             final String windowName = getWindowName(topActivityName);
   1547             assertPinnedStackExists();
   1548             mAmWmState.assertFrontStack("Pinned stack must be the front stack.",
   1549                     WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
   1550             mAmWmState.assertVisibility(topActivityName, true);
   1551 
   1552             if (isFocusable) {
   1553                 mAmWmState.assertFocusedStack("Pinned stack must be the focused stack.",
   1554                         WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
   1555                 mAmWmState.assertFocusedActivity(
   1556                         "Pinned activity must be focused activity.", topActivityName);
   1557                 mAmWmState.assertFocusedWindow(
   1558                         "Pinned window must be focused window.", windowName);
   1559                 // Not checking for resumed state here because PiP overlay can be launched on top
   1560                 // in different task by SystemUI.
   1561             } else {
   1562                 // Don't assert that the stack is not focused as a focusable PiP overlay can be
   1563                 // launched on top as a task overlay by SystemUI.
   1564                 mAmWmState.assertNotFocusedActivity(
   1565                         "Pinned activity can't be the focused activity.", topActivityName);
   1566                 mAmWmState.assertNotResumedActivity(
   1567                         "Pinned activity can't be the resumed activity.", topActivityName);
   1568                 mAmWmState.assertNotFocusedWindow(
   1569                         "Pinned window can't be focused window.", windowName);
   1570             }
   1571         } else {
   1572             mAmWmState.assertDoesNotContainStack("Must not contain pinned stack.",
   1573                     WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
   1574         }
   1575     }
   1576 }
   1577