1 package org.robolectric.android.controller; 2 3 import static android.os.Build.VERSION_CODES.M; 4 import static android.os.Build.VERSION_CODES.N_MR1; 5 import static android.os.Build.VERSION_CODES.O_MR1; 6 import static android.os.Build.VERSION_CODES.P; 7 import static android.os.Build.VERSION_CODES.Q; 8 import static android.os.Build.VERSION_CODES.TIRAMISU; 9 import static com.google.common.base.Preconditions.checkNotNull; 10 import static org.robolectric.shadow.api.Shadow.extract; 11 import static org.robolectric.util.reflector.Reflector.reflector; 12 13 import android.app.Activity; 14 import android.app.Application; 15 import android.app.Instrumentation; 16 import android.content.ComponentName; 17 import android.content.Context; 18 import android.content.Intent; 19 import android.content.pm.ActivityInfo; 20 import android.content.pm.ActivityInfo.Config; 21 import android.content.pm.PackageManager; 22 import android.content.res.Configuration; 23 import android.os.Bundle; 24 import android.util.DisplayMetrics; 25 import android.view.Display; 26 import android.view.ViewRootImpl; 27 import android.view.WindowManager; 28 import com.google.errorprone.annotations.CanIgnoreReturnValue; 29 import javax.annotation.Nullable; 30 import org.robolectric.RuntimeEnvironment; 31 import org.robolectric.shadow.api.Shadow; 32 import org.robolectric.shadows.ShadowActivity; 33 import org.robolectric.shadows.ShadowContextThemeWrapper; 34 import org.robolectric.shadows.ShadowPackageManager; 35 import org.robolectric.shadows.ShadowViewRootImpl; 36 import org.robolectric.shadows._Activity_; 37 import org.robolectric.util.ReflectionHelpers; 38 import org.robolectric.util.ReflectionHelpers.ClassParameter; 39 import org.robolectric.util.reflector.Accessor; 40 import org.robolectric.util.reflector.ForType; 41 import org.robolectric.util.reflector.WithType; 42 43 /** 44 * ActivityController provides low-level APIs to control activity's lifecycle. 45 * 46 * <p>Using ActivityController directly from your tests is strongly discouraged. You have to call 47 * all the lifecycle callback methods (create, postCreate, start, ...) in the same manner as the 48 * Android framework by yourself otherwise you'll see fidelity issues. Consider using {@link 49 * androidx.test.core.app.ActivityScenario} instead, which provides higher-level, streamlined APIs 50 * to control the lifecycle and it works with instrumentation tests too. 51 * 52 * @param <T> a class of the activity which is under control by this class. 53 */ 54 @SuppressWarnings("NewApi") 55 public class ActivityController<T extends Activity> 56 extends ComponentController<ActivityController<T>, T> implements AutoCloseable { 57 58 enum LifecycleState { 59 INITIAL, 60 CREATED, 61 RESTARTED, 62 STARTED, 63 RESUMED, 64 PAUSED, 65 STOPPED, 66 DESTROYED 67 } 68 69 // ActivityInfo constant. 70 private static final int CONFIG_WINDOW_CONFIGURATION = 0x20000000; 71 72 private _Activity_ _component_; 73 private LifecycleState currentState = LifecycleState.INITIAL; 74 of( T activity, Intent intent, @Nullable Bundle activityOptions)75 public static <T extends Activity> ActivityController<T> of( 76 T activity, Intent intent, @Nullable Bundle activityOptions) { 77 return new ActivityController<>(activity, intent).attach(activityOptions); 78 } 79 of(T activity, Intent intent)80 public static <T extends Activity> ActivityController<T> of(T activity, Intent intent) { 81 return new ActivityController<>(activity, intent).attach(/* activityOptions= */ null); 82 } 83 of(T activity)84 public static <T extends Activity> ActivityController<T> of(T activity) { 85 return new ActivityController<>(activity, null).attach(/* activityOptions= */ null); 86 } 87 ActivityController(T activity, Intent intent)88 private ActivityController(T activity, Intent intent) { 89 super(activity, intent); 90 91 _component_ = reflector(_Activity_.class, component); 92 } 93 attach(@ullable Bundle activityOptions)94 private ActivityController<T> attach(@Nullable Bundle activityOptions) { 95 return attach( 96 activityOptions, /* lastNonConfigurationInstances= */ null, /* overrideConfig= */ null); 97 } 98 attach( @ullable Bundle activityOptions, @Nullable @WithType("android.app.Activity$NonConfigurationInstances") Object lastNonConfigurationInstances, @Nullable Configuration overrideConfig)99 private ActivityController<T> attach( 100 @Nullable Bundle activityOptions, 101 @Nullable @WithType("android.app.Activity$NonConfigurationInstances") 102 Object lastNonConfigurationInstances, 103 @Nullable Configuration overrideConfig) { 104 if (attached) { 105 return this; 106 } 107 // make sure the component is enabled 108 Context context = RuntimeEnvironment.getApplication().getBaseContext(); 109 PackageManager packageManager = context.getPackageManager(); 110 ComponentName componentName = 111 new ComponentName(context.getPackageName(), this.component.getClass().getName()); 112 ((ShadowPackageManager) extract(packageManager)).addActivityIfNotPresent(componentName); 113 packageManager.setComponentEnabledSetting( 114 componentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); 115 ShadowActivity shadowActivity = Shadow.extract(component); 116 shadowActivity.callAttach( 117 getIntent(), activityOptions, lastNonConfigurationInstances, overrideConfig); 118 shadowActivity.attachController(this); 119 attached = true; 120 return this; 121 } 122 getActivityInfo(Application application)123 private ActivityInfo getActivityInfo(Application application) { 124 PackageManager packageManager = application.getPackageManager(); 125 ComponentName componentName = 126 new ComponentName(application.getPackageName(), this.component.getClass().getName()); 127 try { 128 return packageManager.getActivityInfo(componentName, PackageManager.GET_META_DATA); 129 } catch (PackageManager.NameNotFoundException e) { 130 throw new RuntimeException(e); 131 } 132 } 133 create(@ullable final Bundle bundle)134 public ActivityController<T> create(@Nullable final Bundle bundle) { 135 shadowMainLooper.runPaused( 136 () -> { 137 getInstrumentation().callActivityOnCreate(component, bundle); 138 currentState = LifecycleState.CREATED; 139 }); 140 return this; 141 } 142 143 @Override create()144 public ActivityController<T> create() { 145 return create(null); 146 } 147 restart()148 public ActivityController<T> restart() { 149 invokeWhilePaused( 150 () -> { 151 if (RuntimeEnvironment.getApiLevel() <= O_MR1) { 152 _component_.performRestart(); 153 } else if (RuntimeEnvironment.getApiLevel() <= TIRAMISU) { 154 _component_.performRestart(true, "restart()"); 155 } else { 156 _component_.performRestart(true); 157 } 158 currentState = LifecycleState.RESTARTED; 159 }); 160 return this; 161 } 162 start()163 public ActivityController<T> start() { 164 // Start and stop are tricky cases. Unlike other lifecycle methods such as 165 // Instrumentation#callActivityOnPause calls Activity#performPause, Activity#performStop calls 166 // Instrumentation#callActivityOnStop internally so the dependency direction is the opposite. 167 invokeWhilePaused( 168 () -> { 169 if (RuntimeEnvironment.getApiLevel() <= O_MR1) { 170 _component_.performStart(); 171 } else { 172 _component_.performStart("start()"); 173 } 174 currentState = LifecycleState.STARTED; 175 }); 176 return this; 177 } 178 restoreInstanceState(Bundle bundle)179 public ActivityController<T> restoreInstanceState(Bundle bundle) { 180 shadowMainLooper.runPaused( 181 () -> getInstrumentation().callActivityOnRestoreInstanceState(component, bundle)); 182 return this; 183 } 184 postCreate(@ullable Bundle bundle)185 public ActivityController<T> postCreate(@Nullable Bundle bundle) { 186 invokeWhilePaused(() -> _component_.onPostCreate(bundle)); 187 return this; 188 } 189 resume()190 public ActivityController<T> resume() { 191 invokeWhilePaused( 192 () -> { 193 if (RuntimeEnvironment.getApiLevel() <= O_MR1) { 194 _component_.performResume(); 195 } else { 196 _component_.performResume(true, "resume()"); 197 } 198 currentState = LifecycleState.RESUMED; 199 }); 200 return this; 201 } 202 postResume()203 public ActivityController<T> postResume() { 204 invokeWhilePaused(() -> _component_.onPostResume()); 205 return this; 206 } 207 208 /** 209 * Calls the same lifecycle methods on the Activity called by Android when an Activity is the top 210 * most resumed activity on Q+. 211 */ 212 @CanIgnoreReturnValue topActivityResumed(boolean isTop)213 public ActivityController<T> topActivityResumed(boolean isTop) { 214 if (RuntimeEnvironment.getApiLevel() < Q) { 215 return this; 216 } 217 invokeWhilePaused( 218 () -> _component_.performTopResumedActivityChanged(isTop, "topStateChangedWhenResumed")); 219 return this; 220 } 221 visible()222 public ActivityController<T> visible() { 223 shadowMainLooper.runPaused( 224 () -> { 225 // emulate logic of ActivityThread#handleResumeActivity 226 component.getWindow().getAttributes().type = 227 WindowManager.LayoutParams.TYPE_BASE_APPLICATION; 228 _component_.setDecor(component.getWindow().getDecorView()); 229 _component_.makeVisible(); 230 }); 231 232 shadowMainLooper.idleIfPaused(); 233 ViewRootImpl root = getViewRoot(); 234 // root can be null if activity does not have content attached, or if looper is paused. 235 // this is unusual but leave the check here for legacy compatibility 236 if (root != null) { 237 shadowMainLooper.idleIfPaused(); 238 } 239 return this; 240 } 241 getViewRoot()242 private ViewRootImpl getViewRoot() { 243 return component.getWindow().getDecorView().getViewRootImpl(); 244 } 245 callDispatchResized(ViewRootImpl root)246 private void callDispatchResized(ViewRootImpl root) { 247 ((ShadowViewRootImpl) extract(root)).callDispatchResized(); 248 } 249 windowFocusChanged(boolean hasFocus)250 public ActivityController<T> windowFocusChanged(boolean hasFocus) { 251 ViewRootImpl root = getViewRoot(); 252 if (root == null) { 253 // root can be null if looper was paused during visible. Flush the looper and try again 254 shadowMainLooper.idle(); 255 256 root = checkNotNull(getViewRoot()); 257 callDispatchResized(root); 258 } 259 260 ((ShadowViewRootImpl) extract(root)).callWindowFocusChanged(hasFocus); 261 262 shadowMainLooper.idleIfPaused(); 263 return this; 264 } 265 userLeaving()266 public ActivityController<T> userLeaving() { 267 shadowMainLooper.runPaused(() -> getInstrumentation().callActivityOnUserLeaving(component)); 268 return this; 269 } 270 pause()271 public ActivityController<T> pause() { 272 shadowMainLooper.runPaused( 273 () -> { 274 getInstrumentation().callActivityOnPause(component); 275 currentState = LifecycleState.PAUSED; 276 }); 277 return this; 278 } 279 saveInstanceState(Bundle outState)280 public ActivityController<T> saveInstanceState(Bundle outState) { 281 shadowMainLooper.runPaused( 282 () -> getInstrumentation().callActivityOnSaveInstanceState(component, outState)); 283 return this; 284 } 285 stop()286 public ActivityController<T> stop() { 287 // Stop and start are tricky cases. Unlike other lifecycle methods such as 288 // Instrumentation#callActivityOnPause calls Activity#performPause, Activity#performStop calls 289 // Instrumentation#callActivityOnStop internally so the dependency direction is the opposite. 290 invokeWhilePaused( 291 () -> { 292 if (RuntimeEnvironment.getApiLevel() <= M) { 293 _component_.performStop(); 294 } else if (RuntimeEnvironment.getApiLevel() <= O_MR1) { 295 _component_.performStop(true); 296 } else { 297 _component_.performStop(true, "stop()"); 298 } 299 currentState = LifecycleState.STOPPED; 300 }); 301 return this; 302 } 303 304 @Override destroy()305 public ActivityController<T> destroy() { 306 shadowMainLooper.runPaused( 307 () -> { 308 getInstrumentation().callActivityOnDestroy(component); 309 makeActivityEligibleForGc(); 310 currentState = LifecycleState.DESTROYED; 311 }); 312 return this; 313 } 314 makeActivityEligibleForGc()315 private void makeActivityEligibleForGc() { 316 // Clear WindowManager state for this activity. On real Android this is done by 317 // ActivityThread.handleDestroyActivity, which is initiated by the window manager 318 // service. 319 boolean windowAdded = _component_.getWindowAdded(); 320 if (windowAdded) { 321 WindowManager windowManager = component.getWindowManager(); 322 windowManager.removeViewImmediate(component.getWindow().getDecorView()); 323 } 324 if (RuntimeEnvironment.getApiLevel() >= O_MR1) { 325 // Starting Android O_MR1, there is a leak in Android where `ContextImpl` holds on to the 326 // activity after being destroyed. This "fixes" the leak in Robolectric only, and will be 327 // properly fixed in Android S. 328 component.setAutofillClient(null); 329 } 330 } 331 332 /** 333 * Calls the same lifecycle methods on the Activity called by Android the first time the Activity 334 * is created. 335 * 336 * @return Activity controller instance. 337 */ setup()338 public ActivityController<T> setup() { 339 return create().start().postCreate(null).resume().visible().topActivityResumed(true); 340 } 341 342 /** 343 * Calls the same lifecycle methods on the Activity called by Android when an Activity is restored 344 * from previously saved state. 345 * 346 * @param savedInstanceState Saved instance state. 347 * @return Activity controller instance. 348 */ setup(Bundle savedInstanceState)349 public ActivityController<T> setup(Bundle savedInstanceState) { 350 return create(savedInstanceState) 351 .start() 352 .restoreInstanceState(savedInstanceState) 353 .postCreate(savedInstanceState) 354 .resume() 355 .visible() 356 .topActivityResumed(true); 357 } 358 newIntent(Intent intent)359 public ActivityController<T> newIntent(Intent intent) { 360 invokeWhilePaused(() -> _component_.onNewIntent(intent)); 361 return this; 362 } 363 364 /** 365 * Performs a configuration change on the Activity. See {@link #configurationChange(Configuration, 366 * DisplayMetrics, int)}. The configuration is taken from the application's configuration. 367 */ 368 @CanIgnoreReturnValue configurationChange()369 public ActivityController<T> configurationChange() { 370 return configurationChange(component.getApplicationContext().getResources().getConfiguration()); 371 } 372 373 /** 374 * Performs a configuration change on the Activity. See {@link #configurationChange(Configuration, 375 * DisplayMetrics, int)}. The changed configuration is calculated based on the activity's existing 376 * configuration. 377 */ 378 @CanIgnoreReturnValue configurationChange(final Configuration newConfiguration)379 public ActivityController<T> configurationChange(final Configuration newConfiguration) { 380 return configurationChange(newConfiguration, component.getResources().getDisplayMetrics()); 381 } 382 383 /** 384 * Performs a configuration change on the Activity. 385 * 386 * <p>If the activity is configured to handle changes without being recreated, {@link 387 * Activity#onConfigurationChanged(Configuration)} will be called. Otherwise, the activity is 388 * recreated as described <a 389 * href="https://developer.android.com/guide/topics/resources/runtime-changes.html">here</a>. 390 * 391 * <p>Typically configuration should be applied using {@link RuntimeEnvironment#setQualifiers} and 392 * then propagated to the activity controller, e.g. 393 * 394 * <pre>{@code 395 * RuntimeEnvironment.setQualifiers("+ar-rXB"); 396 * activityController.configurationChange(); 397 * }</pre> 398 * 399 * @param newConfiguration The new configuration to be set. 400 * @return ActivityController instance 401 */ 402 @CanIgnoreReturnValue configurationChange( Configuration newConfiguration, DisplayMetrics newMetrics)403 public ActivityController<T> configurationChange( 404 Configuration newConfiguration, DisplayMetrics newMetrics) { 405 ActivityReflector activityReflector = reflector(ActivityReflector.class, component); 406 Configuration currentConfig = 407 System.getProperty("robolectric.configurationChangeFix", "true").equals("true") 408 ? activityReflector.getCurrentConfig() 409 : component.getResources().getConfiguration(); 410 return configurationChange(newConfiguration, newMetrics, currentConfig.diff(newConfiguration)); 411 } 412 413 /** 414 * Performs a configuration change on the Activity. 415 * 416 * <p>If the activity is configured to handle changes without being recreated, {@link 417 * Activity#onConfigurationChanged(Configuration)} will be called. Otherwise, the activity is 418 * recreated as described <a 419 * href="https://developer.android.com/guide/topics/resources/runtime-changes.html">here</a>. 420 * 421 * <p>Typically configuration should be applied using {@link RuntimeEnvironment#setQualifiers} and 422 * then propagated to the activity controller, e.g. 423 * 424 * <pre>{@code 425 * Resources resources = RuntimeEnvironment.getApplication().getResources(); 426 * Configuration oldConfig = new Configuration(resources.getConfiguration()); 427 * RuntimeEnvironment.setQualifiers("+ar-rXB"); 428 * Configuration newConfig = resources.getConfiguration(); 429 * activityController.configurationChange( 430 * newConfig, resources.getDisplayMetrics(), oldConfig.diff(newConfig)); 431 * }</pre> 432 * 433 * @param newConfiguration The new configuration to be set. 434 * @param changedConfig The changed configuration properties bitmask (e.g. the result of calling 435 * {@link Configuration#diff(Configuration)}). This will be used to determine whether the 436 * activity handles the configuration change or not, and whether it must be recreated. 437 * @return ActivityController instance 438 * @deprecated The config change should be calculated internally by the activity controller based 439 * on the previous configuration, use {@link #configurationChange(Configuration, 440 * DisplayMetrics)} instead. 441 */ 442 @Deprecated 443 @CanIgnoreReturnValue configurationChange( Configuration newConfiguration, DisplayMetrics newMetrics, @Config int changedConfig)444 public ActivityController<T> configurationChange( 445 Configuration newConfiguration, DisplayMetrics newMetrics, @Config int changedConfig) { 446 component.getResources().updateConfiguration(newConfiguration, newMetrics); 447 448 int filteredChanges = filterConfigChanges(changedConfig); 449 // TODO: throw on changedConfig == 0 since it non-intuitively calls onConfigurationChanged 450 451 // Can the activity handle itself ALL configuration changes? 452 if ((getActivityInfo(component.getApplication()).configChanges & filteredChanges) 453 == filteredChanges) { 454 shadowMainLooper.runPaused( 455 () -> { 456 reflector(ActivityReflector.class, component) 457 .getCurrentConfig() 458 .setTo(newConfiguration); 459 component.onConfigurationChanged(newConfiguration); 460 ViewRootImpl root = getViewRoot(); 461 if (root != null) { 462 if (RuntimeEnvironment.getApiLevel() <= N_MR1) { 463 ReflectionHelpers.callInstanceMethod( 464 root, 465 "updateConfiguration", 466 ClassParameter.from(Configuration.class, newConfiguration), 467 ClassParameter.from(boolean.class, false)); 468 } else { 469 root.updateConfiguration(Display.INVALID_DISPLAY); 470 } 471 } 472 }); 473 474 return this; 475 } else { 476 @SuppressWarnings("unchecked") 477 final T recreatedActivity = (T) ReflectionHelpers.callConstructor(component.getClass()); 478 final _Activity_ _recreatedActivity_ = reflector(_Activity_.class, recreatedActivity); 479 480 shadowMainLooper.runPaused( 481 () -> { 482 // Set flags 483 _component_.setChangingConfigurations(true); 484 _component_.setConfigChangeFlags(filteredChanges); 485 486 // Perform activity destruction 487 final Bundle outState = new Bundle(); 488 489 // The order of onPause/onStop/onSaveInstanceState is undefined, but is usually: 490 // onPause -> onSaveInstanceState -> onStop before API P, and onPause -> onStop -> 491 // onSaveInstanceState from API P. 492 // See 493 // https://developer.android.com/reference/android/app/Activity#onSaveInstanceState(android.os.Bundle) for documentation explained. 494 // And see ActivityThread#callActivityOnStop for related code. 495 getInstrumentation().callActivityOnPause(component); 496 if (RuntimeEnvironment.getApiLevel() < P) { 497 _component_.performSaveInstanceState(outState); 498 if (RuntimeEnvironment.getApiLevel() <= M) { 499 _component_.performStop(); 500 } else { 501 // API from N to O_MR1(both including) 502 _component_.performStop(true); 503 } 504 } else { 505 _component_.performStop(true, "configurationChange"); 506 _component_.performSaveInstanceState(outState); 507 } 508 509 // This is the true and complete retained state, including loaders and retained 510 // fragments. 511 final Object nonConfigInstance = _component_.retainNonConfigurationInstances(); 512 // This is the activity's "user" state 513 final Object activityConfigInstance = 514 nonConfigInstance == null 515 ? null // No framework or user state. 516 : reflector(_NonConfigurationInstances_.class, nonConfigInstance).getActivity(); 517 518 getInstrumentation().callActivityOnDestroy(component); 519 makeActivityEligibleForGc(); 520 521 // Restore theme in case it was set in the test manually. 522 // This is not technically what happens but is purely to make this easier to use in 523 // Robolectric. 524 ShadowContextThemeWrapper shadowContextThemeWrapper = Shadow.extract(component); 525 int theme = shadowContextThemeWrapper.callGetThemeResId(); 526 527 // Setup controller for the new activity 528 attached = false; 529 component = recreatedActivity; 530 _component_ = _recreatedActivity_; 531 532 // TODO: Because robolectric is currently not creating unique context objects per 533 // activity and that the app copmat framework uses weak maps to cache resources per 534 // context the caches end up with stale objects between activity creations (which would 535 // typically be flushed by an onConfigurationChanged when running in real android). To 536 // workaround this we can invoke a gc after running the configuration change and 537 // destroying the old activity which will flush the object references from the weak 538 // maps (the side effect otherwise is flaky tests that behave differently based on when 539 // garbage collection last happened to run). 540 // This should be removed when robolectric.createActivityContexts is enabled. 541 System.gc(); 542 543 // TODO: Pass nonConfigurationInstance here instead of setting 544 // mLastNonConfigurationInstances directly below. This field must be set before 545 // attach. Since current implementation sets it after attach(), initialization is not 546 // done correctly. For instance, fragment marked as retained is not retained. 547 attach( 548 /* activityOptions= */ null, 549 /* lastNonConfigurationInstances= */ null, 550 newConfiguration); 551 552 if (theme != 0) { 553 recreatedActivity.setTheme(theme); 554 } 555 556 // Set saved non config instance 557 _recreatedActivity_.setLastNonConfigurationInstances(nonConfigInstance); 558 ShadowActivity shadowActivity = Shadow.extract(recreatedActivity); 559 shadowActivity.setLastNonConfigurationInstance(activityConfigInstance); 560 561 // Create lifecycle 562 getInstrumentation().callActivityOnCreate(recreatedActivity, outState); 563 564 if (RuntimeEnvironment.getApiLevel() <= O_MR1) { 565 566 _recreatedActivity_.performStart(); 567 568 } else { 569 _recreatedActivity_.performStart("configurationChange"); 570 } 571 572 getInstrumentation().callActivityOnRestoreInstanceState(recreatedActivity, outState); 573 _recreatedActivity_.onPostCreate(outState); 574 if (RuntimeEnvironment.getApiLevel() <= O_MR1) { 575 _recreatedActivity_.performResume(); 576 } else { 577 _recreatedActivity_.performResume(true, "configurationChange"); 578 } 579 _recreatedActivity_.onPostResume(); 580 // TODO: Call visible() too. 581 if (RuntimeEnvironment.getApiLevel() >= Q) { 582 _recreatedActivity_.performTopResumedActivityChanged(true, "configurationChange"); 583 } 584 }); 585 } 586 587 return this; 588 } 589 590 /** 591 * Recreates activity instance which is controlled by this ActivityController. 592 * NonConfigurationInstances and savedInstanceStateBundle are properly passed into a new instance. 593 * After the recreation, it brings back its lifecycle state to the original state. The activity 594 * should not be destroyed yet. 595 */ 596 @SuppressWarnings("unchecked") recreate()597 public ActivityController<T> recreate() { 598 599 LifecycleState originalState = currentState; 600 601 switch (originalState) { 602 case INITIAL: 603 create(); 604 // fall through 605 case CREATED: 606 case RESTARTED: 607 start(); 608 postCreate(null); 609 // fall through 610 case STARTED: 611 resume(); 612 // fall through 613 default: 614 // fall through 615 } 616 617 // Activity#mChangingConfigurations flag should be set prior to Activity recreation process 618 // starts. ActivityThread does set it on real device but here we simulate the Activity 619 // recreation process on behalf of ActivityThread so set the flag here. Note we don't need to 620 // reset the flag to false because this Activity instance is going to be destroyed and disposed. 621 // https://android.googlesource.com/platform/frameworks/base/+/55418eada51d4f5e6532ae9517af66c50 622 // ea495c4/core/java/android/app/ActivityThread.java#4806 623 _component_.setChangingConfigurations(true); 624 625 switch (originalState) { 626 case INITIAL: 627 case CREATED: 628 case RESTARTED: 629 case STARTED: 630 case RESUMED: 631 pause(); 632 // fall through 633 case PAUSED: 634 stop(); 635 // fall through 636 case STOPPED: 637 break; 638 default: 639 throw new IllegalStateException("Cannot recreate activity since it's destroyed already"); 640 } 641 642 Bundle outState = new Bundle(); 643 saveInstanceState(outState); 644 Object lastNonConfigurationInstances = _component_.retainNonConfigurationInstances(); 645 Configuration overrideConfig = component.getResources().getConfiguration(); 646 destroy(); 647 648 component = (T) ReflectionHelpers.callConstructor(component.getClass()); 649 _component_ = reflector(_Activity_.class, component); 650 attached = false; 651 attach(/* activityOptions= */ null, lastNonConfigurationInstances, overrideConfig); 652 create(outState); 653 start(); 654 restoreInstanceState(outState); 655 postCreate(outState); 656 resume(); 657 postResume(); 658 visible(); 659 windowFocusChanged(true); 660 topActivityResumed(true); 661 662 // Move back to the original stage. If the original stage was transient stage, it will bring it 663 // to resumed state to match the on device behavior. 664 switch (originalState) { 665 case PAUSED: 666 pause(); 667 return this; 668 case STOPPED: 669 pause(); 670 stop(); 671 return this; 672 default: 673 return this; 674 } 675 } 676 677 // Get the Instrumentation object scoped to the Activity. getInstrumentation()678 private Instrumentation getInstrumentation() { 679 return _component_.getInstrumentation(); 680 } 681 682 /** 683 * Transitions the underlying Activity to the 'destroyed' state by progressing through the 684 * appropriate lifecycle events. It frees up any resources and makes the Activity eligible for GC. 685 */ 686 @Override close()687 public void close() { 688 689 LifecycleState originalState = currentState; 690 691 switch (originalState) { 692 case INITIAL: 693 case DESTROYED: 694 return; 695 case RESUMED: 696 pause(); 697 // fall through 698 case PAUSED: 699 // fall through 700 case RESTARTED: 701 // fall through 702 case STARTED: 703 stop(); 704 // fall through 705 case STOPPED: 706 // fall through 707 case CREATED: 708 break; 709 } 710 711 destroy(); 712 } 713 714 // See ActivityRecord#getConfigurationChanges for the config changes that are considered for 715 // activity recreation by the window manager. filterConfigChanges(int changedConfig)716 private static int filterConfigChanges(int changedConfig) { 717 // We don't want window configuration to cause relaunches. 718 if ((changedConfig & CONFIG_WINDOW_CONFIGURATION) != 0) { 719 changedConfig &= ~CONFIG_WINDOW_CONFIGURATION; 720 } 721 return changedConfig; 722 } 723 724 /** Accessor interface for android.app.Activity.NonConfigurationInstances's internals. */ 725 @ForType(className = "android.app.Activity$NonConfigurationInstances") 726 interface _NonConfigurationInstances_ { 727 728 @Accessor("activity") getActivity()729 Object getActivity(); 730 } 731 732 @ForType(Activity.class) 733 interface ActivityReflector { 734 @Accessor("mCurrentConfig") getCurrentConfig()735 Configuration getCurrentConfig(); 736 } 737 } 738