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