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.Intent.ACTION_EDIT; 20 import static android.content.Intent.ACTION_VIEW; 21 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 22 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 23 import static android.view.Display.INVALID_DISPLAY; 24 25 import static com.google.common.truth.Truth.assertThat; 26 27 import static org.junit.Assert.assertEquals; 28 import static org.junit.Assert.assertFalse; 29 import static org.junit.Assert.assertNotEquals; 30 import static org.junit.Assert.assertTrue; 31 32 import android.annotation.Nullable; 33 import android.app.Activity; 34 import android.app.ActivityManager; 35 import android.app.ActivityThread; 36 import android.app.ActivityThread.ActivityClientRecord; 37 import android.app.IApplicationThread; 38 import android.app.PictureInPictureParams; 39 import android.app.ResourcesManager; 40 import android.app.servertransaction.ActivityConfigurationChangeItem; 41 import android.app.servertransaction.ActivityRelaunchItem; 42 import android.app.servertransaction.ClientTransaction; 43 import android.app.servertransaction.ClientTransactionItem; 44 import android.app.servertransaction.ConfigurationChangeItem; 45 import android.app.servertransaction.NewIntentItem; 46 import android.app.servertransaction.ResumeActivityItem; 47 import android.app.servertransaction.StopActivityItem; 48 import android.content.Context; 49 import android.content.Intent; 50 import android.content.res.Configuration; 51 import android.content.res.Resources; 52 import android.graphics.Rect; 53 import android.hardware.display.DisplayManager; 54 import android.hardware.display.VirtualDisplay; 55 import android.os.Bundle; 56 import android.os.IBinder; 57 import android.platform.test.annotations.Presubmit; 58 import android.util.DisplayMetrics; 59 import android.util.MergedConfiguration; 60 import android.view.Display; 61 import android.view.View; 62 63 import androidx.test.filters.FlakyTest; 64 import androidx.test.filters.MediumTest; 65 import androidx.test.platform.app.InstrumentationRegistry; 66 import androidx.test.rule.ActivityTestRule; 67 import androidx.test.runner.AndroidJUnit4; 68 69 import com.android.internal.content.ReferrerIntent; 70 71 import org.junit.After; 72 import org.junit.Test; 73 import org.junit.runner.RunWith; 74 75 import java.util.ArrayList; 76 import java.util.List; 77 import java.util.concurrent.CountDownLatch; 78 import java.util.concurrent.TimeUnit; 79 import java.util.function.Consumer; 80 81 /** 82 * Test for verifying {@link android.app.ActivityThread} class. 83 * Build/Install/Run: 84 * atest FrameworksCoreTests:android.app.activity.ActivityThreadTest 85 */ 86 @RunWith(AndroidJUnit4.class) 87 @MediumTest 88 @Presubmit 89 public class ActivityThreadTest { 90 private static final int TIMEOUT_SEC = 10; 91 92 // The first sequence number to try with. Use a large number to avoid conflicts with the first a 93 // few sequence numbers the framework used to launch the test activity. 94 private static final int BASE_SEQ = 10000; 95 96 private final ActivityTestRule<TestActivity> mActivityTestRule = 97 new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */, 98 false /* launchActivity */); 99 100 private ArrayList<VirtualDisplay> mCreatedVirtualDisplays; 101 102 @After tearDown()103 public void tearDown() { 104 if (mCreatedVirtualDisplays != null) { 105 mCreatedVirtualDisplays.forEach(VirtualDisplay::release); 106 mCreatedVirtualDisplays = null; 107 } 108 } 109 110 @Test testDoubleRelaunch()111 public void testDoubleRelaunch() throws Exception { 112 final Activity activity = mActivityTestRule.launchActivity(new Intent()); 113 final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); 114 115 appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); 116 appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); 117 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 118 } 119 120 @Test testResumeAfterRelaunch()121 public void testResumeAfterRelaunch() throws Exception { 122 final Activity activity = mActivityTestRule.launchActivity(new Intent()); 123 final IApplicationThread appThread = activity.getActivityThread().getApplicationThread(); 124 125 appThread.scheduleTransaction(newRelaunchResumeTransaction(activity)); 126 appThread.scheduleTransaction(newResumeTransaction(activity)); 127 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 128 } 129 130 /** Verify that repeated resume requests to activity will be ignored. */ 131 @Test testRepeatedResume()132 public void testRepeatedResume() throws Exception { 133 final Activity activity = mActivityTestRule.launchActivity(new Intent()); 134 final ActivityThread activityThread = activity.getActivityThread(); 135 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 136 activityThread.executeTransaction(newResumeTransaction(activity)); 137 final ActivityClientRecord r = getActivityClientRecord(activity); 138 assertFalse(activityThread.performResumeActivity(r, true /* finalStateRequest */, 139 "test")); 140 141 assertFalse(activityThread.performResumeActivity(r, false /* finalStateRequest */, 142 "test")); 143 }); 144 } 145 146 /** Verify that custom intent set via Activity#setIntent() is preserved on relaunch. */ 147 @Test testCustomIntentPreservedOnRelaunch()148 public void testCustomIntentPreservedOnRelaunch() throws Exception { 149 final Intent initIntent = new Intent(); 150 initIntent.setAction(ACTION_VIEW); 151 final Activity activity = mActivityTestRule.launchActivity(initIntent); 152 IBinder token = activity.getActivityToken(); 153 154 final ActivityThread activityThread = activity.getActivityThread(); 155 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 156 // Recreate and check that intent is still the same. 157 activity.recreate(); 158 159 final Activity newActivity = activityThread.getActivity(token); 160 assertTrue("Original intent must be preserved after recreate", 161 initIntent.filterEquals(newActivity.getIntent())); 162 163 // Set custom intent, recreate and check if it is preserved. 164 final Intent customIntent = new Intent(); 165 customIntent.setAction(ACTION_EDIT); 166 newActivity.setIntent(customIntent); 167 168 activity.recreate(); 169 170 final Activity lastActivity = activityThread.getActivity(token); 171 assertTrue("Custom intent must be preserved after recreate", 172 customIntent.filterEquals(lastActivity.getIntent())); 173 }); 174 } 175 176 @Test testHandleActivityConfigurationChanged()177 public void testHandleActivityConfigurationChanged() { 178 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 179 180 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 181 final int numOfConfig = activity.mNumOfConfigChanges; 182 applyConfigurationChange(activity, BASE_SEQ); 183 assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); 184 }); 185 } 186 187 @Test testRecreateActivity()188 public void testRecreateActivity() { 189 relaunchActivityAndAssertPreserveWindow(Activity::recreate); 190 } 191 relaunchActivityAndAssertPreserveWindow(Consumer<Activity> relaunch)192 private void relaunchActivityAndAssertPreserveWindow(Consumer<Activity> relaunch) { 193 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 194 final ActivityThread activityThread = activity.getActivityThread(); 195 196 final IBinder[] token = new IBinder[1]; 197 final View[] decorView = new View[1]; 198 199 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 200 token[0] = activity.getActivityToken(); 201 decorView[0] = activity.getWindow().getDecorView(); 202 203 relaunch.accept(activity); 204 }); 205 206 final View[] newDecorView = new View[1]; 207 final Activity[] newActivity = new Activity[1]; 208 209 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 210 newActivity[0] = activityThread.getActivity(token[0]); 211 newDecorView[0] = newActivity[0].getWindow().getDecorView(); 212 }); 213 214 assertNotEquals("Activity must be relaunched", activity, newActivity[0]); 215 assertEquals("Window must be preserved", decorView[0], newDecorView[0]); 216 } 217 218 @Test testHandleActivityConfigurationChanged_DropStaleConfigurations()219 public void testHandleActivityConfigurationChanged_DropStaleConfigurations() { 220 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 221 222 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 223 // Set the sequence number to BASE_SEQ. 224 applyConfigurationChange(activity, BASE_SEQ); 225 226 final int orientation = activity.mConfig.orientation; 227 final int numOfConfig = activity.mNumOfConfigChanges; 228 229 // Try to apply an old configuration change. 230 applyConfigurationChange(activity, BASE_SEQ - 1); 231 assertEquals(numOfConfig, activity.mNumOfConfigChanges); 232 assertEquals(orientation, activity.mConfig.orientation); 233 }); 234 } 235 236 @Test testHandleActivityConfigurationChanged_ApplyNewConfigurations()237 public void testHandleActivityConfigurationChanged_ApplyNewConfigurations() { 238 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 239 240 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 241 // Set the sequence number to BASE_SEQ and record the final sequence number it used. 242 final int seq = applyConfigurationChange(activity, BASE_SEQ); 243 244 final int orientation = activity.mConfig.orientation; 245 final int numOfConfig = activity.mNumOfConfigChanges; 246 247 // Try to apply an new configuration change. 248 applyConfigurationChange(activity, seq + 1); 249 assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); 250 assertNotEquals(orientation, activity.mConfig.orientation); 251 }); 252 } 253 254 @Test testHandleActivityConfigurationChanged_SkipWhenNewerConfigurationPending()255 public void testHandleActivityConfigurationChanged_SkipWhenNewerConfigurationPending() { 256 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 257 258 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 259 // Set the sequence number to BASE_SEQ and record the final sequence number it used. 260 final int seq = applyConfigurationChange(activity, BASE_SEQ); 261 262 final int orientation = activity.mConfig.orientation; 263 final int numOfConfig = activity.mNumOfConfigChanges; 264 265 final ActivityThread activityThread = activity.getActivityThread(); 266 267 final Configuration newerConfig = new Configuration(); 268 newerConfig.orientation = orientation == ORIENTATION_LANDSCAPE 269 ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; 270 newerConfig.seq = seq + 2; 271 final ActivityClientRecord r = getActivityClientRecord(activity); 272 activityThread.updatePendingActivityConfiguration(r, newerConfig); 273 274 final Configuration olderConfig = new Configuration(); 275 olderConfig.orientation = orientation; 276 olderConfig.seq = seq + 1; 277 278 activityThread.handleActivityConfigurationChanged(r, olderConfig, INVALID_DISPLAY); 279 assertEquals(numOfConfig, activity.mNumOfConfigChanges); 280 assertEquals(olderConfig.orientation, activity.mConfig.orientation); 281 282 activityThread.handleActivityConfigurationChanged(r, newerConfig, INVALID_DISPLAY); 283 assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges); 284 assertEquals(newerConfig.orientation, activity.mConfig.orientation); 285 }); 286 } 287 288 @Test testHandleActivityConfigurationChanged_EnsureUpdatesProcessedInOrder()289 public void testHandleActivityConfigurationChanged_EnsureUpdatesProcessedInOrder() 290 throws Exception { 291 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 292 293 final ActivityThread activityThread = activity.getActivityThread(); 294 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 295 final Configuration config = new Configuration(); 296 config.seq = BASE_SEQ; 297 config.orientation = ORIENTATION_PORTRAIT; 298 299 activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity), 300 config, INVALID_DISPLAY); 301 }); 302 303 final IApplicationThread appThread = activityThread.getApplicationThread(); 304 final int numOfConfig = activity.mNumOfConfigChanges; 305 306 final Configuration processConfigLandscape = new Configuration(); 307 processConfigLandscape.orientation = ORIENTATION_LANDSCAPE; 308 processConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 60)); 309 processConfigLandscape.seq = BASE_SEQ + 1; 310 311 final Configuration activityConfigLandscape = new Configuration(); 312 activityConfigLandscape.orientation = ORIENTATION_LANDSCAPE; 313 activityConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 50)); 314 activityConfigLandscape.seq = BASE_SEQ + 2; 315 316 final Configuration processConfigPortrait = new Configuration(); 317 processConfigPortrait.orientation = ORIENTATION_PORTRAIT; 318 processConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 60, 100)); 319 processConfigPortrait.seq = BASE_SEQ + 3; 320 321 final Configuration activityConfigPortrait = new Configuration(); 322 activityConfigPortrait.orientation = ORIENTATION_PORTRAIT; 323 activityConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 50, 100)); 324 activityConfigPortrait.seq = BASE_SEQ + 4; 325 326 activity.mConfigLatch = new CountDownLatch(1); 327 activity.mTestLatch = new CountDownLatch(1); 328 329 ClientTransaction transaction = newTransaction(activityThread, null); 330 transaction.addCallback(ConfigurationChangeItem.obtain(processConfigLandscape)); 331 appThread.scheduleTransaction(transaction); 332 333 transaction = newTransaction(activityThread, activity.getActivityToken()); 334 transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigLandscape)); 335 transaction.addCallback(ConfigurationChangeItem.obtain(processConfigPortrait)); 336 transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigPortrait)); 337 appThread.scheduleTransaction(transaction); 338 339 activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS); 340 activity.mConfigLatch.countDown(); 341 342 activity.mConfigLatch = null; 343 activity.mTestLatch = null; 344 345 // Check display metrics, bounds should match the portrait activity bounds. 346 final Rect bounds = activity.getWindowManager().getCurrentWindowMetrics().getBounds(); 347 assertEquals(activityConfigPortrait.windowConfiguration.getBounds(), bounds); 348 349 // Ensure that Activity#onConfigurationChanged() not be called because the changes in 350 // WindowConfiguration shouldn't be reported, and we only apply the latest Configuration 351 // update in transaction. 352 assertEquals(numOfConfig, activity.mNumOfConfigChanges); 353 } 354 355 @Test testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration()356 public void testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration() 357 throws Exception { 358 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 359 360 final ActivityThread activityThread = activity.getActivityThread(); 361 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 362 final Configuration config = new Configuration(); 363 config.seq = BASE_SEQ; 364 config.orientation = ORIENTATION_PORTRAIT; 365 366 activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity), 367 config, INVALID_DISPLAY); 368 }); 369 370 final int numOfConfig = activity.mNumOfConfigChanges; 371 final IApplicationThread appThread = activityThread.getApplicationThread(); 372 373 activity.mConfigLatch = new CountDownLatch(1); 374 activity.mTestLatch = new CountDownLatch(1); 375 376 Configuration config = new Configuration(); 377 config.seq = BASE_SEQ + 1; 378 config.orientation = ORIENTATION_LANDSCAPE; 379 appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); 380 381 // Wait until the main thread is performing the configuration change for the configuration 382 // with sequence number BASE_SEQ + 1 before proceeding. This is to mimic the situation where 383 // the activity takes very long time to process configuration changes. 384 activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS); 385 386 config = new Configuration(); 387 config.seq = BASE_SEQ + 2; 388 config.orientation = ORIENTATION_PORTRAIT; 389 appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); 390 391 config = new Configuration(); 392 config.seq = BASE_SEQ + 3; 393 config.orientation = ORIENTATION_LANDSCAPE; 394 appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); 395 396 config = new Configuration(); 397 config.seq = BASE_SEQ + 4; 398 config.orientation = ORIENTATION_PORTRAIT; 399 appThread.scheduleTransaction(newActivityConfigTransaction(activity, config)); 400 401 activity.mConfigLatch.countDown(); 402 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 403 404 activity.mConfigLatch = null; 405 activity.mTestLatch = null; 406 407 // Only two more configuration changes: one with seq BASE_SEQ + 1; another with seq 408 // BASE_SEQ + 4. Configurations scheduled in between should be dropped. 409 assertEquals(numOfConfig + 2, activity.mNumOfConfigChanges); 410 assertEquals(ORIENTATION_PORTRAIT, activity.mConfig.orientation); 411 } 412 413 @Test testOrientationChanged_DoesntOverrideVirtualDisplayOrientation()414 public void testOrientationChanged_DoesntOverrideVirtualDisplayOrientation() { 415 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 416 final ActivityThread activityThread = activity.getActivityThread(); 417 418 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 419 Context appContext = activity.getApplication(); 420 Configuration originalAppConfig = 421 new Configuration(appContext.getResources().getConfiguration()); 422 423 int virtualDisplayWidth; 424 int virtualDisplayHeight; 425 if (originalAppConfig.orientation == ORIENTATION_PORTRAIT) { 426 virtualDisplayWidth = 100; 427 virtualDisplayHeight = 200; 428 } else { 429 virtualDisplayWidth = 200; 430 virtualDisplayHeight = 100; 431 } 432 final Display virtualDisplay = createVirtualDisplay(appContext, 433 virtualDisplayWidth, virtualDisplayHeight); 434 Context virtualDisplayContext = appContext.createDisplayContext(virtualDisplay); 435 int originalVirtualDisplayOrientation = virtualDisplayContext.getResources() 436 .getConfiguration().orientation; 437 438 439 // Perform global config change and verify there is no config change in derived display 440 // context. 441 Configuration newAppConfig = new Configuration(originalAppConfig); 442 newAppConfig.seq++; 443 newAppConfig.orientation = newAppConfig.orientation == ORIENTATION_PORTRAIT 444 ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; 445 446 activityThread.updatePendingConfiguration(newAppConfig); 447 activityThread.handleConfigurationChanged(newAppConfig); 448 449 try { 450 assertEquals("Virtual display orientation must not change when process" 451 + " configuration orientation changes.", 452 originalVirtualDisplayOrientation, 453 virtualDisplayContext.getResources().getConfiguration().orientation); 454 } finally { 455 // Make sure to reset the process config to prevent side effects to other 456 // tests. 457 Configuration activityThreadConfig = activityThread.getConfiguration(); 458 activityThreadConfig.seq = originalAppConfig.seq - 1; 459 460 Configuration resourceManagerConfig = ResourcesManager.getInstance() 461 .getConfiguration(); 462 resourceManagerConfig.seq = originalAppConfig.seq - 1; 463 464 activityThread.updatePendingConfiguration(originalAppConfig); 465 activityThread.handleConfigurationChanged(originalAppConfig); 466 } 467 }); 468 } 469 470 @Test testActivityOrientationChanged_DoesntOverrideVirtualDisplayOrientation()471 public void testActivityOrientationChanged_DoesntOverrideVirtualDisplayOrientation() { 472 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 473 final ActivityThread activityThread = activity.getActivityThread(); 474 475 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 476 Configuration originalActivityConfig = 477 new Configuration(activity.getResources().getConfiguration()); 478 479 int virtualDisplayWidth; 480 int virtualDisplayHeight; 481 if (originalActivityConfig.orientation == ORIENTATION_PORTRAIT) { 482 virtualDisplayWidth = 100; 483 virtualDisplayHeight = 200; 484 } else { 485 virtualDisplayWidth = 200; 486 virtualDisplayHeight = 100; 487 } 488 final Display virtualDisplay = createVirtualDisplay(activity, 489 virtualDisplayWidth, virtualDisplayHeight); 490 Context virtualDisplayContext = activity.createDisplayContext(virtualDisplay); 491 int originalVirtualDisplayOrientation = virtualDisplayContext.getResources() 492 .getConfiguration().orientation; 493 494 // Perform activity config change and verify there is no config change in derived 495 // display context. 496 Configuration newActivityConfig = new Configuration(originalActivityConfig); 497 newActivityConfig.seq++; 498 newActivityConfig.orientation = newActivityConfig.orientation == ORIENTATION_PORTRAIT 499 ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; 500 501 final ActivityClientRecord r = getActivityClientRecord(activity); 502 activityThread.updatePendingActivityConfiguration(r, newActivityConfig); 503 activityThread.handleActivityConfigurationChanged(r, newActivityConfig, 504 INVALID_DISPLAY); 505 506 assertEquals("Virtual display orientation must not change when activity" 507 + " configuration orientation changes.", 508 originalVirtualDisplayOrientation, 509 virtualDisplayContext.getResources().getConfiguration().orientation); 510 }); 511 } 512 513 @Test 514 @FlakyTest(bugId = 176134235) testHandleConfigurationChanged_DoesntOverrideActivityConfig()515 public void testHandleConfigurationChanged_DoesntOverrideActivityConfig() { 516 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 517 518 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 519 final Configuration oldActivityConfig = 520 new Configuration(activity.getResources().getConfiguration()); 521 final DisplayMetrics oldActivityMetrics = new DisplayMetrics(); 522 activity.getDisplay().getMetrics(oldActivityMetrics); 523 final Resources oldAppResources = activity.getApplication().getResources(); 524 final Configuration oldAppConfig = 525 new Configuration(oldAppResources.getConfiguration()); 526 final DisplayMetrics oldApplicationMetrics = new DisplayMetrics(); 527 oldApplicationMetrics.setTo(oldAppResources.getDisplayMetrics()); 528 assertEquals("Process config must match the top activity config by default", 529 0, oldActivityConfig.diffPublicOnly(oldAppConfig)); 530 assertEquals("Process config must match the top activity config by default", 531 oldActivityMetrics, oldApplicationMetrics); 532 533 // Update the application configuration separately from activity config 534 final Configuration newAppConfig = new Configuration(oldAppConfig); 535 newAppConfig.densityDpi += 100; 536 newAppConfig.screenHeightDp += 100; 537 final Rect newBounds = new Rect(newAppConfig.windowConfiguration.getAppBounds()); 538 newBounds.bottom += 100; 539 newAppConfig.windowConfiguration.setAppBounds(newBounds); 540 newAppConfig.windowConfiguration.setBounds(newBounds); 541 newAppConfig.seq++; 542 543 final ActivityThread activityThread = activity.getActivityThread(); 544 activityThread.handleConfigurationChanged(newAppConfig); 545 546 // Verify that application config update was applied, but didn't change activity config. 547 assertEquals("Activity config must not change if the process config changes", 548 oldActivityConfig, activity.getResources().getConfiguration()); 549 550 final DisplayMetrics newActivityMetrics = new DisplayMetrics(); 551 activity.getDisplay().getMetrics(newActivityMetrics); 552 assertEquals("Activity display size must not change if the process config changes", 553 oldActivityMetrics, newActivityMetrics); 554 final Resources newAppResources = activity.getApplication().getResources(); 555 assertEquals("Application config must be updated", 556 newAppConfig, newAppResources.getConfiguration()); 557 final DisplayMetrics newApplicationMetrics = new DisplayMetrics(); 558 newApplicationMetrics.setTo(newAppResources.getDisplayMetrics()); 559 assertNotEquals("Application display size must be updated after config update", 560 oldApplicationMetrics, newApplicationMetrics); 561 assertNotEquals("Application display size must be updated after config update", 562 newActivityMetrics, newApplicationMetrics); 563 }); 564 } 565 566 @Test testHandleProcessConfigurationChanged_DependOnProcessState()567 public void testHandleProcessConfigurationChanged_DependOnProcessState() { 568 final ActivityThread activityThread = ActivityThread.currentActivityThread(); 569 final Configuration origConfig = activityThread.getConfiguration(); 570 final int newDpi = origConfig.densityDpi + 10; 571 final Configuration newConfig = new Configuration(origConfig); 572 newConfig.seq++; 573 newConfig.densityDpi = newDpi; 574 575 activityThread.updateProcessState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY, 576 false /* fromIPC */); 577 578 applyProcessConfiguration(activityThread, newConfig); 579 try { 580 // In the cached state, the configuration is only set as pending and not applied. 581 assertEquals(origConfig.densityDpi, activityThread.getConfiguration().densityDpi); 582 assertTrue(activityThread.isCachedProcessState()); 583 } finally { 584 // The foreground state is the default state of instrumentation. 585 activityThread.updateProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 586 false /* fromIPC */); 587 } 588 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 589 590 try { 591 // The state becomes non-cached, the pending configuration should be applied. 592 assertEquals(newConfig.densityDpi, activityThread.getConfiguration().densityDpi); 593 assertFalse(activityThread.isCachedProcessState()); 594 } finally { 595 // Restore to the original configuration. 596 activityThread.getConfiguration().seq = origConfig.seq - 1; 597 applyProcessConfiguration(activityThread, origConfig); 598 } 599 } 600 applyProcessConfiguration(ActivityThread thread, Configuration config)601 private static void applyProcessConfiguration(ActivityThread thread, Configuration config) { 602 final ClientTransaction clientTransaction = newTransaction(thread, 603 null /* activityToken */); 604 clientTransaction.addCallback(ConfigurationChangeItem.obtain(config)); 605 final IApplicationThread appThread = thread.getApplicationThread(); 606 try { 607 appThread.scheduleTransaction(clientTransaction); 608 } catch (Exception ignored) { 609 } 610 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 611 } 612 613 @Test testResumeAfterNewIntent()614 public void testResumeAfterNewIntent() { 615 final Activity activity = mActivityTestRule.launchActivity(new Intent()); 616 final ActivityThread activityThread = activity.getActivityThread(); 617 final ArrayList<ReferrerIntent> rIntents = new ArrayList<>(); 618 rIntents.add(new ReferrerIntent(new Intent(), "android.app.activity")); 619 620 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 621 activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, true)); 622 }); 623 assertThat(activity.isResumed()).isTrue(); 624 625 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 626 activityThread.executeTransaction(newStopTransaction(activity)); 627 activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, false)); 628 }); 629 assertThat(activity.isResumed()).isFalse(); 630 } 631 632 @Test testHandlePictureInPictureRequested_overriddenToEnter()633 public void testHandlePictureInPictureRequested_overriddenToEnter() { 634 final Intent startIntent = new Intent(); 635 startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_ENTER, true); 636 final TestActivity activity = mActivityTestRule.launchActivity(startIntent); 637 final ActivityThread activityThread = activity.getActivityThread(); 638 final ActivityClientRecord r = getActivityClientRecord(activity); 639 640 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 641 activityThread.handlePictureInPictureRequested(r); 642 }); 643 644 assertTrue(activity.pipRequested()); 645 assertTrue(activity.enteredPip()); 646 } 647 648 @Test testHandlePictureInPictureRequested_overriddenToSkip()649 public void testHandlePictureInPictureRequested_overriddenToSkip() { 650 final Intent startIntent = new Intent(); 651 startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_SKIP, true); 652 final TestActivity activity = mActivityTestRule.launchActivity(startIntent); 653 final ActivityThread activityThread = activity.getActivityThread(); 654 final ActivityClientRecord r = getActivityClientRecord(activity); 655 656 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 657 activityThread.handlePictureInPictureRequested(r); 658 }); 659 660 assertTrue(activity.pipRequested()); 661 assertTrue(activity.enterPipSkipped()); 662 } 663 664 @Test testHandlePictureInPictureRequested_notOverridden()665 public void testHandlePictureInPictureRequested_notOverridden() { 666 final TestActivity activity = mActivityTestRule.launchActivity(new Intent()); 667 final ActivityThread activityThread = activity.getActivityThread(); 668 final ActivityClientRecord r = getActivityClientRecord(activity); 669 670 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 671 activityThread.handlePictureInPictureRequested(r); 672 }); 673 674 assertTrue(activity.pipRequested()); 675 assertFalse(activity.enteredPip()); 676 assertFalse(activity.enterPipSkipped()); 677 } 678 679 /** 680 * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord, 681 * Configuration, int)} to try to push activity configuration to the activity for the given 682 * sequence number. 683 * <p> 684 * It uses orientation to push the configuration and it tries a different orientation if the 685 * first attempt doesn't make through, to rule out the possibility that the previous 686 * configuration already has the same orientation. 687 * 688 * @param activity the test target activity 689 * @param seq the specified sequence number 690 * @return the sequence number this method tried with the last time, so that the caller can use 691 * the next sequence number for next configuration update. 692 */ applyConfigurationChange(TestActivity activity, int seq)693 private int applyConfigurationChange(TestActivity activity, int seq) { 694 final ActivityThread activityThread = activity.getActivityThread(); 695 final ActivityClientRecord r = getActivityClientRecord(activity); 696 697 final int numOfConfig = activity.mNumOfConfigChanges; 698 Configuration config = new Configuration(); 699 config.orientation = ORIENTATION_PORTRAIT; 700 config.seq = seq; 701 activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY); 702 703 if (activity.mNumOfConfigChanges > numOfConfig) { 704 return config.seq; 705 } 706 707 config = new Configuration(); 708 config.orientation = ORIENTATION_LANDSCAPE; 709 config.seq = seq + 1; 710 activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY); 711 712 return config.seq; 713 } 714 createVirtualDisplay(Context context, int w, int h)715 private Display createVirtualDisplay(Context context, int w, int h) { 716 final DisplayManager dm = context.getSystemService(DisplayManager.class); 717 final VirtualDisplay virtualDisplay = dm.createVirtualDisplay("virtual-display", w, h, 718 200 /* densityDpi */, null /* surface */, 0 /* flags */); 719 if (mCreatedVirtualDisplays == null) { 720 mCreatedVirtualDisplays = new ArrayList<>(); 721 } 722 mCreatedVirtualDisplays.add(virtualDisplay); 723 return virtualDisplay.getDisplay(); 724 } 725 getActivityClientRecord(Activity activity)726 private static ActivityClientRecord getActivityClientRecord(Activity activity) { 727 final ActivityThread thread = activity.getActivityThread(); 728 final IBinder token = activity.getActivityToken(); 729 return thread.getActivityClient(token); 730 } 731 newRelaunchResumeTransaction(Activity activity)732 private static ClientTransaction newRelaunchResumeTransaction(Activity activity) { 733 final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null, 734 null, 0, new MergedConfiguration(), false /* preserveWindow */); 735 final ResumeActivityItem resumeStateRequest = 736 ResumeActivityItem.obtain(true /* isForward */); 737 738 final ClientTransaction transaction = newTransaction(activity); 739 transaction.addCallback(callbackItem); 740 transaction.setLifecycleStateRequest(resumeStateRequest); 741 742 return transaction; 743 } 744 newResumeTransaction(Activity activity)745 private static ClientTransaction newResumeTransaction(Activity activity) { 746 final ResumeActivityItem resumeStateRequest = 747 ResumeActivityItem.obtain(true /* isForward */); 748 749 final ClientTransaction transaction = newTransaction(activity); 750 transaction.setLifecycleStateRequest(resumeStateRequest); 751 752 return transaction; 753 } 754 newStopTransaction(Activity activity)755 private static ClientTransaction newStopTransaction(Activity activity) { 756 final StopActivityItem stopStateRequest = StopActivityItem.obtain(0 /* configChanges */); 757 758 final ClientTransaction transaction = newTransaction(activity); 759 transaction.setLifecycleStateRequest(stopStateRequest); 760 761 return transaction; 762 } 763 newActivityConfigTransaction(Activity activity, Configuration config)764 private static ClientTransaction newActivityConfigTransaction(Activity activity, 765 Configuration config) { 766 final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config); 767 768 final ClientTransaction transaction = newTransaction(activity); 769 transaction.addCallback(item); 770 771 return transaction; 772 } 773 newNewIntentTransaction(Activity activity, List<ReferrerIntent> intents, boolean resume)774 private static ClientTransaction newNewIntentTransaction(Activity activity, 775 List<ReferrerIntent> intents, boolean resume) { 776 final NewIntentItem item = NewIntentItem.obtain(intents, resume); 777 778 final ClientTransaction transaction = newTransaction(activity); 779 transaction.addCallback(item); 780 781 return transaction; 782 } 783 newTransaction(Activity activity)784 private static ClientTransaction newTransaction(Activity activity) { 785 return newTransaction(activity.getActivityThread(), activity.getActivityToken()); 786 } 787 newTransaction(ActivityThread activityThread, @Nullable IBinder activityToken)788 private static ClientTransaction newTransaction(ActivityThread activityThread, 789 @Nullable IBinder activityToken) { 790 return ClientTransaction.obtain(activityThread.getApplicationThread(), activityToken); 791 } 792 793 // Test activity 794 public static class TestActivity extends Activity { 795 static final String PIP_REQUESTED_OVERRIDE_ENTER = "pip_requested_override_enter"; 796 static final String PIP_REQUESTED_OVERRIDE_SKIP = "pip_requested_override_skip"; 797 798 int mNumOfConfigChanges; 799 final Configuration mConfig = new Configuration(); 800 801 private boolean mPipRequested; 802 private boolean mPipEntered; 803 private boolean mPipEnterSkipped; 804 805 /** 806 * A latch used to notify tests that we're about to wait for configuration latch. This 807 * is used to notify test code that preExecute phase for activity configuration change 808 * transaction has passed. 809 */ 810 volatile CountDownLatch mTestLatch; 811 /** 812 * If not {@code null} {@link #onConfigurationChanged(Configuration)} won't return until the 813 * latch reaches 0. 814 */ 815 volatile CountDownLatch mConfigLatch; 816 817 @Override onCreate(Bundle savedInstanceState)818 protected void onCreate(Bundle savedInstanceState) { 819 super.onCreate(savedInstanceState); 820 getWindow().getDecorView().setKeepScreenOn(true); 821 setShowWhenLocked(true); 822 setTurnScreenOn(true); 823 } 824 825 @Override onConfigurationChanged(Configuration config)826 public void onConfigurationChanged(Configuration config) { 827 super.onConfigurationChanged(config); 828 mConfig.setTo(config); 829 ++mNumOfConfigChanges; 830 831 if (mConfigLatch != null) { 832 if (mTestLatch != null) { 833 mTestLatch.countDown(); 834 } 835 try { 836 mConfigLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS); 837 } catch (InterruptedException e) { 838 throw new IllegalStateException(e); 839 } 840 } 841 } 842 843 @Override onPictureInPictureRequested()844 public boolean onPictureInPictureRequested() { 845 mPipRequested = true; 846 if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_ENTER, false)) { 847 enterPictureInPictureMode(new PictureInPictureParams.Builder().build()); 848 mPipEntered = true; 849 return true; 850 } else if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_SKIP, false)) { 851 mPipEnterSkipped = true; 852 return false; 853 } 854 return super.onPictureInPictureRequested(); 855 } 856 pipRequested()857 boolean pipRequested() { 858 return mPipRequested; 859 } 860 enteredPip()861 boolean enteredPip() { 862 return mPipEntered; 863 } 864 enterPipSkipped()865 boolean enterPipSkipped() { 866 return mPipEnterSkipped; 867 } 868 } 869 } 870