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