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