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