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