diff options
| -rw-r--r-- | core/java/android/app/FragmentHostCallback.java | 3 | ||||
| -rw-r--r-- | core/java/android/app/LoaderManager.java | 7 | ||||
| -rw-r--r-- | core/tests/coretests/AndroidManifest.xml | 2 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/app/EmptyActivity.java | 21 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/app/LoaderLifecycleTest.java | 225 |
5 files changed, 258 insertions, 0 deletions
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java index e1d713626e2e..b6aad3b15362 100644 --- a/core/java/android/app/FragmentHostCallback.java +++ b/core/java/android/app/FragmentHostCallback.java @@ -340,6 +340,9 @@ public abstract class FragmentHostCallback<E> extends FragmentContainer { } void restoreLoaderNonConfig(ArrayMap<String, LoaderManager> loaderManagers) { + for (int i = 0, N = loaderManagers.size(); i < N; i++) { + ((LoaderManagerImpl) loaderManagers.valueAt(i)).updateHostController(this); + } mAllLoaderManagers = loaderManagers; } diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java index c14dec93c348..bedf31a22f26 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -195,6 +195,9 @@ public abstract class LoaderManager { public static void enableDebugLogging(boolean enabled) { LoaderManagerImpl.DEBUG = enabled; } + + /** @hide for internal testing only */ + public FragmentHostCallback getFragmentHostCallback() { return null; } } class LoaderManagerImpl extends LoaderManager { @@ -542,6 +545,10 @@ class LoaderManagerImpl extends LoaderManager { void updateHostController(FragmentHostCallback host) { mHost = host; } + + public FragmentHostCallback getFragmentHostCallback() { + return mHost; + } private LoaderInfo createLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callback) { diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 2452cfd66c3f..4416402d0717 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1147,6 +1147,8 @@ </activity> <activity android:name="com.android.internal.policy.PhoneWindowActionModeTestActivity"> </activity> + <activity android:name="android.app.EmptyActivity"> + </activity> <receiver android:name="android.app.activity.AbortReceiver"> <intent-filter android:priority="1"> diff --git a/core/tests/coretests/src/android/app/EmptyActivity.java b/core/tests/coretests/src/android/app/EmptyActivity.java new file mode 100644 index 000000000000..fefd7b724fed --- /dev/null +++ b/core/tests/coretests/src/android/app/EmptyActivity.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.app; + +public class EmptyActivity extends Activity { +} diff --git a/core/tests/coretests/src/android/app/LoaderLifecycleTest.java b/core/tests/coretests/src/android/app/LoaderLifecycleTest.java new file mode 100644 index 000000000000..a3d51a0a9612 --- /dev/null +++ b/core/tests/coretests/src/android/app/LoaderLifecycleTest.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.app; + +import android.content.Context; +import android.os.Handler; +import android.os.Parcelable; +import android.support.test.filters.MediumTest; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.util.ArrayMap; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertNotSame; +import static junit.framework.TestCase.assertSame; + +@RunWith(AndroidJUnit4.class) +public class LoaderLifecycleTest { + @Rule + public ActivityTestRule<EmptyActivity> mActivityRule = + new ActivityTestRule<>(EmptyActivity.class); + @Test + @MediumTest + public void loaderIdentityTest() throws Throwable{ + mActivityRule.runOnUiThread(() -> { + final Handler h = new Handler(); + final FragmentController fc1 = FragmentController.createController( + new TestFragmentHostCallback(mActivityRule.getActivity(), h, 0)); + + fc1.attachHost(null); + fc1.dispatchCreate(); + + final FragmentManager fm1 = fc1.getFragmentManager(); + + final Fragment f1 = new Fragment(); + fm1.beginTransaction().add(f1, "one").commitNow(); + + // Removing and re-adding a fragment completely will destroy its LoaderManager. + // Keep the first one here to confirm this later. + final LoaderManager lm1 = f1.getLoaderManager(); + + // Remove the fragment, add a second one, and re-add the first to + // force its internal index to change. The tests below should still remain consistent. + final Fragment f2 = new Fragment(); + fm1.beginTransaction().remove(f1).commitNow(); + fm1.beginTransaction().add(f2, "two").commitNow(); + fm1.beginTransaction().add(f1, "one").commitNow(); + + // We'll check this to see if we get the same instance back later + // as passed through NonConfigurationInstance. If the keys stay consistent + // across fragment remove/re-add, this will be consistent. + final LoaderManager lm12 = f1.getLoaderManager(); + + assertNotSame("fully removed and re-added fragment got same LoaderManager", lm1, lm12); + + fc1.dispatchActivityCreated(); + fc1.noteStateNotSaved(); + fc1.execPendingActions(); + fc1.doLoaderStart(); + fc1.dispatchStart(); + fc1.reportLoaderStart(); + fc1.dispatchResume(); + fc1.execPendingActions(); + + // Bring the state back down to destroyed, simulating an activity restart + fc1.dispatchPause(); + final Parcelable savedState = fc1.saveAllState(); + fc1.doLoaderStop(true); + fc1.dispatchStop(); + final FragmentManagerNonConfig nonconf = fc1.retainNestedNonConfig(); + + final ArrayMap<String, LoaderManager> loaderNonConfig = fc1.retainLoaderNonConfig(); + assertNotNull("loaderNonConfig was null", loaderNonConfig); + + fc1.dispatchDestroy(); + + // Create the new controller and restore state + final FragmentController fc2 = FragmentController.createController( + new TestFragmentHostCallback(mActivityRule.getActivity(), h, 0)); + + final FragmentManager fm2 = fc2.getFragmentManager(); + + fc2.attachHost(null); + fc2.restoreLoaderNonConfig(loaderNonConfig); + fc2.restoreAllState(savedState, nonconf); + fc2.dispatchCreate(); + + + fc2.dispatchActivityCreated(); + fc2.noteStateNotSaved(); + fc2.execPendingActions(); + fc2.doLoaderStart(); + fc2.dispatchStart(); + fc2.reportLoaderStart(); + fc2.dispatchResume(); + fc2.execPendingActions(); + + // Test that the fragments are in the configuration we expect + final Fragment restoredOne = fm2.findFragmentByTag("one"); + final LoaderManager lm2 = restoredOne.getLoaderManager(); + + assertSame("didn't get same LoaderManager instance back", lm2, lm12); + + // Bring the state back down to destroyed before we finish the test + fc2.dispatchPause(); + fc2.saveAllState(); + fc2.dispatchStop(); + fc2.dispatchDestroy(); + }); + } + + @Test + @MediumTest + public void backStackLoaderIdentityTest() throws Throwable{ + mActivityRule.runOnUiThread(() -> { + final Handler h = new Handler(); + final FragmentHostCallback host1 = + new TestFragmentHostCallback(mActivityRule.getActivity(), h, 0); + final FragmentController fc1 = FragmentController.createController(host1); + + fc1.attachHost(null); + fc1.dispatchCreate(); + + final FragmentManager fm1 = fc1.getFragmentManager(); + + final Fragment f1 = new Fragment(); + fm1.beginTransaction().add(f1, "one").commitNow(); + + final LoaderManager lm1 = f1.getLoaderManager(); + + // Put the fragment on the back stack. + fm1.beginTransaction().remove(f1).addToBackStack("backentry").commit(); + fm1.executePendingTransactions(); + + fc1.dispatchActivityCreated(); + fc1.noteStateNotSaved(); + fc1.execPendingActions(); + fc1.doLoaderStart(); + fc1.dispatchStart(); + fc1.reportLoaderStart(); + fc1.dispatchResume(); + fc1.execPendingActions(); + + // Bring the state back down to destroyed, simulating an activity restart + fc1.dispatchPause(); + final Parcelable savedState = fc1.saveAllState(); + fc1.doLoaderStop(true); + fc1.dispatchStop(); + final FragmentManagerNonConfig nonconf = fc1.retainNestedNonConfig(); + + final ArrayMap<String, LoaderManager> loaderNonConfig = fc1.retainLoaderNonConfig(); + assertNotNull("loaderNonConfig was null", loaderNonConfig); + + fc1.dispatchDestroy(); + + // Create the new controller and restore state + final FragmentHostCallback host2 = + new TestFragmentHostCallback(mActivityRule.getActivity(), h, 0); + final FragmentController fc2 = FragmentController.createController(host2); + + final FragmentManager fm2 = fc2.getFragmentManager(); + + fc2.attachHost(null); + fc2.restoreLoaderNonConfig(loaderNonConfig); + fc2.restoreAllState(savedState, nonconf); + fc2.dispatchCreate(); + + + fc2.dispatchActivityCreated(); + fc2.noteStateNotSaved(); + fc2.execPendingActions(); + fc2.doLoaderStart(); + fc2.dispatchStart(); + fc2.reportLoaderStart(); + fc2.dispatchResume(); + fc2.execPendingActions(); + + assertNotSame("LoaderManager kept reference to old FragmentHostCallback", + host1, lm1.getFragmentHostCallback()); + assertSame("LoaderManager did not refrence new FragmentHostCallback", + host2, lm1.getFragmentHostCallback()); + + // Test that the fragments are in the configuration we expect + final Fragment restoredOne = fm2.findFragmentByTag("one"); + final LoaderManager lm2 = restoredOne.getLoaderManager(); + + assertSame("didn't get same LoaderManager instance back", lm2, lm1); + + // Bring the state back down to destroyed before we finish the test + fc2.dispatchPause(); + fc2.saveAllState(); + fc2.dispatchStop(); + fc2.dispatchDestroy(); + }); + } + + public class TestFragmentHostCallback extends FragmentHostCallback<LoaderLifecycleTest> { + public TestFragmentHostCallback(Context context, Handler handler, int windowAnimations) { + super(context, handler, windowAnimations); + } + + @Override + public LoaderLifecycleTest onGetHost() { + return LoaderLifecycleTest.this; + } + } +} |