From b46ed7636be9341b6ce0b158b3d86f34a437e6da Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Thu, 2 Jun 2011 18:33:15 -0700 Subject: [PATCH] Add new Fragment API for explicitly saving/restoring state. Also fix issue #4519821: Blank screen displayed on tapping "Battery Use" option in the settings We weren't correctly doing the full Activity resume code when coming back from delivering a new Intent or result. And fix a fragment problem where we still weren't correctly restoring the state of list views. (I think this was from a bad manual-merge from master.) Change-Id: If79dc7e998155c39ab8c04781f6c73a82238a9ef --- api/current.xml | 98 ++++++++++++++++++++++ core/java/android/app/ActivityThread.java | 4 +- core/java/android/app/Fragment.java | 61 ++++++++++++++ core/java/android/app/FragmentManager.java | 88 ++++++++++++++----- core/java/android/os/Parcel.java | 3 + core/java/android/os/Parcelable.java | 18 ++++ 6 files changed, 249 insertions(+), 23 deletions(-) diff --git a/api/current.xml b/api/current.xml index 117ecf9aa0099..acea195b7b4fb 100644 --- a/api/current.xml +++ b/api/current.xml @@ -30627,6 +30627,19 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CREATOR + = new Parcelable.ClassLoaderCreator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in, null); + } + + public SavedState createFromParcel(Parcel in, ClassLoader loader) { + return new SavedState(in, loader); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + /** * Thrown by {@link Fragment#instantiate(Context, String, Bundle)} when * there is an instantiation failure. @@ -623,6 +668,22 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener return mArguments; } + /** + * Set the initial saved state that this Fragment should restore itself + * from when first being constructed, as returned by + * {@link FragmentManager#saveFragmentInstanceState(Fragment) + * FragmentManager.saveFragmentInstanceState}. + * + * @param state The state the fragment should be restored from. + */ + public void setInitialSavedState(SavedState state) { + if (mIndex >= 0) { + throw new IllegalStateException("Fragment already active"); + } + mSavedFragmentState = state != null && state.mState != null + ? state.mState : null; + } + /** * Optional target for this fragment. This may be used, for example, * if this fragment is being started by another, and when done wants to diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 0da656fb64196..2164ada862b98 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -273,6 +273,30 @@ public abstract class FragmentManager { */ public abstract Fragment getFragment(Bundle bundle, String key); + /** + * Save the current instance state of the given Fragment. This can be + * used later when creating a new instance of the Fragment and adding + * it to the fragment manager, to have it create itself to match the + * current state returned here. Note that there are limits on how + * this can be used: + * + *
    + *
  • The Fragment must currently be attached to the FragmentManager. + *
  • A new Fragment created using this saved state must be the same class + * type as the Fragment it was created from. + *
  • The saved state can not contain dependencies on other fragments -- + * that is it can't use {@link #putFragment(Bundle, String, Fragment)} to + * store a fragment reference because that reference may not be valid when + * this saved state is later used. Likewise the Fragment's target and + * result code are not included in this state. + *
