1 /* 2 * Copyright 2018 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.app.activity; 18 19 import static android.content.Context.DEVICE_ID_INVALID; 20 import static android.content.Intent.ACTION_EDIT; 21 import static android.content.Intent.ACTION_VIEW; 22 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 23 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 24 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; 25 import static android.view.Display.DEFAULT_DISPLAY; 26 import static android.view.Display.INVALID_DISPLAY; 27 28 import static com.android.window.flags.Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG; 29 30 import static com.google.common.truth.Truth.assertThat; 31 32 import static org.junit.Assert.assertEquals; 33 import static org.junit.Assert.assertFalse; 34 import static org.junit.Assert.assertNotEquals; 35 import static org.junit.Assert.assertTrue; 36 import static org.mockito.ArgumentMatchers.any; 37 import static org.mockito.Mockito.atLeastOnce; 38 import static org.mockito.Mockito.clearInvocations; 39 import static org.mockito.Mockito.mock; 40 import static org.mockito.Mockito.never; 41 import static org.mockito.Mockito.spy; 42 import static org.mockito.Mockito.verify; 43 44 import android.annotation.NonNull; 45 import android.app.Activity; 46 import android.app.ActivityThread; 47 import android.app.ActivityThread.ActivityClientRecord; 48 import android.app.Application; 49 import android.app.IApplicationThread; 50 import android.app.PictureInPictureParams; 51 import android.app.PictureInPictureUiState; 52 import android.app.ResourcesManager; 53 import android.app.servertransaction.ActivityConfigurationChangeItem; 54 import android.app.servertransaction.ActivityRelaunchItem; 55 import android.app.servertransaction.ClientTransaction; 56 import android.app.servertransaction.ClientTransactionItem; 57 import android.app.servertransaction.ClientTransactionListenerController; 58 import android.app.servertransaction.ConfigurationChangeItem; 59 import android.app.servertransaction.NewIntentItem; 60 import android.app.servertransaction.ResumeActivityItem; 61 import android.app.servertransaction.StopActivityItem; 62 import android.content.Context; 63 import android.content.Intent; 64 import android.content.res.CompatibilityInfo; 65 import android.content.res.Configuration; 66 import android.content.res.Resources; 67 import android.graphics.Rect; 68 import android.hardware.display.DisplayManager; 69 import android.hardware.display.VirtualDisplay; 70 import android.os.Bundle; 71 import android.os.IBinder; 72 import android.platform.test.annotations.Presubmit; 73 import android.platform.test.flag.junit.SetFlagsRule; 74 import android.util.DisplayMetrics; 75 import android.util.Log; 76 import android.util.MergedConfiguration; 77 import android.view.Display; 78 import android.view.View; 79 import android.window.ActivityWindowInfo; 80 import android.window.WindowContextInfo; 81 import android.window.WindowTokenClientController; 82 83 import androidx.test.filters.MediumTest; 84 import androidx.test.platform.app.InstrumentationRegistry; 85 import androidx.test.rule.ActivityTestRule; 86 import androidx.test.runner.AndroidJUnit4; 87 88 import com.android.internal.content.ReferrerIntent; 89 90 import org.junit.After; 91 import org.junit.Before; 92 import org.junit.Rule; 93 import org.junit.Test; 94 import org.junit.runner.RunWith; 95 import org.mockito.MockitoAnnotations; 96 97 import java.util.ArrayList; 98 import java.util.List; 99 import java.util.concurrent.CountDownLatch; 100 import java.util.concurrent.TimeUnit; 101 import java.util.function.BiConsumer; 102 import java.util.function.Consumer; 103 104 /** 105 * Test for verifying {@link android.app.ActivityThread} class. 106 * Build/Install/Run: 107 * atest FrameworksCoreTests:ActivityThreadTest 108 */ 109 @RunWith(AndroidJUnit4.class) 110 @MediumTest 111 @Presubmit 112 public class ActivityThreadTest { 113 114 private static final String TAG = "ActivityThreadTest"; 115 116 private static final int TIMEOUT_SEC = 10; 117 118 // The first sequence number to try with. Use a large number to avoid conflicts with the first a 119 // few sequence numbers the framework used to launch the test activity. 120 private static final int BASE_SEQ = 10000000; 121 122 @Rule(order = 0) 123 public final ActivityTestRule<TestActivity> mActivityTestRule = 124 new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */, 125 false /* launchActivity */); 126 127 @Rule(order = 1) 128 public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); 129 130 private ActivityWindowInfoListener mActivityWindowInfoListener; 131 private WindowTokenClientController mOriginalWindowTokenClientController; 132 private Configuration mOriginalAppConfig; 133 134 private ArrayList<VirtualDisplay> mCreatedVirtualDisplays; 135 136 @Before setup()137 public void setup() { 138 MockitoAnnotations.initMocks(this); 139 140 // Keep track of the original controller, so that it can be used to restore in tearDown() 141 // when there is override in some test cases. 142 mOriginalWindowTokenClientController = WindowTokenClientController.getInstance(); 143 mOriginalAppConfig = new Configuration(ActivityThread.currentActivityThread() 144 .getConfiguration()); 145 mActivityWindowInfoListener = spy(new ActivityWindowInfoListener()); 146 } 147 148 @After tearDown()149 public void tearDown() { 150 if (mCreatedVirtualDisplays != null) { 151 mCreatedVirtualDisplays.forEach(VirtualDisplay::release); 152 mCreatedVirtualDisplays = null; 153 } 154 WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController); 155 ClientTransactionListenerController.getInstance() 156 .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener); 157 InstrumentationRegistry.getInstrumentation().runOnMainSync( 158 () -> restoreConfig(ActivityThread.currentActivityThread(), mOriginalAppConfig)); 159 } 160 161 @Test testTemporaryDirectory()162 public void testTemporaryDirectory() throws Exception { 163 assertEquals(System.getProperty("java.io.tmpdir"), System.getenv("TMPDIR")); 164 } 165 166 @Test testDoubleRelaunch()167 public void testDoubleRelaunch() throws Exception { 168 final Activity activity = mActivityTestRule.launchActivity(new Intent()); 169 final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); 170 171 appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); 172 appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); 173 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 174 } 175 176 @Test testResumeAfterRelaunch()177 public void testResumeAfterRelaunch() throws Exception { 178 final Activity activity = mActivityTestRule.launchActivity(new Intent()); 179 final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); 180 181 appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); 182 appThread.scheduleTransaction(newResumeTransaction(activity)); 183 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 184 } 185 186 /** Verify that repeated resume requests to activity will be ignored. */ 187 @Test testRepeatedResume()188 public void testRepeatedResume() throws Exception { 189 final Activity activity = mActivityTestRule.launchActivity(new Intent()); 190 final ActivityThread activityThread = activity.getActivityThread(); 191 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 192 activityThread.executeTransaction(newResumeTransaction(activity)); 193 final ActivityClientRecord r = getActivityClientRecord(activity); 194 assertFalse(activityThread.performResumeActivity(r, true /* finalStateRequest */, 195 "test")); 196 197 assertFalse(activityThread.performResumeActivity(r, false /* finalStateRequest */, 198 "test")); 199 }); 200 } 201 202 /** Verify that custom intent set via Activity#setIntent() is preserved on relaunch. */ 203 @Test testCustomIntentPreservedOnRelaunch()204 public void testCustomIntentPreservedOnRelaunch() throws Exception { 205 final Intent initIntent = new Intent(); 206 initIntent.setAction(ACTION_VIEW); 207 final Activity activity = mActivityTestRule.launchActivity(initIntent); 208 IBinder token = activity.getActivityToken(); 209 210 final ActivityThread activityThread = activity.getActivityThread(); 211 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 212 // Recreate and check that intent is still the same. 213 activity.recreate(); 214 215 final Activity newActivity = activityThread.getActivity(token); 216 assertTrue("Original intent must be preserved after recreate", 217 initIntent.filterEquals(newActivity.getIntent())); 218 219 // Set custom intent, recreate and check if it is preserved. 220 final Intent customIntent = new Intent(); 221 customIntent.setAction(ACTION_EDIT); 222 newActivity.setIntent(customIntent); 223 224 activity.recreate(); 225 226 final Activity lastActivity = activityThread.getActivity(token); 227 assertTrue("Custom intent must be preserved after recreate", 228 customIntent.filterEquals(lastActivity.getIntent())); 229 }); 230 } 231 232 @Test testOverrideScale()233 public void testOverrideScale() throws Exception { 234 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 235 final Application app = activity.getApplication(); 236 final ActivityThread activityThread = activity.getActivityThread(); 237 final IApplicationThread appThread = activityThread.getApplicationThread(); 238 final DisplayMetrics originalAppMetrics = new DisplayMetrics(); 239 originalAppMetrics.setTo(app.getResources().getDisplayMetrics()); 240 final Configuration originalAppConfig = 241 new Configuration(app.getResources().getConfiguration()); 242 final DisplayMetrics originalActivityMetrics = new DisplayMetrics(); 243 originalActivityMetrics.setTo(activity.getResources().getDisplayMetrics()); 244 final Configuration originalActivityConfig = 245 new Configuration(activity.getResources().getConfiguration()); 246 247 final Configuration newConfig = new Configuration(originalAppConfig); 248 newConfig.seq = BASE_SEQ + 1; 249 newConfig.smallestScreenWidthDp++; 250 251 final float originalScale = CompatibilityInfo.getOverrideInvertedScale(); 252 float scale = 0.5f; 253 CompatibilityInfo.setOverrideInvertedScale(scale); 254 try { 255 // Send process level config change. 256 ClientTransaction transaction = newTransaction(activityThread); 257 transaction.addTransactionItem(ConfigurationChangeItem.obtain( 258 newConfig, DEVICE_ID_INVALID)); 259 appThread.scheduleTransaction(transaction); 260 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 261 262 assertScreenScale(scale, app, originalAppConfig, originalAppMetrics); 263 // The activity's config doesn't change because ConfigurationChangeItem is process level 264 // that won't affect activity's override config. 265 assertEquals(originalActivityConfig.densityDpi, 266 activity.getResources().getConfiguration().densityDpi); 267 268 scale = 0.8f; 269 CompatibilityInfo.setOverrideInvertedScale(scale); 270 // Send activity level config change. 271 newConfig.seq++; 272 newConfig.smallestScreenWidthDp++; 273 transaction = newTransaction(activityThread); 274 transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain( 275 activity.getActivityToken(), newConfig, new ActivityWindowInfo())); 276 appThread.scheduleTransaction(transaction); 277 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 278 279 assertScreenScale(scale, activity, originalActivityConfig, originalActivityMetrics); 280 281 // Execute a local relaunch item with current scaled config (e.g. simulate recreate), 282 // the config should not be scaled again. 283 InstrumentationRegistry.getInstrumentation().runOnMainSync( 284 () -> activityThread.executeTransaction( 285 newRelaunchResumeTransaction(activity))); 286 287 assertScreenScale(scale, activity, originalActivityConfig, originalActivityMetrics); 288 } finally { 289 CompatibilityInfo.setOverrideInvertedScale(originalScale); 290 InstrumentationRegistry.getInstrumentation().runOnMainSync( 291 () -> restoreConfig(activityThread, originalAppConfig)); 292 } 293 assertScreenScale(originalScale, app, originalAppConfig, originalAppMetrics); 294 } 295 assertScreenScale(float scale, Context context, Configuration origConfig, DisplayMetrics origMetrics)296 private static void assertScreenScale(float scale, Context context, 297 Configuration origConfig, DisplayMetrics origMetrics) { 298 final int expectedDpi = (int) (origConfig.densityDpi * scale + .5f); 299 final float expectedDensity = origMetrics.density * scale; 300 final int expectedWidthPixels = (int) (origMetrics.widthPixels * scale + .5f); 301 final int expectedHeightPixels = (int) (origMetrics.heightPixels * scale + .5f); 302 final Configuration expectedConfig = new Configuration(origConfig); 303 CompatibilityInfo.scaleConfiguration(scale, expectedConfig); 304 final Rect expectedBounds = expectedConfig.windowConfiguration.getBounds(); 305 final Rect expectedAppBounds = expectedConfig.windowConfiguration.getAppBounds(); 306 final Rect expectedMaxBounds = expectedConfig.windowConfiguration.getMaxBounds(); 307 308 final Configuration currentConfig = context.getResources().getConfiguration(); 309 final DisplayMetrics currentMetrics = context.getResources().getDisplayMetrics(); 310 assertEquals(expectedDpi, currentConfig.densityDpi); 311 assertEquals(expectedDpi, currentMetrics.densityDpi); 312 assertEquals(expectedDensity, currentMetrics.density, 0.001f); 313 assertEquals(expectedWidthPixels, currentMetrics.widthPixels); 314 assertEquals(expectedHeightPixels, currentMetrics.heightPixels); 315 assertEquals(expectedBounds, currentConfig.windowConfiguration.getBounds()); 316 assertEquals(expectedAppBounds, currentConfig.windowConfiguration.getAppBounds()); 317 assertEquals(expectedMaxBounds, currentConfig.windowConfiguration.getMaxBounds()); 318 } 319 320 @Test testHandleActivityConfigurationChanged()321 public void testHandleActivityConfigurationChanged() { 322 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 323 324 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 325 final int numOfConfig = activity.mNumOfConfigChanges; 326 applyConfigurationChange(activity, BASE_SEQ); 327 assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); 328 }); 329 } 330 331 @Test testRecreateActivity()332 public void testRecreateActivity() { 333 relaunchActivityAndAssertPreserveWindow(Activity::recreate); 334 } 335 relaunchActivityAndAssertPreserveWindow(Consumer<Activity> relaunch)336 private void relaunchActivityAndAssertPreserveWindow(Consumer<Activity> relaunch) { 337 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 338 final ActivityThread activityThread = activity.getActivityThread(); 339 340 final IBinder[] token = new IBinder[1]; 341 final View[] decorView = new View[1]; 342 343 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 344 token[0] = activity.getActivityToken(); 345 decorView[0] = activity.getWindow().getDecorView(); 346 347 relaunch.accept(activity); 348 }); 349 350 final View[] newDecorView = new View[1]; 351 final Activity[] newActivity = new Activity[1]; 352 353 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 354 newActivity[0] = activityThread.getActivity(token[0]); 355 newDecorView[0] = newActivity[0].getWindow().getDecorView(); 356 }); 357 358 assertNotEquals("Activity must be relaunched", activity, newActivity[0]); 359 assertEquals("Window must be preserved", decorView[0], newDecorView[0]); 360 } 361 362 @Test testHandleActivityConfigurationChanged_DropStaleConfigurations()363 public void testHandleActivityConfigurationChanged_DropStaleConfigurations() { 364 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 365 366 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 367 // Set the sequence number to BASE_SEQ. 368 applyConfigurationChange(activity, BASE_SEQ); 369 370 final int orientation = activity.mConfig.orientation; 371 final int numOfConfig = activity.mNumOfConfigChanges; 372 373 // Try to apply an old configuration change. 374 applyConfigurationChange(activity, BASE_SEQ - 1); 375 assertEquals(numOfConfig, activity.mNumOfConfigChanges); 376 assertEquals(orientation, activity.mConfig.orientation); 377 }); 378 } 379 380 @Test testHandleActivityConfigurationChanged_ApplyNewConfigurations()381 public void testHandleActivityConfigurationChanged_ApplyNewConfigurations() { 382 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 383 384 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 385 // Set the sequence number to BASE_SEQ and record the final sequence number it used. 386 final int seq = applyConfigurationChange(activity, BASE_SEQ); 387 388 final int orientation = activity.mConfig.orientation; 389 final int numOfConfig = activity.mNumOfConfigChanges; 390 391 // Try to apply an new configuration change. 392 applyConfigurationChange(activity, seq + 1); 393 assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); 394 assertNotEquals(orientation, activity.mConfig.orientation); 395 }); 396 } 397 398 @Test testHandleActivityConfigurationChanged_SkipWhenNewerConfigurationPending()399 public void testHandleActivityConfigurationChanged_SkipWhenNewerConfigurationPending() { 400 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 401 402 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 403 // Set the sequence number to BASE_SEQ and record the final sequence number it used. 404 final int seq = applyConfigurationChange(activity, BASE_SEQ); 405 406 final int orientation = activity.mConfig.orientation; 407 final int numOfConfig = activity.mNumOfConfigChanges; 408 409 final ActivityThread activityThread = activity.getActivityThread(); 410 411 final Configuration newerConfig = new Configuration(); 412 newerConfig.orientation = orientation == ORIENTATION_LANDSCAPE 413 ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; 414 newerConfig.seq = seq + 2; 415 activityThread.updatePendingActivityConfiguration(activity.getActivityToken(), 416 newerConfig); 417 418 final Configuration olderConfig = new Configuration(); 419 olderConfig.orientation = orientation; 420 olderConfig.seq = seq + 1; 421 422 final ActivityClientRecord r = getActivityClientRecord(activity); 423 activityThread.handleActivityConfigurationChanged(r, olderConfig, INVALID_DISPLAY, 424 new ActivityWindowInfo()); 425 assertEquals(numOfConfig, activity.mNumOfConfigChanges); 426 assertEquals(olderConfig.orientation, activity.mConfig.orientation); 427 428 activityThread.handleActivityConfigurationChanged(r, newerConfig, INVALID_DISPLAY, 429 new ActivityWindowInfo()); 430 assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); 431 assertEquals(newerConfig.orientation, activity.mConfig.orientation); 432 }); 433 } 434 435 @Test testHandleActivityConfigurationChanged_EnsureUpdatesProcessedInOrder()436 public void testHandleActivityConfigurationChanged_EnsureUpdatesProcessedInOrder() 437 throws Exception { 438 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 439 440 final ActivityThread activityThread = activity.getActivityThread(); 441 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 442 final Configuration config = new Configuration(); 443 config.seq = BASE_SEQ; 444 config.orientation = ORIENTATION_PORTRAIT; 445 446 activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity), 447 config, INVALID_DISPLAY, new ActivityWindowInfo()); 448 }); 449 450 final IApplicationThread appThread = activityThread.getApplicationThread(); 451 final int numOfConfig = activity.mNumOfConfigChanges; 452 453 final Configuration processConfigLandscape = new Configuration(); 454 processConfigLandscape.orientation = ORIENTATION_LANDSCAPE; 455 processConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 60)); 456 processConfigLandscape.seq = BASE_SEQ + 1; 457 458 final Configuration activityConfigLandscape = new Configuration(); 459 activityConfigLandscape.orientation = ORIENTATION_LANDSCAPE; 460 activityConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 50)); 461 activityConfigLandscape.seq = BASE_SEQ + 2; 462 463 final Configuration processConfigPortrait = new Configuration(); 464 processConfigPortrait.orientation = ORIENTATION_PORTRAIT; 465 processConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 60, 100)); 466 processConfigPortrait.seq = BASE_SEQ + 3; 467 468 final Configuration activityConfigPortrait = new Configuration(); 469 activityConfigPortrait.orientation = ORIENTATION_PORTRAIT; 470 activityConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 50, 100)); 471 activityConfigPortrait.seq = BASE_SEQ + 4; 472 473 activity.mConfigLatch = new CountDownLatch(1); 474 activity.mTestLatch = new CountDownLatch(1); 475 476 ClientTransaction transaction = newTransaction(activityThread); 477 transaction.addTransactionItem(ConfigurationChangeItem.obtain( 478 processConfigLandscape, DEVICE_ID_INVALID)); 479 appThread.scheduleTransaction(transaction); 480 481 transaction = newTransaction(activityThread); 482 transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain( 483 activity.getActivityToken(), activityConfigLandscape, new ActivityWindowInfo())); 484 transaction.addTransactionItem(ConfigurationChangeItem.obtain( 485 processConfigPortrait, DEVICE_ID_INVALID)); 486 transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain( 487 activity.getActivityToken(), activityConfigPortrait, new ActivityWindowInfo())); 488 appThread.scheduleTransaction(transaction); 489 490 activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS); 491 activity.mConfigLatch.countDown(); 492 493 activity.mConfigLatch = null; 494 activity.mTestLatch = null; 495 496 // Check display metrics, bounds should match the portrait activity bounds. 497 final Rect bounds = activity.getWindowManager().getCurrentWindowMetrics().getBounds(); 498 assertEquals(activityConfigPortrait.windowConfiguration.getBounds(), bounds); 499 500 // Ensure that Activity#onConfigurationChanged() not be called because the changes in 501 // WindowConfiguration shouldn't be reported, and we only apply the latest Configuration 502 // update in transaction. 503 assertEquals(numOfConfig, activity.mNumOfConfigChanges); 504 } 505 506 @Test testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration()507 public void testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration() 508 throws Exception { 509 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 510 511 final ActivityThread activityThread = activity.getActivityThread(); 512 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 513 final Configuration config = new Configuration(); 514 config.seq = BASE_SEQ; 515 config.orientation = ORIENTATION_PORTRAIT; 516 517 activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity), 518 config, INVALID_DISPLAY, new ActivityWindowInfo()); 519 }); 520 521 final int numOfConfig = activity.mNumOfConfigChanges; 522 final IApplicationThread appThread = activityThread.getApplicationThread(); 523 524 activity.mConfigLatch = new CountDownLatch(1); 525 activity.mTestLatch = new CountDownLatch(1); 526 527 Configuration config = new Configuration(); 528 config.seq = BASE_SEQ + 1; 529 config.orientation = ORIENTATION_LANDSCAPE; 530 appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); 531 532 // Wait until the main thread is performing the configuration change for the configuration 533 // with sequence number BASE_SEQ + 1 before proceeding. This is to mimic the situation where 534 // the activity takes very long time to process configuration changes. 535 activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS); 536 537 config = new Configuration(); 538 config.seq = BASE_SEQ + 2; 539 config.orientation = ORIENTATION_PORTRAIT; 540 appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); 541 542 config = new Configuration(); 543 config.seq = BASE_SEQ + 3; 544 config.orientation = ORIENTATION_LANDSCAPE; 545 appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); 546 547 config = new Configuration(); 548 config.seq = BASE_SEQ + 4; 549 config.orientation = ORIENTATION_PORTRAIT; 550 appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); 551 552 activity.mConfigLatch.countDown(); 553 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 554 555 activity.mConfigLatch = null; 556 activity.mTestLatch = null; 557 558 // Only two more configuration changes: one with seq BASE_SEQ + 1; another with seq 559 // BASE_SEQ + 4. Configurations scheduled in between should be dropped. 560 assertEquals(numOfConfig + 2, activity.mNumOfConfigChanges); 561 assertEquals(ORIENTATION_PORTRAIT, activity.mConfig.orientation); 562 } 563 564 @Test testOrientationChanged_DoesntOverrideVirtualDisplayOrientation()565 public void testOrientationChanged_DoesntOverrideVirtualDisplayOrientation() { 566 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 567 final ActivityThread activityThread = activity.getActivityThread(); 568 569 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 570 Context appContext = activity.getApplication(); 571 Configuration originalAppConfig = 572 new Configuration(appContext.getResources().getConfiguration()); 573 574 int virtualDisplayWidth; 575 int virtualDisplayHeight; 576 if (originalAppConfig.orientation == ORIENTATION_PORTRAIT) { 577 virtualDisplayWidth = 100; 578 virtualDisplayHeight = 200; 579 } else { 580 virtualDisplayWidth = 200; 581 virtualDisplayHeight = 100; 582 } 583 final Display virtualDisplay = createVirtualDisplay(appContext, 584 virtualDisplayWidth, virtualDisplayHeight); 585 Context virtualDisplayContext = appContext.createDisplayContext(virtualDisplay); 586 int originalVirtualDisplayOrientation = virtualDisplayContext.getResources() 587 .getConfiguration().orientation; 588 589 590 // Perform global config change and verify there is no config change in derived display 591 // context. 592 Configuration newAppConfig = new Configuration(originalAppConfig); 593 newAppConfig.seq++; 594 newAppConfig.orientation = newAppConfig.orientation == ORIENTATION_PORTRAIT 595 ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; 596 597 activityThread.updatePendingConfiguration(newAppConfig); 598 activityThread.handleConfigurationChanged(newAppConfig, DEVICE_ID_INVALID); 599 600 assertEquals("Virtual display orientation must not change when process" 601 + " configuration orientation changes.", 602 originalVirtualDisplayOrientation, 603 virtualDisplayContext.getResources().getConfiguration().orientation); 604 }); 605 } 606 restoreConfig(ActivityThread thread, Configuration originalConfig)607 private static void restoreConfig(ActivityThread thread, Configuration originalConfig) { 608 thread.getConfiguration().seq = originalConfig.seq - 1; 609 ResourcesManager.getInstance().getConfiguration().seq = originalConfig.seq - 1; 610 thread.handleConfigurationChanged(originalConfig, DEVICE_ID_INVALID); 611 } 612 613 @Test testActivityOrientationChanged_DoesntOverrideVirtualDisplayOrientation()614 public void testActivityOrientationChanged_DoesntOverrideVirtualDisplayOrientation() { 615 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 616 final ActivityThread activityThread = activity.getActivityThread(); 617 618 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 619 Configuration originalActivityConfig = 620 new Configuration(activity.getResources().getConfiguration()); 621 622 int virtualDisplayWidth; 623 int virtualDisplayHeight; 624 if (originalActivityConfig.orientation == ORIENTATION_PORTRAIT) { 625 virtualDisplayWidth = 100; 626 virtualDisplayHeight = 200; 627 } else { 628 virtualDisplayWidth = 200; 629 virtualDisplayHeight = 100; 630 } 631 final Display virtualDisplay = createVirtualDisplay(activity, 632 virtualDisplayWidth, virtualDisplayHeight); 633 Context virtualDisplayContext = activity.createDisplayContext(virtualDisplay); 634 int originalVirtualDisplayOrientation = virtualDisplayContext.getResources() 635 .getConfiguration().orientation; 636 637 // Perform activity config change and verify there is no config change in derived 638 // display context. 639 Configuration newActivityConfig = new Configuration(originalActivityConfig); 640 newActivityConfig.seq++; 641 newActivityConfig.orientation = newActivityConfig.orientation == ORIENTATION_PORTRAIT 642 ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; 643 644 final ActivityClientRecord r = getActivityClientRecord(activity); 645 activityThread.updatePendingActivityConfiguration(activity.getActivityToken(), 646 newActivityConfig); 647 activityThread.handleActivityConfigurationChanged(r, newActivityConfig, 648 INVALID_DISPLAY, new ActivityWindowInfo()); 649 650 assertEquals("Virtual display orientation must not change when activity" 651 + " configuration orientation changes.", 652 originalVirtualDisplayOrientation, 653 virtualDisplayContext.getResources().getConfiguration().orientation); 654 }); 655 } 656 657 @Test testHandleConfigurationChanged_DoesntOverrideActivityConfig()658 public void testHandleConfigurationChanged_DoesntOverrideActivityConfig() { 659 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 660 661 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 662 final Configuration oldActivityConfig = 663 new Configuration(activity.getResources().getConfiguration()); 664 final DisplayMetrics oldActivityMetrics = new DisplayMetrics(); 665 activity.getDisplay().getMetrics(oldActivityMetrics); 666 final Resources oldAppResources = activity.getApplication().getResources(); 667 final Configuration oldAppConfig = 668 new Configuration(oldAppResources.getConfiguration()); 669 final DisplayMetrics oldApplicationMetrics = new DisplayMetrics(); 670 oldApplicationMetrics.setTo(oldAppResources.getDisplayMetrics()); 671 assertEquals("Process config must match the top activity config by default" 672 + ", activity=" + oldActivityConfig + ", app=" + oldAppConfig, 673 0, oldActivityConfig.diffPublicOnly(oldAppConfig)); 674 assertEquals("Process config must match the top activity config by default", 675 oldActivityMetrics, oldApplicationMetrics); 676 677 // Update the application configuration separately from activity config 678 final Configuration newAppConfig = new Configuration(oldAppConfig); 679 newAppConfig.densityDpi += 100; 680 newAppConfig.screenHeightDp += 100; 681 final Rect newBounds = new Rect(newAppConfig.windowConfiguration.getAppBounds()); 682 newBounds.bottom += 100; 683 newAppConfig.windowConfiguration.setAppBounds(newBounds); 684 newAppConfig.windowConfiguration.setBounds(newBounds); 685 newAppConfig.seq++; 686 687 final ActivityThread activityThread = activity.getActivityThread(); 688 activityThread.handleConfigurationChanged(newAppConfig, DEVICE_ID_INVALID); 689 690 // Verify that application config update was applied, but didn't change activity config. 691 assertEquals("Activity config must not change if the process config changes", 692 oldActivityConfig, activity.getResources().getConfiguration()); 693 694 final DisplayMetrics newActivityMetrics = new DisplayMetrics(); 695 activity.getDisplay().getMetrics(newActivityMetrics); 696 assertEquals("Activity display size must not change if the process config changes", 697 oldActivityMetrics, newActivityMetrics); 698 final Resources newAppResources = activity.getApplication().getResources(); 699 assertEquals("Application config must be updated", 700 newAppConfig, newAppResources.getConfiguration()); 701 final DisplayMetrics newApplicationMetrics = new DisplayMetrics(); 702 newApplicationMetrics.setTo(newAppResources.getDisplayMetrics()); 703 assertNotEquals("Application display size must be updated after config update", 704 oldApplicationMetrics, newApplicationMetrics); 705 assertNotEquals("Application display size must be updated after config update", 706 newActivityMetrics, newApplicationMetrics); 707 }); 708 } 709 710 @Test testResumeAfterNewIntent()711 public void testResumeAfterNewIntent() { 712 final Activity activity = mActivityTestRule.launchActivity(new Intent()); 713 final ActivityThread activityThread = activity.getActivityThread(); 714 final ArrayList<ReferrerIntent> rIntents = new ArrayList<>(); 715 rIntents.add(new ReferrerIntent(new Intent(), "android.app.activity")); 716 717 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 718 activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, true)); 719 }); 720 assertThat(activity.isResumed()).isTrue(); 721 722 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 723 activityThread.executeTransaction(newStopTransaction(activity)); 724 activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, false)); 725 }); 726 assertThat(activity.isResumed()).isFalse(); 727 } 728 729 @Test testHandlePictureInPictureRequested_overriddenToEnter()730 public void testHandlePictureInPictureRequested_overriddenToEnter() { 731 final Intent startIntent = new Intent(); 732 startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_ENTER, true); 733 final TestActivity activity = mActivityTestRule.launchActivity(startIntent); 734 final ActivityThread activityThread = activity.getActivityThread(); 735 final ActivityClientRecord r = getActivityClientRecord(activity); 736 if (android.app.Flags.enablePipUiStateCallbackOnEntering()) { 737 activity.mPipUiStateLatch = new CountDownLatch(1); 738 } 739 740 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 741 activityThread.handlePictureInPictureRequested(r); 742 }); 743 744 assertTrue(activity.pipRequested()); 745 assertTrue(activity.enteredPip()); 746 } 747 748 @Test testHandlePictureInPictureRequested_overriddenToSkip()749 public void testHandlePictureInPictureRequested_overriddenToSkip() { 750 final Intent startIntent = new Intent(); 751 startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_SKIP, true); 752 final TestActivity activity = mActivityTestRule.launchActivity(startIntent); 753 final ActivityThread activityThread = activity.getActivityThread(); 754 final ActivityClientRecord r = getActivityClientRecord(activity); 755 756 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 757 activityThread.handlePictureInPictureRequested(r); 758 }); 759 760 assertTrue(activity.pipRequested()); 761 assertTrue(activity.enterPipSkipped()); 762 } 763 764 @Test testHandlePictureInPictureRequested_notOverridden()765 public void testHandlePictureInPictureRequested_notOverridden() { 766 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 767 final ActivityThread activityThread = activity.getActivityThread(); 768 final ActivityClientRecord r = getActivityClientRecord(activity); 769 770 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 771 activityThread.handlePictureInPictureRequested(r); 772 }); 773 774 assertTrue(activity.pipRequested()); 775 assertFalse(activity.enteredPip()); 776 assertFalse(activity.enterPipSkipped()); 777 } 778 779 @Test testHandleWindowContextConfigurationChanged()780 public void testHandleWindowContextConfigurationChanged() { 781 final Activity activity = mActivityTestRule.launchActivity(new Intent()); 782 final ActivityThread activityThread = activity.getActivityThread(); 783 final WindowTokenClientController windowTokenClientController = 784 mock(WindowTokenClientController.class); 785 WindowTokenClientController.overrideForTesting(windowTokenClientController); 786 final IBinder clientToken = mock(IBinder.class); 787 final Configuration configuration = new Configuration(); 788 final WindowContextInfo info = new WindowContextInfo(configuration, DEFAULT_DISPLAY); 789 790 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activityThread 791 .handleWindowContextInfoChanged(clientToken, info)); 792 793 verify(windowTokenClientController).onWindowContextInfoChanged(clientToken, info); 794 } 795 796 @Test testHandleWindowContextWindowRemoval()797 public void testHandleWindowContextWindowRemoval() { 798 final Activity activity = mActivityTestRule.launchActivity(new Intent()); 799 final ActivityThread activityThread = activity.getActivityThread(); 800 final WindowTokenClientController windowTokenClientController = 801 mock(WindowTokenClientController.class); 802 WindowTokenClientController.overrideForTesting(windowTokenClientController); 803 final IBinder clientToken = mock(IBinder.class); 804 805 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activityThread 806 .handleWindowContextWindowRemoval(clientToken)); 807 808 verify(windowTokenClientController).onWindowContextWindowRemoved(clientToken); 809 } 810 811 @Test testActivityWindowInfoChanged_activityLaunch()812 public void testActivityWindowInfoChanged_activityLaunch() { 813 mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG); 814 ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener( 815 mActivityWindowInfoListener); 816 817 final Activity activity = mActivityTestRule.launchActivity(new Intent()); 818 mActivityWindowInfoListener.await(); 819 final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity); 820 821 // In case the system change the window after launch, there can be more than one callback. 822 verify(mActivityWindowInfoListener, atLeastOnce()).accept(activityClientRecord.token, 823 activityClientRecord.getActivityWindowInfo()); 824 } 825 826 @Test testActivityWindowInfoChanged_activityRelaunch()827 public void testActivityWindowInfoChanged_activityRelaunch() { 828 mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG); 829 ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener( 830 mActivityWindowInfoListener); 831 832 final Activity activity = mActivityTestRule.launchActivity(new Intent()); 833 mActivityWindowInfoListener.await(); 834 final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity); 835 836 // Run on main thread to avoid racing from updating from window relayout. 837 final ActivityThread activityThread = activity.getActivityThread(); 838 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 839 // Try relaunch with the same ActivityWindowInfo 840 clearInvocations(mActivityWindowInfoListener); 841 activityThread.executeTransaction(newRelaunchResumeTransaction(activity)); 842 843 // The same ActivityWindowInfo won't trigger duplicated callback. 844 verify(mActivityWindowInfoListener, never()).accept(activityClientRecord.token, 845 activityClientRecord.getActivityWindowInfo()); 846 847 // Try relaunch with different ActivityWindowInfo 848 final Configuration currentConfig = activity.getResources().getConfiguration(); 849 final ActivityWindowInfo newInfo = new ActivityWindowInfo(); 850 newInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000), 851 new Rect(0, 0, 1000, 1000)); 852 final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain( 853 activity.getActivityToken(), null, null, 0, 854 new MergedConfiguration(currentConfig, currentConfig), 855 false /* preserveWindow */, newInfo); 856 final ClientTransaction transaction = newTransaction(activity); 857 transaction.addTransactionItem(relaunchItem); 858 859 clearInvocations(mActivityWindowInfoListener); 860 activityThread.executeTransaction(transaction); 861 862 // Trigger callback with a different ActivityWindowInfo 863 verify(mActivityWindowInfoListener).accept(activityClientRecord.token, newInfo); 864 }); 865 } 866 867 @Test testActivityWindowInfoChanged_activityConfigurationChanged()868 public void testActivityWindowInfoChanged_activityConfigurationChanged() { 869 mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG); 870 ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener( 871 mActivityWindowInfoListener); 872 873 final Activity activity = mActivityTestRule.launchActivity(new Intent()); 874 mActivityWindowInfoListener.await(); 875 876 final ActivityThread activityThread = activity.getActivityThread(); 877 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 878 // Trigger callback with different ActivityWindowInfo 879 final Configuration config = new Configuration(activity.getResources() 880 .getConfiguration()); 881 config.seq++; 882 final Rect taskBounds = new Rect(0, 0, 1000, 2000); 883 final Rect taskFragmentBounds = new Rect(0, 0, 1000, 1000); 884 final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo(); 885 activityWindowInfo.set(true /* isEmbedded */, taskBounds, taskFragmentBounds); 886 final ActivityConfigurationChangeItem activityConfigurationChangeItem = 887 ActivityConfigurationChangeItem.obtain( 888 activity.getActivityToken(), config, activityWindowInfo); 889 final ClientTransaction transaction = newTransaction(activity); 890 transaction.addTransactionItem(activityConfigurationChangeItem); 891 892 clearInvocations(mActivityWindowInfoListener); 893 activityThread.executeTransaction(transaction); 894 895 // Trigger callback with a different ActivityWindowInfo 896 verify(mActivityWindowInfoListener).accept(activity.getActivityToken(), 897 activityWindowInfo); 898 899 // Try callback with the same ActivityWindowInfo 900 final ActivityWindowInfo activityWindowInfo2 = 901 new ActivityWindowInfo(activityWindowInfo); 902 config.seq++; 903 final ActivityConfigurationChangeItem activityConfigurationChangeItem2 = 904 ActivityConfigurationChangeItem.obtain( 905 activity.getActivityToken(), config, activityWindowInfo2); 906 final ClientTransaction transaction2 = newTransaction(activity); 907 transaction2.addTransactionItem(activityConfigurationChangeItem2); 908 909 clearInvocations(mActivityWindowInfoListener); 910 activityThread.executeTransaction(transaction); 911 912 // The same ActivityWindowInfo won't trigger duplicated callback. 913 verify(mActivityWindowInfoListener, never()).accept(any(), any()); 914 }); 915 } 916 917 /** 918 * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord, 919 * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the 920 * activity for the given sequence number. 921 * <p> 922 * It uses orientation to push the configuration and it tries a different orientation if the 923 * first attempt doesn't make through, to rule out the possibility that the previous 924 * configuration already has the same orientation. 925 * 926 * @param activity the test target activity 927 * @param seq the specified sequence number 928 * @return the sequence number this method tried with the last time, so that the caller can use 929 * the next sequence number for next configuration update. 930 */ applyConfigurationChange(TestActivity activity, int seq)931 private int applyConfigurationChange(TestActivity activity, int seq) { 932 final ActivityThread activityThread = activity.getActivityThread(); 933 final ActivityClientRecord r = getActivityClientRecord(activity); 934 935 final int numOfConfig = activity.mNumOfConfigChanges; 936 Configuration config = new Configuration(); 937 config.orientation = ORIENTATION_PORTRAIT; 938 config.seq = seq; 939 activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY, 940 new ActivityWindowInfo()); 941 942 if (activity.mNumOfConfigChanges > numOfConfig) { 943 return config.seq; 944 } 945 946 config = new Configuration(); 947 config.orientation = ORIENTATION_LANDSCAPE; 948 config.seq = seq + 1; 949 activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY, 950 new ActivityWindowInfo()); 951 952 return config.seq; 953 } 954 createVirtualDisplay(Context context, int w, int h)955 private Display createVirtualDisplay(Context context, int w, int h) { 956 final DisplayManager dm = context.getSystemService(DisplayManager.class); 957 final VirtualDisplay virtualDisplay = dm.createVirtualDisplay("virtual-display", w, h, 958 200 /* densityDpi */, null /* surface */, 0 /* flags */); 959 if (mCreatedVirtualDisplays == null) { 960 mCreatedVirtualDisplays = new ArrayList<>(); 961 } 962 mCreatedVirtualDisplays.add(virtualDisplay); 963 return virtualDisplay.getDisplay(); 964 } 965 getActivityClientRecord(Activity activity)966 private static ActivityClientRecord getActivityClientRecord(Activity activity) { 967 final ActivityThread thread = activity.getActivityThread(); 968 final IBinder token = activity.getActivityToken(); 969 return thread.getActivityClient(token); 970 } 971 972 @NonNull newRelaunchResumeTransaction(@onNull Activity activity)973 private static ClientTransaction newRelaunchResumeTransaction(@NonNull Activity activity) { 974 final Configuration currentConfig = activity.getResources().getConfiguration(); 975 final ActivityClientRecord record = getActivityClientRecord(activity); 976 final ActivityWindowInfo activityWindowInfo; 977 if (record == null) { 978 Log.d(TAG, "The ActivityClientRecord of r=" + activity + " is not created yet. " 979 + "Likely because this call doesn't wait until activity launch."); 980 activityWindowInfo = new ActivityWindowInfo(); 981 } else { 982 activityWindowInfo = record.getActivityWindowInfo(); 983 } 984 final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain( 985 activity.getActivityToken(), null, null, 0, 986 new MergedConfiguration(currentConfig, currentConfig), 987 false /* preserveWindow */, activityWindowInfo); 988 final ResumeActivityItem resumeStateRequest = 989 ResumeActivityItem.obtain(activity.getActivityToken(), true /* isForward */, 990 false /* shouldSendCompatFakeFocus*/); 991 992 final ClientTransaction transaction = newTransaction(activity); 993 transaction.addTransactionItem(callbackItem); 994 transaction.addTransactionItem(resumeStateRequest); 995 996 return transaction; 997 } 998 999 @NonNull newResumeTransaction(@onNull Activity activity)1000 private static ClientTransaction newResumeTransaction(@NonNull Activity activity) { 1001 final ResumeActivityItem resumeStateRequest = 1002 ResumeActivityItem.obtain(activity.getActivityToken(), true /* isForward */, 1003 false /* shouldSendCompatFakeFocus */); 1004 1005 final ClientTransaction transaction = newTransaction(activity); 1006 transaction.addTransactionItem(resumeStateRequest); 1007 1008 return transaction; 1009 } 1010 1011 @NonNull newStopTransaction(@onNull Activity activity)1012 private static ClientTransaction newStopTransaction(@NonNull Activity activity) { 1013 final StopActivityItem stopStateRequest = StopActivityItem.obtain( 1014 activity.getActivityToken()); 1015 1016 final ClientTransaction transaction = newTransaction(activity); 1017 transaction.addTransactionItem(stopStateRequest); 1018 1019 return transaction; 1020 } 1021 1022 @NonNull newActivityConfigTransaction(@onNull Activity activity, @NonNull Configuration config)1023 private static ClientTransaction newActivityConfigTransaction(@NonNull Activity activity, 1024 @NonNull Configuration config) { 1025 final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain( 1026 activity.getActivityToken(), config, new ActivityWindowInfo()); 1027 1028 final ClientTransaction transaction = newTransaction(activity); 1029 transaction.addTransactionItem(item); 1030 1031 return transaction; 1032 } 1033 1034 @NonNull newNewIntentTransaction(@onNull Activity activity, @NonNull List<ReferrerIntent> intents, boolean resume)1035 private static ClientTransaction newNewIntentTransaction(@NonNull Activity activity, 1036 @NonNull List<ReferrerIntent> intents, boolean resume) { 1037 final NewIntentItem item = NewIntentItem.obtain(activity.getActivityToken(), intents, 1038 resume); 1039 1040 final ClientTransaction transaction = newTransaction(activity); 1041 transaction.addTransactionItem(item); 1042 1043 return transaction; 1044 } 1045 1046 @NonNull newTransaction(@onNull Activity activity)1047 private static ClientTransaction newTransaction(@NonNull Activity activity) { 1048 return newTransaction(activity.getActivityThread()); 1049 } 1050 1051 @NonNull newTransaction(@onNull ActivityThread activityThread)1052 private static ClientTransaction newTransaction(@NonNull ActivityThread activityThread) { 1053 return ClientTransaction.obtain(activityThread.getApplicationThread()); 1054 } 1055 1056 // Test activity 1057 public static class TestActivity extends Activity { 1058 static final String PIP_REQUESTED_OVERRIDE_ENTER = "pip_requested_override_enter"; 1059 static final String PIP_REQUESTED_OVERRIDE_SKIP = "pip_requested_override_skip"; 1060 1061 int mNumOfConfigChanges; 1062 final Configuration mConfig = new Configuration(); 1063 1064 private boolean mPipRequested; 1065 private boolean mPipEntered; 1066 private boolean mPipEnterSkipped; 1067 1068 /** 1069 * A latch used to notify tests that we're about to wait for configuration latch. This 1070 * is used to notify test code that preExecute phase for activity configuration change 1071 * transaction has passed. 1072 */ 1073 volatile CountDownLatch mTestLatch; 1074 /** 1075 * If not {@code null} {@link #onConfigurationChanged(Configuration)} won't return until the 1076 * latch reaches 0. 1077 */ 1078 volatile CountDownLatch mConfigLatch; 1079 /** 1080 * A latch used to notify tests that we're about to wait for the 1081 * onPictureInPictureUiStateChanged callback. 1082 */ 1083 volatile CountDownLatch mPipUiStateLatch; 1084 1085 @Override onCreate(Bundle savedInstanceState)1086 protected void onCreate(Bundle savedInstanceState) { 1087 super.onCreate(savedInstanceState); 1088 getWindow().getDecorView().setKeepScreenOn(true); 1089 setShowWhenLocked(true); 1090 setTurnScreenOn(true); 1091 } 1092 1093 @Override onConfigurationChanged(Configuration config)1094 public void onConfigurationChanged(Configuration config) { 1095 super.onConfigurationChanged(config); 1096 mConfig.setTo(config); 1097 ++mNumOfConfigChanges; 1098 1099 final CountDownLatch configLatch = mConfigLatch; 1100 if (configLatch != null) { 1101 if (mTestLatch != null) { 1102 mTestLatch.countDown(); 1103 } 1104 try { 1105 configLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS); 1106 } catch (InterruptedException e) { 1107 throw new IllegalStateException(e); 1108 } 1109 } 1110 } 1111 1112 @Override onPictureInPictureRequested()1113 public boolean onPictureInPictureRequested() { 1114 mPipRequested = true; 1115 if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_ENTER, false)) { 1116 enterPictureInPictureMode(new PictureInPictureParams.Builder().build()); 1117 mPipEntered = true; 1118 // Await for onPictureInPictureUiStateChanged callback if applicable 1119 if (mPipUiStateLatch != null) { 1120 try { 1121 mPipUiStateLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS); 1122 } catch (InterruptedException e) { 1123 throw new IllegalStateException(e); 1124 } 1125 } 1126 return true; 1127 } else if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_SKIP, false)) { 1128 mPipEnterSkipped = true; 1129 return false; 1130 } 1131 return super.onPictureInPictureRequested(); 1132 } 1133 1134 @Override onPictureInPictureUiStateChanged(PictureInPictureUiState pipState)1135 public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) { 1136 if (mPipUiStateLatch != null && pipState.isTransitioningToPip()) { 1137 mPipUiStateLatch.countDown(); 1138 } 1139 } 1140 pipRequested()1141 boolean pipRequested() { 1142 return mPipRequested; 1143 } 1144 enteredPip()1145 boolean enteredPip() { 1146 return mPipEntered; 1147 } 1148 enterPipSkipped()1149 boolean enterPipSkipped() { 1150 return mPipEnterSkipped; 1151 } 1152 } 1153 1154 public static class ActivityWindowInfoListener implements 1155 BiConsumer<IBinder, ActivityWindowInfo> { 1156 1157 CountDownLatch mCallbackLatch = new CountDownLatch(1); 1158 1159 @Override accept(@onNull IBinder activityToken, @NonNull ActivityWindowInfo activityWindowInfo)1160 public void accept(@NonNull IBinder activityToken, 1161 @NonNull ActivityWindowInfo activityWindowInfo) { 1162 mCallbackLatch.countDown(); 1163 } 1164 1165 /** 1166 * When the test is expecting to receive a callback, waits until the callback is triggered. 1167 */ await()1168 void await() { 1169 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 1170 try { 1171 mCallbackLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS); 1172 } catch (InterruptedException e) { 1173 throw new RuntimeException(e); 1174 } 1175 } 1176 } 1177 } 1178