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