diff options
| author | Dianne Hackborn <hackbod@google.com> | 2010-08-12 16:20:42 -0700 |
|---|---|---|
| committer | Dianne Hackborn <hackbod@google.com> | 2010-08-12 17:23:48 -0700 |
| commit | b7a2e4772220c4b41df1260cedaf8912f4b07547 (patch) | |
| tree | 294dd03b701c4d6e1950e98b94450d4d51dcddec /core/java/android | |
| parent | 67dfaec8cf5c5cda9a3e2bd1c9d514f9cbfd0e73 (diff) | |
Fragment and PreferenceFragment and FragmentManager, oh my!
- Introduce FragmentManager public API, for all Fragment management
needs. Will in the future allow the removal of the (growing number
of) fragment APIs on Activity.
- Fragment now has a concept of arguments. This can be supplied
immediately after creation, and are retained across instances.
- PreferenceActivity now has an API to have it update its headers (note
not tested). Headers now have arguments. Keys for controlling
when PreferenceActivity shows at launch have been added to the SDK.
- Fixes to back stack handling and state saving/restoring.
Change-Id: Ib9d07ae2beb296c4eb3a4d9e1b3b59544675e819
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/app/Activity.java | 83 | ||||
| -rw-r--r-- | core/java/android/app/BackStackEntry.java | 106 | ||||
| -rw-r--r-- | core/java/android/app/DialogFragment.java | 7 | ||||
| -rw-r--r-- | core/java/android/app/Fragment.java | 122 | ||||
| -rw-r--r-- | core/java/android/app/FragmentManager.java | 166 | ||||
| -rw-r--r-- | core/java/android/preference/PreferenceActivity.java | 102 | ||||
| -rw-r--r-- | core/java/android/util/AndroidException.java | 4 | ||||
| -rw-r--r-- | core/java/android/util/AndroidRuntimeException.java | 4 |
8 files changed, 428 insertions, 166 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index d7bab1bc8080..ec3cbc3729c4 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -667,7 +667,7 @@ public class Activity extends ContextThemeWrapper private CharSequence mTitle; private int mTitleColor = 0; - final FragmentManager mFragments = new FragmentManager(); + final FragmentManagerImpl mFragments = new FragmentManagerImpl(); SparseArray<LoaderManagerImpl> mAllLoaderManagers; LoaderManagerImpl mLoaderManager; @@ -1580,11 +1580,19 @@ public class Activity extends ContextThemeWrapper } /** + * Return the FragmentManager for interacting with fragments associated + * with this activity. + */ + public FragmentManager getFragmentManager() { + return mFragments; + } + + /** * Start a series of edit operations on the Fragments associated with * this activity. */ public FragmentTransaction openFragmentTransaction() { - return new BackStackEntry(mFragments); + return mFragments.openTransaction(); } void invalidateFragmentIndex(int index) { @@ -2072,7 +2080,7 @@ public class Activity extends ContextThemeWrapper * to pop, else false. */ public boolean popBackStack() { - return popBackStack(null, 0); + return mFragments.popBackStack(); } /** @@ -2085,7 +2093,7 @@ public class Activity extends ContextThemeWrapper * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}. */ public boolean popBackStack(String name, int flags) { - return mFragments.popBackStackState(mHandler, name, flags); + return mFragments.popBackStack(name, flags); } /** @@ -2099,7 +2107,7 @@ public class Activity extends ContextThemeWrapper * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}. */ public boolean popBackStack(int id, int flags) { - return mFragments.popBackStackState(mHandler, id, flags); + return mFragments.popBackStack(id, flags); } /** @@ -3999,43 +4007,36 @@ public class Activity extends ContextThemeWrapper + ": Must specify unique android:id for " + fname); } - try { - // 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 (FragmentManager.DEBUG) Log.v(TAG, "onCreateView: id=0x" - + Integer.toHexString(id) + " fname=" + fname - + " existing=" + fragment); - if (fragment == null) { - fragment = Fragment.instantiate(this, fname); - fragment.mFromLayout = true; - fragment.mFragmentId = id; - fragment.mTag = tag; - fragment.mImmediateActivity = this; - mFragments.addFragment(fragment, true); - } - // If this fragment is newly instantiated (either right now, or - // from last saved state), then give it the attributes to - // initialize itself. - if (!fragment.mRetaining) { - fragment.onInflate(this, attrs, fragment.mSavedFragmentState); - } - 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 (Exception e) { - InflateException ie = new InflateException(attrs.getPositionDescription() - + ": Error inflating fragment " + fname); - ie.initCause(e); - throw ie; + // 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 (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x" + + Integer.toHexString(id) + " fname=" + fname + + " existing=" + fragment); + if (fragment == null) { + fragment = Fragment.instantiate(this, fname); + fragment.mFromLayout = true; + fragment.mFragmentId = id; + fragment.mTag = tag; + fragment.mImmediateActivity = this; + mFragments.addFragment(fragment, true); + } + // If this fragment is newly instantiated (either right now, or + // from last saved state), then give it the attributes to + // initialize itself. + if (!fragment.mRetaining) { + fragment.onInflate(this, attrs, fragment.mSavedFragmentState); + } + 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; } /** diff --git a/core/java/android/app/BackStackEntry.java b/core/java/android/app/BackStackEntry.java index d63b862ac3d8..520e4fd586dd 100644 --- a/core/java/android/app/BackStackEntry.java +++ b/core/java/android/app/BackStackEntry.java @@ -29,7 +29,7 @@ final class BackStackState implements Parcelable { final String mName; final int mIndex; - public BackStackState(FragmentManager fm, BackStackEntry bse) { + public BackStackState(FragmentManagerImpl fm, BackStackEntry bse) { int numRemoved = 0; BackStackEntry.Op op = bse.mHead; while (op != null) { @@ -38,6 +38,10 @@ final class BackStackState implements Parcelable { } mOps = new int[bse.mNumOp*5 + numRemoved]; + if (!bse.mAddToBackStack) { + throw new IllegalStateException("Not on back stack"); + } + op = bse.mHead; int pos = 0; while (op != null) { @@ -70,14 +74,15 @@ final class BackStackState implements Parcelable { mIndex = in.readInt(); } - public BackStackEntry instantiate(FragmentManager fm) { + public BackStackEntry instantiate(FragmentManagerImpl fm) { BackStackEntry bse = new BackStackEntry(fm); int pos = 0; while (pos < mOps.length) { BackStackEntry.Op op = new BackStackEntry.Op(); op.cmd = mOps[pos++]; + if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, + "BSE " + bse + " set base fragment #" + mOps[pos]); Fragment f = fm.mActive.get(mOps[pos++]); - f.mBackStackNesting++; op.fragment = f; op.enterAnim = mOps[pos++]; op.exitAnim = mOps[pos++]; @@ -85,7 +90,10 @@ final class BackStackState implements Parcelable { if (N > 0) { op.removed = new ArrayList<Fragment>(N); for (int i=0; i<N; i++) { - op.removed.add(fm.mActive.get(mOps[pos++])); + if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, + "BSE " + bse + " set remove fragment #" + mOps[pos]); + Fragment r = fm.mActive.get(mOps[pos++]); + op.removed.add(r); } } bse.addOp(op); @@ -94,6 +102,8 @@ final class BackStackState implements Parcelable { bse.mTransitionStyle = mTransitionStyle; bse.mName = mName; bse.mIndex = mIndex; + bse.mAddToBackStack = true; + bse.bumpBackStackNesting(1); return bse; } @@ -127,7 +137,7 @@ final class BackStackState implements Parcelable { final class BackStackEntry implements FragmentTransaction, Runnable { static final String TAG = "BackStackEntry"; - final FragmentManager mManager; + final FragmentManagerImpl mManager; static final int OP_NULL = 0; static final int OP_ADD = 1; @@ -158,7 +168,7 @@ final class BackStackEntry implements FragmentTransaction, Runnable { boolean mCommitted; int mIndex; - public BackStackEntry(FragmentManager manager) { + public BackStackEntry(FragmentManagerImpl manager) { mManager = manager; } @@ -295,9 +305,32 @@ final class BackStackEntry implements FragmentTransaction, Runnable { return this; } + void bumpBackStackNesting(int amt) { + if (!mAddToBackStack) { + return; + } + if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this + + " by " + amt); + Op op = mHead; + while (op != null) { + op.fragment.mBackStackNesting += amt; + if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " + + op.fragment + " to " + op.fragment.mBackStackNesting); + if (op.removed != null) { + for (int i=op.removed.size()-1; i>=0; i--) { + Fragment r = op.removed.get(i); + r.mBackStackNesting += amt; + if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " + + r + " to " + r.mBackStackNesting); + } + } + op = op.next; + } + } + public int commit() { if (mCommitted) throw new IllegalStateException("commit already called"); - if (FragmentManager.DEBUG) Log.v(TAG, "Commit: " + this); + if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Commit: " + this); mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); @@ -309,7 +342,7 @@ final class BackStackEntry implements FragmentTransaction, Runnable { } public void run() { - if (FragmentManager.DEBUG) Log.v(TAG, "Run: " + this); + if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Run: " + this); if (mAddToBackStack) { if (mIndex < 0) { @@ -317,14 +350,13 @@ final class BackStackEntry implements FragmentTransaction, Runnable { } } + bumpBackStackNesting(1); + Op op = mHead; while (op != null) { switch (op.cmd) { case OP_ADD: { Fragment f = op.fragment; - if (mAddToBackStack) { - f.mBackStackNesting++; - } f.mNextAnim = op.enterAnim; mManager.addFragment(f, false); } break; @@ -333,48 +365,38 @@ final class BackStackEntry implements FragmentTransaction, Runnable { if (mManager.mAdded != null) { for (int i=0; i<mManager.mAdded.size(); i++) { Fragment old = mManager.mAdded.get(i); - if (FragmentManager.DEBUG) Log.v(TAG, + if (FragmentManagerImpl.DEBUG) Log.v(TAG, "OP_REPLACE: adding=" + f + " old=" + old); if (old.mContainerId == f.mContainerId) { if (op.removed == null) { op.removed = new ArrayList<Fragment>(); } op.removed.add(old); + old.mNextAnim = op.exitAnim; if (mAddToBackStack) { - old.mBackStackNesting++; + old.mBackStackNesting += 1; + if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " + + old + " to " + old.mBackStackNesting); } - old.mNextAnim = op.exitAnim; mManager.removeFragment(old, mTransition, mTransitionStyle); } } } - if (mAddToBackStack) { - f.mBackStackNesting++; - } f.mNextAnim = op.enterAnim; mManager.addFragment(f, false); } break; case OP_REMOVE: { Fragment f = op.fragment; - if (mAddToBackStack) { - f.mBackStackNesting++; - } f.mNextAnim = op.exitAnim; mManager.removeFragment(f, mTransition, mTransitionStyle); } break; case OP_HIDE: { Fragment f = op.fragment; - if (mAddToBackStack) { - f.mBackStackNesting++; - } f.mNextAnim = op.exitAnim; mManager.hideFragment(f, mTransition, mTransitionStyle); } break; case OP_SHOW: { Fragment f = op.fragment; - if (mAddToBackStack) { - f.mBackStackNesting++; - } f.mNextAnim = op.enterAnim; mManager.showFragment(f, mTransition, mTransitionStyle); } break; @@ -399,36 +421,29 @@ final class BackStackEntry implements FragmentTransaction, Runnable { } public void popFromBackStack() { - if (FragmentManager.DEBUG) Log.v(TAG, "popFromBackStack: " + this); + if (FragmentManagerImpl.DEBUG) Log.v(TAG, "popFromBackStack: " + this); + + bumpBackStackNesting(-1); Op op = mTail; while (op != null) { switch (op.cmd) { case OP_ADD: { Fragment f = op.fragment; - if (mAddToBackStack) { - f.mBackStackNesting--; - } f.mImmediateActivity = null; mManager.removeFragment(f, - FragmentManager.reverseTransit(mTransition), + FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); } break; case OP_REPLACE: { Fragment f = op.fragment; - if (mAddToBackStack) { - f.mBackStackNesting--; - } f.mImmediateActivity = null; mManager.removeFragment(f, - FragmentManager.reverseTransit(mTransition), + FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); if (op.removed != null) { for (int i=0; i<op.removed.size(); i++) { Fragment old = op.removed.get(i); - if (mAddToBackStack) { - old.mBackStackNesting--; - } f.mImmediateActivity = mManager.mActivity; mManager.addFragment(old, false); } @@ -436,27 +451,18 @@ final class BackStackEntry implements FragmentTransaction, Runnable { } break; case OP_REMOVE: { Fragment f = op.fragment; - if (mAddToBackStack) { - f.mBackStackNesting--; - } f.mImmediateActivity = mManager.mActivity; mManager.addFragment(f, false); } break; case OP_HIDE: { Fragment f = op.fragment; - if (mAddToBackStack) { - f.mBackStackNesting--; - } mManager.showFragment(f, - FragmentManager.reverseTransit(mTransition), mTransitionStyle); + FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); } break; case OP_SHOW: { Fragment f = op.fragment; - if (mAddToBackStack) { - f.mBackStackNesting--; - } mManager.hideFragment(f, - FragmentManager.reverseTransit(mTransition), mTransitionStyle); + FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); } break; default: { throw new IllegalArgumentException("Unknown cmd: " + op.cmd); @@ -467,7 +473,7 @@ final class BackStackEntry implements FragmentTransaction, Runnable { } mManager.moveToState(mManager.mCurState, - FragmentManager.reverseTransit(mTransition), mTransitionStyle, true); + FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true); if (mManager.mNeedMenuInvalidate && mManager.mActivity != null) { mManager.mActivity.invalidateOptionsMenu(); mManager.mNeedMenuInvalidate = false; diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java index 391f67282329..5bb4c4246a9f 100644 --- a/core/java/android/app/DialogFragment.java +++ b/core/java/android/app/DialogFragment.java @@ -84,11 +84,12 @@ public class DialogFragment extends Fragment } /** - * Constructor to customize the basic appearance and behavior of the + * Call to customize the basic appearance and behavior of the * fragment's dialog. This can be used for some common dialog behaviors, * taking care of selecting flags, theme, and other options for you. The * same effect can be achieve by manually setting Dialog and Window - * attributes yourself. + * attributes yourself. Calling this after the fragment's Dialog is + * created will have no effect. * * @param style Selects a standard style: may be {@link #STYLE_NORMAL}, * {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or @@ -96,7 +97,7 @@ public class DialogFragment extends Fragment * @param theme Optional custom theme. If 0, an appropriate theme (based * on the style) will be selected for you. */ - public DialogFragment(int style, int theme) { + public void setStyle(int style, int theme) { mStyle = style; if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) { mTheme = android.R.style.Theme_Dialog_NoFrame; diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 2f61345e4195..bc839d70d4a8 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -24,6 +24,7 @@ import android.content.res.Configuration; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.util.AndroidRuntimeException; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; @@ -38,7 +39,6 @@ import android.view.ContextMenu.ContextMenuInfo; import android.view.View.OnCreateContextMenuListener; import android.widget.AdapterView; -import java.lang.reflect.InvocationTargetException; import java.util.HashMap; final class FragmentState implements Parcelable { @@ -51,6 +51,7 @@ final class FragmentState implements Parcelable { final int mContainerId; final String mTag; final boolean mRetainInstance; + final Bundle mArguments; Bundle mSavedFragmentState; @@ -64,6 +65,7 @@ final class FragmentState implements Parcelable { mContainerId = frag.mContainerId; mTag = frag.mTag; mRetainInstance = frag.mRetainInstance; + mArguments = frag.mArguments; } public FragmentState(Parcel in) { @@ -74,6 +76,7 @@ final class FragmentState implements Parcelable { mContainerId = in.readInt(); mTag = in.readString(); mRetainInstance = in.readInt() != 0; + mArguments = in.readBundle(); mSavedFragmentState = in.readBundle(); } @@ -82,11 +85,7 @@ final class FragmentState implements Parcelable { return mInstance; } - try { - mInstance = Fragment.instantiate(activity, mClassName); - } catch (Exception e) { - throw new RuntimeException("Unable to restore fragment " + mClassName, e); - } + mInstance = Fragment.instantiate(activity, mClassName, mArguments); if (mSavedFragmentState != null) { mSavedFragmentState.setClassLoader(activity.getClassLoader()); @@ -116,6 +115,7 @@ final class FragmentState implements Parcelable { dest.writeInt(mContainerId); dest.writeString(mTag); dest.writeInt(mRetainInstance ? 1 : 0); + dest.writeBundle(mArguments); dest.writeBundle(mSavedFragmentState); } @@ -157,6 +157,9 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener // Internal unique name for this fragment; String mWho; + // Construction arguments; + Bundle mArguments; + // True if the fragment is in the list of added fragments. boolean mAdded; @@ -220,46 +223,81 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener boolean mCheckedForLoaderManager; /** + * Thrown by {@link Fragment#instantiate(Context, String, Bundle)} when + * there is an instantiation failure. + */ + static public class InstantiationException extends AndroidRuntimeException { + public InstantiationException(String msg, Exception cause) { + super(msg, cause); + } + } + + /** * Default constructor. <strong>Every</string> fragment must have an * empty constructor, so it can be instantiated when restoring its * activity's state. It is strongly recommended that subclasses do not * have other constructors with parameters, since these constructors * will not be called when the fragment is re-instantiated; instead, + * arguments can be supplied by the caller with {@link #setArguments} + * and later retrieved by the Fragment with {@link #getArguments}. + * + * <p>The first place where application code should generally run is in + * {@link #onAttach(Activity)}, which is the point where the fragment is + * actually attached to its activity and thus capable of doing most * retrieve such parameters from the activity in {@link #onAttach(Activity)}. */ public Fragment() { } /** + * Like {@link #instantiate(Context, String, Bundle)} but with a null + * argument Bundle. + */ + public static Fragment instantiate(Context context, String fname) { + return instantiate(context, fname, null); + } + + /** * Create a new instance of a Fragment with the given class name. This is * the same as calling its empty constructor. * * @param context The calling context being used to instantiate the fragment. * This is currently just used to get its ClassLoader. * @param fname The class name of the fragment to instantiate. + * @param args Bundle of arguments to supply to the fragment, which it + * can retrieve with {@link #getArguments()}. May be null. * @return Returns a new fragment instance. - * @throws NoSuchMethodException The fragment does not have an empty constructor. - * @throws ClassNotFoundException The fragment class does not exist. - * @throws IllegalArgumentException Bad arguments supplied to fragment class - * constructor (should not happen). - * @throws InstantiationException Caller does not have permission to instantiate - * the fragment (for example its constructor is not public). - * @throws IllegalAccessException Caller does not have permission to access - * the given fragment class. - * @throws InvocationTargetException Failure running the fragment's constructor. - */ - public static Fragment instantiate(Context context, String fname) - throws NoSuchMethodException, ClassNotFoundException, - IllegalArgumentException, InstantiationException, - IllegalAccessException, InvocationTargetException { - Class<?> clazz = sClassMap.get(fname); - - if (clazz == null) { - // Class not found in the cache, see if it's real, and try to add it - clazz = context.getClassLoader().loadClass(fname); - sClassMap.put(fname, clazz); + * @throws InstantiationException If there is a failure in instantiating + * the given fragment class. This is a runtime exception; it is not + * normally expected to happen. + */ + public static Fragment instantiate(Context context, String fname, Bundle args) { + try { + Class<?> clazz = sClassMap.get(fname); + if (clazz == null) { + // Class not found in the cache, see if it's real, and try to add it + clazz = context.getClassLoader().loadClass(fname); + sClassMap.put(fname, clazz); + } + Fragment f = (Fragment)clazz.newInstance(); + if (args != null) { + args.setClassLoader(f.getClass().getClassLoader()); + f.mArguments = args; + } + return f; + } catch (ClassNotFoundException e) { + throw new InstantiationException("Unable to instantiate fragment " + fname + + ": make sure class name exists, is public, and has an" + + " empty constructor that is public", e); + } catch (java.lang.InstantiationException e) { + throw new InstantiationException("Unable to instantiate fragment " + fname + + ": make sure class name exists, is public, and has an" + + " empty constructor that is public", e); + } catch (IllegalAccessException e) { + throw new InstantiationException("Unable to instantiate fragment " + fname + + ": make sure class name exists, is public, and has an" + + " empty constructor that is public", e); } - return (Fragment)clazz.newInstance(); } void restoreViewState() { @@ -331,6 +369,28 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener } /** + * Supply the construction arguments for this fragment. This can only + * be called before the fragment has been attached to its activity; that + * is, you should call it immediately after constructing the fragment. The + * arguments supplied here will be retained across fragment destroy and + * creation. + */ + final public void setArguments(Bundle args) { + if (mIndex >= 0) { + throw new IllegalStateException("Fragment already active"); + } + mArguments = args; + } + + /** + * Return the arguments supplied when the fragment was instantiated, + * if any. + */ + final public Bundle getArguments() { + return mArguments; + } + + /** * Return the Activity this fragment is currently associated with. */ final public Activity getActivity() { @@ -338,6 +398,14 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener } /** + * Return the FragmentManager for interacting with fragments associated + * with this fragment's activity. + */ + final public FragmentManager getFragmentManager() { + return mActivity.mFragments; + } + + /** * Return true if the fragment is currently added to its activity. */ final public boolean isAdded() { diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 54e37b0df573..0556f0592d1e 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -35,6 +35,100 @@ import android.view.animation.AnimationUtils; import java.util.ArrayList; +/** + * Interface for interacting with {@link Fragment} objects inside of an + * {@link Activity} + */ +public interface FragmentManager { + /** + * Start a series of edit operations on the Fragments associated with + * this FragmentManager. + */ + public FragmentTransaction openTransaction(); + + /** + * 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 first + * searches through fragments that are currently added to the manager's + * activity; if no such fragment is found, then all fragments currently + * on the back stack associated with this ID are searched. + * @return The fragment if found or null otherwise. + */ + public Fragment findFragmentById(int 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 first + * searches through fragments that are currently added to the manager's + * activity; if no such fragment is found, then all fragments currently + * on the back stack are searched. + * @return The fragment if found or null otherwise. + */ + public Fragment findFragmentByTag(String tag); + + /** + * Flag for {@link #popBackStack(String, int)} + * and {@link #popBackStack(int, int)}: If set, and the name or ID of + * a back stack entry has been supplied, then all matching entries will + * be consumed until one that doesn't match is found or the bottom of + * the stack is reached. Otherwise, all entries up to but not including that entry + * will be removed. + */ + public static final int POP_BACK_STACK_INCLUSIVE = 1<<0; + + /** + * Pop the top state off the back stack. Returns true if there was one + * to pop, else false. + */ + public boolean popBackStack(); + + /** + * Pop the last fragment transition from the manager's fragment + * back stack. If there is nothing to pop, false is returned. + * @param name If non-null, this is the name of a previous back state + * to look for; if found, all states up to that state will be popped. The + * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether + * the named state itself is popped. If null, only the top state is popped. + * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}. + */ + public boolean popBackStack(String name, int flags); + + /** + * Pop all back stack states up to the one with the given identifier. + * @param id Identifier of the stated to be popped. If no identifier exists, + * false is returned. + * The identifier is the number returned by + * {@link FragmentTransaction#commit() FragmentTransaction.commit()}. The + * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether + * the named state itself is popped. + * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}. + */ + public boolean popBackStack(int id, int flags); + + /** + * Put a reference to a fragment in a Bundle. This Bundle can be + * persisted as saved state, and when later restoring + * {@link #getFragment(Bundle, String)} will return the current + * instance of the same fragment. + * + * @param bundle The bundle in which to put the fragment reference. + * @param key The name of the entry in the bundle. + * @param fragment The Fragment whose reference is to be stored. + */ + public void putFragment(Bundle bundle, String key, Fragment fragment); + + /** + * Retrieve the current Fragment instance for a reference previously + * placed with {@link #putFragment(Bundle, String, Fragment)}. + * + * @param bundle The bundle from which to retrieve the fragment reference. + * @param key The name of the entry in the bundle. + * @return Returns the current Fragment instance that is associated with + * the given reference. + */ + public Fragment getFragment(Bundle bundle, String key); +} + final class FragmentManagerState implements Parcelable { FragmentState[] mActive; int[] mAdded; @@ -75,7 +169,7 @@ final class FragmentManagerState implements Parcelable { * @hide * Container for fragments associated with an activity. */ -public class FragmentManager { +class FragmentManagerImpl implements FragmentManager { static final boolean DEBUG = true; static final String TAG = "FragmentManager"; @@ -108,6 +202,47 @@ public class FragmentManager { } }; + public FragmentTransaction openTransaction() { + return new BackStackEntry(this); + } + + public boolean popBackStack() { + return popBackStackState(mActivity.mHandler, null, -1, 0); + } + + public boolean popBackStack(String name, int flags) { + return popBackStackState(mActivity.mHandler, name, -1, flags); + } + + public boolean popBackStack(int id, int flags) { + if (id < 0) { + throw new IllegalArgumentException("Bad id: " + id); + } + return popBackStackState(mActivity.mHandler, null, id, flags); + } + + public void putFragment(Bundle bundle, String key, Fragment fragment) { + if (fragment.mIndex < 0) { + throw new IllegalStateException("Fragment " + fragment + + " is not currently in the FragmentManager"); + } + bundle.putInt(key, fragment.mIndex); + } + + public Fragment getFragment(Bundle bundle, String key) { + int index = bundle.getInt(key); + if (index >= mActive.size()) { + throw new IllegalStateException("Fragement no longer exists for key " + + key + ": index " + index); + } + Fragment f = mActive.get(index); + if (f == null) { + throw new IllegalStateException("Fragement no longer exists for key " + + key + ": index " + index); + } + return f; + } + Animatable loadAnimatable(Fragment fragment, int transit, boolean enter, int transitionStyle) { Animatable animObj = fragment.onCreateAnimatable(transit, enter, @@ -387,6 +522,7 @@ public class FragmentManager { return; } + if (DEBUG) Log.v(TAG, "Freeing fragment index " + f.mIndex); mActive.set(f.mIndex, null); if (mAvailIndices == null) { mAvailIndices = new ArrayList<Integer>(); @@ -636,17 +772,6 @@ public class FragmentManager { mBackStack.add(state); } - public boolean popBackStackState(Handler handler, String name, int flags) { - return popBackStackState(handler, name, -1, flags); - } - - public boolean popBackStackState(Handler handler, int id, int flags) { - if (id < 0) { - return false; - } - return popBackStackState(handler, null, id, flags); - } - boolean popBackStackState(Handler handler, String name, int id, int flags) { if (mBackStack == null) { return false; @@ -787,10 +912,13 @@ public class FragmentManager { } } + if (DEBUG) Log.v(TAG, "Saved state of " + f + ": " + + fs.mSavedFragmentState); } } if (!haveFragments) { + if (DEBUG) Log.v(TAG, "saveAllState: no fragments!"); return null; } @@ -803,6 +931,8 @@ public class FragmentManager { added = new int[N]; for (int i=0; i<N; i++) { added[i] = mAdded.get(i).mIndex; + if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i + + ": " + mAdded.get(i)); } } @@ -813,6 +943,8 @@ public class FragmentManager { backStack = new BackStackState[N]; for (int i=0; i<N; i++) { backStack[i] = new BackStackState(this, mBackStack.get(i)); + if (DEBUG) Log.v(TAG, "saveAllState: adding back stack #" + i + + ": " + mBackStack.get(i)); } } } @@ -836,6 +968,7 @@ public class FragmentManager { if (nonConfig != null) { for (int i=0; i<nonConfig.size(); i++) { Fragment f = nonConfig.get(i); + if (DEBUG) Log.v(TAG, "restoreAllState: re-attaching retained " + f); FragmentState fs = fms.mActive[f.mIndex]; fs.mInstance = f; f.mSavedViewState = null; @@ -857,12 +990,16 @@ public class FragmentManager { for (int i=0; i<fms.mActive.length; i++) { FragmentState fs = fms.mActive[i]; if (fs != null) { - mActive.add(fs.instantiate(mActivity)); + Fragment f = fs.instantiate(mActivity); + if (DEBUG) Log.v(TAG, "restoreAllState: adding #" + i + ": " + f); + mActive.add(f); } else { + if (DEBUG) Log.v(TAG, "restoreAllState: adding #" + i + ": (null)"); mActive.add(null); if (mAvailIndices == null) { mAvailIndices = new ArrayList<Integer>(); } + if (DEBUG) Log.v(TAG, "restoreAllState: adding avail #" + i); mAvailIndices.add(i); } } @@ -878,6 +1015,7 @@ public class FragmentManager { } f.mAdded = true; f.mImmediateActivity = mActivity; + if (DEBUG) Log.v(TAG, "restoreAllState: making added #" + i + ": " + f); mAdded.add(f); } } else { @@ -889,6 +1027,8 @@ public class FragmentManager { mBackStack = new ArrayList<BackStackEntry>(fms.mBackStack.length); for (int i=0; i<fms.mBackStack.length; i++) { BackStackEntry bse = fms.mBackStack[i].instantiate(this); + if (DEBUG) Log.v(TAG, "restoreAllState: adding bse #" + i + + " (index " + bse.mIndex + "): " + bse); mBackStack.add(bse); if (bse.mIndex >= 0) { setBackStackIndex(bse.mIndex, bse); diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index 114f67dc7a83..ec2ca57bca12 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -113,9 +113,28 @@ public abstract class PreferenceActivity extends ListActivity implements private static final String PREFERENCES_TAG = "android:preferences"; - private static final String EXTRA_PREFS_SHOW_FRAGMENT = ":android:show_fragment"; + /** + * When starting this activity, the invoking Intent can contain this extra + * string to specify which fragment should be initially displayed. + */ + public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment"; - private static final String EXTRA_PREFS_NO_HEADERS = ":android:no_headers"; + /** + * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT}, + * this extra can also be specify to supply a Bundle of arguments to pass + * to that fragment when it is instantiated during the initial creation + * of PreferenceActivity. + */ + public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args"; + + /** + * When starting this activity, the invoking Intent can contain this extra + * boolean that the header list should not be displayed. This is most often + * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch + * the activity to display a specific fragment that the user has navigated + * to. + */ + public static final String EXTRA_NO_HEADERS = ":android:no_headers"; private static final String BACK_STACK_PREFS = ":android:prefs"; @@ -159,14 +178,18 @@ public abstract class PreferenceActivity extends ListActivity implements private static final int FIRST_REQUEST_CODE = 100; private static final int MSG_BIND_PREFERENCES = 0; + private static final int MSG_BUILD_HEADERS = 1; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_BIND_PREFERENCES: bindPreferences(); break; + case MSG_BUILD_HEADERS: + onBuildHeaders(mHeaders); + mAdapter.notifyDataSetChanged(); + break; } } }; @@ -250,6 +273,12 @@ public abstract class PreferenceActivity extends ListActivity implements * @attr ref android.R.styleable#PreferenceHeader_fragment */ String fragment; + + /** + * Optional arguments to supply to the fragment when it is + * instantiated. + */ + Bundle fragmentArguments; } @Override @@ -261,7 +290,8 @@ public abstract class PreferenceActivity extends ListActivity implements mPrefsContainer = findViewById(com.android.internal.R.id.prefs); boolean hidingHeaders = onIsHidingHeaders(); mSinglePane = hidingHeaders || !onIsMultiPane(); - String initialFragment = getIntent().getStringExtra(EXTRA_PREFS_SHOW_FRAGMENT); + String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT); + Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS); if (initialFragment != null && mSinglePane) { // If we are just showing a fragment, we want to run in @@ -269,7 +299,7 @@ public abstract class PreferenceActivity extends ListActivity implements // the headers. getListView().setVisibility(View.GONE); mPrefsContainer.setVisibility(View.VISIBLE); - switchToHeader(initialFragment); + switchToHeader(initialFragment, initialArguments); } else { // We need to try to build the headers. @@ -283,8 +313,12 @@ public abstract class PreferenceActivity extends ListActivity implements setListAdapter(mAdapter); if (!mSinglePane) { mPrefsContainer.setVisibility(View.VISIBLE); - switchToHeader(initialFragment != null - ? initialFragment : onGetInitialFragment()); + if (initialFragment != null) { + Header h = onGetInitialHeader(); + initialFragment = h.fragment; + initialArguments = h.fragmentArguments; + } + switchToHeader(initialFragment, initialArguments); } // If there are no headers, we are in the old "just show a screen @@ -371,15 +405,18 @@ public abstract class PreferenceActivity extends ListActivity implements * when not in multi-pane mode. */ public boolean onIsHidingHeaders() { - return getIntent().getBooleanExtra(EXTRA_PREFS_NO_HEADERS, false); + return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false); } /** - * Called to determine the initial fragment to be shown. The default - * implementation simply returns the fragment of the first header. + * Called to determine the initial header to be shown. The default + * implementation simply returns the fragment of the first header. Note + * that the returned Header object does not actually need to exist in + * your header list -- whatever its fragment is will simply be used to + * show for the initial UI. */ - public String onGetInitialFragment() { - return mHeaders.get(0).fragment; + public Header onGetInitialHeader() { + return mHeaders.get(0); } /** @@ -399,6 +436,16 @@ public abstract class PreferenceActivity extends ListActivity implements } /** + * Call when you need to change the headers being displayed. Will result + * in onBuildHeaders() later being called to retrieve the new list. + */ + public void invalidateHeaders() { + if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) { + mHandler.sendEmptyMessage(MSG_BUILD_HEADERS); + } + } + + /** * Parse the given XML file as a header description, adding each * parsed Header into the target list. * @@ -552,9 +599,9 @@ public abstract class PreferenceActivity extends ListActivity implements */ public void onHeaderClick(Header header, int position) { if (mSinglePane) { - startWithFragment(header.fragment); + startWithFragment(header.fragment, header.fragmentArguments); } else { - switchToHeader(header.fragment); + switchToHeader(header.fragment, header.fragmentArguments); } } @@ -565,12 +612,14 @@ public abstract class PreferenceActivity extends ListActivity implements * and fill the entire activity. * * @param fragmentName The name of the fragment to display. + * @param args Optional arguments to supply to the fragment. */ - public void startWithFragment(String fragmentName) { + public void startWithFragment(String fragmentName, Bundle args) { Intent intent = new Intent(Intent.ACTION_MAIN); intent.setClass(this, getClass()); - intent.putExtra(EXTRA_PREFS_SHOW_FRAGMENT, fragmentName); - intent.putExtra(EXTRA_PREFS_NO_HEADERS, true); + intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName); + intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); + intent.putExtra(EXTRA_NO_HEADERS, true); startActivity(intent); } @@ -579,29 +628,18 @@ public abstract class PreferenceActivity extends ListActivity implements * preference fragment. * * @param fragmentName The name of the fragment to display. + * @param args Optional arguments to supply to the fragment. */ - public void switchToHeader(String fragmentName) { + public void switchToHeader(String fragmentName, Bundle args) { popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE); - Fragment f; - try { - f = Fragment.instantiate(this, fragmentName); - } catch (Exception e) { - Log.w(TAG, "Failure instantiating fragment " + fragmentName, e); - return; - } + Fragment f = Fragment.instantiate(this, fragmentName, args); openFragmentTransaction().replace(com.android.internal.R.id.prefs, f).commit(); } @Override public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) { - Fragment f; - try { - f = Fragment.instantiate(this, pref.getFragment()); - } catch (Exception e) { - Log.w(TAG, "Failure instantiating fragment " + pref.getFragment(), e); - return false; - } + Fragment f = Fragment.instantiate(this, pref.getFragment()); openFragmentTransaction().replace(com.android.internal.R.id.prefs, f) .addToBackStack(BACK_STACK_PREFS).commit(); return true; diff --git a/core/java/android/util/AndroidException.java b/core/java/android/util/AndroidException.java index a767ea1e975c..dfe00c9bd49f 100644 --- a/core/java/android/util/AndroidException.java +++ b/core/java/android/util/AndroidException.java @@ -27,6 +27,10 @@ public class AndroidException extends Exception { super(name); } + public AndroidException(String name, Throwable cause) { + super(name, cause); + } + public AndroidException(Exception cause) { super(cause); } diff --git a/core/java/android/util/AndroidRuntimeException.java b/core/java/android/util/AndroidRuntimeException.java index 4ed17bcd31c6..2b824bf9cb2a 100644 --- a/core/java/android/util/AndroidRuntimeException.java +++ b/core/java/android/util/AndroidRuntimeException.java @@ -27,6 +27,10 @@ public class AndroidRuntimeException extends RuntimeException { super(name); } + public AndroidRuntimeException(String name, Throwable cause) { + super(name, cause); + } + public AndroidRuntimeException(Exception cause) { super(cause); } |
