diff options
-rw-r--r-- | api/current.txt | 1 | ||||
-rw-r--r-- | api/system-current.txt | 1 | ||||
-rw-r--r-- | api/test-current.txt | 1 | ||||
-rw-r--r-- | core/java/android/app/BackStackRecord.java | 272 | ||||
-rw-r--r-- | core/java/android/app/Fragment.java | 9 | ||||
-rw-r--r-- | core/java/android/app/FragmentManager.java | 696 | ||||
-rw-r--r-- | core/java/android/app/FragmentTransaction.java | 26 |
7 files changed, 696 insertions, 310 deletions
diff --git a/api/current.txt b/api/current.txt index 9aa8c59dbf70..a3d8131758d0 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4671,6 +4671,7 @@ package android.app { method public abstract android.app.FragmentTransaction remove(android.app.Fragment); method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment); method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment, java.lang.String); + method public abstract android.app.FragmentTransaction setAllowOptimization(boolean); method public abstract android.app.FragmentTransaction setBreadCrumbShortTitle(int); method public abstract android.app.FragmentTransaction setBreadCrumbShortTitle(java.lang.CharSequence); method public abstract android.app.FragmentTransaction setBreadCrumbTitle(int); diff --git a/api/system-current.txt b/api/system-current.txt index e6e5d3835657..b2f72fc20139 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4816,6 +4816,7 @@ package android.app { method public abstract android.app.FragmentTransaction remove(android.app.Fragment); method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment); method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment, java.lang.String); + method public abstract android.app.FragmentTransaction setAllowOptimization(boolean); method public abstract android.app.FragmentTransaction setBreadCrumbShortTitle(int); method public abstract android.app.FragmentTransaction setBreadCrumbShortTitle(java.lang.CharSequence); method public abstract android.app.FragmentTransaction setBreadCrumbTitle(int); diff --git a/api/test-current.txt b/api/test-current.txt index ee3cb0d66e4d..22fd95547a71 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -4674,6 +4674,7 @@ package android.app { method public abstract android.app.FragmentTransaction remove(android.app.Fragment); method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment); method public abstract android.app.FragmentTransaction replace(int, android.app.Fragment, java.lang.String); + method public abstract android.app.FragmentTransaction setAllowOptimization(boolean); method public abstract android.app.FragmentTransaction setBreadCrumbShortTitle(int); method public abstract android.app.FragmentTransaction setBreadCrumbShortTitle(java.lang.CharSequence); method public abstract android.app.FragmentTransaction setBreadCrumbTitle(int); diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java index a4b1a1f50104..c589466f3b13 100644 --- a/core/java/android/app/BackStackRecord.java +++ b/core/java/android/app/BackStackRecord.java @@ -16,6 +16,7 @@ package android.app; +import android.content.Context; import android.graphics.Rect; import android.os.Build; import android.os.Parcel; @@ -52,6 +53,7 @@ final class BackStackState implements Parcelable { final CharSequence mBreadCrumbShortTitleText; final ArrayList<String> mSharedElementSourceNames; final ArrayList<String> mSharedElementTargetNames; + final boolean mAllowOptimization; public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) { final int numOps = bse.mOps.size(); @@ -81,6 +83,7 @@ final class BackStackState implements Parcelable { mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText; mSharedElementSourceNames = bse.mSharedElementSourceNames; mSharedElementTargetNames = bse.mSharedElementTargetNames; + mAllowOptimization = bse.mAllowOptimization; } public BackStackState(Parcel in) { @@ -95,6 +98,7 @@ final class BackStackState implements Parcelable { mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mSharedElementSourceNames = in.createStringArrayList(); mSharedElementTargetNames = in.createStringArrayList(); + mAllowOptimization = in.readInt() != 0; } public BackStackRecord instantiate(FragmentManagerImpl fm) { @@ -137,6 +141,7 @@ final class BackStackState implements Parcelable { bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText; bse.mSharedElementSourceNames = mSharedElementSourceNames; bse.mSharedElementTargetNames = mSharedElementTargetNames; + bse.mAllowOptimization = mAllowOptimization; bse.bumpBackStackNesting(1); return bse; } @@ -157,6 +162,7 @@ final class BackStackState implements Parcelable { TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0); dest.writeStringList(mSharedElementSourceNames); dest.writeStringList(mSharedElementTargetNames); + dest.writeInt(mAllowOptimization ? 1 : 0); } public static final Parcelable.Creator<BackStackState> CREATOR @@ -175,7 +181,7 @@ final class BackStackState implements Parcelable { * @hide Entry of an operation on the fragment back stack. */ final class BackStackRecord extends FragmentTransaction implements - FragmentManager.BackStackEntry, Runnable { + FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator { static final String TAG = FragmentManagerImpl.TAG; final FragmentManagerImpl mManager; @@ -210,6 +216,7 @@ final class BackStackRecord extends FragmentTransaction implements String mName; boolean mCommitted; int mIndex = -1; + boolean mAllowOptimization; int mBreadCrumbTitleRes; CharSequence mBreadCrumbTitleText; @@ -352,6 +359,7 @@ final class BackStackRecord extends FragmentTransaction implements public BackStackRecord(FragmentManagerImpl manager) { mManager = manager; + mAllowOptimization = Build.isAtLeastO(); } public int getId() { @@ -633,6 +641,12 @@ final class BackStackRecord extends FragmentTransaction implements mManager.execSingleAction(this, true); } + @Override + public FragmentTransaction setAllowOptimization(boolean allowOptimization) { + mAllowOptimization = allowOptimization; + return this; + } + int commitInternal(boolean allowStateLoss) { if (mCommitted) { throw new IllegalStateException("commit already called"); @@ -654,30 +668,40 @@ final class BackStackRecord extends FragmentTransaction implements return mIndex; } - public void run() { + /** + * Implementation of {@link FragmentManagerImpl.android.app.FragmentManagerImpl.OpGenerator}. + * This operation is added to the list of pending actions during {@link #commit()}, and + * will be executed on the UI thread to run this FragmentTransaction. + * + * @param records Modified to add this BackStackRecord + * @param isRecordPop Modified to add a false (this isn't a pop) + * @return true always because the records and isRecordPop will always be changed + */ + @Override + public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) { if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "Run: " + this); } + records.add(this); + isRecordPop.add(false); if (mAddToBackStack) { - if (mIndex < 0) { - throw new IllegalStateException("addToBackStack() called after commit()"); - } - } - - expandReplaceOps(); - bumpBackStackNesting(1); - - if (mManager.mCurState >= Fragment.CREATED) { - SparseArray<FragmentContainerTransition> transitioningFragments = new SparseArray<>(); - calculateFragments(transitioningFragments); - beginTransition(transitioningFragments); + mManager.addBackStackState(this); } + return true; + } + /** + * Executes the operations contained within this transaction. The Fragment states will only + * be modified if optimizations are not allowed. + */ + void executeOps() { final int numOps = mOps.size(); for (int opNum = 0; opNum < numOps; opNum++) { final Op op = mOps.get(opNum); - Fragment f = op.fragment; + final Fragment f = op.fragment; + f.mNextTransition = mTransition; + f.mNextTransitionStyle = mTransitionStyle; switch (op.cmd) { case OP_ADD: f.mNextAnim = op.enterAnim; @@ -685,56 +709,94 @@ final class BackStackRecord extends FragmentTransaction implements break; case OP_REMOVE: f.mNextAnim = op.exitAnim; - mManager.removeFragment(f, mTransition, mTransitionStyle); + mManager.removeFragment(f); break; case OP_HIDE: f.mNextAnim = op.exitAnim; - mManager.hideFragment(f, mTransition, mTransitionStyle); + mManager.hideFragment(f); break; case OP_SHOW: f.mNextAnim = op.enterAnim; - mManager.showFragment(f, mTransition, mTransitionStyle); + mManager.showFragment(f); break; case OP_DETACH: f.mNextAnim = op.exitAnim; - mManager.detachFragment(f, mTransition, mTransitionStyle); + mManager.detachFragment(f); break; case OP_ATTACH: f.mNextAnim = op.enterAnim; - mManager.attachFragment(f, mTransition, mTransitionStyle); + mManager.attachFragment(f); break; default: throw new IllegalArgumentException("Unknown cmd: " + op.cmd); } + if (!mAllowOptimization && op.cmd != OP_ADD) { + mManager.moveFragmentToExpectedState(f); + } } - - mManager.moveToState(mManager.mCurState, mTransition, - mTransitionStyle, true); - - if (mAddToBackStack) { - mManager.addBackStackState(this); + if (!mAllowOptimization) { + // Added fragments are added at the end to comply with prior behavior. + mManager.moveToState(mManager.mCurState); } } - private void expandReplaceOps() { - final int numOps = mOps.size(); - - boolean hasReplace = false; - // Before we do anything, check to see if any replace operations exist: - for (int opNum = 0; opNum < numOps; opNum++) { + /** + * Reverses the execution of the operations within this transaction. The Fragment states will + * only be modified if optimizations are not allowed. + */ + void executePopOps() { + for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) { final Op op = mOps.get(opNum); - if (op.cmd == OP_REPLACE) { - hasReplace = true; - break; + Fragment f = op.fragment; + f.mNextTransition = FragmentManagerImpl.reverseTransit(mTransition); + f.mNextTransitionStyle = mTransitionStyle; + switch (op.cmd) { + case OP_ADD: + f.mNextAnim = op.popExitAnim; + mManager.removeFragment(f); + break; + case OP_REMOVE: + f.mNextAnim = op.popEnterAnim; + mManager.addFragment(f, false); + break; + case OP_HIDE: + f.mNextAnim = op.popEnterAnim; + mManager.showFragment(f); + break; + case OP_SHOW: + f.mNextAnim = op.popExitAnim; + mManager.hideFragment(f); + break; + case OP_DETACH: + f.mNextAnim = op.popEnterAnim; + mManager.attachFragment(f); + break; + case OP_ATTACH: + f.mNextAnim = op.popExitAnim; + mManager.detachFragment(f); + break; + default: + throw new IllegalArgumentException("Unknown cmd: " + op.cmd); + } + if (!mAllowOptimization && op.cmd != OP_ADD) { + mManager.moveFragmentToExpectedState(f); } } - - if (!hasReplace) { - return; // nothing to expand + if (!mAllowOptimization) { + mManager.moveToState(mManager.mCurState); } + } - ArrayList<Fragment> added = (mManager.mAdded == null) ? new ArrayList<Fragment>() : - new ArrayList<>(mManager.mAdded); + /** + * 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. + * + * @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()}. + */ + void expandReplaceOps(ArrayList<Fragment> added) { for (int opNum = 0; opNum < mOps.size(); opNum++) { final Op op = mOps.get(opNum); switch (op.cmd) { @@ -822,13 +884,14 @@ final class BackStackRecord extends FragmentTransaction implements fragments.firstOut = null; } } + /** * Ensure that fragments that are entering are at least at the CREATED state * so that they may load Transitions using TransitionInflater. */ if (fragment.mState < Fragment.CREATED && mManager.mCurState >= Fragment.CREATED && mManager.mHost.getContext().getApplicationInfo().targetSdkVersion >= - Build.VERSION_CODES.N) { + Build.VERSION_CODES.N && !mAllowOptimization) { mManager.makeActive(fragment); mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false); } @@ -843,7 +906,7 @@ final class BackStackRecord extends FragmentTransaction implements * and last fragments to be added. This will be modified by * this method. */ - private void calculateFragments( + public void calculateFragments( SparseArray<FragmentContainerTransition> transitioningFragments) { if (!mManager.mContainer.onHasView()) { return; // nothing to see, so no transitions @@ -874,7 +937,7 @@ final class BackStackRecord extends FragmentTransaction implements * and last fragments to be added. This will be modified by * this method. */ - public void calculateBackFragments( + public void calculatePopFragments( SparseArray<FragmentContainerTransition> transitioningFragments) { if (!mManager.mContainer.onHasView()) { return; // nothing to see, so no transitions @@ -923,7 +986,7 @@ final class BackStackRecord extends FragmentTransaction implements * in {@link #setNameOverrides(android.app.BackStackRecord.TransitionState, java.util.ArrayList, * java.util.ArrayList)}. */ - private TransitionState beginTransition(SparseArray<FragmentContainerTransition> containers) { + TransitionState beginTransition(SparseArray<FragmentContainerTransition> containers) { TransitionState state = new TransitionState(); // Adding a non-existent target view makes sure that the transitions don't target @@ -947,28 +1010,28 @@ final class BackStackRecord extends FragmentTransaction implements return transition; } - private static Transition getEnterTransition(Fragment inFragment, boolean isBack) { + private static Transition getEnterTransition(Fragment inFragment, boolean isPop) { if (inFragment == null) { return null; } - return cloneTransition(isBack ? inFragment.getReenterTransition() : + return cloneTransition(isPop ? inFragment.getReenterTransition() : inFragment.getEnterTransition()); } - private static Transition getExitTransition(Fragment outFragment, boolean isBack) { + private static Transition getExitTransition(Fragment outFragment, boolean isPop) { if (outFragment == null) { return null; } - return cloneTransition(isBack ? outFragment.getReturnTransition() : + return cloneTransition(isPop ? outFragment.getReturnTransition() : outFragment.getExitTransition()); } private static TransitionSet getSharedElementTransition(Fragment inFragment, - Fragment outFragment, boolean isBack) { + Fragment outFragment, boolean isPop) { if (inFragment == null || outFragment == null) { return null; } - Transition transition = cloneTransition(isBack + Transition transition = cloneTransition(isPop ? outFragment.getSharedElementReturnTransition() : inFragment.getSharedElementEnterTransition()); if (transition == null) { @@ -998,11 +1061,11 @@ final class BackStackRecord extends FragmentTransaction implements } private ArrayMap<String, View> remapSharedElements(TransitionState state, Fragment outFragment, - boolean isBack) { + boolean isPop) { ArrayMap<String, View> namedViews = new ArrayMap<String, View>(); if (mSharedElementSourceNames != null) { outFragment.getView().findNamedViews(namedViews); - if (isBack) { + if (isPop) { namedViews.retainAll(mSharedElementTargetNames); } else { namedViews = remapNames(mSharedElementSourceNames, mSharedElementTargetNames, @@ -1010,7 +1073,7 @@ final class BackStackRecord extends FragmentTransaction implements } } - if (isBack) { + if (isPop) { outFragment.mEnterTransitionCallback.onMapSharedElements( mSharedElementTargetNames, namedViews); setBackNameOverrides(state, namedViews, false); @@ -1038,7 +1101,7 @@ final class BackStackRecord extends FragmentTransaction implements final Transition enterTransition, final TransitionSet sharedElementTransition, final Transition exitTransition, final Transition overallTransition, final View container, final Fragment inFragment, final Fragment outFragment, - final ArrayList<View> hiddenFragmentViews, final boolean isBack, + final ArrayList<View> hiddenFragmentViews, final boolean isPop, final ArrayList<View> sharedElementTargets) { if (enterTransition == null && sharedElementTransition == null && overallTransition == null) { @@ -1059,7 +1122,7 @@ final class BackStackRecord extends FragmentTransaction implements ArrayMap<String, View> namedViews = null; if (sharedElementTransition != null) { - namedViews = mapSharedElementsIn(state, isBack, inFragment); + namedViews = mapSharedElementsIn(state, isPop, inFragment); removeTargets(sharedElementTransition, sharedElementTargets); // keep the nonExistentView as excluded so the list doesn't get emptied sharedElementTargets.remove(state.nonExistentView); @@ -1073,7 +1136,7 @@ final class BackStackRecord extends FragmentTransaction implements setEpicenterIn(namedViews, state); - callSharedElementEnd(state, inFragment, outFragment, isBack, + callSharedElementEnd(state, inFragment, outFragment, isPop, namedViews); } @@ -1104,8 +1167,8 @@ final class BackStackRecord extends FragmentTransaction implements } private void callSharedElementEnd(TransitionState state, Fragment inFragment, - Fragment outFragment, boolean isBack, ArrayMap<String, View> namedViews) { - SharedElementCallback sharedElementCallback = isBack ? + Fragment outFragment, boolean isPop, ArrayMap<String, View> namedViews) { + SharedElementCallback sharedElementCallback = isPop ? outFragment.mEnterTransitionCallback : inFragment.mEnterTransitionCallback; ArrayList<String> names = new ArrayList<String>(namedViews.keySet()); @@ -1125,13 +1188,13 @@ final class BackStackRecord extends FragmentTransaction implements } private ArrayMap<String, View> mapSharedElementsIn(TransitionState state, - boolean isBack, Fragment inFragment) { + boolean isPop, Fragment inFragment) { // Now map the shared elements in the incoming fragment - ArrayMap<String, View> namedViews = mapEnteringSharedElements(state, inFragment, isBack); + ArrayMap<String, View> namedViews = mapEnteringSharedElements(state, inFragment, isPop); // remap shared elements and set the name mapping used // in the shared element transition. - if (isBack) { + if (isPop) { inFragment.mExitTransitionCallback.onMapSharedElements( mSharedElementTargetNames, namedViews); setBackNameOverrides(state, namedViews, true); @@ -1145,10 +1208,10 @@ final class BackStackRecord extends FragmentTransaction implements private static Transition mergeTransitions(Transition enterTransition, Transition exitTransition, Transition sharedElementTransition, Fragment inFragment, - boolean isBack) { + boolean isPop) { boolean overlap = true; if (enterTransition != null && exitTransition != null && inFragment != null) { - overlap = isBack ? inFragment.getAllowReturnTransitionOverlap() : + overlap = isPop ? inFragment.getAllowReturnTransitionOverlap() : inFragment.getAllowEnterTransitionOverlap(); } @@ -1496,17 +1559,17 @@ final class BackStackRecord extends FragmentTransaction implements * @param state The transition State as returned from {@link #beginTransition( * android.util.SparseArray, android.util.SparseArray, boolean)}. * @param inFragment The last fragment to be added. - * @param isBack true if this is popping the back stack or false if this is a + * @param isPop true if this is popping the back stack or false if this is a * forward operation. */ private ArrayMap<String, View> mapEnteringSharedElements(TransitionState state, - Fragment inFragment, boolean isBack) { + Fragment inFragment, boolean isPop) { ArrayMap<String, View> namedViews = new ArrayMap<String, View>(); View root = inFragment.getView(); if (root != null) { if (mSharedElementSourceNames != null) { root.findNamedViews(namedViews); - if (isBack) { + if (isPop) { namedViews = remapNames(mSharedElementSourceNames, mSharedElementTargetNames, namedViews); } else { @@ -1565,81 +1628,6 @@ final class BackStackRecord extends FragmentTransaction implements }); } - public TransitionState popFromBackStack(boolean doStateMove, TransitionState state, - SparseArray<FragmentContainerTransition> transitioningFragments) { - if (FragmentManagerImpl.DEBUG) { - Log.v(TAG, "popFromBackStack: " + this); - LogWriter logw = new LogWriter(Log.VERBOSE, TAG); - PrintWriter pw = new FastPrintWriter(logw, false, 1024); - dump(" ", null, pw, null); - pw.flush(); - } - - if (mManager.mCurState >= Fragment.CREATED) { - if (state == null) { - if (transitioningFragments.size() != 0) { - state = beginTransition(transitioningFragments); - } - } else if (!doStateMove) { - setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames); - } - } - - bumpBackStackNesting(-1); - - final int numOps = mOps.size(); - for (int opNum = numOps - 1; opNum >= 0; opNum--) { - final Op op = mOps.get(opNum); - Fragment f = op.fragment; - switch (op.cmd) { - case OP_ADD: - f.mNextAnim = op.popExitAnim; - mManager.removeFragment(f, - FragmentManagerImpl.reverseTransit(mTransition), - mTransitionStyle); - break; - case OP_REMOVE: - f.mNextAnim = op.popEnterAnim; - mManager.addFragment(f, false); - break; - case OP_HIDE: - f.mNextAnim = op.popEnterAnim; - mManager.showFragment(f, - FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - break; - case OP_SHOW: - f.mNextAnim = op.popExitAnim; - mManager.hideFragment(f, - FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - break; - case OP_DETACH: - f.mNextAnim = op.popEnterAnim; - mManager.attachFragment(f, - FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - break; - case OP_ATTACH: - f.mNextAnim = op.popExitAnim; - mManager.detachFragment(f, - FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); - break; - default: - throw new IllegalArgumentException("Unknown cmd: " + op.cmd); - } - } - - if (doStateMove) { - mManager.moveToState(mManager.mCurState, - FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true); - state = null; - } - - if (mIndex >= 0) { - mManager.freeBackStackIndex(mIndex); - mIndex = -1; - } - return state; - } - private static void setNameOverride(ArrayMap<String, String> overrides, String source, String target) { if (source != null && target != null && !source.equals(target)) { @@ -1653,7 +1641,7 @@ final class BackStackRecord extends FragmentTransaction implements } } - private static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames, + static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames, ArrayList<String> targetNames) { if (sourceNames != null && targetNames != null) { for (int i = 0; i < sourceNames.size(); i++) { diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 6ea170ea1a8e..b72b96026af4 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -481,6 +481,12 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene // If app has requested a specific animation, this is the one to use. int mNextAnim; + // If app has requested a specific transition, this is the one to use. + int mNextTransition; + + // If app has requested a specific transition style, this is the one to use. + int mNextTransitionStyle; + // The parent container of the fragment after dynamically added to UI. ViewGroup mContainer; @@ -510,6 +516,9 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene SharedElementCallback mEnterTransitionCallback = SharedElementCallback.NULL_CALLBACK; SharedElementCallback mExitTransitionCallback = SharedElementCallback.NULL_CALLBACK; + // True if mHidden has been changed and the animation should be scheduled. + boolean mHiddenChanged; + /** * State information that has been retrieved from a fragment instance * through {@link FragmentManager#saveFragmentInstanceState(Fragment) diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 674c3f7166f1..b2df1acbadaf 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -28,10 +28,10 @@ import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; import android.os.Bundle; import android.os.Debug; -import android.os.Handler; import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; +import android.transition.Transition; import android.util.AttributeSet; import android.util.DebugUtils; import android.util.Log; @@ -445,8 +445,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } } - ArrayList<Runnable> mPendingActions; - Runnable[] mTmpActions; + ArrayList<OpGenerator> mPendingActions; boolean mExecutingActions; ArrayList<Fragment> mActive; @@ -463,7 +462,6 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate int mCurState = Fragment.INITIALIZING; FragmentHostCallback<?> mHost; - FragmentController mController; FragmentContainer mContainer; Fragment mParent; @@ -473,6 +471,12 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate String mNoTransactionsBecause; boolean mHavePendingDeferredStart; + // Temporary vars for optimizing execution of BackStackRecords: + ArrayList<BackStackRecord> mTmpRecords; + ArrayList<Boolean> mTmpIsPop; + SparseArray<BackStackRecord.FragmentContainerTransition> mTmpFragmentsContainerTransitions; + ArrayList<Fragment> mTmpAddedFragments; + // Temporary vars for state save and restore. Bundle mStateBundle = null; SparseArray<Parcelable> mStateArray = null; @@ -565,56 +569,66 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate @Override public void popBackStack() { - enqueueAction(new Runnable() { - @Override public void run() { - popBackStackState(mHost.getHandler(), null, -1, 0); - } - }, false); + enqueueAction(new PopBackStackState(null, -1, 0), false); } @Override public boolean popBackStackImmediate() { checkStateLoss(); - executePendingTransactions(); - return popBackStackState(mHost.getHandler(), null, -1, 0); + return popBackStackImmediate(null, -1, 0); } @Override - public void popBackStack(final String name, final int flags) { - enqueueAction(new Runnable() { - @Override public void run() { - popBackStackState(mHost.getHandler(), name, -1, flags); - } - }, false); + public void popBackStack(String name, int flags) { + enqueueAction(new PopBackStackState(name, -1, flags), false); } @Override public boolean popBackStackImmediate(String name, int flags) { checkStateLoss(); - executePendingTransactions(); - return popBackStackState(mHost.getHandler(), name, -1, flags); + return popBackStackImmediate(name, -1, flags); } @Override - public void popBackStack(final int id, final int flags) { + public void popBackStack(int id, int flags) { if (id < 0) { throw new IllegalArgumentException("Bad id: " + id); } - enqueueAction(new Runnable() { - @Override public void run() { - popBackStackState(mHost.getHandler(), null, id, flags); - } - }, false); + enqueueAction(new PopBackStackState(null, id, flags), false); } @Override public boolean popBackStackImmediate(int id, int flags) { checkStateLoss(); - executePendingTransactions(); if (id < 0) { throw new IllegalArgumentException("Bad id: " + id); } - return popBackStackState(mHost.getHandler(), null, id, flags); + return popBackStackImmediate(null, id, flags); + } + + /** + * Used by all public popBackStackImmediate methods, this executes pending transactions and + * returns true if the pop action did anything, regardless of what other pending + * transactions did. + * + * @return true if the pop operation did anything or false otherwise. + */ + private boolean popBackStackImmediate(String name, int id, int flags) { + executePendingTransactions(); + ensureExecReady(true); + + boolean executePop = popBackStackState(mTmpRecords, mTmpIsPop, name, id, flags); + if (executePop) { + mExecutingActions = true; + try { + optimizeAndExecuteOps(mTmpRecords, mTmpIsPop); + } finally { + cleanupExec(); + } + } + + doPendingDeferredStart(); + return executePop; } @Override @@ -785,7 +799,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (N > 0) { writer.print(prefix); writer.println("Pending Actions:"); for (int i=0; i<N; i++) { - Runnable r = mPendingActions.get(i); + OpGenerator r = mPendingActions.get(i); writer.print(prefix); writer.print(" #"); writer.print(i); writer.print(": "); writer.println(r); } @@ -1149,26 +1163,115 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate moveToState(f, mCurState, 0, 0, false); } - void moveToState(int newState, boolean always) { - moveToState(newState, 0, 0, always); - } - - void moveToState(int newState, int transit, int transitStyle, boolean always) { - if (mHost == null && newState != Fragment.INITIALIZING) { - throw new IllegalStateException("No activity"); + /** + * Fragments that have been shown or hidden don't have their visibility changed or + * animations run during the {@link #showFragment(Fragment)} or {@link #hideFragment(Fragment)} + * calls. After fragments are brought to their final state in + * {@link #moveFragmentToExpectedState(Fragment)} the fragments that have been shown or + * hidden must have their visibility changed and their animations started here. + * + * @param fragment The fragment with mHiddenChanged = true that should change its View's + * visibility and start the show or hide animation. + */ + void completeShowHideFragment(final Fragment fragment) { + if (fragment.mView != null) { + Animator anim = loadAnimator(fragment, fragment.mNextTransition, !fragment.mHidden, + fragment.mNextTransitionStyle); + if (anim != null) { + anim.setTarget(fragment.mView); + if (fragment.mHidden) { + // Delay the actual hide operation until the animation finishes, otherwise + // the fragment will just immediately disappear + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + animation.removeListener(this); + if (fragment.mView != null) { + fragment.mView.setVisibility(View.GONE); + } + } + }); + } + setHWLayerAnimListenerIfAlpha(fragment.mView, anim); + anim.start(); + } else { + final int visibility = fragment.mHidden ? View.GONE : View.VISIBLE; + fragment.mView.setVisibility(visibility); + } + } + if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) { + mNeedMenuInvalidate = true; } + fragment.mHiddenChanged = false; + fragment.onHiddenChanged(fragment.mHidden); + } - if (!always && mCurState == newState) { + /** + * Moves a fragment to its expected final state or the fragment manager's state, depending + * on whether the fragment manager's state is raised properly. + * + * @param f The fragment to change. + */ + void moveFragmentToExpectedState(Fragment f) { + if (f == null) { return; } + int nextState = mCurState; + if (f.mRemoving) { + if (f.isInBackStack()) { + nextState = Fragment.CREATED; + } else { + nextState = Fragment.INITIALIZING; + } + } + moveToState(f, nextState, f.mNextTransition, f.mNextTransitionStyle, false); + + if (f.mView != null) { + Fragment underFragment = findFragmentUnder(f); + if (underFragment != null) { + final View underView = underFragment.mView; + // make sure this fragment is in the right order. + final ViewGroup container = f.mContainer; + int underIndex = container.indexOfChild(underView); + int viewIndex = container.indexOfChild(f.mView); + if (viewIndex < underIndex) { + container.removeViewAt(viewIndex); + container.addView(f.mView, underIndex); + } + } + } + if (f.mHiddenChanged) { + completeShowHideFragment(f); + } + } + + void moveToState(int newState) { + if (mHost == null && newState != Fragment.INITIALIZING) { + throw new IllegalStateException("No activity"); + } mCurState = newState; + if (mActive != null) { boolean loadersRunning = false; - for (int i=0; i<mActive.size(); i++) { + + // Must add them in the proper order. mActive fragments may be out of order + final int numAdded = mAdded.size(); + for (int i = 0; i < numAdded; i++) { + Fragment f = mAdded.get(i); + moveFragmentToExpectedState(f); + if (f.mLoaderManager != null) { + loadersRunning |= f.mLoaderManager.hasRunningLoaders(); + } + } + + // Now iterate through all active fragments. These will include those that are removed + // and detached. + final int numActive = mActive.size(); + for (int i = 0; i < numActive; i++) { Fragment f = mActive.get(i); - if (f != null) { - moveToState(f, newState, transit, transitStyle, false); + if (f != null && (f.mRemoving || f.mDetached)) { + moveFragmentToExpectedState(f); if (f.mLoaderManager != null) { loadersRunning |= f.mLoaderManager.hasRunningLoaders(); } @@ -1244,6 +1347,9 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate mAdded.add(fragment); fragment.mAdded = true; fragment.mRemoving = false; + if (fragment.mView == null) { + fragment.mHiddenChanged = false; + } if (fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } @@ -1252,8 +1358,8 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } } } - - public void removeFragment(Fragment fragment, int transition, int transitionStyle) { + + public void removeFragment(Fragment fragment) { if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting); final boolean inactive = !fragment.isInBackStack(); if (!fragment.mDetached || inactive) { @@ -1273,66 +1379,42 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } fragment.mAdded = false; fragment.mRemoving = true; - moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED, - transition, transitionStyle, false); } } - - public void hideFragment(Fragment fragment, int transition, int transitionStyle) { + + /** + * Marks a fragment as hidden to be later animated in with + * {@link #completeShowHideFragment(Fragment)}. + * + * @param fragment The fragment to be shown. + */ + public void hideFragment(Fragment fragment) { if (DEBUG) Log.v(TAG, "hide: " + fragment); if (!fragment.mHidden) { fragment.mHidden = true; - if (fragment.mView != null) { - Animator anim = loadAnimator(fragment, transition, false, - transitionStyle); - if (anim != null) { - anim.setTarget(fragment.mView); - // Delay the actual hide operation until the animation finishes, otherwise - // the fragment will just immediately disappear - final Fragment finalFragment = fragment; - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (finalFragment.mView != null) { - finalFragment.mView.setVisibility(View.GONE); - } - } - }); - setHWLayerAnimListenerIfAlpha(finalFragment.mView, anim); - anim.start(); - } else { - fragment.mView.setVisibility(View.GONE); - } - } - if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) { - mNeedMenuInvalidate = true; - } - fragment.onHiddenChanged(true); + // Toggle hidden changed so that if a fragment goes through show/hide/show + // it doesn't go through the animation. + fragment.mHiddenChanged = !fragment.mHiddenChanged; } } - - public void showFragment(Fragment fragment, int transition, int transitionStyle) { + + /** + * Marks a fragment as shown to be later animated in with + * {@link #completeShowHideFragment(Fragment)}. + * + * @param fragment The fragment to be shown. + */ + public void showFragment(Fragment fragment) { if (DEBUG) Log.v(TAG, "show: " + fragment); if (fragment.mHidden) { fragment.mHidden = false; - if (fragment.mView != null) { - Animator anim = loadAnimator(fragment, transition, true, - transitionStyle); - if (anim != null) { - anim.setTarget(fragment.mView); - setHWLayerAnimListenerIfAlpha(fragment.mView, anim); - anim.start(); - } - fragment.mView.setVisibility(View.VISIBLE); - } - if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) { - mNeedMenuInvalidate = true; - } - fragment.onHiddenChanged(false); + // Toggle hidden changed so that if a fragment goes through show/hide/show + // it doesn't go through the animation. + fragment.mHiddenChanged = !fragment.mHiddenChanged; } } - - public void detachFragment(Fragment fragment, int transition, int transitionStyle) { + + public void detachFragment(Fragment fragment) { if (DEBUG) Log.v(TAG, "detach: " + fragment); if (!fragment.mDetached) { fragment.mDetached = true; @@ -1346,12 +1428,11 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate mNeedMenuInvalidate = true; } fragment.mAdded = false; - moveToState(fragment, Fragment.CREATED, transition, transitionStyle, false); } } } - public void attachFragment(Fragment fragment, int transition, int transitionStyle) { + public void attachFragment(Fragment fragment) { if (DEBUG) Log.v(TAG, "attach: " + fragment); if (fragment.mDetached) { fragment.mDetached = false; @@ -1368,7 +1449,6 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } - moveToState(fragment, mCurState, transition, transitionStyle, false); } } } @@ -1447,7 +1527,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate * @param allowStateLoss whether to allow loss of state information * @throws IllegalStateException if the activity has been destroyed */ - public void enqueueAction(Runnable action, boolean allowStateLoss) { + public void enqueueAction(OpGenerator action, boolean allowStateLoss) { if (!allowStateLoss) { checkStateLoss(); } @@ -1456,7 +1536,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate throw new IllegalStateException("Activity has been destroyed"); } if (mPendingActions == null) { - mPendingActions = new ArrayList<Runnable>(); + mPendingActions = new ArrayList<>(); } mPendingActions.add(action); if (mPendingActions.size() == 1) { @@ -1522,7 +1602,13 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } } - public void execSingleAction(Runnable action, boolean allowStateLoss) { + /** + * Broken out from exec*, this prepares for gathering and executing operations. + * + * @param allowStateLoss true if state loss should be ignored or false if it should be + * checked. + */ + private void ensureExecReady(boolean allowStateLoss) { if (mExecutingActions) { throw new IllegalStateException("FragmentManager is already executing transactions"); } @@ -1535,55 +1621,49 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate checkStateLoss(); } - mExecutingActions = true; - try { - action.run(); - } finally { - mExecutingActions = false; + if (mTmpRecords == null) { + mTmpRecords = new ArrayList<>(); + mTmpIsPop = new ArrayList<>(); + } + } + + public void execSingleAction(OpGenerator action, boolean allowStateLoss) { + ensureExecReady(allowStateLoss); + if (action.generateOps(mTmpRecords, mTmpIsPop)) { + mExecutingActions = true; + try { + optimizeAndExecuteOps(mTmpRecords, mTmpIsPop); + } finally { + cleanupExec(); + } } doPendingDeferredStart(); } /** + * Broken out of exec*, this cleans up the mExecutingActions and the temporary structures + * used in executing operations. + */ + private void cleanupExec() { + mExecutingActions = false; + mTmpIsPop.clear(); + mTmpRecords.clear(); + } + + /** * Only call from main thread! */ public boolean execPendingActions() { - if (mExecutingActions) { - throw new IllegalStateException("Recursive entry to executePendingTransactions"); - } - - if (Looper.myLooper() != mHost.getHandler().getLooper()) { - throw new IllegalStateException("Must be called from main thread of process"); - } + ensureExecReady(true); boolean didSomething = false; - - while (true) { - int numActions; - - synchronized (this) { - if (mPendingActions == null || mPendingActions.size() == 0) { - break; - } - - numActions = mPendingActions.size(); - if (mTmpActions == null || mTmpActions.length < numActions) { - mTmpActions = new Runnable[numActions]; - } - mPendingActions.toArray(mTmpActions); - mPendingActions.clear(); - mHost.getHandler().removeCallbacks(mExecCommit); - } - + while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) { mExecutingActions = true; try { - for (int i = 0; i < numActions; i++) { - mTmpActions[i].run(); - mTmpActions[i] = null; - } + optimizeAndExecuteOps(mTmpRecords, mTmpIsPop); } finally { - mExecutingActions = false; + cleanupExec(); } didSomething = true; } @@ -1593,6 +1673,265 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate return didSomething; } + /** + * Optimizes BackStackRecord operations. This method merges operations of proximate records + * that allow optimization. See {@link FragmentTransaction#setAllowOptimization(boolean)}. + * <p> + * For example, a transaction that adds to the back stack and then another that pops that + * back stack record will be optimized. + * <p> + * Likewise, two transactions committed that are executed at the same time will be optimized + * as well as two pop operations executed together. + * + * @param records The records pending execution + * @param isRecordPop The direction that these records are being run. + */ + private void optimizeAndExecuteOps(ArrayList<BackStackRecord> records, + ArrayList<Boolean> isRecordPop) { + if (records == null || records.isEmpty()) { + return; + } + + if (isRecordPop == null || records.size() != isRecordPop.size()) { + throw new IllegalStateException("Internal error with the back stack records"); + } + + final int numRecords = records.size(); + int startIndex = 0; + for (int recordNum = 0; recordNum < numRecords; recordNum++) { + final boolean canOptimize = records.get(recordNum).mAllowOptimization; + if (!canOptimize) { + // execute all previous transactions + if (startIndex != recordNum) { + executeOpsTogether(records, isRecordPop, startIndex, recordNum); + } + // execute all unoptimized together + int optimizeEnd; + for (optimizeEnd = recordNum + 1; optimizeEnd < numRecords; optimizeEnd++) { + if (records.get(optimizeEnd).mAllowOptimization) { + break; + } + } + executeOpsTogether(records, isRecordPop, recordNum, optimizeEnd); + startIndex = optimizeEnd; + recordNum = optimizeEnd - 1; + } + } + if (startIndex != numRecords) { + executeOpsTogether(records, isRecordPop, startIndex, numRecords); + } + } + + /** + * Optimizes a subset of a list of BackStackRecords, all of which either allow optimization or + * do not allow optimization. + * @param records A list of BackStackRecords that are to be optimized + * @param isRecordPop The direction that these records are being run. + * @param startIndex The index of the first record in <code>records</code> to be optimized + * @param endIndex One more than the final record index in <code>records</code> to optimize. + */ + private void executeOpsTogether(ArrayList<BackStackRecord> records, + ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) { + final boolean allowOptimization = records.get(startIndex).mAllowOptimization; + boolean addToBackStack = false; + if (mTmpAddedFragments == null) { + mTmpAddedFragments = new ArrayList<>(); + mTmpFragmentsContainerTransitions = new SparseArray<>(); + } else { + mTmpAddedFragments.clear(); + mTmpFragmentsContainerTransitions.clear(); + } + if (mAdded != null) { + mTmpAddedFragments.addAll(mAdded); + } + for (int recordNum = startIndex; recordNum < endIndex; recordNum++) { + final BackStackRecord record = records.get(recordNum); + final boolean isPop = isRecordPop.get(recordNum); + if (!isPop) { + record.expandReplaceOps(mTmpAddedFragments); + } + final int bumpAmount = isPop ? -1 : 1; + record.bumpBackStackNesting(bumpAmount); + addToBackStack = addToBackStack || record.mAddToBackStack; + + if (mCurState >= Fragment.CREATED) { + if (isPop) { + record.calculatePopFragments(mTmpFragmentsContainerTransitions); + } else { + record.calculateFragments(mTmpFragmentsContainerTransitions); + } + } + } + mTmpAddedFragments.clear(); + + if (!allowOptimization) { + startTransitions(records, isRecordPop, startIndex, endIndex); + } + executeOps(records, isRecordPop, startIndex, endIndex); + + if (allowOptimization) { + moveFragmentsToAtLeastCreated(); + startTransitions(records, isRecordPop, startIndex, endIndex); + moveToState(mCurState); + } + + for (int recordNum = startIndex; recordNum < endIndex; recordNum++) { + final BackStackRecord record = records.get(recordNum); + final boolean isPop = isRecordPop.get(recordNum); + if (isPop && record.mIndex >= 0) { + freeBackStackIndex(record.mIndex); + record.mIndex = -1; + } + } + if (addToBackStack) { + reportBackStackChanged(); + } + } + + /** + * Find a fragment within the fragment's container whose View should be below the passed + * fragment. {@code null} is returned when the fragment has no View or if there should be + * no fragment with a View below the given fragment. + * + * As an example, if mAdded has two Fragments with Views sharing the same container: + * FragmentA + * FragmentB + * + * Then, when processing FragmentB, FragmentA will be returned. If, however, FragmentA + * had no View, null would be returned. + * + * @param f The fragment that may be on top of another fragment. + * @return The fragment with a View under f, if one exists or null if f has no View or + * there are no fragments with Views in the same container. + */ + private Fragment findFragmentUnder(Fragment f) { + final ViewGroup container = f.mContainer; + final View view = f.mView; + + if (container == null || view == null) { + return null; + } + + final int fragmentIndex = mAdded.indexOf(f); + for (int i = fragmentIndex - 1; i >= 0; i--) { + Fragment underFragment = mAdded.get(i); + if (underFragment.mContainer == container && underFragment.mView != null) { + // Found the fragment under this one + return underFragment; + } + } + return null; + } + + /** + * Run the operations in the BackStackRecords, either to push or pop. + * + * @param records The list of records whose operations should be run. + * @param isRecordPop The direction that these records are being run. + * @param startIndex The index of the first entry in records to run. + * @param endIndex One past the index of the final entry in records to run. + */ + private static void executeOps(ArrayList<BackStackRecord> records, + ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) { + for (int i = startIndex; i < endIndex; i++) { + final BackStackRecord record = records.get(i); + final boolean isPop = isRecordPop.get(i); + if (isPop) { + record.executePopOps(); + } else { + record.executeOps(); + } + } + } + + /** + * Prepares the fragments for Transitions and starts it. If the FragmentManager is not + * at Fragment.CREATED, no transition will be run. + * + * This is explicitly for {@link Fragment#setEnterTransition(Transition)} and its siblings, + * not for {@link FragmentTransaction#setTransition(int)}. + * + * @param records The entries to examine for transitions. + * @param isRecordPop The direction that these records are being run. + * @param startIndex The index of the first entry in records to run transitions for. + * @param endIndex One past the index of the final entry in records to run transitions for. + */ + private void startTransitions(ArrayList<BackStackRecord> records, + ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) { + if (mCurState >= Fragment.CREATED) { + if (mTmpFragmentsContainerTransitions.size() != 0) { + BackStackRecord record = records.get(0); + BackStackRecord.TransitionState state = + record.beginTransition(mTmpFragmentsContainerTransitions); + if (state != null) { + for (int i = startIndex + 1; i < endIndex - 1; i++) { + final BackStackRecord nameRecord = records.get(i); + final boolean isPop = isRecordPop.get(i); + ArrayList<String> sourceNames = isPop + ? nameRecord.mSharedElementTargetNames + : record.mSharedElementSourceNames; + ArrayList<String> targetNames = isPop + ? nameRecord.mSharedElementSourceNames + : record.mSharedElementTargetNames; + BackStackRecord.setNameOverrides(state, sourceNames, targetNames); + } + } + mTmpFragmentsContainerTransitions.clear(); + } + } + } + + /** + * Ensure that fragments that are added are at least at the CREATED state + * so that they may load Transitions using TransitionInflater. When the transaction + * cannot be optimized, this is executed in + * {@link BackStackRecord#setLastIn(SparseArray, SparseArray, Fragment)} instead. Prior to + * N, this wasn't supported, so no out-of-order creation can be done for compatibility. + * <p> + * This won't change the state of the fragment manager, nor will it change the fragment's + * state if the fragment manager isn't at least at the CREATED state. + */ + private void moveFragmentsToAtLeastCreated() { + if (mCurState < Fragment.CREATED) { + return; + } + final int numAdded = mAdded == null ? 0 : mAdded.size(); + for (int i = 0; i < numAdded; i++) { + Fragment fragment = mAdded.get(i); + if (fragment.mState < Fragment.CREATED) { + moveToState(fragment, Fragment.CREATED, 0, 0, false); + } + } + } + + /** + * Adds all records in the pending actions to records and whether they are add or pop + * operations to isPop. After executing, the pending actions will be empty. + * + * @param records All pending actions will generate BackStackRecords added to this. + * This contains the transactions, in order, to execute. + * @param isPop All pending actions will generate booleans to add to this. This contains + * an entry for each entry in records to indicate whether or not it is a + * pop action. + */ + private boolean generateOpsForPendingActions(ArrayList<BackStackRecord> records, + ArrayList<Boolean> isPop) { + int numActions; + synchronized (this) { + if (mPendingActions == null || mPendingActions.size() == 0) { + return false; + } + + numActions = mPendingActions.size(); + for (int i = 0; i < numActions; i++) { + mPendingActions.get(i).generateOps(records, isPop); + } + mPendingActions.clear(); + mHost.getHandler().removeCallbacks(mExecCommit); + } + return numActions > 0; + } + void doPendingDeferredStart() { if (mHavePendingDeferredStart) { boolean loadersRunning = false; @@ -1624,24 +1963,19 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate mBackStack.add(state); reportBackStackChanged(); } - - boolean popBackStackState(Handler handler, String name, int id, int flags) { + + boolean popBackStackState(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop, + String name, int id, int flags) { if (mBackStack == null) { return false; } - if (name == null && id < 0 && (flags&POP_BACK_STACK_INCLUSIVE) == 0) { - int last = mBackStack.size()-1; + if (name == null && id < 0 && (flags & POP_BACK_STACK_INCLUSIVE) == 0) { + int last = mBackStack.size() - 1; if (last < 0) { return false; } - final BackStackRecord bss = mBackStack.remove(last); - SparseArray<BackStackRecord.FragmentContainerTransition> transitioningFragments = - new SparseArray<>(); - if (mCurState >= Fragment.CREATED) { - bss.calculateBackFragments(transitioningFragments); - } - bss.popFromBackStack(true, null, transitioningFragments); - reportBackStackChanged(); + records.add(mBackStack.remove(last)); + isRecordPop.add(true); } else { int index = -1; if (name != null || id >= 0) { @@ -1678,25 +2012,10 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (index == mBackStack.size()-1) { return false; } - final ArrayList<BackStackRecord> states - = new ArrayList<BackStackRecord>(); - for (int i=mBackStack.size()-1; i>index; i--) { - states.add(mBackStack.remove(i)); + for (int i = mBackStack.size() - 1; i > index; i--) { + records.add(mBackStack.remove(i)); + isRecordPop.add(true); } - final int LAST = states.size()-1; - SparseArray<BackStackRecord.FragmentContainerTransition> transitioningFragments = - new SparseArray<>(); - if (mCurState >= Fragment.CREATED) { - for (int i = 0; i <= LAST; i++) { - states.get(i).calculateBackFragments(transitioningFragments); - } - } - BackStackRecord.TransitionState state = null; - for (int i=0; i<=LAST; i++) { - if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i)); - state = states.get(i).popFromBackStack(i == LAST, state, transitioningFragments); - } - reportBackStackChanged(); } return true; } @@ -2036,40 +2355,40 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate public void dispatchCreate() { mStateSaved = false; - moveToState(Fragment.CREATED, false); + moveToState(Fragment.CREATED); } public void dispatchActivityCreated() { mStateSaved = false; - moveToState(Fragment.ACTIVITY_CREATED, false); + moveToState(Fragment.ACTIVITY_CREATED); } public void dispatchStart() { mStateSaved = false; - moveToState(Fragment.STARTED, false); + moveToState(Fragment.STARTED); } public void dispatchResume() { mStateSaved = false; - moveToState(Fragment.RESUMED, false); + moveToState(Fragment.RESUMED); } public void dispatchPause() { - moveToState(Fragment.STARTED, false); + moveToState(Fragment.STARTED); } public void dispatchStop() { - moveToState(Fragment.STOPPED, false); + moveToState(Fragment.STOPPED); } public void dispatchDestroyView() { - moveToState(Fragment.CREATED, false); + moveToState(Fragment.CREATED); } public void dispatchDestroy() { mDestroyed = true; execPendingActions(); - moveToState(Fragment.INITIALIZING, false); + moveToState(Fragment.INITIALIZING); mHost = null; mContainer = null; mParent = null; @@ -2363,4 +2682,45 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate LayoutInflater.Factory2 getLayoutInflaterFactory() { return this; } + + /** + * An add or pop transaction to be scheduled for the UI thread. + */ + interface OpGenerator { + /** + * Generate transactions to add to {@code records} and whether or not the transaction is + * an add or pop to {@code isRecordPop}. + * + * records and isRecordPop must be added equally so that each transaction in records + * matches the boolean for whether or not it is a pop in isRecordPop. + * + * @param records A list to add transactions to. + * @param isRecordPop A list to add whether or not the transactions added to records is + * a pop transaction. + * @return true if something was added or false otherwise. + */ + boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop); + } + + /** + * A pop operation OpGenerator. This will be run on the UI thread and will generate the + * transactions that will be popped if anything can be popped. + */ + private class PopBackStackState implements OpGenerator { + final String mName; + final int mId; + final int mFlags; + + public PopBackStackState(String name, int id, int flags) { + mName = name; + mId = id; + mFlags = flags; + } + + @Override + public boolean generateOps(ArrayList<BackStackRecord> records, + ArrayList<Boolean> isRecordPop) { + return popBackStackState(records, isRecordPop, mName, mId, mFlags); + } + } } diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java index 633e85b78118..25a7839fa1ed 100644 --- a/core/java/android/app/FragmentTransaction.java +++ b/core/java/android/app/FragmentTransaction.java @@ -260,6 +260,32 @@ public abstract class FragmentTransaction { public abstract FragmentTransaction setBreadCrumbShortTitle(CharSequence text); /** + * Sets whether or not to allow optimizing operations within and across + * transactions. Optimizing fragment transaction's operations can eliminate + * operations that cancel. For example, if two transactions are executed + * together, one that adds a fragment A and the next replaces it with fragment B, + * the operations will cancel and only fragment B will be added. That means that + * fragment A may not go through the creation/destruction lifecycle. + * <p> + * The side effect of optimization is that fragments may have state changes + * out of the expected order. For example, one transaction adds fragment A, + * a second adds fragment B, then a third removes fragment A. Without optimization, + * fragment B could expect that while it is being created, fragment A will also + * exist because fragment A will be removed after fragment B was added. + * With optimization, fragment B cannot expect fragment A to exist when + * it has been created because fragment A's add/remove will be optimized out. + * <p> + * The default is {@code false} for applications targeting version + * versions prior to O and {@code true} for applications targeting O and + * later. + * + * @param allowOptimization {@code true} to enable optimizing operations + * or {@code false} to disable optimizing + * operations on this transaction. + */ + public abstract FragmentTransaction setAllowOptimization(boolean allowOptimization); + + /** * Schedules a commit of this transaction. The commit does * not happen immediately; it will be scheduled as work on the main thread * to be done the next time that thread is ready. |