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