diff options
| -rw-r--r-- | api/current.xml | 127 | ||||
| -rw-r--r-- | core/java/android/app/Activity.java | 289 | ||||
| -rw-r--r-- | core/java/android/app/ActivityThread.java | 29 | ||||
| -rw-r--r-- | core/java/android/app/Fragment.java | 318 | ||||
| -rw-r--r-- | core/java/android/app/FragmentManager.java | 588 | ||||
| -rw-r--r-- | core/java/android/app/FragmentTransaction.java | 2 | ||||
| -rw-r--r-- | core/java/android/app/Instrumentation.java | 6 | ||||
| -rw-r--r-- | core/java/android/app/LocalActivityManager.java | 13 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 46 | ||||
| -rw-r--r-- | core/java/android/view/ViewGroup.java | 10 |
10 files changed, 1140 insertions, 288 deletions
diff --git a/api/current.xml b/api/current.xml index 1eaa6f4a8961..26de53c13d98 100644 --- a/api/current.xml +++ b/api/current.xml @@ -19904,6 +19904,19 @@ <parameter name="id" type="int"> </parameter> </method> +<method name="findFragmentByTag" + return="android.app.Fragment" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="tag" type="java.lang.String"> +</parameter> +</method> <method name="findViewById" return="android.view.View" abstract="false" @@ -25414,6 +25427,19 @@ visibility="public" > </constructor> +<method name="equals" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<parameter name="o" type="java.lang.Object"> +</parameter> +</method> <method name="getActivity" return="android.app.Activity" abstract="false" @@ -25425,6 +25451,61 @@ visibility="public" > </method> +<method name="getId" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getRetainInstance" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getTag" + return="java.lang.String" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getView" + return="android.view.View" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="hashCode" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="onAttach" return="void" abstract="false" @@ -25493,6 +25574,8 @@ </parameter> <parameter name="container" type="android.view.ViewGroup"> </parameter> +<parameter name="savedInstanceState" type="android.os.Bundle"> +</parameter> </method> <method name="onDestroy" return="void" @@ -25553,7 +25636,7 @@ visibility="public" > </method> -<method name="onRestoreInstanceState" +<method name="onReady" return="void" abstract="false" native="false" @@ -25577,8 +25660,8 @@ visibility="public" > </method> -<method name="onRetainNonConfigurationInstance" - return="java.lang.Object" +<method name="onSaveInstanceState" + return="void" abstract="false" native="false" synchronized="false" @@ -25587,8 +25670,10 @@ deprecated="not deprecated" visibility="public" > +<parameter name="outState" type="android.os.Bundle"> +</parameter> </method> -<method name="onSaveInstanceState" +<method name="onStart" return="void" abstract="false" native="false" @@ -25598,10 +25683,8 @@ deprecated="not deprecated" visibility="public" > -<parameter name="outState" type="android.os.Bundle"> -</parameter> </method> -<method name="onStart" +<method name="onStop" return="void" abstract="false" native="false" @@ -25612,7 +25695,7 @@ visibility="public" > </method> -<method name="onStop" +<method name="setRetainInstance" return="void" abstract="false" native="false" @@ -25622,6 +25705,8 @@ deprecated="not deprecated" visibility="public" > +<parameter name="retain" type="boolean"> +</parameter> </method> </class> <interface name="FragmentTransaction" @@ -25643,6 +25728,8 @@ > <parameter name="fragment" type="android.app.Fragment"> </parameter> +<parameter name="tag" type="java.lang.String"> +</parameter> </method> <method name="add" return="android.app.FragmentTransaction" @@ -181501,6 +181588,17 @@ visibility="public" > </method> +<method name="isSaveFromParentEnabled" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="isScrollbarFadingEnabled" return="boolean" abstract="false" @@ -182979,6 +183077,19 @@ <parameter name="enabled" type="boolean"> </parameter> </method> +<method name="setSaveFromParentEnabled" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="enabled" type="boolean"> +</parameter> +</method> <method name="setScrollBarStyle" return="void" abstract="false" diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 21c6d2a49767..16f3265e6d39 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -42,6 +42,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Parcelable; import android.os.RemoteException; import android.text.Selection; import android.text.SpannableStringBuilder; @@ -77,6 +78,9 @@ import android.widget.LinearLayout; import com.android.internal.app.SplitActionBar; import com.android.internal.policy.PolicyManager; +import java.util.ArrayList; +import java.util.HashMap; + /** * An activity is a single, focused thing that the user can do. Almost all * activities interact with the user, so the Activity class takes care of @@ -611,6 +615,7 @@ public class Activity extends ContextThemeWrapper private static long sInstanceCount = 0; private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState"; + private static final String FRAGMENTS_TAG = "android:fragments"; private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds"; private static final String SAVED_DIALOGS_TAG = "android:savedDialogs"; private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_"; @@ -632,8 +637,6 @@ public class Activity extends ContextThemeWrapper private ComponentName mComponent; /*package*/ ActivityInfo mActivityInfo; /*package*/ ActivityThread mMainThread; - /*package*/ Object mLastNonConfigurationInstance; - /*package*/ HashMap<String,Object> mLastNonConfigurationChildInstances; Activity mParent; boolean mCalled; private boolean mResumed; @@ -646,6 +649,13 @@ public class Activity extends ContextThemeWrapper /*package*/ Configuration mCurrentConfig; private SearchManager mSearchManager; + static final class NonConfigurationInstances { + Object activity; + HashMap<String, Object> children; + ArrayList<Fragment> fragments; + } + /* package */ NonConfigurationInstances mLastNonConfigurationInstances; + private Window mWindow; private WindowManager mWindowManager; @@ -660,148 +670,6 @@ public class Activity extends ContextThemeWrapper final FragmentManager mFragments = new FragmentManager(); - private final Object[] sConstructorArgs = new Object[0]; - - private static final Class[] sConstructorSignature = new Class[] { }; - - private static final HashMap<String, Constructor> sConstructorMap = - new HashMap<String, Constructor>(); - - private final class FragmentTransactionImpl implements FragmentTransaction, - Runnable, BackStackState { - ArrayList<Fragment> mAdded; - ArrayList<Fragment> mRemoved; - int mTransition; - int mTransitionStyle; - boolean mAddToBackStack; - String mName; - boolean mCommitted; - - public FragmentTransaction add(Fragment fragment) { - return add(fragment, 0); - } - - public FragmentTransaction add(Fragment fragment, int containerViewId) { - if (fragment.mActivity != null) { - throw new IllegalStateException("Fragment already added: " + fragment); - } - if (mRemoved != null) { - mRemoved.remove(fragment); - } - if (mAdded == null) { - mAdded = new ArrayList<Fragment>(); - } - fragment.mContainerId = containerViewId; - mAdded.add(fragment); - return this; - } - - public FragmentTransaction replace(Fragment fragment, int containerViewId) { - if (containerViewId == 0) { - throw new IllegalArgumentException("Must use non-zero containerViewId"); - } - if (mFragments.mFragments != null) { - for (int i=0; i<mFragments.mFragments.size(); i++) { - Fragment old = mFragments.mFragments.get(i); - if (old.mContainerId == containerViewId) { - remove(old); - } - } - } - return add(fragment, containerViewId); - } - - public FragmentTransaction remove(Fragment fragment) { - if (fragment.mActivity == null) { - throw new IllegalStateException("Fragment not added: " + fragment); - } - if (mAdded != null) { - mAdded.remove(fragment); - } - if (mRemoved == null) { - mRemoved = new ArrayList<Fragment>(); - } - mRemoved.add(fragment); - return this; - } - - public FragmentTransaction setTransition(int transition) { - mTransition = transition; - return this; - } - - public FragmentTransaction setTransitionStyle(int styleRes) { - mTransitionStyle = styleRes; - return this; - } - - public FragmentTransaction addToBackStack(String name) { - mAddToBackStack = true; - mName = name; - return this; - } - - public void commit() { - if (mCommitted) throw new IllegalStateException("commit already called"); - mCommitted = true; - mHandler.post(this); - } - - public void run() { - if (mRemoved != null) { - for (int i=mRemoved.size()-1; i>=0; i--) { - mFragments.removeFragment(mRemoved.get(i), mTransition, - mTransitionStyle); - } - } - if (mAdded != null) { - for (int i=mAdded.size()-1; i>=0; i--) { - Fragment f = mAdded.get(i); - mFragments.addFragment(f, false); - if (mAddToBackStack) { - f.mBackStackNesting++; - } - } - } - mFragments.moveToState(mFragments.mCurState, mTransition, - mTransitionStyle, true); - if (mAddToBackStack) { - mFragments.addBackStackState(this); - } - } - - public void popFromBackStack() { - if (mAdded != null) { - for (int i=mAdded.size()-1; i>=0; i--) { - Fragment f = mAdded.get(i); - if (mAddToBackStack) { - f.mBackStackNesting--; - } - mFragments.removeFragment(f, - FragmentManager.reverseTransit(mTransition), - mTransitionStyle); - } - } - if (mRemoved != null) { - for (int i=mRemoved.size()-1; i>=0; i--) { - mFragments.addFragment(mRemoved.get(i), false); - } - } - } - - public String getName() { - return mName; - } - - public int getTransition() { - return mTransition; - } - - public int getTransitionStyle() { - return mTransitionStyle; - } - } - private static final class ManagedCursor { ManagedCursor(Cursor cursor) { mCursor = cursor; @@ -828,7 +696,7 @@ public class Activity extends ContextThemeWrapper protected static final int[] FOCUSED_STATE_SET = {com.android.internal.R.attr.state_focused}; private Thread mUiThread; - private final Handler mHandler = new Handler(); + final Handler mHandler = new Handler(); // Used for debug only /* @@ -952,7 +820,12 @@ public class Activity extends ContextThemeWrapper protected void onCreate(Bundle savedInstanceState) { mVisibleFromClient = !mWindow.getWindowStyle().getBoolean( com.android.internal.R.styleable.Window_windowNoDisplay, false); - mFragments.dispatchCreate(savedInstanceState); + if (savedInstanceState != null) { + Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); + mFragments.restoreAllState(p, mLastNonConfigurationInstances != null + ? mLastNonConfigurationInstances.fragments : null); + } + mFragments.dispatchCreate(); mCalled = true; } @@ -1237,6 +1110,10 @@ public class Activity extends ContextThemeWrapper */ protected void onSaveInstanceState(Bundle outState) { outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); + Parcelable p = mFragments.saveAllState(); + if (p != null) { + outState.putParcelable(FRAGMENTS_TAG, p); + } } /** @@ -1541,7 +1418,8 @@ public class Activity extends ContextThemeWrapper * {@link #onRetainNonConfigurationInstance()}. */ public Object getLastNonConfigurationInstance() { - return mLastNonConfigurationInstance; + return mLastNonConfigurationInstances != null + ? mLastNonConfigurationInstances.activity : null; } /** @@ -1597,8 +1475,9 @@ public class Activity extends ContextThemeWrapper * @return Returns the object previously returned by * {@link #onRetainNonConfigurationChildInstances()} */ - HashMap<String,Object> getLastNonConfigurationChildInstances() { - return mLastNonConfigurationChildInstances; + HashMap<String, Object> getLastNonConfigurationChildInstances() { + return mLastNonConfigurationInstances != null + ? mLastNonConfigurationInstances.children : null; } /** @@ -1612,6 +1491,21 @@ public class Activity extends ContextThemeWrapper return null; } + NonConfigurationInstances retainNonConfigurationInstances() { + Object activity = onRetainNonConfigurationInstance(); + HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); + ArrayList<Fragment> fragments = mFragments.retainNonConfig(); + if (activity == null && children == null && fragments == null) { + return null; + } + + NonConfigurationInstances nci = new NonConfigurationInstances(); + nci.activity = activity; + nci.children = children; + nci.fragments = fragments; + return nci; + } + public void onLowMemory() { mCalled = true; } @@ -1621,7 +1515,7 @@ public class Activity extends ContextThemeWrapper * this activity. */ public FragmentTransaction openFragmentTransaction() { - return new FragmentTransactionImpl(); + return new BackStackEntry(mFragments); } /** @@ -1811,16 +1705,6 @@ public class Activity extends ContextThemeWrapper } /** - * Finds a fragment that was identified by the given id either when inflated - * from XML or as the container ID when added in a transaction. This only - * returns fragments that are currently added to the activity's content. - * @return The fragment if found or null otherwise. - */ - public Fragment findFragmentById(int id) { - return mFragments.findFragmentById(id); - } - - /** * Creates a new ActionBar, locates the inflated ActionBarView, * initializes the ActionBar with the view, and sets mActionBar. */ @@ -1842,6 +1726,26 @@ public class Activity extends ContextThemeWrapper } /** + * Finds a fragment that was identified by the given id either when inflated + * from XML or as the container ID when added in a transaction. This only + * returns fragments that are currently added to the activity's content. + * @return The fragment if found or null otherwise. + */ + public Fragment findFragmentById(int id) { + return mFragments.findFragmentById(id); + } + + /** + * Finds a fragment that was identified by the given tag either when inflated + * from XML or as supplied when added in a transaction. This only + * returns fragments that are currently added to the activity's content. + * @return The fragment if found or null otherwise. + */ + public Fragment findFragmentByTag(String tag) { + return mFragments.findFragmentByTag(tag); + } + + /** * Set the activity content from a layout resource. The resource will be * inflated, adding all top-level views to the activity. * @@ -2198,6 +2102,11 @@ public class Activity extends ContextThemeWrapper } public void onContentChanged() { + // First time content is available, let the fragment manager + // attach all of the fragments to it. + if (mFragments.mCurState < Fragment.CONTENT) { + mFragments.moveToState(Fragment.CONTENT, false); + } } /** @@ -3960,44 +3869,42 @@ public class Activity extends ContextThemeWrapper TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment); String fname = a.getString(com.android.internal.R.styleable.Fragment_name); - int id = a.getInt(com.android.internal.R.styleable.Fragment_id, 0); + int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, 0); String tag = a.getString(com.android.internal.R.styleable.Fragment_tag); a.recycle(); - Constructor constructor = sConstructorMap.get(fname); - Class clazz = null; - + if (id == 0) { + throw new IllegalArgumentException(attrs.getPositionDescription() + + ": Must specify unique android:id for " + fname); + } + try { - if (constructor == null) { - // Class not found in the cache, see if it's real, and try to add it - clazz = getClassLoader().loadClass(fname); - constructor = clazz.getConstructor(sConstructorSignature); - sConstructorMap.put(fname, constructor); + // If we restored from a previous state, we may already have + // instantiated this fragment from the state and should use + // that instance instead of making a new one. + Fragment fragment = mFragments.findFragmentById(id); + if (fragment == null) { + fragment = Fragment.instantiate(this, fname); + fragment.mFromLayout = true; + fragment.mFragmentId = id; + fragment.mTag = tag; + fragment.onInflate(this, attrs); + mFragments.addFragment(fragment, true); } - Fragment fragment = (Fragment)constructor.newInstance(sConstructorArgs); - fragment.onInflate(this, attrs); - mFragments.addFragment(fragment, true); if (fragment.mView == null) { throw new IllegalStateException("Fragment " + fname + " did not create a view."); } + fragment.mView.setId(id); + if (fragment.mView.getTag() == null) { + fragment.mView.setTag(tag); + } return fragment.mView; - - } catch (NoSuchMethodException e) { - InflateException ie = new InflateException(attrs.getPositionDescription() - + ": Error inflating class " + fname); - ie.initCause(e); - throw ie; - - } catch (ClassNotFoundException e) { - // If loadClass fails, we should propagate the exception. - throw new RuntimeException(e); } catch (Exception e) { InflateException ie = new InflateException(attrs.getPositionDescription() - + ": Error inflating class " - + (clazz == null ? "<unknown>" : clazz.getName())); + + ": Error inflating fragment " + fname); ie.initCause(e); - throw new RuntimeException(ie); + throw ie; } } @@ -4009,18 +3916,17 @@ public class Activity extends ContextThemeWrapper final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, Application application, Intent intent, ActivityInfo info, CharSequence title, - Activity parent, String id, Object lastNonConfigurationInstance, + Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config) { attach(context, aThread, instr, token, 0, application, intent, info, title, parent, id, - lastNonConfigurationInstance, null, config); + lastNonConfigurationInstances, config); } final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, - Object lastNonConfigurationInstance, - HashMap<String,Object> lastNonConfigurationChildInstances, + NonConfigurationInstances lastNonConfigurationInstances, Configuration config) { attachBaseContext(context); @@ -4045,8 +3951,7 @@ public class Activity extends ContextThemeWrapper mTitle = title; mParent = parent; mEmbeddedID = id; - mLastNonConfigurationInstance = lastNonConfigurationInstance; - mLastNonConfigurationChildInstances = lastNonConfigurationChildInstances; + mLastNonConfigurationInstances = lastNonConfigurationInstances; mWindow.setWindowManager(null, mToken, mComponent.flattenToString()); if (mParent != null) { @@ -4104,7 +4009,7 @@ public class Activity extends ContextThemeWrapper final void performResume() { performRestart(); - mLastNonConfigurationInstance = null; + mLastNonConfigurationInstances = null; // First call onResume() -before- setting mResumed, so we don't // send out any status bar / menu notifications the client makes. diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 3ec8bba79716..1f2fa26d4220 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1300,8 +1300,7 @@ public final class ActivityThread { Window window; Activity parent; String embeddedID; - Object lastNonConfigurationInstance; - HashMap<String,Object> lastNonConfigurationChildInstances; + Activity.NonConfigurationInstances lastNonConfigurationInstances; boolean paused; boolean stopped; boolean hideForNow; @@ -2479,7 +2478,7 @@ public final class ActivityThread { public final Activity startActivityNow(Activity parent, String id, Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state, - Object lastNonConfigurationInstance) { + Activity.NonConfigurationInstances lastNonConfigurationInstances) { ActivityRecord r = new ActivityRecord(); r.token = token; r.ident = 0; @@ -2488,7 +2487,7 @@ public final class ActivityThread { r.parent = parent; r.embeddedID = id; r.activityInfo = activityInfo; - r.lastNonConfigurationInstance = lastNonConfigurationInstance; + r.lastNonConfigurationInstances = lastNonConfigurationInstances; if (localLOGV) { ComponentName compname = intent.getComponent(); String name; @@ -2610,14 +2609,12 @@ public final class ActivityThread { + r.activityInfo.name + " with config " + config); activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, - r.embeddedID, r.lastNonConfigurationInstance, - r.lastNonConfigurationChildInstances, config); + r.embeddedID, r.lastNonConfigurationInstances, config); if (customIntent != null) { activity.mIntent = customIntent; } - r.lastNonConfigurationInstance = null; - r.lastNonConfigurationChildInstances = null; + r.lastNonConfigurationInstances = null; activity.mStartedActivity = false; int theme = r.activityInfo.getThemeResource(); if (theme != 0) { @@ -3618,8 +3615,8 @@ public final class ActivityThread { } if (getNonConfigInstance) { try { - r.lastNonConfigurationInstance - = r.activity.onRetainNonConfigurationInstance(); + r.lastNonConfigurationInstances + = r.activity.retainNonConfigurationInstances(); } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( @@ -3628,18 +3625,6 @@ public final class ActivityThread { + ": " + e.toString(), e); } } - try { - r.lastNonConfigurationChildInstances - = r.activity.onRetainNonConfigurationChildInstances(); - } catch (Exception e) { - if (!mInstrumentation.onException(r.activity, e)) { - throw new RuntimeException( - "Unable to retain child activities " - + safeToComponentShortString(r.intent) - + ": " + e.toString(), e); - } - } - } try { r.activity.mCalled = false; diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index c5e9e6f01c6e..1b877b226fcb 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -17,46 +17,281 @@ package android.app; import android.content.ComponentCallbacks; -import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; import android.util.AttributeSet; +import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; + +final class FragmentState implements Parcelable { + static final String VIEW_STATE_TAG = "android:view_state"; + + final String mClassName; + final boolean mFromLayout; + final int mSavedStateId; + final int mFragmentId; + final int mContainerId; + final String mTag; + final boolean mRetainInstance; + + Bundle mSavedFragmentState; + + Fragment mInstance; + + public FragmentState(Fragment frag) { + mClassName = frag.getClass().getName(); + mFromLayout = frag.mFromLayout; + mSavedStateId = frag.mSavedStateId; + mFragmentId = frag.mFragmentId; + mContainerId = frag.mContainerId; + mTag = frag.mTag; + mRetainInstance = frag.mRetainInstance; + } + + public FragmentState(Parcel in) { + mClassName = in.readString(); + mFromLayout = in.readInt() != 0; + mSavedStateId = in.readInt(); + mFragmentId = in.readInt(); + mContainerId = in.readInt(); + mTag = in.readString(); + mRetainInstance = in.readInt() != 0; + mSavedFragmentState = in.readBundle(); + } + + public Fragment instantiate(Activity activity) { + if (mFromLayout) { + return null; + } + + if (mInstance != null) { + return mInstance; + } + + try { + mInstance = Fragment.instantiate(activity, mClassName); + } catch (Exception e) { + throw new RuntimeException("Unable to restore fragment " + mClassName, e); + } + + if (mSavedFragmentState != null) { + mSavedFragmentState.setClassLoader(activity.getClassLoader()); + mInstance.mSavedFragmentState = mSavedFragmentState; + mInstance.mSavedViewState + = mSavedFragmentState.getSparseParcelableArray(VIEW_STATE_TAG); + } + mInstance.mSavedStateId = mSavedStateId; + mInstance.mFragmentId = mFragmentId; + mInstance.mContainerId = mContainerId; + mInstance.mTag = mTag; + mInstance.mRetainInstance = mRetainInstance; + + return mInstance; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mClassName); + dest.writeInt(mFromLayout ? 1 : 0); + dest.writeInt(mSavedStateId); + dest.writeInt(mFragmentId); + dest.writeInt(mContainerId); + dest.writeString(mTag); + dest.writeInt(mRetainInstance ? 1 : 0); + dest.writeBundle(mSavedFragmentState); + } + + public static final Parcelable.Creator<FragmentState> CREATOR + = new Parcelable.Creator<FragmentState>() { + public FragmentState createFromParcel(Parcel in) { + return new FragmentState(in); + } + + public FragmentState[] newArray(int size) { + return new FragmentState[size]; + } + }; +} + /** * A Fragment is a piece of an application's user interface or behavior * that can be placed in an {@link Activity}. */ public class Fragment implements ComponentCallbacks { + private static final Object[] sConstructorArgs = new Object[0]; + + private static final Class[] sConstructorSignature = new Class[] { }; + + private static final HashMap<String, Constructor> sConstructorMap = + new HashMap<String, Constructor>(); + static final int INITIALIZING = 0; // Not yet created. static final int CREATED = 1; // Created. - static final int STARTED = 2; // Created and started, not resumed. - static final int RESUMED = 3; // Created started and resumed. + static final int CONTENT = 2; // View hierarchy content available. + static final int STARTED = 3; // Created and started, not resumed. + static final int RESUMED = 4; // Created started and resumed. int mState = INITIALIZING; + + // When instantiated from saved state, this is the saved state. + Bundle mSavedFragmentState; + SparseArray<Parcelable> mSavedViewState; + + // Set to true if this fragment was instantiated from a layout file. + boolean mFromLayout; + + // Number of active back stack entries this fragment is in. int mBackStackNesting; + + // Activity this fragment is attached to. Activity mActivity; - boolean mCalled; + // The optional identifier for this fragment -- either the container ID if it + // was dynamically added to the view hierarchy, or the ID supplied in + // layout. + int mFragmentId; + + // When a fragment is being dynamically added to the view hierarchy, this + // is the identifier of the parent container it is being added to. int mContainerId; + // The optional named tag for this fragment -- usually used to find + // fragments that are not part of the layout. + String mTag; + + // If set this fragment would like its instance retained across + // configuration changes. + boolean mRetainInstance; + + // If set this fragment is being retained across the current config change. + boolean mRetaining; + + // Used to verify that subclasses call through to super class. + boolean mCalled; + + // The parent container of the fragment after dynamically added to UI. ViewGroup mContainer; + + // The View generated for this fragment. View mView; + // Used for performing save state of fragments. + int mSavedStateSeq = 0; + int mSavedStateId; + public Fragment() { } + + static Fragment instantiate(Activity activity, String fname) + throws NoSuchMethodException, ClassNotFoundException, + IllegalArgumentException, InstantiationException, + IllegalAccessException, InvocationTargetException { + Constructor constructor = sConstructorMap.get(fname); + Class clazz = null; + + if (constructor == null) { + // Class not found in the cache, see if it's real, and try to add it + clazz = activity.getClassLoader().loadClass(fname); + constructor = clazz.getConstructor(sConstructorSignature); + sConstructorMap.put(fname, constructor); + } + return (Fragment)constructor.newInstance(sConstructorArgs); + } + + void restoreViewState() { + if (mSavedViewState != null) { + mView.restoreHierarchyState(mSavedViewState); + mSavedViewState = null; + } + } + + /** + * Subclasses can not override equals(). + */ + @Override final public boolean equals(Object o) { + return super.equals(o); + } + + /** + * Subclasses can not override hashCode(). + */ + @Override final public int hashCode() { + return super.hashCode(); + } + /** + * Return the identifier this fragment is known by. This is either + * the android:id value supplied in a layout or the container view ID + * supplied when adding the fragment. + */ + public int getId() { + return mFragmentId; + } + + /** + * Get the tag name of the fragment, if specified. + */ + public String getTag() { + return mTag; + } + + /** + * Return the Activity this fragment is currently associated with. + */ public Activity getActivity() { return mActivity; } + /** + * Control whether a fragment instance is retained across Activity + * re-creation (such as from a configuration change). This can only + * be used with fragments not in the back stack. If set, the fragment + * lifecycle will be slightly different when an activity is recreated: + * <ul> + * <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still + * will be, because the fragment is being detached from its current activity). + * <li> {@link #onCreate(Bundle)} will not be called since the fragment + * is not being re-created. + * <li> {@link #onAttach(Activity)} and {@link #onReady(Bundle)} <b>will</b> + * still be called. + * </ul> + */ + public void setRetainInstance(boolean retain) { + mRetainInstance = retain; + } + + public boolean getRetainInstance() { + return mRetainInstance; + } + + /** + * Called when a fragment is being created as part of a view layout + * inflation, typically from setting the content view of an activity. + * + * @param activity The Activity that is inflating the fragment. + * @param attrs The attributes at the tag where the fragment is + * being created. + */ public void onInflate(Activity activity, AttributeSet attrs) { mCalled = true; } + /** + * Called when a fragment is first attached to its activity. + * {@link #onCreate(Bundle)} will be called after this. + */ public void onAttach(Activity activity) { mCalled = true; } @@ -65,21 +300,70 @@ public class Fragment implements ComponentCallbacks { return null; } + /** + * Called to do initial creation of a fragment. This is called after + * {@link #onAttach(Activity)} and before {@link #onReady(Bundle)}. + * @param savedInstanceState If the fragment is being re-created from + * a previous saved state, this is the state. + */ public void onCreate(Bundle savedInstanceState) { mCalled = true; } - public View onCreateView(LayoutInflater inflater, ViewGroup container) { + /** + * Called to have the fragment instantiate its user interface view. + * This is optional, and non-graphical fragments can return null (which + * is the default implementation). This will be called between + * {@link #onCreate(Bundle)} and {@link #onReady(Bundle)}. + * + * @param inflater The LayoutInflater object that can be used to inflate + * any views in the fragment, + * @param container If non-null, this is the parent view that the fragment's + * UI should be attached to. The fragment should not add the view itself, + * but this can be used to generate the LayoutParams of the view. + * @param savedInstanceState If non-null, this fragment is being re-constructed + * from a previous saved state as given here. + * + * @return Return the View for the fragment's UI, or null. + */ + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { return null; } - public void onRestoreInstanceState(Bundle savedInstanceState) { + public View getView() { + return mView; + } + + /** + * Called when the activity is ready for the fragment to run. This is + * most useful for fragments that use {@link #setRetainInstance(boolean)} + * instance, as this tells the fragment when it is fully associated with + * the new activity instance. This is called after {@link #onCreate(Bundle)} + * and before {@link #onStart()}. + * + * @param savedInstanceState If the fragment is being re-created from + * a previous saved state, this is the state. + */ + public void onReady(Bundle savedInstanceState) { + mCalled = true; } + /** + * Called when the Fragment is visible to the user. This is generally + * tied to {@link Activity#onStart() Activity.onStart} of the containing + * Activity's lifecycle. + */ public void onStart() { mCalled = true; } + /** + * Called when the fragment is visible to the user and actively running. + * This is generally + * tied to {@link Activity#onResume() Activity.onResume} of the containing + * Activity's lifecycle. + */ public void onResume() { mCalled = true; } @@ -91,14 +375,20 @@ public class Fragment implements ComponentCallbacks { mCalled = true; } - public Object onRetainNonConfigurationInstance() { - return null; - } - + /** + * Called when the Fragment is no longer resumed. This is generally + * tied to {@link Activity#onPause() Activity.onPause} of the containing + * Activity's lifecycle. + */ public void onPause() { mCalled = true; } + /** + * Called when the Fragment is no longer started. This is generally + * tied to {@link Activity#onStop() Activity.onStop} of the containing + * Activity's lifecycle. + */ public void onStop() { mCalled = true; } @@ -107,10 +397,18 @@ public class Fragment implements ComponentCallbacks { mCalled = true; } + /** + * Called when the fragment is no longer in use. This is called + * after {@link #onStop()} and before {@link #onDetach()}. + */ public void onDestroy() { mCalled = true; } + /** + * Called when the fragment is no longer attached to its activity. This + * is called after {@link #onDestroy()}. + */ public void onDetach() { mCalled = true; } diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 9a4580e474fd..0bc1b8320a71 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -19,17 +19,269 @@ package android.app; import android.content.res.TypedArray; import android.os.Bundle; import android.os.Handler; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseArray; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import java.util.ArrayList; +import java.util.HashMap; -interface BackStackState { - public void popFromBackStack(); - public String getName(); - public int getTransition(); - public int getTransitionStyle(); +final class BackStackEntry implements FragmentTransaction, Runnable { + final FragmentManager mManager; + + ArrayList<Fragment> mAdded; + ArrayList<Fragment> mRemoved; + int mTransition; + int mTransitionStyle; + boolean mAddToBackStack; + String mName; + boolean mCommitted; + + public BackStackEntry(FragmentManager manager) { + mManager = manager; + } + + public FragmentTransaction add(Fragment fragment, String tag) { + fragment.mTag = tag; + return add(fragment, 0); + } + + public FragmentTransaction add(Fragment fragment, int containerViewId) { + if (fragment.mActivity != null) { + throw new IllegalStateException("Fragment already added: " + fragment); + } + if (mRemoved != null) { + mRemoved.remove(fragment); + } + if (mAdded == null) { + mAdded = new ArrayList<Fragment>(); + } + fragment.mContainerId = fragment.mFragmentId = containerViewId; + mAdded.add(fragment); + return this; + } + + public FragmentTransaction replace(Fragment fragment, int containerViewId) { + if (containerViewId == 0) { + throw new IllegalArgumentException("Must use non-zero containerViewId"); + } + if (mManager.mFragments != null) { + for (int i=0; i<mManager.mFragments.size(); i++) { + Fragment old = mManager.mFragments.get(i); + if (old.mContainerId == containerViewId) { + remove(old); + } + } + } + return add(fragment, containerViewId); + } + + public FragmentTransaction remove(Fragment fragment) { + if (fragment.mActivity == null) { + throw new IllegalStateException("Fragment not added: " + fragment); + } + if (mAdded != null) { + mAdded.remove(fragment); + } + if (mRemoved == null) { + mRemoved = new ArrayList<Fragment>(); + } + mRemoved.add(fragment); + return this; + } + + public FragmentTransaction setTransition(int transition) { + mTransition = transition; + return this; + } + + public FragmentTransaction setTransitionStyle(int styleRes) { + mTransitionStyle = styleRes; + return this; + } + + public FragmentTransaction addToBackStack(String name) { + mAddToBackStack = true; + mName = name; + return this; + } + + public void commit() { + if (mCommitted) throw new IllegalStateException("commit already called"); + mCommitted = true; + mManager.mActivity.mHandler.post(this); + } + + public void run() { + if (mRemoved != null) { + for (int i=mRemoved.size()-1; i>=0; i--) { + mManager.removeFragment(mRemoved.get(i), mTransition, + mTransitionStyle); + } + } + if (mAdded != null) { + for (int i=mAdded.size()-1; i>=0; i--) { + Fragment f = mAdded.get(i); + mManager.addFragment(f, false); + if (mAddToBackStack) { + f.mBackStackNesting++; + } + } + } + mManager.moveToState(mManager.mCurState, mTransition, + mTransitionStyle, true); + if (mAddToBackStack) { + mManager.addBackStackState(this); + } + } + + public void popFromBackStack() { + if (mAdded != null) { + for (int i=mAdded.size()-1; i>=0; i--) { + Fragment f = mAdded.get(i); + if (mAddToBackStack) { + f.mBackStackNesting--; + } + mManager.removeFragment(f, + FragmentManager.reverseTransit(mTransition), + mTransitionStyle); + } + } + if (mRemoved != null) { + for (int i=mRemoved.size()-1; i>=0; i--) { + mManager.addFragment(mRemoved.get(i), false); + } + } + } + + public String getName() { + return mName; + } + + public int getTransition() { + return mTransition; + } + + public int getTransitionStyle() { + return mTransitionStyle; + } +} + +final class BackStackState implements Parcelable { + final int[] mAdded; + final int[] mRemoved; + final int mTransition; + final int mTransitionStyle; + final String mName; + + public BackStackState(FragmentManager fm, BackStackEntry bse) { + mAdded = buildFragmentStateList(fm, bse.mAdded); + mRemoved = buildFragmentStateList(fm, bse.mRemoved); + mTransition = bse.mTransition; + mTransitionStyle = bse.mTransitionStyle; + mName = bse.mName; + } + + public BackStackState(Parcel in) { + mAdded = in.createIntArray(); + mRemoved = in.createIntArray(); + mTransition = in.readInt(); + mTransitionStyle = in.readInt(); + mName = in.readString(); + } + + public BackStackEntry instantiate(FragmentManager fm) { + BackStackEntry bse = new BackStackEntry(fm); + bse.mAdded = buildFragmentList(fm, mAdded); + bse.mRemoved = buildFragmentList(fm, mRemoved); + bse.mTransition = mTransition; + bse.mTransitionStyle = mTransitionStyle; + bse.mName = mName; + return bse; + } + + static int[] buildFragmentStateList(FragmentManager fm, ArrayList<Fragment> frags) { + if (frags == null) return null; + final int N = frags.size(); + int[] ids = new int[N]; + for (int i=0; i<N; i++) { + FragmentState fs = fm.saveFragment(frags.get(i)); + ids[i] = fs.mSavedStateId; + } + return ids; + } + + static ArrayList<Fragment> buildFragmentList(FragmentManager fm, int[] states) { + if (states == null) return null; + final int N = states.length; + ArrayList<Fragment> frags = new ArrayList<Fragment>(N); + for (int i=0; i<N; i++) { + frags.add(fm.mRestoredFragments.get(states[i]).instantiate(fm.mActivity)); + } + return frags; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeIntArray(mAdded); + dest.writeIntArray(mRemoved); + dest.writeInt(mTransition); + dest.writeInt(mTransitionStyle); + dest.writeString(mName); + } + + public static final Parcelable.Creator<BackStackState> CREATOR + = new Parcelable.Creator<BackStackState>() { + public BackStackState createFromParcel(Parcel in) { + return new BackStackState(in); + } + + public BackStackState[] newArray(int size) { + return new BackStackState[size]; + } + }; +} + +final class FragmentManagerState implements Parcelable { + FragmentState[] mFragments; + int[] mAdded; + BackStackState[] mBackStack; + + public FragmentManagerState() { + } + + public FragmentManagerState(Parcel in) { + mFragments = in.createTypedArray(FragmentState.CREATOR); + mAdded = in.createIntArray(); + mBackStack = in.createTypedArray(BackStackState.CREATOR); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedArray(mFragments, flags); + dest.writeIntArray(mAdded); + dest.writeTypedArray(mBackStack, flags); + } + + public static final Parcelable.Creator<FragmentManagerState> CREATOR + = new Parcelable.Creator<FragmentManagerState>() { + public FragmentManagerState createFromParcel(Parcel in) { + return new FragmentManagerState(in); + } + + public FragmentManagerState[] newArray(int size) { + return new FragmentManagerState[size]; + } + }; } /** @@ -38,11 +290,20 @@ interface BackStackState { */ public class FragmentManager { ArrayList<Fragment> mFragments; - ArrayList<BackStackState> mBackStack; + ArrayList<BackStackEntry> mBackStack; int mCurState = Fragment.INITIALIZING; Activity mActivity; + int mSaveStateSeq = 0; + + // Temporary vars for state save and restore. + int mCurSaveId = 0; + HashMap<Fragment, FragmentState> mSavedFragments; + SparseArray<FragmentState> mRestoredFragments; + Bundle mStateBundle = null; + SparseArray<Parcelable> mStateArray = null; + Animation loadAnimation(Fragment fragment, int transit, boolean enter, int transitionStyle) { Animation animObj = fragment.onCreateAnimation(transitionStyle, enter); @@ -89,32 +350,60 @@ public class FragmentManager { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onAttach()"); } - f.mCalled = false; - f.onCreate(null); - if (!f.mCalled) { - throw new SuperNotCalledException("Fragment " + f - + " did not call through to super.onCreate()"); - } - ViewGroup container = null; - if (f.mContainerId != 0) { - container = (ViewGroup)mActivity.findViewById(f.mContainerId); - if (container == null) { - throw new IllegalArgumentException("New view found for id 0x" - + Integer.toHexString(f.mContainerId) - + " for fragment " + f); + if (!f.mRetaining) { + f.mCalled = false; + f.onCreate(f.mSavedFragmentState); + if (!f.mCalled) { + throw new SuperNotCalledException("Fragment " + f + + " did not call through to super.onCreate()"); } } - f.mContainer = container; - f.mView = f.onCreateView(mActivity.getLayoutInflater(), container); - if (container != null && f.mView != null) { - Animation anim = loadAnimation(f, transit, true, transitionStyle); - if (anim != null) { - f.mView.setAnimation(anim); + f.mRetaining = false; + if (f.mFromLayout) { + // For fragments that are part of the content view + // layout, we need to instantiate the view immediately + // and the inflater will take care of adding it. + f.mView = f.onCreateView(mActivity.getLayoutInflater(), + null, f.mSavedFragmentState); + } + case Fragment.CONTENT: + if (newState > Fragment.CONTENT) { + if (!f.mFromLayout) { + ViewGroup container = null; + if (f.mContainerId != 0) { + container = (ViewGroup)mActivity.findViewById(f.mContainerId); + if (container == null) { + throw new IllegalArgumentException("New view found for id 0x" + + Integer.toHexString(f.mContainerId) + + " for fragment " + f); + } + } + f.mContainer = container; + f.mView = f.onCreateView(mActivity.getLayoutInflater(), + container, f.mSavedFragmentState); + if (f.mView != null) { + f.mView.setSaveFromParentEnabled(false); + if (container != null) { + Animation anim = loadAnimation(f, transit, true, + transitionStyle); + if (anim != null) { + f.mView.setAnimation(anim); + } + container.addView(f.mView); + f.restoreViewState(); + } + } + } + + f.mCalled = false; + f.onReady(f.mSavedFragmentState); + if (!f.mCalled) { + throw new SuperNotCalledException("Fragment " + f + + " did not call through to super.onReady()"); } - container.addView(f.mView); + f.mSavedFragmentState = null; } - case Fragment.CREATED: if (newState > Fragment.CREATED) { f.mCalled = false; @@ -154,24 +443,39 @@ public class FragmentManager { + " did not call through to super.onStop()"); } } - case Fragment.CREATED: - if (newState < Fragment.CREATED) { - if (f.mContainer != null) { - Animation anim = loadAnimation(f, transit, false, transitionStyle); - if (anim != null) { - f.mView.setAnimation(anim); + case Fragment.CONTENT: + if (newState < Fragment.CONTENT) { + if (f.mView != null) { + // Need to save the current view state if not + // done already. + if (!mActivity.isFinishing()) { + saveFragmentViewState(f); + } + if (f.mContainer != null) { + if (mCurState > Fragment.INITIALIZING) { + Animation anim = loadAnimation(f, transit, false, + transitionStyle); + if (anim != null) { + f.mView.setAnimation(anim); + } + } + f.mContainer.removeView(f.mView); } - f.mContainer.removeView(f.mView); } f.mContainer = null; f.mView = null; - - f.mCalled = false; - f.onDestroy(); - if (!f.mCalled) { - throw new SuperNotCalledException("Fragment " + f - + " did not call through to super.onDestroy()"); + } + case Fragment.CREATED: + if (newState < Fragment.CREATED) { + if (!f.mRetaining) { + f.mCalled = false; + f.onDestroy(); + if (!f.mCalled) { + throw new SuperNotCalledException("Fragment " + f + + " did not call through to super.onDestroy()"); + } } + f.mCalled = false; f.onDetach(); if (!f.mCalled) { @@ -227,7 +531,19 @@ public class FragmentManager { if (mFragments != null) { for (int i=mFragments.size()-1; i>=0; i--) { Fragment f = mFragments.get(i); - if (f.mContainerId == id) { + if (f.mFragmentId == id) { + return f; + } + } + } + return null; + } + + public Fragment findFragmentByTag(String tag) { + if (mFragments != null && tag != null) { + for (int i=mFragments.size()-1; i>=0; i--) { + Fragment f = mFragments.get(i); + if (tag.equals(f.mTag)) { return f; } } @@ -235,9 +551,9 @@ public class FragmentManager { return null; } - public void addBackStackState(BackStackState state) { + public void addBackStackState(BackStackEntry state) { if (mBackStack == null) { - mBackStack = new ArrayList<BackStackState>(); + mBackStack = new ArrayList<BackStackEntry>(); } mBackStack.add(state); } @@ -251,7 +567,7 @@ public class FragmentManager { if (last < 0) { return false; } - final BackStackState bss = mBackStack.remove(last); + final BackStackEntry bss = mBackStack.remove(last); handler.post(new Runnable() { public void run() { bss.popFromBackStack(); @@ -262,7 +578,7 @@ public class FragmentManager { } else { int index = mBackStack.size()-1; while (index >= 0) { - BackStackState bss = mBackStack.get(index); + BackStackEntry bss = mBackStack.get(index); if (name.equals(bss.getName())) { break; } @@ -270,7 +586,8 @@ public class FragmentManager { if (index < 0 || index == mBackStack.size()-1) { return false; } - final ArrayList<BackStackState> states = new ArrayList<BackStackState>(); + final ArrayList<BackStackEntry> states + = new ArrayList<BackStackEntry>(); for (int i=mBackStack.size()-1; i>index; i--) { states.add(mBackStack.remove(i)); } @@ -286,12 +603,189 @@ public class FragmentManager { return true; } + ArrayList<Fragment> retainNonConfig() { + ArrayList<Fragment> fragments = null; + if (mFragments != null) { + for (int i=0; i<mFragments.size(); i++) { + Fragment f = mFragments.get(i); + if (f.mBackStackNesting <= 0 && f.mRetainInstance && f.mTag != null) { + if (fragments == null) { + fragments = new ArrayList<Fragment>(); + } + fragments.add(f); + f.mRetaining = true; + } + } + } + return fragments; + } + + void saveFragmentViewState(Fragment f) { + if (f.mSavedViewState != null) { + return; + } + if (mStateArray == null) { + mStateArray = new SparseArray<Parcelable>(); + } + f.mView.saveHierarchyState(mStateArray); + if (mStateArray.size() > 0) { + f.mSavedViewState = mStateArray; + mStateArray = null; + } + } + + FragmentState saveFragment(Fragment f) { + if (mSavedFragments != null) { + FragmentState fs = mSavedFragments.get(f); + if (fs != null) { + return fs; + } + } else { + mSavedFragments = new HashMap<Fragment, FragmentState>(); + } + + f.mSavedStateSeq = mSaveStateSeq; + f.mSavedStateId = ++mCurSaveId; + + FragmentState fs = new FragmentState(f); + mSavedFragments.put(f, fs); + + 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( + FragmentState.VIEW_STATE_TAG, f.mSavedViewState); + } + } + + return fs; + } + + Parcelable saveAllState() { + mSavedFragments = null; + mSaveStateSeq++; + + if (mFragments == null) { + return null; + } + + int[] added = null; + BackStackState[] backStack = null; + + // First collect all active fragments. + int N = mFragments.size(); + if (N > 0) { + added = new int[N]; + for (int i=0; i<N; i++) { + added[i] = saveFragment(mFragments.get(i)).mSavedStateId; + } + } + + // Now save back stack. + if (mBackStack != null) { + N = mBackStack.size(); + if (N > 0) { + backStack = new BackStackState[N]; + for (int i=0; i<N; i++) { + backStack[i] = new BackStackState(this, mBackStack.get(i)); + } + } + } + + if (mSavedFragments == null) { + return null; + } + N = mSavedFragments.size(); + if (N <= 0) { + return null; + } + + FragmentManagerState fms = new FragmentManagerState(); + fms.mFragments = new FragmentState[N]; + int i = 0; + for (FragmentState fs : mSavedFragments.values()) { + fms.mFragments[i] = fs; + i++; + } + fms.mAdded = added; + fms.mBackStack = backStack; + return fms; + } + + void restoreAllState(Parcelable state, ArrayList<Fragment> nonConfig) { + // If there is no saved state at all, then there can not be + // any nonConfig fragments either, so that is that. + if (state == null) return; + FragmentManagerState fms = (FragmentManagerState)state; + if (fms.mFragments == null) return; + + // First build our lookup table of all known Fragment objects. + mRestoredFragments = new SparseArray<FragmentState>(); + for (int i=0; i<fms.mFragments.length; i++) { + FragmentState fs = fms.mFragments[i]; + mRestoredFragments.put(fs.mSavedStateId, fs); + } + + // Stick any non-config instances we are retaining directly + // into the lookup table, so we don't try to instantiate them again. + if (nonConfig != null) { + for (int i=0; i<nonConfig.size(); i++) { + Fragment f = nonConfig.get(i); + FragmentState fs = mRestoredFragments.get(f.mSavedStateId); + fs.mInstance = f; + f.mSavedViewState = null; + if (fs.mSavedFragmentState != null) { + f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray( + FragmentState.VIEW_STATE_TAG); + } + addFragment(f, false); + } + } + + // Now build our data structures from the saved state, instantiating + // the fragment objects as needed. + if (fms.mAdded != null) { + if (mFragments == null) { + mFragments = new ArrayList<Fragment>(fms.mAdded.length); + } + for (int i=0; i<fms.mAdded.length; i++) { + FragmentState fs = mRestoredFragments.get(fms.mAdded[i]); + Fragment f = fs.instantiate(mActivity); + // This will return null if this is a layout fragment, + // since the instance for such a fragment will be created + // later during layout inflation. + if (f != null) { + mFragments.add(f); + } + } + } + if (fms.mBackStack != null) { + mBackStack = new ArrayList<BackStackEntry>(fms.mBackStack.length); + for (int i=0; i<fms.mBackStack.length; i++) { + BackStackEntry bse = fms.mBackStack[i].instantiate(this); + mBackStack.add(bse); + } + } + } + public void attachActivity(Activity activity) { if (mActivity != null) throw new IllegalStateException(); mActivity = activity; } - public void dispatchCreate(Bundle state) { + public void dispatchCreate() { moveToState(Fragment.CREATED, false); } diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java index 94d3b88a79a2..a28530fecae8 100644 --- a/core/java/android/app/FragmentTransaction.java +++ b/core/java/android/app/FragmentTransaction.java @@ -4,7 +4,7 @@ package android.app; * API for performing a set of Fragment operations. */ public interface FragmentTransaction { - public FragmentTransaction add(Fragment fragment); + public FragmentTransaction add(Fragment fragment, String tag); public FragmentTransaction add(Fragment fragment, int containerViewId); public FragmentTransaction replace(Fragment fragment, int containerViewId); public FragmentTransaction remove(Fragment fragment); diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index b937dd6b6b82..cbb1c1a3bb2a 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -997,8 +997,10 @@ public class Instrumentation { IllegalAccessException { Activity activity = (Activity)clazz.newInstance(); ActivityThread aThread = null; - activity.attach(context, aThread, this, token, application, intent, info, title, - parent, id, lastNonConfigurationInstance, new Configuration()); + activity.attach(context, aThread, this, token, application, intent, + info, title, parent, id, + (Activity.NonConfigurationInstances)lastNonConfigurationInstance, + new Configuration()); return activity; } diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java index a24fcae26639..be8f413d63a8 100644 --- a/core/java/android/app/LocalActivityManager.java +++ b/core/java/android/app/LocalActivityManager.java @@ -112,11 +112,16 @@ public class LocalActivityManager { if (r.curState == INITIALIZING) { // Get the lastNonConfigurationInstance for the activity - HashMap<String,Object> lastNonConfigurationInstances = - mParent.getLastNonConfigurationChildInstances(); - Object instance = null; + HashMap<String, Object> lastNonConfigurationInstances = + mParent.getLastNonConfigurationChildInstances(); + Object instanceObj = null; if (lastNonConfigurationInstances != null) { - instance = lastNonConfigurationInstances.get(r.id); + instanceObj = lastNonConfigurationInstances.get(r.id); + } + Activity.NonConfigurationInstances instance = null; + if (instanceObj != null) { + instance = new Activity.NonConfigurationInstances(); + instance.activity = instanceObj; } // We need to have always created the activity. diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 11e5ad138807..527b4f44fea9 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -887,6 +887,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility public static final int HAPTIC_FEEDBACK_ENABLED = 0x10000000; /** + * <p>Indicates that the view hierarchy should stop saving state when + * it reaches this view. If state saving is initiated immediately at + * the view, it will be allowed. + * {@hide} + */ + static final int PARENT_SAVE_DISABLED = 0x20000000; + + /** + * <p>Mask for use with setFlags indicating bits used for PARENT_SAVE_DISABLED.</p> + * {@hide} + */ + static final int PARENT_SAVE_DISABLED_MASK = 0x20000000; + + /** * View flag indicating whether {@link #addFocusables(ArrayList, int, int)} * should add all focusable Views regardless if they are focusable in touch mode. */ @@ -3342,6 +3356,38 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility /** + * Indicates whether the entire hierarchy under this view will save its + * state when a state saving traversal occurs from its parent. The default + * is true; if false, these views will not be saved unless + * {@link #saveHierarchyState(SparseArray)} is called directly on this view. + * + * @return Returns true if the view state saving from parent is enabled, else false. + * + * @see #setSaveFromParentEnabled(boolean) + */ + public boolean isSaveFromParentEnabled() { + return (mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED; + } + + /** + * Controls whether the entire hierarchy under this view will save its + * state when a state saving traversal occurs from its parent. The default + * is true; if false, these views will not be saved unless + * {@link #saveHierarchyState(SparseArray)} is called directly on this view. + * + * @param enabled Set to false to <em>disable</em> state saving, or true + * (the default) to allow it. + * + * @see #isSaveFromParentEnabled() + * @see #setId(int) + * @see #onSaveInstanceState() + */ + public void setSaveFromParentEnabled(boolean enabled) { + setFlags(enabled ? 0 : PARENT_SAVE_DISABLED, PARENT_SAVE_DISABLED_MASK); + } + + + /** * Returns whether this View is able to take focus. * * @return True if this view can take focus, or false otherwise. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index c71f4bde6630..9329d94dad44 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1181,7 +1181,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { - children[i].dispatchSaveInstanceState(container); + View c = children[i]; + if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) { + c.dispatchSaveInstanceState(container); + } } } @@ -1206,7 +1209,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { - children[i].dispatchRestoreInstanceState(container); + View c = children[i]; + if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) { + c.dispatchRestoreInstanceState(container); + } } } |