FragmentLifecycleTest.java revision 96cd95c37930d6d8f79ee8068991f9686c884f7a
1/* 2 * Copyright (C) 2016 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 18package android.support.v4.app; 19 20import android.content.Intent; 21import android.os.Bundle; 22import android.os.Parcelable; 23import android.support.annotation.NonNull; 24import android.support.annotation.Nullable; 25import android.support.test.annotation.UiThreadTest; 26import android.support.test.rule.ActivityTestRule; 27import android.support.test.runner.AndroidJUnit4; 28import android.support.v4.app.test.EmptyFragmentTestActivity; 29import android.support.v4.test.R; 30import android.test.suitebuilder.annotation.MediumTest; 31import android.view.LayoutInflater; 32import android.view.View; 33import android.view.ViewGroup; 34import android.view.Window; 35 36import org.junit.Assert; 37import org.junit.Rule; 38import org.junit.Test; 39import org.junit.runner.RunWith; 40 41import java.io.FileDescriptor; 42import java.io.PrintWriter; 43 44import static junit.framework.Assert.assertEquals; 45import static junit.framework.Assert.assertFalse; 46import static junit.framework.Assert.assertNotNull; 47import static junit.framework.Assert.assertNotSame; 48import static junit.framework.Assert.assertNull; 49import static junit.framework.Assert.assertSame; 50import static junit.framework.Assert.assertTrue; 51import static org.junit.Assert.assertNotEquals; 52 53@RunWith(AndroidJUnit4.class) 54@MediumTest 55public class FragmentLifecycleTest { 56 57 @Rule 58 public ActivityTestRule<EmptyFragmentTestActivity> mActivityRule = 59 new ActivityTestRule<EmptyFragmentTestActivity>(EmptyFragmentTestActivity.class); 60 61 @Test 62 public void basicLifecycle() throws Throwable { 63 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 64 final StrictFragment strictFragment = new StrictFragment(); 65 66 // Add fragment; StrictFragment will throw if it detects any violation 67 // in standard lifecycle method ordering or expected preconditions. 68 fm.beginTransaction().add(strictFragment, "EmptyHeadless").commit(); 69 executePendingTransactions(fm); 70 71 assertTrue("fragment is not added", strictFragment.isAdded()); 72 assertFalse("fragment is detached", strictFragment.isDetached()); 73 assertTrue("fragment is not resumed", strictFragment.isResumed()); 74 75 // Test removal as well; StrictFragment will throw here too. 76 fm.beginTransaction().remove(strictFragment).commit(); 77 executePendingTransactions(fm); 78 79 assertFalse("fragment is added", strictFragment.isAdded()); 80 assertFalse("fragment is resumed", strictFragment.isResumed()); 81 82 // This one is perhaps counterintuitive; "detached" means specifically detached 83 // but still managed by a FragmentManager. The .remove call above 84 // should not enter this state. 85 assertFalse("fragment is detached", strictFragment.isDetached()); 86 } 87 88 @Test 89 public void detachment() throws Throwable { 90 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 91 final StrictFragment f1 = new StrictFragment(); 92 final StrictFragment f2 = new StrictFragment(); 93 94 fm.beginTransaction().add(f1, "1").add(f2, "2").commit(); 95 executePendingTransactions(fm); 96 97 assertTrue("fragment 1 is not added", f1.isAdded()); 98 assertTrue("fragment 2 is not added", f2.isAdded()); 99 100 // Test detaching fragments using StrictFragment to throw on errors. 101 fm.beginTransaction().detach(f1).detach(f2).commit(); 102 executePendingTransactions(fm); 103 104 assertTrue("fragment 1 is not detached", f1.isDetached()); 105 assertTrue("fragment 2 is not detached", f2.isDetached()); 106 assertFalse("fragment 1 is added", f1.isAdded()); 107 assertFalse("fragment 2 is added", f2.isAdded()); 108 109 // Only reattach f1; leave v2 detached. 110 fm.beginTransaction().attach(f1).commit(); 111 executePendingTransactions(fm); 112 113 assertTrue("fragment 1 is not added", f1.isAdded()); 114 assertFalse("fragment 1 is detached", f1.isDetached()); 115 assertTrue("fragment 2 is not detached", f2.isDetached()); 116 117 // Remove both from the FragmentManager. 118 fm.beginTransaction().remove(f1).remove(f2).commit(); 119 executePendingTransactions(fm); 120 121 assertFalse("fragment 1 is added", f1.isAdded()); 122 assertFalse("fragment 2 is added", f2.isAdded()); 123 assertFalse("fragment 1 is detached", f1.isDetached()); 124 assertFalse("fragment 2 is detached", f2.isDetached()); 125 } 126 127 @Test 128 public void basicBackStack() throws Throwable { 129 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 130 final StrictFragment f1 = new StrictFragment(); 131 final StrictFragment f2 = new StrictFragment(); 132 133 // Add a fragment normally to set up 134 fm.beginTransaction().add(f1, "1").commit(); 135 executePendingTransactions(fm); 136 137 assertTrue("fragment 1 is not added", f1.isAdded()); 138 139 // Remove the first one and add a second. We're not using replace() here since 140 // these fragments are headless and as of this test writing, replace() only works 141 // for fragments with views and a container view id. 142 // Add it to the back stack so we can pop it afterwards. 143 fm.beginTransaction().remove(f1).add(f2, "2").addToBackStack("stack1").commit(); 144 executePendingTransactions(fm); 145 146 assertFalse("fragment 1 is added", f1.isAdded()); 147 assertTrue("fragment 2 is not added", f2.isAdded()); 148 149 // Test popping the stack 150 fm.popBackStack(); 151 executePendingTransactions(fm); 152 153 assertFalse("fragment 2 is added", f2.isAdded()); 154 assertTrue("fragment 1 is not added", f1.isAdded()); 155 } 156 157 @Test 158 public void attachBackStack() throws Throwable { 159 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 160 final StrictFragment f1 = new StrictFragment(); 161 final StrictFragment f2 = new StrictFragment(); 162 163 // Add a fragment normally to set up 164 fm.beginTransaction().add(f1, "1").commit(); 165 executePendingTransactions(fm); 166 167 assertTrue("fragment 1 is not added", f1.isAdded()); 168 169 fm.beginTransaction().detach(f1).add(f2, "2").addToBackStack("stack1").commit(); 170 executePendingTransactions(fm); 171 172 assertTrue("fragment 1 is not detached", f1.isDetached()); 173 assertFalse("fragment 2 is detached", f2.isDetached()); 174 assertFalse("fragment 1 is added", f1.isAdded()); 175 assertTrue("fragment 2 is not added", f2.isAdded()); 176 } 177 178 @Test 179 public void viewLifecycle() throws Throwable { 180 // Test basic lifecycle when the fragment creates a view 181 182 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 183 final StrictViewFragment f1 = new StrictViewFragment(); 184 185 fm.beginTransaction().add(android.R.id.content, f1).commit(); 186 executePendingTransactions(fm); 187 188 assertTrue("fragment 1 is not added", f1.isAdded()); 189 final View view = f1.getView(); 190 assertNotNull("fragment 1 returned null from getView", view); 191 assertTrue("fragment 1's view is not attached to a window", view.isAttachedToWindow()); 192 193 fm.beginTransaction().remove(f1).commit(); 194 executePendingTransactions(fm); 195 196 assertFalse("fragment 1 is added", f1.isAdded()); 197 assertNull("fragment 1 returned non-null from getView after removal", f1.getView()); 198 assertFalse("fragment 1's previous view is still attached to a window", 199 view.isAttachedToWindow()); 200 } 201 202 @Test 203 public void viewReplace() throws Throwable { 204 // Replace one view with another, then reverse it with the back stack 205 206 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 207 final StrictViewFragment f1 = new StrictViewFragment(); 208 final StrictViewFragment f2 = new StrictViewFragment(); 209 210 fm.beginTransaction().add(android.R.id.content, f1).commit(); 211 executePendingTransactions(fm); 212 213 assertTrue("fragment 1 is not added", f1.isAdded()); 214 215 View origView1 = f1.getView(); 216 assertNotNull("fragment 1 returned null view", origView1); 217 assertTrue("fragment 1's view not attached", origView1.isAttachedToWindow()); 218 219 fm.beginTransaction().replace(android.R.id.content, f2).addToBackStack("stack1").commit(); 220 executePendingTransactions(fm); 221 222 assertFalse("fragment 1 is added", f1.isAdded()); 223 assertTrue("fragment 2 is added", f2.isAdded()); 224 assertNull("fragment 1 returned non-null view", f1.getView()); 225 assertFalse("fragment 1's old view still attached", origView1.isAttachedToWindow()); 226 View origView2 = f2.getView(); 227 assertNotNull("fragment 2 returned null view", origView2); 228 assertTrue("fragment 2's view not attached", origView2.isAttachedToWindow()); 229 230 fm.popBackStack(); 231 executePendingTransactions(fm); 232 233 assertTrue("fragment 1 is not added", f1.isAdded()); 234 assertFalse("fragment 2 is added", f2.isAdded()); 235 assertNull("fragment 2 returned non-null view", f2.getView()); 236 assertFalse("fragment 2's view still attached", origView2.isAttachedToWindow()); 237 View newView1 = f1.getView(); 238 assertNotSame("fragment 1 had same view from last attachment", origView1, newView1); 239 assertTrue("fragment 1's view not attached", newView1.isAttachedToWindow()); 240 } 241 242 @Test 243 @UiThreadTest 244 public void restoreRetainedInstanceFragments() throws Throwable { 245 // Create a new FragmentManager in isolation, nest some assorted fragments 246 // and then restore them to a second new FragmentManager. 247 248 final FragmentController fc1 = FragmentController.createController( 249 new HostCallbacks(mActivityRule.getActivity())); 250 251 final FragmentManager fm1 = fc1.getSupportFragmentManager(); 252 253 fc1.attachHost(null); 254 fc1.dispatchCreate(); 255 256 // Configure fragments. 257 258 // Grandparent fragment will not retain instance 259 final StateSaveFragment grandparentFragment = new StateSaveFragment("Grandparent", 260 "UnsavedGrandparent"); 261 assertNotNull("grandparent fragment saved state not initialized", 262 grandparentFragment.getSavedState()); 263 assertNotNull("grandparent fragment unsaved state not initialized", 264 grandparentFragment.getUnsavedState()); 265 fm1.beginTransaction().add(grandparentFragment, "tag:grandparent").commitNow(); 266 267 // Parent fragment will retain instance 268 final StateSaveFragment parentFragment = new StateSaveFragment("Parent", "UnsavedParent"); 269 assertNotNull("parent fragment saved state not initialized", 270 parentFragment.getSavedState()); 271 assertNotNull("parent fragment unsaved state not initialized", 272 parentFragment.getUnsavedState()); 273 parentFragment.setRetainInstance(true); 274 grandparentFragment.getChildFragmentManager().beginTransaction() 275 .add(parentFragment, "tag:parent").commitNow(); 276 assertSame("parent fragment is not a child of grandparent", 277 grandparentFragment, parentFragment.getParentFragment()); 278 279 // Child fragment will not retain instance 280 final StateSaveFragment childFragment = new StateSaveFragment("Child", "UnsavedChild"); 281 assertNotNull("child fragment saved state not initialized", 282 childFragment.getSavedState()); 283 assertNotNull("child fragment unsaved state not initialized", 284 childFragment.getUnsavedState()); 285 parentFragment.getChildFragmentManager().beginTransaction() 286 .add(childFragment, "tag:child").commitNow(); 287 assertSame("child fragment is not a child of grandpanret", 288 parentFragment, childFragment.getParentFragment()); 289 290 // Saved for comparison later 291 final FragmentManager parentChildFragmentManager = parentFragment.getChildFragmentManager(); 292 293 fc1.dispatchActivityCreated(); 294 fc1.noteStateNotSaved(); 295 fc1.execPendingActions(); 296 fc1.doLoaderStart(); 297 fc1.dispatchStart(); 298 fc1.reportLoaderStart(); 299 fc1.dispatchResume(); 300 fc1.execPendingActions(); 301 302 // Bring the state back down to destroyed, simulating an activity restart 303 fc1.dispatchPause(); 304 final Parcelable savedState = fc1.saveAllState(); 305 final FragmentManagerNonConfig nonconf = fc1.retainNestedNonConfig(); 306 fc1.dispatchStop(); 307 fc1.dispatchReallyStop(); 308 fc1.dispatchDestroy(); 309 310 // Create the new controller and restore state 311 final FragmentController fc2 = FragmentController.createController( 312 new HostCallbacks(mActivityRule.getActivity())); 313 314 final FragmentManager fm2 = fc2.getSupportFragmentManager(); 315 316 fc2.attachHost(null); 317 fc2.restoreAllState(savedState, nonconf); 318 fc2.dispatchCreate(); 319 320 // Confirm that the restored fragments are available and in the expected states 321 final StateSaveFragment restoredGrandparent = (StateSaveFragment) fm2.findFragmentByTag( 322 "tag:grandparent"); 323 assertNotNull("grandparent fragment not restored", restoredGrandparent); 324 325 assertNotSame("grandparent fragment instance was saved", 326 grandparentFragment, restoredGrandparent); 327 assertEquals("grandparent fragment saved state was not equal", 328 grandparentFragment.getSavedState(), restoredGrandparent.getSavedState()); 329 assertNotEquals("grandparent fragment unsaved state was unexpectedly preserved", 330 grandparentFragment.getUnsavedState(), restoredGrandparent.getUnsavedState()); 331 332 final StateSaveFragment restoredParent = (StateSaveFragment) restoredGrandparent 333 .getChildFragmentManager().findFragmentByTag("tag:parent"); 334 assertNotNull("parent fragment not restored", restoredParent); 335 336 assertSame("parent fragment instance was not saved", parentFragment, restoredParent); 337 assertEquals("parent fragment saved state was not equal", 338 parentFragment.getSavedState(), restoredParent.getSavedState()); 339 assertEquals("parent fragment unsaved state was not equal", 340 parentFragment.getUnsavedState(), restoredParent.getUnsavedState()); 341 assertNotSame("parent fragment has the same child FragmentManager", 342 parentChildFragmentManager, restoredParent.getChildFragmentManager()); 343 344 final StateSaveFragment restoredChild = (StateSaveFragment) restoredParent 345 .getChildFragmentManager().findFragmentByTag("tag:child"); 346 assertNotNull("child fragment not restored", restoredChild); 347 348 assertNotSame("child fragment instance state was saved", childFragment, restoredChild); 349 assertEquals("child fragment saved state was not equal", 350 childFragment.getSavedState(), restoredChild.getSavedState()); 351 assertNotEquals("child fragment saved state was unexpectedly equal", 352 childFragment.getUnsavedState(), restoredChild.getUnsavedState()); 353 354 fc2.dispatchActivityCreated(); 355 fc2.noteStateNotSaved(); 356 fc2.execPendingActions(); 357 fc2.doLoaderStart(); 358 fc2.dispatchStart(); 359 fc2.reportLoaderStart(); 360 fc2.dispatchResume(); 361 fc2.execPendingActions(); 362 363 // Test that the fragments are in the configuration we expect 364 365 // Bring the state back down to destroyed before we finish the test 366 fc2.dispatchPause(); 367 fc2.saveAllState(); 368 fc2.dispatchStop(); 369 fc2.dispatchReallyStop(); 370 fc2.dispatchDestroy(); 371 372 assertTrue("grandparent not destroyed", restoredGrandparent.mCalledOnDestroy); 373 assertTrue("parent not destroyed", restoredParent.mCalledOnDestroy); 374 assertTrue("child not destroyed", restoredChild.mCalledOnDestroy); 375 } 376 377 @Test 378 @UiThreadTest 379 public void saveAnimationState() throws Throwable { 380 FragmentController fc = startupFragmentController(null); 381 FragmentManager fm = fc.getSupportFragmentManager(); 382 383 fm.beginTransaction() 384 .setCustomAnimations(0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out) 385 .add(android.R.id.content, SimpleFragment.create(R.layout.fragment_a)) 386 .addToBackStack(null) 387 .commit(); 388 fm.executePendingTransactions(); 389 390 assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out); 391 392 // Causes save and restore of fragments and back stack 393 fc = restartFragmentController(fc); 394 fm = fc.getSupportFragmentManager(); 395 396 assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out); 397 398 fm.beginTransaction() 399 .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, 0, 0) 400 .replace(android.R.id.content, SimpleFragment.create(R.layout.fragment_b)) 401 .addToBackStack(null) 402 .commit(); 403 fm.executePendingTransactions(); 404 405 assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0); 406 407 // Causes save and restore of fragments and back stack 408 fc = restartFragmentController(fc); 409 fm = fc.getSupportFragmentManager(); 410 411 assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0); 412 413 fm.popBackStackImmediate(); 414 415 assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out); 416 417 shutdownFragmentController(fc); 418 } 419 420 private void assertAnimationsMatch(FragmentManager fm, int enter, int exit, int popEnter, 421 int popExit) { 422 FragmentManagerImpl fmImpl = (FragmentManagerImpl) fm; 423 BackStackRecord record = fmImpl.mBackStack.get(fmImpl.mBackStack.size() - 1); 424 425 Assert.assertEquals(enter, record.mEnterAnim); 426 Assert.assertEquals(exit, record.mExitAnim); 427 Assert.assertEquals(popEnter, record.mPopEnterAnim); 428 Assert.assertEquals(popExit, record.mPopExitAnim); 429 } 430 431 private FragmentController restartFragmentController(FragmentController fc) { 432 Parcelable savedState = shutdownFragmentController(fc); 433 return startupFragmentController(savedState); 434 } 435 436 private FragmentController startupFragmentController(Parcelable savedState) { 437 final FragmentController fc = FragmentController.createController( 438 new HostCallbacks(mActivityRule.getActivity())); 439 fc.attachHost(null); 440 fc.restoreAllState(savedState, (FragmentManagerNonConfig) null); 441 fc.dispatchCreate(); 442 fc.dispatchActivityCreated(); 443 fc.noteStateNotSaved(); 444 fc.execPendingActions(); 445 fc.doLoaderStart(); 446 fc.dispatchStart(); 447 fc.reportLoaderStart(); 448 fc.dispatchResume(); 449 fc.execPendingActions(); 450 return fc; 451 } 452 453 private Parcelable shutdownFragmentController(FragmentController fc) { 454 fc.dispatchPause(); 455 final Parcelable savedState = fc.saveAllState(); 456 fc.dispatchStop(); 457 fc.dispatchReallyStop(); 458 fc.dispatchDestroy(); 459 return savedState; 460 } 461 462 private void executePendingTransactions(final FragmentManager fm) throws Throwable { 463 mActivityRule.runOnUiThread(new Runnable() { 464 @Override 465 public void run() { 466 fm.executePendingTransactions(); 467 } 468 }); 469 } 470 471 public static class StateSaveFragment extends StrictFragment { 472 private static final String STATE_KEY = "state"; 473 474 private String mSavedState; 475 private String mUnsavedState; 476 477 public StateSaveFragment() { 478 } 479 480 public StateSaveFragment(String savedState, String unsavedState) { 481 mSavedState = savedState; 482 mUnsavedState = unsavedState; 483 } 484 485 public String getSavedState() { 486 return mSavedState; 487 } 488 489 public String getUnsavedState() { 490 return mUnsavedState; 491 } 492 493 @Override 494 public void onCreate(Bundle savedInstanceState) { 495 super.onCreate(savedInstanceState); 496 if (savedInstanceState != null) { 497 mSavedState = savedInstanceState.getString(STATE_KEY); 498 } 499 } 500 501 @Override 502 public void onSaveInstanceState(Bundle outState) { 503 super.onSaveInstanceState(outState); 504 outState.putString(STATE_KEY, mSavedState); 505 } 506 } 507 508 static class HostCallbacks extends FragmentHostCallback<FragmentActivity> { 509 private final FragmentActivity mActivity; 510 511 public HostCallbacks(FragmentActivity activity) { 512 super(activity); 513 mActivity = activity; 514 } 515 516 @Override 517 public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 518 } 519 520 @Override 521 public boolean onShouldSaveFragmentState(Fragment fragment) { 522 return !mActivity.isFinishing(); 523 } 524 525 @Override 526 public LayoutInflater onGetLayoutInflater() { 527 return mActivity.getLayoutInflater().cloneInContext(mActivity); 528 } 529 530 @Override 531 public FragmentActivity onGetHost() { 532 return mActivity; 533 } 534 535 @Override 536 public void onSupportInvalidateOptionsMenu() { 537 mActivity.supportInvalidateOptionsMenu(); 538 } 539 540 @Override 541 public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode) { 542 mActivity.startActivityFromFragment(fragment, intent, requestCode); 543 } 544 545 @Override 546 public void onStartActivityFromFragment( 547 Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) { 548 mActivity.startActivityFromFragment(fragment, intent, requestCode, options); 549 } 550 551 @Override 552 public void onRequestPermissionsFromFragment(@NonNull Fragment fragment, 553 @NonNull String[] permissions, int requestCode) { 554 throw new UnsupportedOperationException(); 555 } 556 557 @Override 558 public boolean onShouldShowRequestPermissionRationale(@NonNull String permission) { 559 return ActivityCompat.shouldShowRequestPermissionRationale( 560 mActivity, permission); 561 } 562 563 @Override 564 public boolean onHasWindowAnimations() { 565 return mActivity.getWindow() != null; 566 } 567 568 @Override 569 public int onGetWindowAnimations() { 570 final Window w = mActivity.getWindow(); 571 return (w == null) ? 0 : w.getAttributes().windowAnimations; 572 } 573 574 @Override 575 public void onAttachFragment(Fragment fragment) { 576 mActivity.onAttachFragment(fragment); 577 } 578 579 @Nullable 580 @Override 581 public View onFindViewById(int id) { 582 return mActivity.findViewById(id); 583 } 584 585 @Override 586 public boolean onHasView() { 587 final Window w = mActivity.getWindow(); 588 return (w != null && w.peekDecorView() != null); 589 } 590 } 591 592 public static class SimpleFragment extends Fragment { 593 private int mLayoutId; 594 private static final String LAYOUT_ID = "layoutId"; 595 596 @Override 597 public void onCreate(Bundle savedInstanceState) { 598 super.onCreate(savedInstanceState); 599 if (savedInstanceState != null) { 600 mLayoutId = savedInstanceState.getInt(LAYOUT_ID, mLayoutId); 601 } 602 } 603 604 @Override 605 public void onSaveInstanceState(Bundle outState) { 606 super.onSaveInstanceState(outState); 607 outState.putInt(LAYOUT_ID, mLayoutId); 608 } 609 610 @Override 611 public View onCreateView(LayoutInflater inflater, ViewGroup container, 612 Bundle savedInstanceState) { 613 return inflater.inflate(mLayoutId, container, false); 614 } 615 616 public static SimpleFragment create(int layoutId) { 617 SimpleFragment fragment = new SimpleFragment(); 618 fragment.mLayoutId = layoutId; 619 return fragment; 620 } 621 } 622} 623