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