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