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