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