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 18package androidx.fragment.app; 19 20import static org.junit.Assert.assertEquals; 21import static org.junit.Assert.assertFalse; 22import static org.junit.Assert.assertNotEquals; 23import static org.junit.Assert.assertNotNull; 24import static org.junit.Assert.assertNotSame; 25import static org.junit.Assert.assertNull; 26import static org.junit.Assert.assertSame; 27import static org.junit.Assert.assertTrue; 28import static org.junit.Assert.fail; 29import static org.mockito.Mockito.mock; 30import static org.mockito.Mockito.times; 31import static org.mockito.Mockito.verify; 32 33import android.content.Context; 34import android.content.Intent; 35import android.os.Build; 36import android.os.Bundle; 37import android.os.Parcelable; 38import android.support.test.InstrumentationRegistry; 39import android.support.test.annotation.UiThreadTest; 40import android.support.test.filters.MediumTest; 41import android.support.test.filters.SdkSuppress; 42import android.support.test.rule.ActivityTestRule; 43import android.support.test.runner.AndroidJUnit4; 44import android.util.Pair; 45import android.view.LayoutInflater; 46import android.view.Menu; 47import android.view.View; 48import android.view.ViewGroup; 49import android.view.Window; 50import android.widget.TextView; 51 52import androidx.annotation.NonNull; 53import androidx.annotation.Nullable; 54import androidx.core.app.ActivityCompat; 55import androidx.core.view.ViewCompat; 56import androidx.fragment.app.test.EmptyFragmentTestActivity; 57import androidx.fragment.app.test.FragmentTestActivity; 58import androidx.fragment.test.R; 59 60import org.junit.Assert; 61import org.junit.Rule; 62import org.junit.Test; 63import org.junit.runner.RunWith; 64 65import java.io.FileDescriptor; 66import java.io.PrintWriter; 67import java.util.concurrent.TimeUnit; 68 69@RunWith(AndroidJUnit4.class) 70@MediumTest 71public class FragmentLifecycleTest { 72 73 @Rule 74 public ActivityTestRule<EmptyFragmentTestActivity> mActivityRule = 75 new ActivityTestRule<EmptyFragmentTestActivity>(EmptyFragmentTestActivity.class); 76 77 @Test 78 public void basicLifecycle() throws Throwable { 79 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 80 final StrictFragment strictFragment = new StrictFragment(); 81 82 // Add fragment; StrictFragment will throw if it detects any violation 83 // in standard lifecycle method ordering or expected preconditions. 84 fm.beginTransaction().add(strictFragment, "EmptyHeadless").commit(); 85 executePendingTransactions(fm); 86 87 assertTrue("fragment is not added", strictFragment.isAdded()); 88 assertFalse("fragment is detached", strictFragment.isDetached()); 89 assertTrue("fragment is not resumed", strictFragment.isResumed()); 90 91 // Test removal as well; StrictFragment will throw here too. 92 fm.beginTransaction().remove(strictFragment).commit(); 93 executePendingTransactions(fm); 94 95 assertFalse("fragment is added", strictFragment.isAdded()); 96 assertFalse("fragment is resumed", strictFragment.isResumed()); 97 98 // This one is perhaps counterintuitive; "detached" means specifically detached 99 // but still managed by a FragmentManager. The .remove call above 100 // should not enter this state. 101 assertFalse("fragment is detached", strictFragment.isDetached()); 102 } 103 104 @Test 105 public void detachment() throws Throwable { 106 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 107 final StrictFragment f1 = new StrictFragment(); 108 final StrictFragment f2 = new StrictFragment(); 109 110 fm.beginTransaction().add(f1, "1").add(f2, "2").commit(); 111 executePendingTransactions(fm); 112 113 assertTrue("fragment 1 is not added", f1.isAdded()); 114 assertTrue("fragment 2 is not added", f2.isAdded()); 115 116 // Test detaching fragments using StrictFragment to throw on errors. 117 fm.beginTransaction().detach(f1).detach(f2).commit(); 118 executePendingTransactions(fm); 119 120 assertTrue("fragment 1 is not detached", f1.isDetached()); 121 assertTrue("fragment 2 is not detached", f2.isDetached()); 122 assertFalse("fragment 1 is added", f1.isAdded()); 123 assertFalse("fragment 2 is added", f2.isAdded()); 124 125 // Only reattach f1; leave v2 detached. 126 fm.beginTransaction().attach(f1).commit(); 127 executePendingTransactions(fm); 128 129 assertTrue("fragment 1 is not added", f1.isAdded()); 130 assertFalse("fragment 1 is detached", f1.isDetached()); 131 assertTrue("fragment 2 is not detached", f2.isDetached()); 132 133 // Remove both from the FragmentManager. 134 fm.beginTransaction().remove(f1).remove(f2).commit(); 135 executePendingTransactions(fm); 136 137 assertFalse("fragment 1 is added", f1.isAdded()); 138 assertFalse("fragment 2 is added", f2.isAdded()); 139 assertFalse("fragment 1 is detached", f1.isDetached()); 140 assertFalse("fragment 2 is detached", f2.isDetached()); 141 } 142 143 @Test 144 public void basicBackStack() throws Throwable { 145 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 146 final StrictFragment f1 = new StrictFragment(); 147 final StrictFragment f2 = new StrictFragment(); 148 149 // Add a fragment normally to set up 150 fm.beginTransaction().add(f1, "1").commit(); 151 executePendingTransactions(fm); 152 153 assertTrue("fragment 1 is not added", f1.isAdded()); 154 155 // Remove the first one and add a second. We're not using replace() here since 156 // these fragments are headless and as of this test writing, replace() only works 157 // for fragments with views and a container view id. 158 // Add it to the back stack so we can pop it afterwards. 159 fm.beginTransaction().remove(f1).add(f2, "2").addToBackStack("stack1").commit(); 160 executePendingTransactions(fm); 161 162 assertFalse("fragment 1 is added", f1.isAdded()); 163 assertTrue("fragment 2 is not added", f2.isAdded()); 164 165 // Test popping the stack 166 fm.popBackStack(); 167 executePendingTransactions(fm); 168 169 assertFalse("fragment 2 is added", f2.isAdded()); 170 assertTrue("fragment 1 is not added", f1.isAdded()); 171 } 172 173 @Test 174 public void attachBackStack() throws Throwable { 175 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 176 final StrictFragment f1 = new StrictFragment(); 177 final StrictFragment f2 = new StrictFragment(); 178 179 // Add a fragment normally to set up 180 fm.beginTransaction().add(f1, "1").commit(); 181 executePendingTransactions(fm); 182 183 assertTrue("fragment 1 is not added", f1.isAdded()); 184 185 fm.beginTransaction().detach(f1).add(f2, "2").addToBackStack("stack1").commit(); 186 executePendingTransactions(fm); 187 188 assertTrue("fragment 1 is not detached", f1.isDetached()); 189 assertFalse("fragment 2 is detached", f2.isDetached()); 190 assertFalse("fragment 1 is added", f1.isAdded()); 191 assertTrue("fragment 2 is not added", f2.isAdded()); 192 } 193 194 @Test 195 public void viewLifecycle() throws Throwable { 196 // Test basic lifecycle when the fragment creates a view 197 198 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 199 final StrictViewFragment f1 = new StrictViewFragment(); 200 201 fm.beginTransaction().add(android.R.id.content, f1).commit(); 202 executePendingTransactions(fm); 203 204 assertTrue("fragment 1 is not added", f1.isAdded()); 205 final View view = f1.getView(); 206 assertNotNull("fragment 1 returned null from getView", view); 207 assertTrue("fragment 1's view is not attached to a window", 208 ViewCompat.isAttachedToWindow(view)); 209 210 fm.beginTransaction().remove(f1).commit(); 211 executePendingTransactions(fm); 212 213 assertFalse("fragment 1 is added", f1.isAdded()); 214 assertNull("fragment 1 returned non-null from getView after removal", f1.getView()); 215 assertFalse("fragment 1's previous view is still attached to a window", 216 ViewCompat.isAttachedToWindow(view)); 217 } 218 219 @Test 220 public void viewReplace() throws Throwable { 221 // Replace one view with another, then reverse it with the back stack 222 223 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 224 final StrictViewFragment f1 = new StrictViewFragment(); 225 final StrictViewFragment f2 = new StrictViewFragment(); 226 227 fm.beginTransaction().add(android.R.id.content, f1).commit(); 228 executePendingTransactions(fm); 229 230 assertTrue("fragment 1 is not added", f1.isAdded()); 231 232 View origView1 = f1.getView(); 233 assertNotNull("fragment 1 returned null view", origView1); 234 assertTrue("fragment 1's view not attached", ViewCompat.isAttachedToWindow(origView1)); 235 236 fm.beginTransaction().replace(android.R.id.content, f2).addToBackStack("stack1").commit(); 237 executePendingTransactions(fm); 238 239 assertFalse("fragment 1 is added", f1.isAdded()); 240 assertTrue("fragment 2 is added", f2.isAdded()); 241 assertNull("fragment 1 returned non-null view", f1.getView()); 242 assertFalse("fragment 1's old view still attached", 243 ViewCompat.isAttachedToWindow(origView1)); 244 View origView2 = f2.getView(); 245 assertNotNull("fragment 2 returned null view", origView2); 246 assertTrue("fragment 2's view not attached", ViewCompat.isAttachedToWindow(origView2)); 247 248 fm.popBackStack(); 249 executePendingTransactions(fm); 250 251 assertTrue("fragment 1 is not added", f1.isAdded()); 252 assertFalse("fragment 2 is added", f2.isAdded()); 253 assertNull("fragment 2 returned non-null view", f2.getView()); 254 assertFalse("fragment 2's view still attached", ViewCompat.isAttachedToWindow(origView2)); 255 View newView1 = f1.getView(); 256 assertNotSame("fragment 1 had same view from last attachment", origView1, newView1); 257 assertTrue("fragment 1's view not attached", ViewCompat.isAttachedToWindow(newView1)); 258 } 259 260 @Test 261 @UiThreadTest 262 public void setInitialSavedState() throws Throwable { 263 FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 264 265 // Add a StateSaveFragment 266 StateSaveFragment fragment = new StateSaveFragment("Saved", ""); 267 fm.beginTransaction().add(fragment, "tag").commit(); 268 executePendingTransactions(fm); 269 270 // Change the user visible hint before we save state 271 fragment.setUserVisibleHint(false); 272 273 // Save its state and remove it 274 Fragment.SavedState state = fm.saveFragmentInstanceState(fragment); 275 fm.beginTransaction().remove(fragment).commit(); 276 executePendingTransactions(fm); 277 278 // Create a new instance, calling setInitialSavedState 279 fragment = new StateSaveFragment("", ""); 280 fragment.setInitialSavedState(state); 281 282 // Add the new instance 283 fm.beginTransaction().add(fragment, "tag").commit(); 284 executePendingTransactions(fm); 285 286 assertEquals("setInitialSavedState did not restore saved state", 287 "Saved", fragment.getSavedState()); 288 assertEquals("setInitialSavedState did not restore user visible hint", 289 false, fragment.getUserVisibleHint()); 290 } 291 292 @Test 293 @UiThreadTest 294 public void setInitialSavedStateWithSetUserVisibleHint() throws Throwable { 295 FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 296 297 // Add a StateSaveFragment 298 StateSaveFragment fragment = new StateSaveFragment("Saved", ""); 299 fm.beginTransaction().add(fragment, "tag").commit(); 300 executePendingTransactions(fm); 301 302 // Save its state and remove it 303 Fragment.SavedState state = fm.saveFragmentInstanceState(fragment); 304 fm.beginTransaction().remove(fragment).commit(); 305 executePendingTransactions(fm); 306 307 // Create a new instance, calling setInitialSavedState 308 fragment = new StateSaveFragment("", ""); 309 fragment.setInitialSavedState(state); 310 311 // Change the user visible hint after we call setInitialSavedState 312 fragment.setUserVisibleHint(false); 313 314 // Add the new instance 315 fm.beginTransaction().add(fragment, "tag").commit(); 316 executePendingTransactions(fm); 317 318 assertEquals("setInitialSavedState did not restore saved state", 319 "Saved", fragment.getSavedState()); 320 assertEquals("setUserVisibleHint should override setInitialSavedState", 321 false, fragment.getUserVisibleHint()); 322 } 323 324 @Test 325 @UiThreadTest 326 public void restoreRetainedInstanceFragments() throws Throwable { 327 // Create a new FragmentManager in isolation, nest some assorted fragments 328 // and then restore them to a second new FragmentManager. 329 330 final FragmentController fc1 = FragmentController.createController( 331 new HostCallbacks(mActivityRule.getActivity())); 332 333 final FragmentManager fm1 = fc1.getSupportFragmentManager(); 334 335 fc1.attachHost(null); 336 fc1.dispatchCreate(); 337 338 // Configure fragments. 339 340 // Grandparent fragment will not retain instance 341 final StateSaveFragment grandparentFragment = new StateSaveFragment("Grandparent", 342 "UnsavedGrandparent"); 343 assertNotNull("grandparent fragment saved state not initialized", 344 grandparentFragment.getSavedState()); 345 assertNotNull("grandparent fragment unsaved state not initialized", 346 grandparentFragment.getUnsavedState()); 347 fm1.beginTransaction().add(grandparentFragment, "tag:grandparent").commitNow(); 348 349 // Parent fragment will retain instance 350 final StateSaveFragment parentFragment = new StateSaveFragment("Parent", "UnsavedParent"); 351 assertNotNull("parent fragment saved state not initialized", 352 parentFragment.getSavedState()); 353 assertNotNull("parent fragment unsaved state not initialized", 354 parentFragment.getUnsavedState()); 355 parentFragment.setRetainInstance(true); 356 grandparentFragment.getChildFragmentManager().beginTransaction() 357 .add(parentFragment, "tag:parent").commitNow(); 358 assertSame("parent fragment is not a child of grandparent", 359 grandparentFragment, parentFragment.getParentFragment()); 360 361 // Child fragment will not retain instance 362 final StateSaveFragment childFragment = new StateSaveFragment("Child", "UnsavedChild"); 363 assertNotNull("child fragment saved state not initialized", 364 childFragment.getSavedState()); 365 assertNotNull("child fragment unsaved state not initialized", 366 childFragment.getUnsavedState()); 367 parentFragment.getChildFragmentManager().beginTransaction() 368 .add(childFragment, "tag:child").commitNow(); 369 assertSame("child fragment is not a child of grandpanret", 370 parentFragment, childFragment.getParentFragment()); 371 372 // Saved for comparison later 373 final FragmentManager parentChildFragmentManager = parentFragment.getChildFragmentManager(); 374 375 fc1.dispatchActivityCreated(); 376 fc1.noteStateNotSaved(); 377 fc1.execPendingActions(); 378 fc1.dispatchStart(); 379 fc1.dispatchResume(); 380 fc1.execPendingActions(); 381 382 // Bring the state back down to destroyed, simulating an activity restart 383 fc1.dispatchPause(); 384 final Parcelable savedState = fc1.saveAllState(); 385 final FragmentManagerNonConfig nonconf = fc1.retainNestedNonConfig(); 386 fc1.dispatchStop(); 387 fc1.dispatchDestroy(); 388 389 // Create the new controller and restore state 390 final FragmentController fc2 = FragmentController.createController( 391 new HostCallbacks(mActivityRule.getActivity())); 392 393 final FragmentManager fm2 = fc2.getSupportFragmentManager(); 394 395 fc2.attachHost(null); 396 fc2.restoreAllState(savedState, nonconf); 397 fc2.dispatchCreate(); 398 399 // Confirm that the restored fragments are available and in the expected states 400 final StateSaveFragment restoredGrandparent = (StateSaveFragment) fm2.findFragmentByTag( 401 "tag:grandparent"); 402 assertNotNull("grandparent fragment not restored", restoredGrandparent); 403 404 assertNotSame("grandparent fragment instance was saved", 405 grandparentFragment, restoredGrandparent); 406 assertEquals("grandparent fragment saved state was not equal", 407 grandparentFragment.getSavedState(), restoredGrandparent.getSavedState()); 408 assertNotEquals("grandparent fragment unsaved state was unexpectedly preserved", 409 grandparentFragment.getUnsavedState(), restoredGrandparent.getUnsavedState()); 410 411 final StateSaveFragment restoredParent = (StateSaveFragment) restoredGrandparent 412 .getChildFragmentManager().findFragmentByTag("tag:parent"); 413 assertNotNull("parent fragment not restored", restoredParent); 414 415 assertSame("parent fragment instance was not saved", parentFragment, restoredParent); 416 assertEquals("parent fragment saved state was not equal", 417 parentFragment.getSavedState(), restoredParent.getSavedState()); 418 assertEquals("parent fragment unsaved state was not equal", 419 parentFragment.getUnsavedState(), restoredParent.getUnsavedState()); 420 assertNotSame("parent fragment has the same child FragmentManager", 421 parentChildFragmentManager, restoredParent.getChildFragmentManager()); 422 423 final StateSaveFragment restoredChild = (StateSaveFragment) restoredParent 424 .getChildFragmentManager().findFragmentByTag("tag:child"); 425 assertNotNull("child fragment not restored", restoredChild); 426 427 assertNotSame("child fragment instance state was saved", childFragment, restoredChild); 428 assertEquals("child fragment saved state was not equal", 429 childFragment.getSavedState(), restoredChild.getSavedState()); 430 assertNotEquals("child fragment saved state was unexpectedly equal", 431 childFragment.getUnsavedState(), restoredChild.getUnsavedState()); 432 433 fc2.dispatchActivityCreated(); 434 fc2.noteStateNotSaved(); 435 fc2.execPendingActions(); 436 fc2.dispatchStart(); 437 fc2.dispatchResume(); 438 fc2.execPendingActions(); 439 440 // Test that the fragments are in the configuration we expect 441 442 // Bring the state back down to destroyed before we finish the test 443 fc2.dispatchPause(); 444 fc2.saveAllState(); 445 fc2.dispatchStop(); 446 fc2.dispatchDestroy(); 447 448 assertTrue("grandparent not destroyed", restoredGrandparent.mCalledOnDestroy); 449 assertTrue("parent not destroyed", restoredParent.mCalledOnDestroy); 450 assertTrue("child not destroyed", restoredChild.mCalledOnDestroy); 451 } 452 453 @Test 454 @UiThreadTest 455 public void saveAnimationState() throws Throwable { 456 FragmentController fc = startupFragmentController(null); 457 FragmentManager fm = fc.getSupportFragmentManager(); 458 459 fm.beginTransaction() 460 .setCustomAnimations(0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out) 461 .add(android.R.id.content, SimpleFragment.create(R.layout.fragment_a)) 462 .addToBackStack(null) 463 .commit(); 464 fm.executePendingTransactions(); 465 466 assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out); 467 468 // Causes save and restore of fragments and back stack 469 fc = restartFragmentController(fc); 470 fm = fc.getSupportFragmentManager(); 471 472 assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out); 473 474 fm.beginTransaction() 475 .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, 0, 0) 476 .replace(android.R.id.content, SimpleFragment.create(R.layout.fragment_b)) 477 .addToBackStack(null) 478 .commit(); 479 fm.executePendingTransactions(); 480 481 assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0); 482 483 // Causes save and restore of fragments and back stack 484 fc = restartFragmentController(fc); 485 fm = fc.getSupportFragmentManager(); 486 487 assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0); 488 489 fm.popBackStackImmediate(); 490 491 assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out); 492 493 shutdownFragmentController(fc); 494 } 495 496 /** 497 * This test confirms that as long as a parent fragment has called super.onCreate, 498 * any child fragments added, committed and with transactions executed will be brought 499 * to at least the CREATED state by the time the parent fragment receives onCreateView. 500 * This means the child fragment will have received onAttach/onCreate. 501 */ 502 @Test 503 @UiThreadTest 504 public void childFragmentManagerAttach() throws Throwable { 505 FragmentController fc = FragmentController.createController( 506 new HostCallbacks(mActivityRule.getActivity())); 507 fc.attachHost(null); 508 fc.dispatchCreate(); 509 510 FragmentManager.FragmentLifecycleCallbacks 511 mockLc = mock(FragmentManager.FragmentLifecycleCallbacks.class); 512 FragmentManager.FragmentLifecycleCallbacks 513 mockRecursiveLc = mock(FragmentManager.FragmentLifecycleCallbacks.class); 514 515 FragmentManager fm = fc.getSupportFragmentManager(); 516 fm.registerFragmentLifecycleCallbacks(mockLc, false); 517 fm.registerFragmentLifecycleCallbacks(mockRecursiveLc, true); 518 519 ChildFragmentManagerFragment fragment = new ChildFragmentManagerFragment(); 520 fm.beginTransaction() 521 .add(android.R.id.content, fragment) 522 .commitNow(); 523 524 verify(mockLc, times(1)).onFragmentCreated(fm, fragment, null); 525 526 fc.dispatchActivityCreated(); 527 528 Fragment childFragment = fragment.getChildFragment(); 529 530 verify(mockLc, times(1)).onFragmentActivityCreated(fm, fragment, null); 531 verify(mockRecursiveLc, times(1)).onFragmentActivityCreated(fm, fragment, null); 532 verify(mockRecursiveLc, times(1)).onFragmentActivityCreated(fm, childFragment, null); 533 534 fc.dispatchStart(); 535 536 verify(mockLc, times(1)).onFragmentStarted(fm, fragment); 537 verify(mockRecursiveLc, times(1)).onFragmentStarted(fm, fragment); 538 verify(mockRecursiveLc, times(1)).onFragmentStarted(fm, childFragment); 539 540 fc.dispatchResume(); 541 542 verify(mockLc, times(1)).onFragmentResumed(fm, fragment); 543 verify(mockRecursiveLc, times(1)).onFragmentResumed(fm, fragment); 544 verify(mockRecursiveLc, times(1)).onFragmentResumed(fm, childFragment); 545 546 // Confirm that the parent fragment received onAttachFragment 547 assertTrue("parent fragment did not receive onAttachFragment", 548 fragment.mCalledOnAttachFragment); 549 550 fc.dispatchStop(); 551 552 verify(mockLc, times(1)).onFragmentStopped(fm, fragment); 553 verify(mockRecursiveLc, times(1)).onFragmentStopped(fm, fragment); 554 verify(mockRecursiveLc, times(1)).onFragmentStopped(fm, childFragment); 555 556 fc.dispatchDestroy(); 557 558 verify(mockLc, times(1)).onFragmentDestroyed(fm, fragment); 559 verify(mockRecursiveLc, times(1)).onFragmentDestroyed(fm, fragment); 560 verify(mockRecursiveLc, times(1)).onFragmentDestroyed(fm, childFragment); 561 } 562 563 /** 564 * This test checks that FragmentLifecycleCallbacks are invoked when expected. 565 */ 566 @Test 567 @UiThreadTest 568 public void fragmentLifecycleCallbacks() throws Throwable { 569 FragmentController fc = FragmentController.createController( 570 new HostCallbacks(mActivityRule.getActivity())); 571 fc.attachHost(null); 572 fc.dispatchCreate(); 573 574 FragmentManager fm = fc.getSupportFragmentManager(); 575 576 ChildFragmentManagerFragment fragment = new ChildFragmentManagerFragment(); 577 fm.beginTransaction() 578 .add(android.R.id.content, fragment) 579 .commitNow(); 580 581 fc.dispatchActivityCreated(); 582 583 fc.dispatchStart(); 584 fc.dispatchResume(); 585 586 // Confirm that the parent fragment received onAttachFragment 587 assertTrue("parent fragment did not receive onAttachFragment", 588 fragment.mCalledOnAttachFragment); 589 590 fc.dispatchStop(); 591 fc.dispatchDestroy(); 592 } 593 594 /** 595 * This tests that fragments call onDestroy when the activity finishes. 596 */ 597 @Test 598 @UiThreadTest 599 public void fragmentDestroyedOnFinish() throws Throwable { 600 FragmentController fc = startupFragmentController(null); 601 FragmentManager fm = fc.getSupportFragmentManager(); 602 603 StrictViewFragment fragmentA = StrictViewFragment.create(R.layout.fragment_a); 604 StrictViewFragment fragmentB = StrictViewFragment.create(R.layout.fragment_b); 605 fm.beginTransaction() 606 .add(android.R.id.content, fragmentA) 607 .commit(); 608 fm.executePendingTransactions(); 609 fm.beginTransaction() 610 .replace(android.R.id.content, fragmentB) 611 .addToBackStack(null) 612 .commit(); 613 fm.executePendingTransactions(); 614 shutdownFragmentController(fc); 615 assertTrue(fragmentB.mCalledOnDestroy); 616 assertTrue(fragmentA.mCalledOnDestroy); 617 } 618 619 // Make sure that executing transactions during activity lifecycle events 620 // is properly prevented. 621 @Test 622 public void preventReentrantCalls() throws Throwable { 623 testLifecycleTransitionFailure(StrictFragment.ATTACHED, StrictFragment.CREATED); 624 testLifecycleTransitionFailure(StrictFragment.CREATED, StrictFragment.ACTIVITY_CREATED); 625 testLifecycleTransitionFailure(StrictFragment.ACTIVITY_CREATED, StrictFragment.STARTED); 626 testLifecycleTransitionFailure(StrictFragment.STARTED, StrictFragment.RESUMED); 627 628 testLifecycleTransitionFailure(StrictFragment.RESUMED, StrictFragment.STARTED); 629 testLifecycleTransitionFailure(StrictFragment.STARTED, StrictFragment.CREATED); 630 testLifecycleTransitionFailure(StrictFragment.CREATED, StrictFragment.ATTACHED); 631 testLifecycleTransitionFailure(StrictFragment.ATTACHED, StrictFragment.DETACHED); 632 } 633 634 private void testLifecycleTransitionFailure(final int fromState, 635 final int toState) throws Throwable { 636 mActivityRule.runOnUiThread(new Runnable() { 637 @Override 638 public void run() { 639 final FragmentController fc1 = FragmentController.createController( 640 new HostCallbacks(mActivityRule.getActivity())); 641 FragmentTestUtil.resume(mActivityRule, fc1, null); 642 643 final FragmentManager fm1 = fc1.getSupportFragmentManager(); 644 645 final Fragment reentrantFragment = ReentrantFragment.create(fromState, toState); 646 647 fm1.beginTransaction() 648 .add(reentrantFragment, "reentrant") 649 .commit(); 650 try { 651 fm1.executePendingTransactions(); 652 } catch (IllegalStateException e) { 653 fail("An exception shouldn't happen when initially adding the fragment"); 654 } 655 656 // Now shut down the fragment controller. When fromState > toState, this should 657 // result in an exception 658 Pair<Parcelable, FragmentManagerNonConfig> savedState = null; 659 try { 660 savedState = FragmentTestUtil.destroy(mActivityRule, fc1); 661 if (fromState > toState) { 662 fail("Expected IllegalStateException when moving from " 663 + StrictFragment.stateToString(fromState) + " to " 664 + StrictFragment.stateToString(toState)); 665 } 666 } catch (IllegalStateException e) { 667 if (fromState < toState) { 668 fail("Unexpected IllegalStateException when moving from " 669 + StrictFragment.stateToString(fromState) + " to " 670 + StrictFragment.stateToString(toState)); 671 } 672 return; // test passed! 673 } 674 675 // now restore from saved state. This will be reached when 676 // fromState < toState. We want to catch the fragment while it 677 // is being restored as the fragment controller state is being brought up. 678 679 final FragmentController fc2 = FragmentController.createController( 680 new HostCallbacks(mActivityRule.getActivity())); 681 try { 682 FragmentTestUtil.resume(mActivityRule, fc2, savedState); 683 684 fail("Expected IllegalStateException when moving from " 685 + StrictFragment.stateToString(fromState) + " to " 686 + StrictFragment.stateToString(toState)); 687 } catch (IllegalStateException e) { 688 // expected, so the test passed! 689 } 690 } 691 }); 692 } 693 694 /** 695 * Test to ensure that when dispatch* is called that the fragment manager 696 * doesn't cause the contained fragment states to change even if no state changes. 697 */ 698 @Test 699 @UiThreadTest 700 public void noPrematureStateChange() throws Throwable { 701 FragmentController fc = startupFragmentController(null); 702 FragmentManager fm = fc.getSupportFragmentManager(); 703 704 fm.beginTransaction() 705 .add(new StrictFragment(), "1") 706 .commitNow(); 707 708 Parcelable savedState = shutdownFragmentController(fc); 709 fc = FragmentController.createController( 710 new HostCallbacks(mActivityRule.getActivity())); 711 712 fc.attachHost(null); 713 fc.dispatchCreate(); 714 fc.dispatchActivityCreated(); 715 fc.noteStateNotSaved(); 716 fc.execPendingActions(); 717 fc.dispatchStart(); 718 fc.dispatchResume(); 719 fc.restoreAllState(savedState, (FragmentManagerNonConfig) null); 720 fc.dispatchResume(); 721 fm = fc.getSupportFragmentManager(); 722 723 StrictFragment fragment1 = (StrictFragment) fm.findFragmentByTag("1"); 724 725 assertFalse(fragment1.mCalledOnResume); 726 } 727 728 @Test 729 @UiThreadTest 730 public void testIsStateSaved() throws Throwable { 731 FragmentController fc = startupFragmentController(null); 732 FragmentManager fm = fc.getSupportFragmentManager(); 733 734 Fragment f = new StrictFragment(); 735 fm.beginTransaction() 736 .add(f, "1") 737 .commitNow(); 738 739 assertFalse("fragment reported state saved while resumed", f.isStateSaved()); 740 741 fc.dispatchPause(); 742 fc.saveAllState(); 743 744 assertTrue("fragment reported state not saved after saveAllState", f.isStateSaved()); 745 746 fc.dispatchStop(); 747 748 assertTrue("fragment reported state not saved after stop", f.isStateSaved()); 749 750 fc.dispatchDestroy(); 751 752 assertFalse("fragment reported state saved after destroy", f.isStateSaved()); 753 } 754 755 @Test 756 @UiThreadTest 757 public void testSetArgumentsLifecycle() throws Throwable { 758 FragmentController fc = startupFragmentController(null); 759 FragmentManager fm = fc.getSupportFragmentManager(); 760 761 Fragment f = new StrictFragment(); 762 f.setArguments(new Bundle()); 763 764 fm.beginTransaction() 765 .add(f, "1") 766 .commitNow(); 767 768 f.setArguments(new Bundle()); 769 770 fc.dispatchPause(); 771 fc.saveAllState(); 772 773 boolean threw = false; 774 try { 775 f.setArguments(new Bundle()); 776 } catch (IllegalStateException ise) { 777 threw = true; 778 } 779 assertTrue("fragment allowed setArguments after state save", threw); 780 781 fc.dispatchStop(); 782 783 threw = false; 784 try { 785 f.setArguments(new Bundle()); 786 } catch (IllegalStateException ise) { 787 threw = true; 788 } 789 assertTrue("fragment allowed setArguments after stop", threw); 790 791 fc.dispatchDestroy(); 792 793 // Fully destroyed, so fragments have been removed. 794 f.setArguments(new Bundle()); 795 } 796 797 /* 798 * Test that target fragments are in a useful state when we restore them, even if they're 799 * on the back stack. 800 */ 801 802 @Test 803 @UiThreadTest 804 public void targetFragmentRestoreLifecycleStateBackStack() throws Throwable { 805 final FragmentController fc1 = FragmentController.createController( 806 new HostCallbacks(mActivityRule.getActivity())); 807 808 final FragmentManager fm1 = fc1.getSupportFragmentManager(); 809 810 fc1.attachHost(null); 811 fc1.dispatchCreate(); 812 813 final Fragment target = new TargetFragment(); 814 fm1.beginTransaction().add(target, "target").commitNow(); 815 816 final Fragment referrer = new ReferrerFragment(); 817 referrer.setTargetFragment(target, 0); 818 819 fm1.beginTransaction() 820 .remove(target) 821 .add(referrer, "referrer") 822 .addToBackStack(null) 823 .commit(); 824 825 fc1.dispatchActivityCreated(); 826 fc1.noteStateNotSaved(); 827 fc1.execPendingActions(); 828 fc1.dispatchStart(); 829 fc1.dispatchResume(); 830 fc1.execPendingActions(); 831 832 // Bring the state back down to destroyed, simulating an activity restart 833 fc1.dispatchPause(); 834 final Parcelable savedState = fc1.saveAllState(); 835 final FragmentManagerNonConfig nonconf = fc1.retainNestedNonConfig(); 836 fc1.dispatchStop(); 837 fc1.dispatchDestroy(); 838 839 final FragmentController fc2 = FragmentController.createController( 840 new HostCallbacks(mActivityRule.getActivity())); 841 final FragmentManager fm2 = fc2.getSupportFragmentManager(); 842 843 fc2.attachHost(null); 844 fc2.restoreAllState(savedState, nonconf); 845 fc2.dispatchCreate(); 846 847 fc2.dispatchActivityCreated(); 848 fc2.noteStateNotSaved(); 849 fc2.execPendingActions(); 850 fc2.dispatchStart(); 851 fc2.dispatchResume(); 852 fc2.execPendingActions(); 853 854 // Bring the state back down to destroyed before we finish the test 855 fc2.dispatchPause(); 856 fc2.saveAllState(); 857 fc2.dispatchStop(); 858 fc2.dispatchDestroy(); 859 } 860 861 @Test 862 @UiThreadTest 863 public void targetFragmentRestoreLifecycleStateManagerOrder() throws Throwable { 864 final FragmentController fc1 = FragmentController.createController( 865 new HostCallbacks(mActivityRule.getActivity())); 866 867 final FragmentManager fm1 = fc1.getSupportFragmentManager(); 868 869 fc1.attachHost(null); 870 fc1.dispatchCreate(); 871 872 final Fragment target1 = new TargetFragment(); 873 final Fragment referrer1 = new ReferrerFragment(); 874 referrer1.setTargetFragment(target1, 0); 875 876 fm1.beginTransaction().add(target1, "target1").add(referrer1, "referrer1").commitNow(); 877 878 final Fragment target2 = new TargetFragment(); 879 final Fragment referrer2 = new ReferrerFragment(); 880 referrer2.setTargetFragment(target2, 0); 881 882 // Order shouldn't matter. 883 fm1.beginTransaction().add(referrer2, "referrer2").add(target2, "target2").commitNow(); 884 885 fc1.dispatchActivityCreated(); 886 fc1.noteStateNotSaved(); 887 fc1.execPendingActions(); 888 fc1.dispatchStart(); 889 fc1.dispatchResume(); 890 fc1.execPendingActions(); 891 892 // Bring the state back down to destroyed, simulating an activity restart 893 fc1.dispatchPause(); 894 final Parcelable savedState = fc1.saveAllState(); 895 final FragmentManagerNonConfig nonconf = fc1.retainNestedNonConfig(); 896 fc1.dispatchStop(); 897 fc1.dispatchDestroy(); 898 899 final FragmentController fc2 = FragmentController.createController( 900 new HostCallbacks(mActivityRule.getActivity())); 901 final FragmentManager fm2 = fc2.getSupportFragmentManager(); 902 903 fc2.attachHost(null); 904 fc2.restoreAllState(savedState, nonconf); 905 fc2.dispatchCreate(); 906 907 fc2.dispatchActivityCreated(); 908 fc2.noteStateNotSaved(); 909 fc2.execPendingActions(); 910 fc2.dispatchStart(); 911 fc2.dispatchResume(); 912 fc2.execPendingActions(); 913 914 // Bring the state back down to destroyed before we finish the test 915 fc2.dispatchPause(); 916 fc2.saveAllState(); 917 fc2.dispatchStop(); 918 fc2.dispatchDestroy(); 919 } 920 921 @Test 922 public void targetFragmentNoCycles() throws Throwable { 923 final Fragment one = new Fragment(); 924 final Fragment two = new Fragment(); 925 final Fragment three = new Fragment(); 926 927 try { 928 one.setTargetFragment(two, 0); 929 two.setTargetFragment(three, 0); 930 three.setTargetFragment(one, 0); 931 assertTrue("creating a fragment target cycle did not throw IllegalArgumentException", 932 false); 933 } catch (IllegalArgumentException e) { 934 // Success! 935 } 936 } 937 938 @Test 939 public void targetFragmentSetClear() throws Throwable { 940 final Fragment one = new Fragment(); 941 final Fragment two = new Fragment(); 942 943 one.setTargetFragment(two, 0); 944 one.setTargetFragment(null, 0); 945 } 946 947 /** 948 * FragmentActivity should not raise the state of a Fragment while it is being destroyed. 949 */ 950 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1) 951 @Test 952 public void fragmentActivityFinishEarly() throws Throwable { 953 Intent intent = new Intent(mActivityRule.getActivity(), FragmentTestActivity.class); 954 intent.putExtra("finishEarly", true); 955 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 956 957 FragmentTestActivity activity = (FragmentTestActivity) 958 InstrumentationRegistry.getInstrumentation().startActivitySync(intent); 959 960 assertTrue(activity.onDestroyLatch.await(1000, TimeUnit.MILLISECONDS)); 961 } 962 963 /** 964 * When a fragment is saved in non-config, it should be restored to the same index. 965 */ 966 @Test 967 @UiThreadTest 968 public void restoreNonConfig() throws Throwable { 969 FragmentController fc = FragmentTestUtil.createController(mActivityRule); 970 FragmentTestUtil.resume(mActivityRule, fc, null); 971 FragmentManager fm = fc.getSupportFragmentManager(); 972 973 Fragment fragment1 = new StrictFragment(); 974 fm.beginTransaction() 975 .add(fragment1, "1") 976 .addToBackStack(null) 977 .commit(); 978 fm.executePendingTransactions(); 979 Fragment fragment2 = new StrictFragment(); 980 fragment2.setRetainInstance(true); 981 fragment2.setTargetFragment(fragment1, 0); 982 Fragment fragment3 = new StrictFragment(); 983 fm.beginTransaction() 984 .remove(fragment1) 985 .add(fragment2, "2") 986 .add(fragment3, "3") 987 .addToBackStack(null) 988 .commit(); 989 fm.executePendingTransactions(); 990 991 Pair<Parcelable, FragmentManagerNonConfig> savedState = 992 FragmentTestUtil.destroy(mActivityRule, fc); 993 994 fc = FragmentTestUtil.createController(mActivityRule); 995 FragmentTestUtil.resume(mActivityRule, fc, savedState); 996 boolean foundFragment2 = false; 997 for (Fragment fragment : fc.getSupportFragmentManager().getFragments()) { 998 if (fragment == fragment2) { 999 foundFragment2 = true; 1000 assertNotNull(fragment.getTargetFragment()); 1001 assertEquals("1", fragment.getTargetFragment().getTag()); 1002 } else { 1003 assertNotEquals("2", fragment.getTag()); 1004 } 1005 } 1006 assertTrue(foundFragment2); 1007 } 1008 1009 /** 1010 * Check that retained fragments in the backstack correctly restored after two "configChanges" 1011 */ 1012 @Test 1013 @UiThreadTest 1014 public void retainedFragmentInBackstack() throws Throwable { 1015 FragmentController fc = FragmentTestUtil.createController(mActivityRule); 1016 FragmentTestUtil.resume(mActivityRule, fc, null); 1017 FragmentManager fm = fc.getSupportFragmentManager(); 1018 1019 Fragment fragment1 = new StrictFragment(); 1020 fm.beginTransaction() 1021 .add(fragment1, "1") 1022 .addToBackStack(null) 1023 .commit(); 1024 fm.executePendingTransactions(); 1025 1026 Fragment child = new StrictFragment(); 1027 child.setRetainInstance(true); 1028 fragment1.getChildFragmentManager().beginTransaction() 1029 .add(child, "child").commit(); 1030 fragment1.getChildFragmentManager().executePendingTransactions(); 1031 1032 Fragment fragment2 = new StrictFragment(); 1033 fm.beginTransaction() 1034 .remove(fragment1) 1035 .add(fragment2, "2") 1036 .addToBackStack(null) 1037 .commit(); 1038 fm.executePendingTransactions(); 1039 1040 Pair<Parcelable, FragmentManagerNonConfig> savedState = 1041 FragmentTestUtil.destroy(mActivityRule, fc); 1042 1043 fc = FragmentTestUtil.createController(mActivityRule); 1044 FragmentTestUtil.resume(mActivityRule, fc, savedState); 1045 savedState = FragmentTestUtil.destroy(mActivityRule, fc); 1046 fc = FragmentTestUtil.createController(mActivityRule); 1047 FragmentTestUtil.resume(mActivityRule, fc, savedState); 1048 fm = fc.getSupportFragmentManager(); 1049 fm.popBackStackImmediate(); 1050 Fragment retainedChild = fm.findFragmentByTag("1") 1051 .getChildFragmentManager().findFragmentByTag("child"); 1052 assertEquals(child, retainedChild); 1053 } 1054 1055 /** 1056 * When a fragment has been optimized out, it state should still be saved during 1057 * save and restore instance state. 1058 */ 1059 @Test 1060 @UiThreadTest 1061 public void saveRemovedFragment() throws Throwable { 1062 FragmentController fc = FragmentTestUtil.createController(mActivityRule); 1063 FragmentTestUtil.resume(mActivityRule, fc, null); 1064 FragmentManager fm = fc.getSupportFragmentManager(); 1065 1066 SaveStateFragment fragment1 = SaveStateFragment.create(1); 1067 fm.beginTransaction() 1068 .add(android.R.id.content, fragment1, "1") 1069 .addToBackStack(null) 1070 .commit(); 1071 SaveStateFragment fragment2 = SaveStateFragment.create(2); 1072 fm.beginTransaction() 1073 .replace(android.R.id.content, fragment2, "2") 1074 .addToBackStack(null) 1075 .commit(); 1076 fm.executePendingTransactions(); 1077 1078 Pair<Parcelable, FragmentManagerNonConfig> savedState = 1079 FragmentTestUtil.destroy(mActivityRule, fc); 1080 1081 fc = FragmentTestUtil.createController(mActivityRule); 1082 FragmentTestUtil.resume(mActivityRule, fc, savedState); 1083 fm = fc.getSupportFragmentManager(); 1084 fragment2 = (SaveStateFragment) fm.findFragmentByTag("2"); 1085 assertNotNull(fragment2); 1086 assertEquals(2, fragment2.getValue()); 1087 fm.popBackStackImmediate(); 1088 fragment1 = (SaveStateFragment) fm.findFragmentByTag("1"); 1089 assertNotNull(fragment1); 1090 assertEquals(1, fragment1.getValue()); 1091 } 1092 1093 /** 1094 * When there are no retained instance fragments, the FragmentManagerNonConfig's fragments 1095 * should be null 1096 */ 1097 @Test 1098 @UiThreadTest 1099 public void nullNonConfig() throws Throwable { 1100 FragmentController fc = FragmentTestUtil.createController(mActivityRule); 1101 FragmentTestUtil.resume(mActivityRule, fc, null); 1102 FragmentManager fm = fc.getSupportFragmentManager(); 1103 1104 Fragment fragment1 = new StrictFragment(); 1105 fm.beginTransaction() 1106 .add(fragment1, "1") 1107 .addToBackStack(null) 1108 .commit(); 1109 fm.executePendingTransactions(); 1110 Pair<Parcelable, FragmentManagerNonConfig> savedState = 1111 FragmentTestUtil.destroy(mActivityRule, fc); 1112 assertNull(savedState.second); 1113 } 1114 1115 /** 1116 * When the FragmentManager state changes, the pending transactions should execute. 1117 */ 1118 @Test 1119 @UiThreadTest 1120 public void runTransactionsOnChange() throws Throwable { 1121 FragmentController fc = FragmentTestUtil.createController(mActivityRule); 1122 FragmentTestUtil.resume(mActivityRule, fc, null); 1123 FragmentManager fm = fc.getSupportFragmentManager(); 1124 1125 RemoveHelloInOnResume fragment1 = new RemoveHelloInOnResume(); 1126 StrictFragment fragment2 = new StrictFragment(); 1127 fm.beginTransaction() 1128 .add(fragment1, "1") 1129 .setReorderingAllowed(false) 1130 .commit(); 1131 fm.beginTransaction() 1132 .add(fragment2, "Hello") 1133 .setReorderingAllowed(false) 1134 .commit(); 1135 fm.executePendingTransactions(); 1136 1137 assertEquals(2, fm.getFragments().size()); 1138 assertTrue(fm.getFragments().contains(fragment1)); 1139 assertTrue(fm.getFragments().contains(fragment2)); 1140 1141 Pair<Parcelable, FragmentManagerNonConfig> savedState = 1142 FragmentTestUtil.destroy(mActivityRule, fc); 1143 fc = FragmentTestUtil.createController(mActivityRule); 1144 FragmentTestUtil.resume(mActivityRule, fc, savedState); 1145 fm = fc.getSupportFragmentManager(); 1146 1147 assertEquals(1, fm.getFragments().size()); 1148 for (Fragment fragment : fm.getFragments()) { 1149 assertTrue(fragment instanceof RemoveHelloInOnResume); 1150 } 1151 } 1152 1153 @Test 1154 @UiThreadTest 1155 public void optionsMenu() throws Throwable { 1156 FragmentController fc = FragmentTestUtil.createController(mActivityRule); 1157 FragmentTestUtil.resume(mActivityRule, fc, null); 1158 FragmentManager fm = fc.getSupportFragmentManager(); 1159 1160 InvalidateOptionFragment fragment = new InvalidateOptionFragment(); 1161 fm.beginTransaction() 1162 .add(android.R.id.content, fragment) 1163 .commit(); 1164 fm.executePendingTransactions(); 1165 1166 Menu menu = mock(Menu.class); 1167 fc.dispatchPrepareOptionsMenu(menu); 1168 assertTrue(fragment.onPrepareOptionsMenuCalled); 1169 fragment.onPrepareOptionsMenuCalled = false; 1170 FragmentTestUtil.destroy(mActivityRule, fc); 1171 fc.dispatchPrepareOptionsMenu(menu); 1172 assertFalse(fragment.onPrepareOptionsMenuCalled); 1173 } 1174 1175 /** 1176 * When a retained instance fragment is saved while in the back stack, it should go 1177 * through onCreate() when it is popped back. 1178 */ 1179 @Test 1180 @UiThreadTest 1181 public void retainInstanceWithOnCreate() throws Throwable { 1182 FragmentController fc = FragmentTestUtil.createController(mActivityRule); 1183 FragmentTestUtil.resume(mActivityRule, fc, null); 1184 FragmentManager fm = fc.getSupportFragmentManager(); 1185 1186 OnCreateFragment fragment1 = new OnCreateFragment(); 1187 1188 fm.beginTransaction() 1189 .add(fragment1, "1") 1190 .commit(); 1191 fm.beginTransaction() 1192 .remove(fragment1) 1193 .addToBackStack(null) 1194 .commit(); 1195 1196 Pair<Parcelable, FragmentManagerNonConfig> savedState = 1197 FragmentTestUtil.destroy(mActivityRule, fc); 1198 Pair<Parcelable, FragmentManagerNonConfig> restartState = 1199 Pair.create(savedState.first, null); 1200 1201 fc = FragmentTestUtil.createController(mActivityRule); 1202 FragmentTestUtil.resume(mActivityRule, fc, restartState); 1203 1204 // Save again, but keep the state 1205 savedState = FragmentTestUtil.destroy(mActivityRule, fc); 1206 1207 fc = FragmentTestUtil.createController(mActivityRule); 1208 FragmentTestUtil.resume(mActivityRule, fc, savedState); 1209 1210 fm = fc.getSupportFragmentManager(); 1211 1212 fm.popBackStackImmediate(); 1213 OnCreateFragment fragment2 = (OnCreateFragment) fm.findFragmentByTag("1"); 1214 assertTrue(fragment2.onCreateCalled); 1215 fm.popBackStackImmediate(); 1216 } 1217 1218 /** 1219 * A retained instance fragment should go through onCreate() once, even through save and 1220 * restore. 1221 */ 1222 @Test 1223 @UiThreadTest 1224 public void retainInstanceOneOnCreate() throws Throwable { 1225 FragmentController fc = FragmentTestUtil.createController(mActivityRule); 1226 FragmentTestUtil.resume(mActivityRule, fc, null); 1227 FragmentManager fm = fc.getSupportFragmentManager(); 1228 1229 OnCreateFragment fragment = new OnCreateFragment(); 1230 1231 fm.beginTransaction() 1232 .add(fragment, "fragment") 1233 .commit(); 1234 fm.executePendingTransactions(); 1235 1236 fm.beginTransaction() 1237 .remove(fragment) 1238 .addToBackStack(null) 1239 .commit(); 1240 1241 assertTrue(fragment.onCreateCalled); 1242 fragment.onCreateCalled = false; 1243 1244 Pair<Parcelable, FragmentManagerNonConfig> savedState = 1245 FragmentTestUtil.destroy(mActivityRule, fc); 1246 1247 fc = FragmentTestUtil.createController(mActivityRule); 1248 FragmentTestUtil.resume(mActivityRule, fc, savedState); 1249 fm = fc.getSupportFragmentManager(); 1250 1251 fm.popBackStackImmediate(); 1252 assertFalse(fragment.onCreateCalled); 1253 } 1254 1255 private void assertAnimationsMatch(FragmentManager fm, int enter, int exit, int popEnter, 1256 int popExit) { 1257 FragmentManagerImpl fmImpl = (FragmentManagerImpl) fm; 1258 BackStackRecord record = fmImpl.mBackStack.get(fmImpl.mBackStack.size() - 1); 1259 1260 Assert.assertEquals(enter, record.mEnterAnim); 1261 Assert.assertEquals(exit, record.mExitAnim); 1262 Assert.assertEquals(popEnter, record.mPopEnterAnim); 1263 Assert.assertEquals(popExit, record.mPopExitAnim); 1264 } 1265 1266 private FragmentController restartFragmentController(FragmentController fc) { 1267 Parcelable savedState = shutdownFragmentController(fc); 1268 return startupFragmentController(savedState); 1269 } 1270 1271 private FragmentController startupFragmentController(Parcelable savedState) { 1272 final FragmentController fc = FragmentController.createController( 1273 new HostCallbacks(mActivityRule.getActivity())); 1274 fc.attachHost(null); 1275 fc.restoreAllState(savedState, (FragmentManagerNonConfig) null); 1276 fc.dispatchCreate(); 1277 fc.dispatchActivityCreated(); 1278 fc.noteStateNotSaved(); 1279 fc.execPendingActions(); 1280 fc.dispatchStart(); 1281 fc.dispatchResume(); 1282 fc.execPendingActions(); 1283 return fc; 1284 } 1285 1286 private Parcelable shutdownFragmentController(FragmentController fc) { 1287 fc.dispatchPause(); 1288 final Parcelable savedState = fc.saveAllState(); 1289 fc.dispatchStop(); 1290 fc.dispatchDestroy(); 1291 return savedState; 1292 } 1293 1294 private void executePendingTransactions(final FragmentManager fm) throws Throwable { 1295 mActivityRule.runOnUiThread(new Runnable() { 1296 @Override 1297 public void run() { 1298 fm.executePendingTransactions(); 1299 } 1300 }); 1301 } 1302 1303 public static class StateSaveFragment extends StrictFragment { 1304 private static final String STATE_KEY = "state"; 1305 1306 private String mSavedState; 1307 private String mUnsavedState; 1308 1309 public StateSaveFragment() { 1310 } 1311 1312 public StateSaveFragment(String savedState, String unsavedState) { 1313 mSavedState = savedState; 1314 mUnsavedState = unsavedState; 1315 } 1316 1317 public String getSavedState() { 1318 return mSavedState; 1319 } 1320 1321 public String getUnsavedState() { 1322 return mUnsavedState; 1323 } 1324 1325 @Override 1326 public void onCreate(Bundle savedInstanceState) { 1327 super.onCreate(savedInstanceState); 1328 if (savedInstanceState != null) { 1329 mSavedState = savedInstanceState.getString(STATE_KEY); 1330 } 1331 } 1332 1333 @Override 1334 public void onSaveInstanceState(Bundle outState) { 1335 super.onSaveInstanceState(outState); 1336 outState.putString(STATE_KEY, mSavedState); 1337 } 1338 } 1339 1340 /** 1341 * This tests a deliberately odd use of a child fragment, added in onCreateView instead 1342 * of elsewhere. It simulates creating a UI child fragment added to the view hierarchy 1343 * created by this fragment. 1344 */ 1345 public static class ChildFragmentManagerFragment extends StrictFragment { 1346 private FragmentManager mSavedChildFragmentManager; 1347 private ChildFragmentManagerChildFragment mChildFragment; 1348 1349 @Override 1350 public void onAttach(Context context) { 1351 super.onAttach(context); 1352 mSavedChildFragmentManager = getChildFragmentManager(); 1353 } 1354 1355 @Nullable 1356 @Override 1357 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 1358 @Nullable Bundle savedInstanceState) { 1359 assertSame("child FragmentManagers not the same instance", mSavedChildFragmentManager, 1360 getChildFragmentManager()); 1361 ChildFragmentManagerChildFragment child = 1362 (ChildFragmentManagerChildFragment) mSavedChildFragmentManager 1363 .findFragmentByTag("tag"); 1364 if (child == null) { 1365 child = new ChildFragmentManagerChildFragment("foo"); 1366 mSavedChildFragmentManager.beginTransaction() 1367 .add(child, "tag") 1368 .commitNow(); 1369 assertEquals("argument strings don't match", "foo", child.getString()); 1370 } 1371 mChildFragment = child; 1372 return new TextView(container.getContext()); 1373 } 1374 1375 @Nullable 1376 public Fragment getChildFragment() { 1377 return mChildFragment; 1378 } 1379 } 1380 1381 public static class ChildFragmentManagerChildFragment extends StrictFragment { 1382 private String mString; 1383 1384 public ChildFragmentManagerChildFragment() { 1385 } 1386 1387 public ChildFragmentManagerChildFragment(String arg) { 1388 final Bundle b = new Bundle(); 1389 b.putString("string", arg); 1390 setArguments(b); 1391 } 1392 1393 @Override 1394 public void onAttach(Context context) { 1395 super.onAttach(context); 1396 mString = getArguments().getString("string", "NO VALUE"); 1397 } 1398 1399 public String getString() { 1400 return mString; 1401 } 1402 } 1403 1404 static class HostCallbacks extends FragmentHostCallback<FragmentActivity> { 1405 private final FragmentActivity mActivity; 1406 1407 public HostCallbacks(FragmentActivity activity) { 1408 super(activity); 1409 mActivity = activity; 1410 } 1411 1412 @Override 1413 public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 1414 } 1415 1416 @Override 1417 public boolean onShouldSaveFragmentState(Fragment fragment) { 1418 return !mActivity.isFinishing(); 1419 } 1420 1421 @Override 1422 public LayoutInflater onGetLayoutInflater() { 1423 return mActivity.getLayoutInflater().cloneInContext(mActivity); 1424 } 1425 1426 @Override 1427 public FragmentActivity onGetHost() { 1428 return mActivity; 1429 } 1430 1431 @Override 1432 public void onSupportInvalidateOptionsMenu() { 1433 mActivity.supportInvalidateOptionsMenu(); 1434 } 1435 1436 @Override 1437 public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode) { 1438 mActivity.startActivityFromFragment(fragment, intent, requestCode); 1439 } 1440 1441 @Override 1442 public void onStartActivityFromFragment( 1443 Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) { 1444 mActivity.startActivityFromFragment(fragment, intent, requestCode, options); 1445 } 1446 1447 @Override 1448 public void onRequestPermissionsFromFragment(@NonNull Fragment fragment, 1449 @NonNull String[] permissions, int requestCode) { 1450 throw new UnsupportedOperationException(); 1451 } 1452 1453 @Override 1454 public boolean onShouldShowRequestPermissionRationale(@NonNull String permission) { 1455 return ActivityCompat.shouldShowRequestPermissionRationale( 1456 mActivity, permission); 1457 } 1458 1459 @Override 1460 public boolean onHasWindowAnimations() { 1461 return mActivity.getWindow() != null; 1462 } 1463 1464 @Override 1465 public int onGetWindowAnimations() { 1466 final Window w = mActivity.getWindow(); 1467 return (w == null) ? 0 : w.getAttributes().windowAnimations; 1468 } 1469 1470 @Override 1471 public void onAttachFragment(Fragment fragment) { 1472 mActivity.onAttachFragment(fragment); 1473 } 1474 1475 @Nullable 1476 @Override 1477 public View onFindViewById(int id) { 1478 return mActivity.findViewById(id); 1479 } 1480 1481 @Override 1482 public boolean onHasView() { 1483 final Window w = mActivity.getWindow(); 1484 return (w != null && w.peekDecorView() != null); 1485 } 1486 } 1487 1488 public static class SimpleFragment extends Fragment { 1489 private int mLayoutId; 1490 private static final String LAYOUT_ID = "layoutId"; 1491 1492 @Override 1493 public void onCreate(Bundle savedInstanceState) { 1494 super.onCreate(savedInstanceState); 1495 if (savedInstanceState != null) { 1496 mLayoutId = savedInstanceState.getInt(LAYOUT_ID, mLayoutId); 1497 } 1498 } 1499 1500 @Override 1501 public void onSaveInstanceState(Bundle outState) { 1502 super.onSaveInstanceState(outState); 1503 outState.putInt(LAYOUT_ID, mLayoutId); 1504 } 1505 1506 @Override 1507 public View onCreateView(LayoutInflater inflater, ViewGroup container, 1508 Bundle savedInstanceState) { 1509 return inflater.inflate(mLayoutId, container, false); 1510 } 1511 1512 public static SimpleFragment create(int layoutId) { 1513 SimpleFragment fragment = new SimpleFragment(); 1514 fragment.mLayoutId = layoutId; 1515 return fragment; 1516 } 1517 } 1518 1519 public static class TargetFragment extends Fragment { 1520 public boolean calledCreate; 1521 1522 @Override 1523 public void onCreate(@Nullable Bundle savedInstanceState) { 1524 super.onCreate(savedInstanceState); 1525 calledCreate = true; 1526 } 1527 } 1528 1529 public static class ReferrerFragment extends Fragment { 1530 @Override 1531 public void onCreate(@Nullable Bundle savedInstanceState) { 1532 super.onCreate(savedInstanceState); 1533 1534 Fragment target = getTargetFragment(); 1535 assertNotNull("target fragment was null during referrer onCreate", target); 1536 1537 if (!(target instanceof TargetFragment)) { 1538 throw new IllegalStateException("target fragment was not a TargetFragment"); 1539 } 1540 1541 assertTrue("target fragment has not yet been created", 1542 ((TargetFragment) target).calledCreate); 1543 } 1544 } 1545 1546 public static class SaveStateFragment extends Fragment { 1547 private static final String VALUE_KEY = "SaveStateFragment.mValue"; 1548 private int mValue; 1549 1550 public static SaveStateFragment create(int value) { 1551 SaveStateFragment saveStateFragment = new SaveStateFragment(); 1552 saveStateFragment.mValue = value; 1553 return saveStateFragment; 1554 } 1555 1556 @Override 1557 public void onSaveInstanceState(Bundle outState) { 1558 super.onSaveInstanceState(outState); 1559 outState.putInt(VALUE_KEY, mValue); 1560 } 1561 1562 @Override 1563 public void onCreate(Bundle savedInstanceState) { 1564 super.onCreate(savedInstanceState); 1565 if (savedInstanceState != null) { 1566 mValue = savedInstanceState.getInt(VALUE_KEY, mValue); 1567 } 1568 } 1569 1570 public int getValue() { 1571 return mValue; 1572 } 1573 } 1574 1575 public static class RemoveHelloInOnResume extends Fragment { 1576 @Override 1577 public void onResume() { 1578 super.onResume(); 1579 Fragment fragment = getFragmentManager().findFragmentByTag("Hello"); 1580 if (fragment != null) { 1581 getFragmentManager().beginTransaction().remove(fragment).commit(); 1582 } 1583 } 1584 } 1585 1586 public static class InvalidateOptionFragment extends Fragment { 1587 public boolean onPrepareOptionsMenuCalled; 1588 1589 public InvalidateOptionFragment() { 1590 setHasOptionsMenu(true); 1591 } 1592 1593 @Override 1594 public void onPrepareOptionsMenu(Menu menu) { 1595 onPrepareOptionsMenuCalled = true; 1596 assertNotNull(getContext()); 1597 super.onPrepareOptionsMenu(menu); 1598 } 1599 } 1600 1601 public static class OnCreateFragment extends Fragment { 1602 public boolean onCreateCalled; 1603 1604 public OnCreateFragment() { 1605 setRetainInstance(true); 1606 } 1607 1608 @Override 1609 public void onCreate(Bundle savedInstanceState) { 1610 super.onCreate(savedInstanceState); 1611 onCreateCalled = true; 1612 } 1613 } 1614} 1615