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.app;
19
20import android.content.Context;
21import android.os.Handler;
22import android.os.Parcelable;
23import android.support.test.filters.MediumTest;
24import android.support.test.rule.ActivityTestRule;
25import android.support.test.runner.AndroidJUnit4;
26import android.util.ArrayMap;
27import org.junit.Rule;
28import org.junit.Test;
29import org.junit.runner.RunWith;
30
31import static junit.framework.TestCase.assertNotNull;
32import static junit.framework.TestCase.assertNotSame;
33import static junit.framework.TestCase.assertSame;
34
35@RunWith(AndroidJUnit4.class)
36public class LoaderLifecycleTest {
37    @Rule
38    public ActivityTestRule<EmptyActivity> mActivityRule =
39            new ActivityTestRule<>(EmptyActivity.class);
40    @Test
41    @MediumTest
42    public void loaderIdentityTest() throws Throwable{
43        mActivityRule.runOnUiThread(() -> {
44            final Handler h = new Handler();
45            final FragmentController fc1 = FragmentController.createController(
46                    new TestFragmentHostCallback(mActivityRule.getActivity(), h, 0));
47
48            fc1.attachHost(null);
49            fc1.dispatchCreate();
50
51            final FragmentManager fm1 = fc1.getFragmentManager();
52
53            final Fragment f1 = new Fragment();
54            fm1.beginTransaction().add(f1, "one").commitNow();
55
56            // Removing and re-adding a fragment completely will destroy its LoaderManager.
57            // Keep the first one here to confirm this later.
58            final LoaderManager lm1 = f1.getLoaderManager();
59
60            // Remove the fragment, add a second one, and re-add the first to
61            // force its internal index to change. The tests below should still remain consistent.
62            final Fragment f2 = new Fragment();
63            fm1.beginTransaction().remove(f1).commitNow();
64            fm1.beginTransaction().add(f2, "two").commitNow();
65            fm1.beginTransaction().add(f1, "one").commitNow();
66
67            // We'll check this to see if we get the same instance back later
68            // as passed through NonConfigurationInstance. If the keys stay consistent
69            // across fragment remove/re-add, this will be consistent.
70            final LoaderManager lm12 = f1.getLoaderManager();
71
72            assertNotSame("fully removed and re-added fragment got same LoaderManager", lm1, lm12);
73
74            fc1.dispatchActivityCreated();
75            fc1.noteStateNotSaved();
76            fc1.execPendingActions();
77            fc1.doLoaderStart();
78            fc1.dispatchStart();
79            fc1.reportLoaderStart();
80            fc1.dispatchResume();
81            fc1.execPendingActions();
82
83            // Bring the state back down to destroyed, simulating an activity restart
84            fc1.dispatchPause();
85            final Parcelable savedState = fc1.saveAllState();
86            fc1.doLoaderStop(true);
87            fc1.dispatchStop();
88            final FragmentManagerNonConfig nonconf = fc1.retainNestedNonConfig();
89
90            final ArrayMap<String, LoaderManager> loaderNonConfig = fc1.retainLoaderNonConfig();
91            assertNotNull("loaderNonConfig was null", loaderNonConfig);
92
93            fc1.dispatchDestroy();
94
95            // Create the new controller and restore state
96            final FragmentController fc2 = FragmentController.createController(
97                    new TestFragmentHostCallback(mActivityRule.getActivity(), h, 0));
98
99            final FragmentManager fm2 = fc2.getFragmentManager();
100
101            fc2.attachHost(null);
102            // Make sure nothing blows up on a null here
103            fc2.restoreLoaderNonConfig(null);
104            // for real this time
105            fc2.restoreLoaderNonConfig(loaderNonConfig);
106            fc2.restoreAllState(savedState, nonconf);
107            fc2.dispatchCreate();
108
109
110            fc2.dispatchActivityCreated();
111            fc2.noteStateNotSaved();
112            fc2.execPendingActions();
113            fc2.doLoaderStart();
114            fc2.dispatchStart();
115            fc2.reportLoaderStart();
116            fc2.dispatchResume();
117            fc2.execPendingActions();
118
119            // Test that the fragments are in the configuration we expect
120            final Fragment restoredOne = fm2.findFragmentByTag("one");
121            final LoaderManager lm2 = restoredOne.getLoaderManager();
122
123            assertSame("didn't get same LoaderManager instance back", lm2, lm12);
124
125            // Bring the state back down to destroyed before we finish the test
126            fc2.dispatchPause();
127            fc2.saveAllState();
128            fc2.dispatchStop();
129            fc2.dispatchDestroy();
130        });
131    }
132
133    @Test
134    @MediumTest
135    public void backStackLoaderIdentityTest() throws Throwable{
136        mActivityRule.runOnUiThread(() -> {
137            final Handler h = new Handler();
138            final FragmentHostCallback host1 =
139                    new TestFragmentHostCallback(mActivityRule.getActivity(), h, 0);
140            final FragmentController fc1 = FragmentController.createController(host1);
141
142            fc1.attachHost(null);
143            fc1.dispatchCreate();
144
145            final FragmentManager fm1 = fc1.getFragmentManager();
146
147            final Fragment f1 = new Fragment();
148            fm1.beginTransaction().add(f1, "one").commitNow();
149
150            final LoaderManager lm1 = f1.getLoaderManager();
151
152            // Put the fragment on the back stack.
153            fm1.beginTransaction().remove(f1).addToBackStack("backentry").commit();
154            fm1.executePendingTransactions();
155
156            fc1.dispatchActivityCreated();
157            fc1.noteStateNotSaved();
158            fc1.execPendingActions();
159            fc1.doLoaderStart();
160            fc1.dispatchStart();
161            fc1.reportLoaderStart();
162            fc1.dispatchResume();
163            fc1.execPendingActions();
164
165            // Bring the state back down to destroyed, simulating an activity restart
166            fc1.dispatchPause();
167            final Parcelable savedState = fc1.saveAllState();
168            fc1.doLoaderStop(true);
169            fc1.dispatchStop();
170            final FragmentManagerNonConfig nonconf = fc1.retainNestedNonConfig();
171
172            final ArrayMap<String, LoaderManager> loaderNonConfig = fc1.retainLoaderNonConfig();
173            assertNotNull("loaderNonConfig was null", loaderNonConfig);
174
175            fc1.dispatchDestroy();
176
177            // Create the new controller and restore state
178            final FragmentHostCallback host2 =
179                    new TestFragmentHostCallback(mActivityRule.getActivity(), h, 0);
180            final FragmentController fc2 = FragmentController.createController(host2);
181
182            final FragmentManager fm2 = fc2.getFragmentManager();
183
184            fc2.attachHost(null);
185            fc2.restoreLoaderNonConfig(loaderNonConfig);
186            fc2.restoreAllState(savedState, nonconf);
187            fc2.dispatchCreate();
188
189
190            fc2.dispatchActivityCreated();
191            fc2.noteStateNotSaved();
192            fc2.execPendingActions();
193            fc2.doLoaderStart();
194            fc2.dispatchStart();
195            fc2.reportLoaderStart();
196            fc2.dispatchResume();
197            fc2.execPendingActions();
198
199            assertNotSame("LoaderManager kept reference to old FragmentHostCallback",
200                    host1, lm1.getFragmentHostCallback());
201            assertSame("LoaderManager did not refrence new FragmentHostCallback",
202                    host2, lm1.getFragmentHostCallback());
203
204            // Test that the fragments are in the configuration we expect
205            final Fragment restoredOne = fm2.findFragmentByTag("one");
206            final LoaderManager lm2 = restoredOne.getLoaderManager();
207
208            assertSame("didn't get same LoaderManager instance back", lm2, lm1);
209
210            // Bring the state back down to destroyed before we finish the test
211            fc2.dispatchPause();
212            fc2.saveAllState();
213            fc2.dispatchStop();
214            fc2.dispatchDestroy();
215        });
216    }
217
218    public class TestFragmentHostCallback extends FragmentHostCallback<LoaderLifecycleTest> {
219        public TestFragmentHostCallback(Context context, Handler handler, int windowAnimations) {
220            super(context, handler, windowAnimations);
221        }
222
223        @Override
224        public LoaderLifecycleTest onGetHost() {
225            return LoaderLifecycleTest.this;
226        }
227    }
228}
229