From 5f3a05c15fa636e911a646e35765ba8bbed7d5e1 Mon Sep 17 00:00:00 2001 From: Adam Powell Date: Fri, 27 Jan 2017 16:51:03 -0800 Subject: [PATCH] Primary navigation fragments Port of I82cd22cda6e9ee22fe293c36859e99f49d36a129 from frameworks/support Add API to track the "primary navigation fragment" for a FragmentManager. This allows designating one active fragment in a FragmentManager as the intended nested recipient of navigation actions such as popping the back stack. The FragmentManager popBackStack methods will now delegate to the child FragmentManager of the current primary navigation fragment first before trying to pop its own back stack. This can only happen if a back stack transaction id or back stack entry name is not provided to the pop operation, as these names and ids are namespaced to each individual FragmentManager. Primary navigation fragments are set as part of a FragmentTransaction and follow the associated expectations. Transactions added to the back stack will be fully reversed when popped, restoring the previous primary navigation fragment for the FragmentManager. If a primary navigation fragment is removed from the FragmentManager, the primary navigation fragment will be set to null. Bug: 30596279 Test: CTS Change-Id: Ied7c1515f27142d6f4cd523f16eb35d38a2a2380 --- api/current.txt | 2 + api/system-current.txt | 2 + api/test-current.txt | 2 + core/java/android/app/BackStackRecord.java | 149 ++++++++++++------ core/java/android/app/FragmentManager.java | 67 +++++++- .../java/android/app/FragmentTransaction.java | 18 +++ core/java/android/app/FragmentTransition.java | 21 ++- 7 files changed, 203 insertions(+), 58 deletions(-) diff --git a/api/current.txt b/api/current.txt index 31114842036cb..dd5ffef252320 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4684,6 +4684,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(); @@ -4760,6 +4761,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 29179cfc8fcf6..a776f56225132 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4844,6 +4844,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(); @@ -4920,6 +4921,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 334f4e62c97f3..38da898724d60 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -4694,6 +4694,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(); @@ -4770,6 +4771,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