diff --git a/api/current.txt b/api/current.txt index 6810969642c2b..850ffaaa364eb 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4687,6 +4687,7 @@ package android.app { method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int); method public abstract int getBackStackEntryCount(); method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String); + method public abstract android.app.Fragment getPrimaryNavigationFragment(); method public void invalidateOptionsMenu(); method public abstract boolean isDestroyed(); method public abstract void popBackStack(); @@ -4763,6 +4764,7 @@ package android.app { method public abstract android.app.FragmentTransaction setBreadCrumbTitle(java.lang.CharSequence); method public abstract android.app.FragmentTransaction setCustomAnimations(int, int); method public abstract android.app.FragmentTransaction setCustomAnimations(int, int, int, int); + method public abstract android.app.FragmentTransaction setPrimaryNavigationFragment(android.app.Fragment); method public abstract android.app.FragmentTransaction setTransition(int); method public abstract android.app.FragmentTransaction setTransitionStyle(int); method public abstract android.app.FragmentTransaction show(android.app.Fragment); diff --git a/api/system-current.txt b/api/system-current.txt index 14229cf21484a..9050beac0ea58 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4847,6 +4847,7 @@ package android.app { method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int); method public abstract int getBackStackEntryCount(); method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String); + method public abstract android.app.Fragment getPrimaryNavigationFragment(); method public void invalidateOptionsMenu(); method public abstract boolean isDestroyed(); method public abstract void popBackStack(); @@ -4923,6 +4924,7 @@ package android.app { method public abstract android.app.FragmentTransaction setBreadCrumbTitle(java.lang.CharSequence); method public abstract android.app.FragmentTransaction setCustomAnimations(int, int); method public abstract android.app.FragmentTransaction setCustomAnimations(int, int, int, int); + method public abstract android.app.FragmentTransaction setPrimaryNavigationFragment(android.app.Fragment); method public abstract android.app.FragmentTransaction setTransition(int); method public abstract android.app.FragmentTransaction setTransitionStyle(int); method public abstract android.app.FragmentTransaction show(android.app.Fragment); diff --git a/api/test-current.txt b/api/test-current.txt index f2460a9ccd196..03942fdbc4b1a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -4697,6 +4697,7 @@ package android.app { method public abstract android.app.FragmentManager.BackStackEntry getBackStackEntryAt(int); method public abstract int getBackStackEntryCount(); method public abstract android.app.Fragment getFragment(android.os.Bundle, java.lang.String); + method public abstract android.app.Fragment getPrimaryNavigationFragment(); method public void invalidateOptionsMenu(); method public abstract boolean isDestroyed(); method public abstract void popBackStack(); @@ -4773,6 +4774,7 @@ package android.app { method public abstract android.app.FragmentTransaction setBreadCrumbTitle(java.lang.CharSequence); method public abstract android.app.FragmentTransaction setCustomAnimations(int, int); method public abstract android.app.FragmentTransaction setCustomAnimations(int, int, int, int); + method public abstract android.app.FragmentTransaction setPrimaryNavigationFragment(android.app.Fragment); method public abstract android.app.FragmentTransaction setTransition(int); method public abstract android.app.FragmentTransaction setTransitionStyle(int); method public abstract android.app.FragmentTransaction show(android.app.Fragment); diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java index 20a9a93b74f91..a27775ea4d851 100644 --- a/core/java/android/app/BackStackRecord.java +++ b/core/java/android/app/BackStackRecord.java @@ -186,6 +186,8 @@ final class BackStackRecord extends FragmentTransaction implements static final int OP_SHOW = 5; static final int OP_DETACH = 6; static final int OP_ATTACH = 7; + static final int OP_SET_PRIMARY_NAV = 8; + static final int OP_UNSET_PRIMARY_NAV = 9; static final class Op { int cmd; @@ -194,6 +196,14 @@ final class BackStackRecord extends FragmentTransaction implements int exitAnim; int popEnterAnim; int popExitAnim; + + Op() { + } + + Op(int cmd, Fragment fragment) { + this.cmd = cmd; + this.fragment = fragment; + } } ArrayList mOps = new ArrayList<>(); @@ -318,6 +328,13 @@ final class BackStackRecord extends FragmentTransaction implements case OP_ATTACH: cmdStr = "ATTACH"; break; + case OP_SET_PRIMARY_NAV: + cmdStr="SET_PRIMARY_NAV"; + break; + case OP_UNSET_PRIMARY_NAV: + cmdStr="UNSET_PRIMARY_NAV"; + break; + default: cmdStr = "cmd=" + op.cmd; break; @@ -438,10 +455,7 @@ final class BackStackRecord extends FragmentTransaction implements fragment.mContainerId = fragment.mFragmentId = containerViewId; } - Op op = new Op(); - op.cmd = opcmd; - op.fragment = fragment; - addOp(op); + addOp(new Op(opcmd, fragment)); } public FragmentTransaction replace(int containerViewId, Fragment fragment) { @@ -458,46 +472,37 @@ final class BackStackRecord extends FragmentTransaction implements } public FragmentTransaction remove(Fragment fragment) { - Op op = new Op(); - op.cmd = OP_REMOVE; - op.fragment = fragment; - addOp(op); + addOp(new Op(OP_REMOVE, fragment)); return this; } public FragmentTransaction hide(Fragment fragment) { - Op op = new Op(); - op.cmd = OP_HIDE; - op.fragment = fragment; - addOp(op); + addOp(new Op(OP_HIDE, fragment)); return this; } public FragmentTransaction show(Fragment fragment) { - Op op = new Op(); - op.cmd = OP_SHOW; - op.fragment = fragment; - addOp(op); + addOp(new Op(OP_SHOW, fragment)); return this; } public FragmentTransaction detach(Fragment fragment) { - Op op = new Op(); - op.cmd = OP_DETACH; - op.fragment = fragment; - addOp(op); + addOp(new Op(OP_DETACH, fragment)); return this; } public FragmentTransaction attach(Fragment fragment) { - Op op = new Op(); - op.cmd = OP_ATTACH; - op.fragment = fragment; - addOp(op); + addOp(new Op(OP_ATTACH, fragment)); + + return this; + } + + public FragmentTransaction setPrimaryNavigationFragment(Fragment fragment) { + addOp(new Op(OP_SET_PRIMARY_NAV, fragment)); return this; } @@ -689,7 +694,8 @@ final class BackStackRecord extends FragmentTransaction implements final int numOps = mOps.size(); for (int opNum = 0; opNum < numOps; opNum++) { final Op op = mOps.get(opNum); - if (op.fragment.mContainerId == containerId) { + final int fragContainer = op.fragment != null ? op.fragment.mContainerId : 0; + if (fragContainer != 0 && fragContainer == containerId) { return true; } } @@ -704,7 +710,7 @@ final class BackStackRecord extends FragmentTransaction implements int lastContainer = -1; for (int opNum = 0; opNum < numOps; opNum++) { final Op op = mOps.get(opNum); - final int container = op.fragment.mContainerId; + final int container = op.fragment != null ? op.fragment.mContainerId : 0; if (container != 0 && container != lastContainer) { lastContainer = container; for (int i = startIndex; i < endIndex; i++) { @@ -712,7 +718,9 @@ final class BackStackRecord extends FragmentTransaction implements final int numThoseOps = record.mOps.size(); for (int thoseOpIndex = 0; thoseOpIndex < numThoseOps; thoseOpIndex++) { final Op thatOp = record.mOps.get(thoseOpIndex); - if (thatOp.fragment.mContainerId == container) { + final int thatContainer = thatOp.fragment != null + ? thatOp.fragment.mContainerId : 0; + if (thatContainer == container) { return true; } } @@ -731,7 +739,9 @@ final class BackStackRecord extends FragmentTransaction implements for (int opNum = 0; opNum < numOps; opNum++) { final Op op = mOps.get(opNum); final Fragment f = op.fragment; - f.setNextTransition(mTransition, mTransitionStyle); + if (f != null) { + f.setNextTransition(mTransition, mTransitionStyle); + } switch (op.cmd) { case OP_ADD: f.setNextAnim(op.enterAnim); @@ -757,10 +767,16 @@ final class BackStackRecord extends FragmentTransaction implements f.setNextAnim(op.enterAnim); mManager.attachFragment(f); break; + case OP_SET_PRIMARY_NAV: + mManager.setPrimaryNavigationFragment(f); + break; + case OP_UNSET_PRIMARY_NAV: + mManager.setPrimaryNavigationFragment(null); + break; default: throw new IllegalArgumentException("Unknown cmd: " + op.cmd); } - if (!mAllowOptimization && op.cmd != OP_ADD) { + if (!mAllowOptimization && op.cmd != OP_ADD && f != null) { mManager.moveFragmentToExpectedState(f); } } @@ -778,7 +794,10 @@ final class BackStackRecord extends FragmentTransaction implements for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) { final Op op = mOps.get(opNum); Fragment f = op.fragment; - f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); + if (f != null) { + f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition), + mTransitionStyle); + } switch (op.cmd) { case OP_ADD: f.setNextAnim(op.popExitAnim); @@ -804,10 +823,16 @@ final class BackStackRecord extends FragmentTransaction implements f.setNextAnim(op.popExitAnim); mManager.detachFragment(f); break; + case OP_SET_PRIMARY_NAV: + mManager.setPrimaryNavigationFragment(null); + break; + case OP_UNSET_PRIMARY_NAV: + mManager.setPrimaryNavigationFragment(f); + break; default: throw new IllegalArgumentException("Unknown cmd: " + op.cmd); } - if (!mAllowOptimization && op.cmd != OP_REMOVE) { + if (!mAllowOptimization && op.cmd != OP_REMOVE && f != null) { mManager.moveFragmentToExpectedState(f); } } @@ -817,15 +842,28 @@ final class BackStackRecord extends FragmentTransaction implements } /** - * Removes all OP_REPLACE ops and replaces them with the proper add and remove - * operations that are equivalent to the replace. This must be called prior to - * {@link #executeOps()} or any other call that operations on mOps. + * Expands all meta-ops into their more primitive equivalents. This must be called prior to + * {@link #executeOps()} or any other call that operations on mOps for forward navigation. + * It should not be called for pop/reverse navigation operations. + * + *

Removes all OP_REPLACE ops and replaces them with the proper add and remove + * operations that are equivalent to the replace.

+ * + *

Adds OP_UNSET_PRIMARY_NAV ops to match OP_SET_PRIMARY_NAV, OP_REMOVE and OP_DETACH + * ops so that we can restore the old primary nav fragment later. Since callers call this + * method in a loop before running ops from several transactions at once, the caller should + * pass the return value from this method as the oldPrimaryNav parameter for the next call. + * The first call in such a loop should pass the value of + * {@link FragmentManager#getPrimaryNavigationFragment()}.

* * @param added Initialized to the fragments that are in the mManager.mAdded, this * will be modified to contain the fragments that will be in mAdded * after the execution ({@link #executeOps()}. + * @param oldPrimaryNav The tracked primary navigation fragment as of the beginning of + * this set of ops + * @return the new oldPrimaryNav fragment after this record's ops would be run */ - void expandReplaceOps(ArrayList added) { + Fragment expandOps(ArrayList added, Fragment oldPrimaryNav) { for (int opNum = 0; opNum < mOps.size(); opNum++) { final Op op = mOps.get(opNum); switch (op.cmd) { @@ -834,22 +872,33 @@ final class BackStackRecord extends FragmentTransaction implements added.add(op.fragment); break; case OP_REMOVE: - case OP_DETACH: + case OP_DETACH: { added.remove(op.fragment); - break; + if (op.fragment == oldPrimaryNav) { + mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.fragment)); + opNum++; + oldPrimaryNav = null; + } + } + break; case OP_REPLACE: { - Fragment f = op.fragment; - int containerId = f.mContainerId; + final Fragment f = op.fragment; + final int containerId = f.mContainerId; boolean alreadyAdded = false; for (int i = added.size() - 1; i >= 0; i--) { - Fragment old = added.get(i); + final Fragment old = added.get(i); if (old.mContainerId == containerId) { if (old == f) { alreadyAdded = true; } else { - Op removeOp = new Op(); - removeOp.cmd = OP_REMOVE; - removeOp.fragment = old; + // This is duplicated from above since we only make + // a single pass for expanding ops. Unset any outgoing primary nav. + if (old == oldPrimaryNav) { + mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old)); + opNum++; + oldPrimaryNav = null; + } + final Op removeOp = new Op(OP_REMOVE, old); removeOp.enterAnim = op.enterAnim; removeOp.popEnterAnim = op.popEnterAnim; removeOp.exitAnim = op.exitAnim; @@ -869,8 +918,18 @@ final class BackStackRecord extends FragmentTransaction implements } } break; + case OP_SET_PRIMARY_NAV: { + // It's ok if this is null, that means we will restore to no active + // primary navigation fragment on a pop. + mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav)); + opNum++; + // Will be set by the OP_SET_PRIMARY_NAV we inserted before when run + oldPrimaryNav = op.fragment; + } + break; } } + return oldPrimaryNav; } /** @@ -917,8 +976,8 @@ final class BackStackRecord extends FragmentTransaction implements private static boolean isFragmentPostponed(Op op) { final Fragment fragment = op.fragment; - return (fragment.mAdded && fragment.mView != null && !fragment.mDetached && - !fragment.mHidden && fragment.isPostponed()); + return fragment != null && fragment.mAdded && fragment.mView != null && !fragment.mDetached + && !fragment.mHidden && fragment.isPostponed(); } public String getName() { diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 32cf1c341b4c4..b0150bdf19360 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -359,6 +359,18 @@ public abstract class FragmentManager { */ public abstract void unregisterFragmentLifecycleCallbacks(FragmentLifecycleCallbacks cb); + /** + * Return the currently active primary navigation fragment for this FragmentManager. + * + *

The primary navigation fragment's + * {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first + * to process delegated navigation actions such as {@link #popBackStack()} if no ID + * or transaction name is provided to pop to.

+ * + * @return the fragment designated as the primary navigation fragment + */ + public abstract Fragment getPrimaryNavigationFragment(); + /** * Print the FragmentManager's state into the given stream. * @@ -524,6 +536,7 @@ final class FragmentManagerState implements Parcelable { FragmentState[] mActive; int[] mAdded; BackStackState[] mBackStack; + int mPrimaryNavActiveIndex = -1; public FragmentManagerState() { } @@ -532,6 +545,7 @@ final class FragmentManagerState implements Parcelable { mActive = in.createTypedArray(FragmentState.CREATOR); mAdded = in.createIntArray(); mBackStack = in.createTypedArray(BackStackState.CREATOR); + mPrimaryNavActiveIndex = in.readInt(); } public int describeContents() { @@ -542,6 +556,7 @@ final class FragmentManagerState implements Parcelable { dest.writeTypedArray(mActive, flags); dest.writeIntArray(mAdded); dest.writeTypedArray(mBackStack, flags); + dest.writeInt(mPrimaryNavActiveIndex); } public static final Parcelable.Creator CREATOR @@ -626,6 +641,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate FragmentHostCallback mHost; FragmentContainer mContainer; Fragment mParent; + Fragment mPrimaryNav; boolean mNeedMenuInvalidate; boolean mStateSaved; @@ -783,6 +799,16 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate execPendingActions(); ensureExecReady(true); + if (mPrimaryNav != null // We have a primary nav fragment + && id < 0 // No valid id (since they're local) + && name == null) { // no name to pop to (since they're local) + final FragmentManager childManager = mPrimaryNav.mChildFragmentManager; + if (childManager != null && childManager.popBackStackImmediate()) { + // We did something, just not to this specific FragmentManager. Return true. + return true; + } + } + boolean executePop = popBackStackState(mTmpRecords, mTmpIsPop, name, id, flags); if (executePop) { mExecutingActions = true; @@ -2038,11 +2064,12 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (mAdded != null) { mTmpAddedFragments.addAll(mAdded); } + Fragment oldPrimaryNav = getPrimaryNavigationFragment(); for (int recordNum = startIndex; recordNum < endIndex; recordNum++) { final BackStackRecord record = records.get(recordNum); final boolean isPop = isRecordPop.get(recordNum); if (!isPop) { - record.expandReplaceOps(mTmpAddedFragments); + oldPrimaryNav = record.expandOps(mTmpAddedFragments, oldPrimaryNav); } else { record.trackAddedFragmentsInPop(mTmpAddedFragments); } @@ -2318,20 +2345,20 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate */ private boolean generateOpsForPendingActions(ArrayList records, ArrayList isPop) { - int numActions; + boolean didSomething = false; synchronized (this) { if (mPendingActions == null || mPendingActions.size() == 0) { return false; } - numActions = mPendingActions.size(); + final int numActions = mPendingActions.size(); for (int i = 0; i < numActions; i++) { - mPendingActions.get(i).generateOps(records, isPop); + didSomething |= mPendingActions.get(i).generateOps(records, isPop); } mPendingActions.clear(); mHost.getHandler().removeCallbacks(mExecCommit); } - return numActions > 0; + return didSomething; } void doPendingDeferredStart() { @@ -2618,6 +2645,9 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate fms.mActive = active; fms.mAdded = added; fms.mBackStack = backStack; + if (mPrimaryNav != null) { + fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex; + } return fms; } @@ -2744,6 +2774,10 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } else { mBackStack = null; } + + if (fms.mPrimaryNavActiveIndex >= 0) { + mPrimaryNav = mActive.get(fms.mPrimaryNavActiveIndex); + } } public void attachController(FragmentHostCallback host, FragmentContainer container, @@ -2942,6 +2976,19 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } } + public void setPrimaryNavigationFragment(Fragment f) { + if (f != null && (f.getFragmentManager() != this || f.mIndex >= mActive.size() + || mActive.get(f.mIndex) != f)) { + throw new IllegalArgumentException("Fragment " + f + + " is not an active fragment of FragmentManager " + this); + } + mPrimaryNav = f; + } + + public Fragment getPrimaryNavigationFragment() { + return mPrimaryNav; + } + public void registerFragmentLifecycleCallbacks(FragmentLifecycleCallbacks cb, boolean recursive) { if (mLifecycleCallbacks == null) { @@ -3386,6 +3433,16 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate @Override public boolean generateOps(ArrayList records, ArrayList isRecordPop) { + if (mPrimaryNav != null // We have a primary nav fragment + && mId < 0 // No valid id (since they're local) + && mName == null) { // no name to pop to (since they're local) + final FragmentManager childManager = mPrimaryNav.mChildFragmentManager; + if (childManager != null && childManager.popBackStackImmediate()) { + // We didn't add any operations for this FragmentManager even though + // a child did do work. + return false; + } + } return popBackStackState(records, isRecordPop, mName, mId, mFlags); } } diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java index 25a7839fa1eda..07a313c9eeaf6 100644 --- a/core/java/android/app/FragmentTransaction.java +++ b/core/java/android/app/FragmentTransaction.java @@ -131,6 +131,24 @@ public abstract class FragmentTransaction { */ public abstract FragmentTransaction attach(Fragment fragment); + /** + * Set a currently active fragment in this FragmentManager as the primary navigation fragment. + * + *

The primary navigation fragment's + * {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first + * to process delegated navigation actions such as {@link FragmentManager#popBackStack()} + * if no ID or transaction name is provided to pop to. Navigation operations outside of the + * fragment system may choose to delegate those actions to the primary navigation fragment + * as returned by {@link FragmentManager#getPrimaryNavigationFragment()}.

+ * + *

The fragment provided must currently be added to the FragmentManager to be set as + * a primary navigation fragment, or previously added as part of this transaction.

+ * + * @param fragment the fragment to set as the primary navigation fragment + * @return the same FragmentTransaction instance + */ + public abstract FragmentTransaction setPrimaryNavigationFragment(Fragment fragment); + /** * @return true if this transaction contains no operations, * false otherwise. diff --git a/core/java/android/app/FragmentTransition.java b/core/java/android/app/FragmentTransition.java index 80a5aacbd9dd2..2570d9207ed80 100644 --- a/core/java/android/app/FragmentTransition.java +++ b/core/java/android/app/FragmentTransition.java @@ -43,14 +43,16 @@ class FragmentTransition { * REPLACE operations have already been replaced by add/remove operations. */ private static final int[] INVERSE_OPS = { - BackStackRecord.OP_NULL, // inverse of OP_NULL (error) - BackStackRecord.OP_REMOVE, // inverse of OP_ADD - BackStackRecord.OP_NULL, // inverse of OP_REPLACE (error) - BackStackRecord.OP_ADD, // inverse of OP_REMOVE - BackStackRecord.OP_SHOW, // inverse of OP_HIDE - BackStackRecord.OP_HIDE, // inverse of OP_SHOW - BackStackRecord.OP_ATTACH, // inverse of OP_DETACH - BackStackRecord.OP_DETACH, // inverse of OP_ATTACH + BackStackRecord.OP_NULL, // inverse of OP_NULL (error) + BackStackRecord.OP_REMOVE, // inverse of OP_ADD + BackStackRecord.OP_NULL, // inverse of OP_REPLACE (error) + BackStackRecord.OP_ADD, // inverse of OP_REMOVE + BackStackRecord.OP_SHOW, // inverse of OP_HIDE + BackStackRecord.OP_HIDE, // inverse of OP_SHOW + BackStackRecord.OP_ATTACH, // inverse of OP_DETACH + BackStackRecord.OP_DETACH, // inverse of OP_ATTACH + BackStackRecord.OP_UNSET_PRIMARY_NAV, // inverse of OP_SET_PRIMARY_NAV + BackStackRecord.OP_SET_PRIMARY_NAV, // inverse of OP_UNSET_PRIMARY_NAV }; /** @@ -1232,6 +1234,9 @@ class FragmentTransition { SparseArray transitioningFragments, boolean isPop, boolean isOptimizedTransaction) { final Fragment fragment = op.fragment; + if (fragment == null) { + return; // no fragment, no transition + } final int containerId = fragment.mContainerId; if (containerId == 0) { return; // no container, no transition