+ * + * @param f The Fragment whose state is to be saved. + * @return The generated state. This will be null if there was no + * interesting state created by the fragment. + */ + public abstract Fragment.SavedState saveFragmentInstanceState(Fragment f); + /** * Print the FragmentManager's state into the given stream. * @@ -491,6 +515,19 @@ final class FragmentManagerImpl extends FragmentManager { return f; } + @Override + public Fragment.SavedState saveFragmentInstanceState(Fragment fragment) { + if (fragment.mIndex < 0) { + throw new IllegalStateException("Fragment " + fragment + + " is not currently in the FragmentManager"); + } + if (fragment.mState > Fragment.INITIALIZING) { + Bundle result = saveFragmentBasicState(fragment); + return result != null ? new Fragment.SavedState(result) : null; + } + return null; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(128); @@ -715,7 +752,6 @@ final class FragmentManagerImpl extends FragmentManager { if (f.mView != null) { f.mView.setSaveFromParentEnabled(false); if (f.mHidden) f.mView.setVisibility(View.GONE); - f.restoreViewState(); f.onViewCreated(f.mView, f.mSavedFragmentState); } } @@ -747,7 +783,6 @@ final class FragmentManagerImpl extends FragmentManager { container.addView(f.mView); } if (f.mHidden) f.mView.setVisibility(View.GONE); - f.restoreViewState(); f.onViewCreated(f.mView, f.mSavedFragmentState); } } @@ -759,6 +794,7 @@ final class FragmentManagerImpl extends FragmentManager { + " did not call through to super.onActivityCreated()"); } if (f.mView != null) { + f.restoreViewState(); } f.mSavedFragmentState = null; } @@ -1375,6 +1411,8 @@ final class FragmentManagerImpl extends FragmentManager { } if (mStateArray == null) { mStateArray = new SparseArray(); + } else { + mStateArray.clear(); } f.mView.saveHierarchyState(mStateArray); if (mStateArray.size() > 0) { @@ -1383,6 +1421,32 @@ final class FragmentManagerImpl extends FragmentManager { } } + Bundle saveFragmentBasicState(Fragment f) { + Bundle result = null; + + if (mStateBundle == null) { + mStateBundle = new Bundle(); + } + f.onSaveInstanceState(mStateBundle); + if (!mStateBundle.isEmpty()) { + result = mStateBundle; + mStateBundle = null; + } + + if (f.mView != null) { + saveFragmentViewState(f); + if (f.mSavedViewState != null) { + if (result == null) { + result = new Bundle(); + } + result.putSparseParcelableArray( + FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState); + } + } + + return result; + } + Parcelable saveAllState() { // Make sure all pending operations have now been executed to get // our state update-to-date. @@ -1407,25 +1471,7 @@ final class FragmentManagerImpl extends FragmentManager { active[i] = fs; if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) { - if (mStateBundle == null) { - mStateBundle = new Bundle(); - } - f.onSaveInstanceState(mStateBundle); - if (!mStateBundle.isEmpty()) { - fs.mSavedFragmentState = mStateBundle; - mStateBundle = null; - } - - if (f.mView != null) { - saveFragmentViewState(f); - if (f.mSavedViewState != null) { - if (fs.mSavedFragmentState == null) { - fs.mSavedFragmentState = new Bundle(); - } - fs.mSavedFragmentState.putSparseParcelableArray( - FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState); - } - } + fs.mSavedFragmentState = saveFragmentBasicState(f); if (f.mTarget != null) { if (f.mTarget.mIndex < 0) { diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 6b352158120c2..e9ed67650ca46 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -1980,6 +1980,9 @@ public final class Parcel { } } + if (creator instanceof Parcelable.ClassLoaderCreator) { + return ((Parcelable.ClassLoaderCreator)creator).createFromParcel(this, loader); + } return creator.createFromParcel(this); } diff --git a/core/java/android/os/Parcelable.java b/core/java/android/os/Parcelable.java index 0a4b60fdd811a..594fbb2c46e55 100644 --- a/core/java/android/os/Parcelable.java +++ b/core/java/android/os/Parcelable.java @@ -113,4 +113,22 @@ public interface Parcelable { */ public T[] newArray(int size); } + + /** + * Specialization of {@link Creator} that allows you to receive the + * ClassLoader the object is being created in. + */ + public interface ClassLoaderCreator extends Creator { + /** + * Create a new instance of the Parcelable class, instantiating it + * from the given Parcel whose data had previously been written by + * {@link Parcelable#writeToParcel Parcelable.writeToParcel()} and + * using the given ClassLoader. + * + * @param source The Parcel to read the object's data from. + * @param loader The ClassLoader that this object is being created in. + * @return Returns a new instance of the Parcelable class. + */ + public T createFromParcel(Parcel source, ClassLoader loader); + } }