diff --git a/api/current.xml b/api/current.xml index 1eaa6f4a89612..26de53c13d983 100644 --- a/api/current.xml +++ b/api/current.xml @@ -19904,6 +19904,19 @@ + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + 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 children; + ArrayList 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 sConstructorMap = - new HashMap(); - - private final class FragmentTransactionImpl implements FragmentTransaction, - Runnable, BackStackState { - ArrayList mAdded; - ArrayList 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.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(); - } - 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 getLastNonConfigurationChildInstances() { - return mLastNonConfigurationChildInstances; + HashMap getLastNonConfigurationChildInstances() { + return mLastNonConfigurationInstances != null + ? mLastNonConfigurationInstances.children : null; } /** @@ -1612,6 +1491,21 @@ public class Activity extends ContextThemeWrapper return null; } + NonConfigurationInstances retainNonConfigurationInstances() { + Object activity = onRetainNonConfigurationInstance(); + HashMap children = onRetainNonConfigurationChildInstances(); + ArrayList 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); } /** @@ -1810,16 +1704,6 @@ public class Activity extends ContextThemeWrapper return mActionBar; } - /** - * 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. @@ -1841,6 +1725,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 ? "" : 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 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 3ec8bba79716d..1f2fa26d4220f 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 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 c5e9e6f01c6ed..1b877b226fcbb 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 CREATOR + = new Parcelable.Creator() { + 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 sConstructorMap = + new HashMap(); + 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 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: + *
    + *
  • {@link #onDestroy()} will not be called (but {@link #onDetach()} still + * will be, because the fragment is being detached from its current activity). + *
  • {@link #onCreate(Bundle)} will not be called since the fragment + * is not being re-created. + *
  • {@link #onAttach(Activity)} and {@link #onReady(Bundle)} will + * still be called. + *
+ */ + 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 9a4580e474fdc..0bc1b8320a712 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 mAdded; + ArrayList 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.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(); + } + 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 frags) { + if (frags == null) return null; + final int N = frags.size(); + int[] ids = new int[N]; + for (int i=0; i buildFragmentList(FragmentManager fm, int[] states) { + if (states == null) return null; + final int N = states.length; + ArrayList frags = new ArrayList(N); + for (int i=0; i CREATOR + = new Parcelable.Creator() { + 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 CREATOR + = new Parcelable.Creator() { + 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 mFragments; - ArrayList mBackStack; + ArrayList mBackStack; int mCurState = Fragment.INITIALIZING; Activity mActivity; + int mSaveStateSeq = 0; + + // Temporary vars for state save and restore. + int mCurSaveId = 0; + HashMap mSavedFragments; + SparseArray mRestoredFragments; + Bundle mStateBundle = null; + SparseArray 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); - } - container.addView(f.mView); + 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()"); + } + 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,7 @@ 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; } } @@ -235,9 +539,21 @@ public class FragmentManager { return null; } - public void addBackStackState(BackStackState state) { + 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; + } + } + } + return null; + } + + public void addBackStackState(BackStackEntry state) { if (mBackStack == null) { - mBackStack = new ArrayList(); + mBackStack = new ArrayList(); } 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 states = new ArrayList(); + final ArrayList states + = new ArrayList(); for (int i=mBackStack.size()-1; i>index; i--) { states.add(mBackStack.remove(i)); } @@ -286,12 +603,189 @@ public class FragmentManager { return true; } + ArrayList retainNonConfig() { + ArrayList fragments = null; + if (mFragments != null) { + for (int i=0; i(); + } + fragments.add(f); + f.mRetaining = true; + } + } + } + return fragments; + } + + void saveFragmentViewState(Fragment f) { + if (f.mSavedViewState != null) { + return; + } + if (mStateArray == null) { + mStateArray = new SparseArray(); + } + 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(); + } + + 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 0) { + backStack = new BackStackState[N]; + for (int i=0; i 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(); + for (int i=0; i(fms.mAdded.length); + } + for (int i=0; i(fms.mBackStack.length); + for (int i=0; i lastNonConfigurationInstances = - mParent.getLastNonConfigurationChildInstances(); - Object instance = null; + HashMap 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 11e5ad1388073..527b4f44fea94 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -886,6 +886,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility */ public static final int HAPTIC_FEEDBACK_ENABLED = 0x10000000; + /** + *

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; + + /** + *

Mask for use with setFlags indicating bits used for PARENT_SAVE_DISABLED.

+ * {@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. @@ -3341,6 +3355,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 disable 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. * diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index c71f4bde66302..9329d94dad444 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); + } } }