/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app;

import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import android.util.LogWriter;
import android.view.View;

import com.android.internal.util.FastPrintWriter;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.reflect.Modifier;
import java.util.ArrayList;

final class BackStackState implements Parcelable {
    final int[] mOps;
    final int mTransition;
    final int mTransitionStyle;
    final String mName;
    final int mIndex;
    final int mBreadCrumbTitleRes;
    final CharSequence mBreadCrumbTitleText;
    final int mBreadCrumbShortTitleRes;
    final CharSequence mBreadCrumbShortTitleText;
    final ArrayList<String> mSharedElementSourceNames;
    final ArrayList<String> mSharedElementTargetNames;
    final boolean mReorderingAllowed;

    public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
        final int numOps = bse.mOps.size();
        mOps = new int[numOps * 6];

        if (!bse.mAddToBackStack) {
            throw new IllegalStateException("Not on back stack");
        }

        int pos = 0;
        for (int opNum = 0; opNum < numOps; opNum++) {
            final BackStackRecord.Op op = bse.mOps.get(opNum);
            mOps[pos++] = op.cmd;
            mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1;
            mOps[pos++] = op.enterAnim;
            mOps[pos++] = op.exitAnim;
            mOps[pos++] = op.popEnterAnim;
            mOps[pos++] = op.popExitAnim;
        }
        mTransition = bse.mTransition;
        mTransitionStyle = bse.mTransitionStyle;
        mName = bse.mName;
        mIndex = bse.mIndex;
        mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
        mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
        mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
        mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
        mSharedElementSourceNames = bse.mSharedElementSourceNames;
        mSharedElementTargetNames = bse.mSharedElementTargetNames;
        mReorderingAllowed = bse.mReorderingAllowed;
    }

    public BackStackState(Parcel in) {
        mOps = in.createIntArray();
        mTransition = in.readInt();
        mTransitionStyle = in.readInt();
        mName = in.readString();
        mIndex = in.readInt();
        mBreadCrumbTitleRes = in.readInt();
        mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
        mBreadCrumbShortTitleRes = in.readInt();
        mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
        mSharedElementSourceNames = in.createStringArrayList();
        mSharedElementTargetNames = in.createStringArrayList();
        mReorderingAllowed = in.readInt() != 0;
    }

    public BackStackRecord instantiate(FragmentManagerImpl fm) {
        BackStackRecord bse = new BackStackRecord(fm);
        int pos = 0;
        int num = 0;
        while (pos < mOps.length) {
            BackStackRecord.Op op = new BackStackRecord.Op();
            op.cmd = mOps[pos++];
            if (FragmentManagerImpl.DEBUG) {
                Log.v(FragmentManagerImpl.TAG,
                        "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
            }
            int findex = mOps[pos++];
            if (findex >= 0) {
                Fragment f = fm.mActive.get(findex);
                op.fragment = f;
            } else {
                op.fragment = null;
            }
            op.enterAnim = mOps[pos++];
            op.exitAnim = mOps[pos++];
            op.popEnterAnim = mOps[pos++];
            op.popExitAnim = mOps[pos++];
            bse.mEnterAnim = op.enterAnim;
            bse.mExitAnim = op.exitAnim;
            bse.mPopEnterAnim = op.popEnterAnim;
            bse.mPopExitAnim = op.popExitAnim;
            bse.addOp(op);
            num++;
        }
        bse.mTransition = mTransition;
        bse.mTransitionStyle = mTransitionStyle;
        bse.mName = mName;
        bse.mIndex = mIndex;
        bse.mAddToBackStack = true;
        bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes;
        bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
        bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
        bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
        bse.mSharedElementSourceNames = mSharedElementSourceNames;
        bse.mSharedElementTargetNames = mSharedElementTargetNames;
        bse.mReorderingAllowed = mReorderingAllowed;
        bse.bumpBackStackNesting(1);
        return bse;
    }

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int flags) {
        dest.writeIntArray(mOps);
        dest.writeInt(mTransition);
        dest.writeInt(mTransitionStyle);
        dest.writeString(mName);
        dest.writeInt(mIndex);
        dest.writeInt(mBreadCrumbTitleRes);
        TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
        dest.writeInt(mBreadCrumbShortTitleRes);
        TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
        dest.writeStringList(mSharedElementSourceNames);
        dest.writeStringList(mSharedElementTargetNames);
        dest.writeInt(mReorderingAllowed ? 1 : 0);
    }

    public static final Parcelable.Creator<BackStackState> CREATOR
            = new Parcelable.Creator<BackStackState>() {
        public BackStackState createFromParcel(Parcel in) {
            return new BackStackState(in);
        }

        public BackStackState[] newArray(int size) {
            return new BackStackState[size];
        }
    };
}

@hideEntry of an operation on the fragment back stack.
/** * @hide Entry of an operation on the fragment back stack. */
final class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator { static final String TAG = FragmentManagerImpl.TAG; final FragmentManagerImpl mManager; static final int OP_NULL = 0; static final int OP_ADD = 1; static final int OP_REPLACE = 2; static final int OP_REMOVE = 3; static final int OP_HIDE = 4; 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; Fragment fragment; int enterAnim; int exitAnim; int popEnterAnim; int popExitAnim; Op() { } Op(int cmd, Fragment fragment) { this.cmd = cmd; this.fragment = fragment; } } ArrayList<Op> mOps = new ArrayList<>(); int mEnterAnim; int mExitAnim; int mPopEnterAnim; int mPopExitAnim; int mTransition; int mTransitionStyle; boolean mAddToBackStack; boolean mAllowAddToBackStack = true; String mName; boolean mCommitted; int mIndex = -1; boolean mReorderingAllowed; ArrayList<Runnable> mCommitRunnables; int mBreadCrumbTitleRes; CharSequence mBreadCrumbTitleText; int mBreadCrumbShortTitleRes; CharSequence mBreadCrumbShortTitleText; ArrayList<String> mSharedElementSourceNames; ArrayList<String> mSharedElementTargetNames; @Override public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("BackStackEntry{"); sb.append(Integer.toHexString(System.identityHashCode(this))); if (mIndex >= 0) { sb.append(" #"); sb.append(mIndex); } if (mName != null) { sb.append(" "); sb.append(mName); } sb.append("}"); return sb.toString(); } public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { dump(prefix, writer, true); } void dump(String prefix, PrintWriter writer, boolean full) { if (full) { writer.print(prefix); writer.print("mName="); writer.print(mName); writer.print(" mIndex="); writer.print(mIndex); writer.print(" mCommitted="); writer.println(mCommitted); if (mTransition != FragmentTransaction.TRANSIT_NONE) { writer.print(prefix); writer.print("mTransition=#"); writer.print(Integer.toHexString(mTransition)); writer.print(" mTransitionStyle=#"); writer.println(Integer.toHexString(mTransitionStyle)); } if (mEnterAnim != 0 || mExitAnim != 0) { writer.print(prefix); writer.print("mEnterAnim=#"); writer.print(Integer.toHexString(mEnterAnim)); writer.print(" mExitAnim=#"); writer.println(Integer.toHexString(mExitAnim)); } if (mPopEnterAnim != 0 || mPopExitAnim != 0) { writer.print(prefix); writer.print("mPopEnterAnim=#"); writer.print(Integer.toHexString(mPopEnterAnim)); writer.print(" mPopExitAnim=#"); writer.println(Integer.toHexString(mPopExitAnim)); } if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) { writer.print(prefix); writer.print("mBreadCrumbTitleRes=#"); writer.print(Integer.toHexString(mBreadCrumbTitleRes)); writer.print(" mBreadCrumbTitleText="); writer.println(mBreadCrumbTitleText); } if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) { writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#"); writer.print(Integer.toHexString(mBreadCrumbShortTitleRes)); writer.print(" mBreadCrumbShortTitleText="); writer.println(mBreadCrumbShortTitleText); } } if (!mOps.isEmpty()) { writer.print(prefix); writer.println("Operations:"); String innerPrefix = prefix + " "; final int numOps = mOps.size(); for (int opNum = 0; opNum < numOps; opNum++) { final Op op = mOps.get(opNum); String cmdStr; switch (op.cmd) { case OP_NULL: cmdStr = "NULL"; break; case OP_ADD: cmdStr = "ADD"; break; case OP_REPLACE: cmdStr = "REPLACE"; break; case OP_REMOVE: cmdStr = "REMOVE"; break; case OP_HIDE: cmdStr = "HIDE"; break; case OP_SHOW: cmdStr = "SHOW"; break; case OP_DETACH: cmdStr = "DETACH"; break; 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; } writer.print(prefix); writer.print(" Op #"); writer.print(opNum); writer.print(": "); writer.print(cmdStr); writer.print(" "); writer.println(op.fragment); if (full) { if (op.enterAnim != 0 || op.exitAnim != 0) { writer.print(innerPrefix); writer.print("enterAnim=#"); writer.print(Integer.toHexString(op.enterAnim)); writer.print(" exitAnim=#"); writer.println(Integer.toHexString(op.exitAnim)); } if (op.popEnterAnim != 0 || op.popExitAnim != 0) { writer.print(innerPrefix); writer.print("popEnterAnim=#"); writer.print(Integer.toHexString(op.popEnterAnim)); writer.print(" popExitAnim=#"); writer.println(Integer.toHexString(op.popExitAnim)); } } } } } public BackStackRecord(FragmentManagerImpl manager) { mManager = manager; mReorderingAllowed = mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1; } public int getId() { return mIndex; } public int getBreadCrumbTitleRes() { return mBreadCrumbTitleRes; } public int getBreadCrumbShortTitleRes() { return mBreadCrumbShortTitleRes; } public CharSequence getBreadCrumbTitle() { if (mBreadCrumbTitleRes != 0 && mManager.mHost != null) { return mManager.mHost.getContext().getText(mBreadCrumbTitleRes); } return mBreadCrumbTitleText; } public CharSequence getBreadCrumbShortTitle() { if (mBreadCrumbShortTitleRes != 0 && mManager.mHost != null) { return mManager.mHost.getContext().getText(mBreadCrumbShortTitleRes); } return mBreadCrumbShortTitleText; } void addOp(Op op) { mOps.add(op); op.enterAnim = mEnterAnim; op.exitAnim = mExitAnim; op.popEnterAnim = mPopEnterAnim; op.popExitAnim = mPopExitAnim; } public FragmentTransaction add(Fragment fragment, String tag) { doAddOp(0, fragment, tag, OP_ADD); return this; } public FragmentTransaction add(int containerViewId, Fragment fragment) { doAddOp(containerViewId, fragment, null, OP_ADD); return this; } public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) { doAddOp(containerViewId, fragment, tag, OP_ADD); return this; } private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { if (mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1) { final Class fragmentClass = fragment.getClass(); final int modifiers = fragmentClass.getModifiers(); if ((fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers) || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers)))) { throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName() + " must be a public static class to be properly recreated from" + " instance state."); } } fragment.mFragmentManager = mManager; if (tag != null) { if (fragment.mTag != null && !tag.equals(fragment.mTag)) { throw new IllegalStateException("Can't change tag of fragment " + fragment + ": was " + fragment.mTag + " now " + tag); } fragment.mTag = tag; } if (containerViewId != 0) { if (containerViewId == View.NO_ID) { throw new IllegalArgumentException("Can't add fragment " + fragment + " with tag " + tag + " to container view with no id"); } if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) { throw new IllegalStateException("Can't change container ID of fragment " + fragment + ": was " + fragment.mFragmentId + " now " + containerViewId); } fragment.mContainerId = fragment.mFragmentId = containerViewId; } addOp(new Op(opcmd, fragment)); } public FragmentTransaction replace(int containerViewId, Fragment fragment) { return replace(containerViewId, fragment, null); } public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) { if (containerViewId == 0) { throw new IllegalArgumentException("Must use non-zero containerViewId"); } doAddOp(containerViewId, fragment, tag, OP_REPLACE); return this; } public FragmentTransaction remove(Fragment fragment) { addOp(new Op(OP_REMOVE, fragment)); return this; } public FragmentTransaction hide(Fragment fragment) { addOp(new Op(OP_HIDE, fragment)); return this; } public FragmentTransaction show(Fragment fragment) { addOp(new Op(OP_SHOW, fragment)); return this; } public FragmentTransaction detach(Fragment fragment) { addOp(new Op(OP_DETACH, fragment)); return this; } public FragmentTransaction attach(Fragment fragment) { addOp(new Op(OP_ATTACH, fragment)); return this; } public FragmentTransaction setPrimaryNavigationFragment(Fragment fragment) { addOp(new Op(OP_SET_PRIMARY_NAV, fragment)); return this; } public FragmentTransaction setCustomAnimations(int enter, int exit) { return setCustomAnimations(enter, exit, 0, 0); } public FragmentTransaction setCustomAnimations(int enter, int exit, int popEnter, int popExit) { mEnterAnim = enter; mExitAnim = exit; mPopEnterAnim = popEnter; mPopExitAnim = popExit; return this; } public FragmentTransaction setTransition(int transition) { mTransition = transition; return this; } @Override public FragmentTransaction addSharedElement(View sharedElement, String name) { String transitionName = sharedElement.getTransitionName(); if (transitionName == null) { throw new IllegalArgumentException("Unique transitionNames are required for all" + " sharedElements"); } if (mSharedElementSourceNames == null) { mSharedElementSourceNames = new ArrayList<String>(); mSharedElementTargetNames = new ArrayList<String>(); } else if (mSharedElementTargetNames.contains(name)) { throw new IllegalArgumentException("A shared element with the target name '" + name + "' has already been added to the transaction."); } else if (mSharedElementSourceNames.contains(transitionName)) { throw new IllegalArgumentException("A shared element with the source name '" + transitionName + " has already been added to the transaction."); } mSharedElementSourceNames.add(transitionName); mSharedElementTargetNames.add(name); return this; } public FragmentTransaction setTransitionStyle(int styleRes) { mTransitionStyle = styleRes; return this; } public FragmentTransaction addToBackStack(String name) { if (!mAllowAddToBackStack) { throw new IllegalStateException( "This FragmentTransaction is not allowed to be added to the back stack."); } mAddToBackStack = true; mName = name; return this; } public boolean isAddToBackStackAllowed() { return mAllowAddToBackStack; } public FragmentTransaction disallowAddToBackStack() { if (mAddToBackStack) { throw new IllegalStateException( "This transaction is already being added to the back stack"); } mAllowAddToBackStack = false; return this; } public FragmentTransaction setBreadCrumbTitle(int res) { mBreadCrumbTitleRes = res; mBreadCrumbTitleText = null; return this; } public FragmentTransaction setBreadCrumbTitle(CharSequence text) { mBreadCrumbTitleRes = 0; mBreadCrumbTitleText = text; return this; } public FragmentTransaction setBreadCrumbShortTitle(int res) { mBreadCrumbShortTitleRes = res; mBreadCrumbShortTitleText = null; return this; } public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) { mBreadCrumbShortTitleRes = 0; mBreadCrumbShortTitleText = text; return this; } void bumpBackStackNesting(int amt) { if (!mAddToBackStack) { return; } if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "Bump nesting in " + this + " by " + amt); } final int numOps = mOps.size(); for (int opNum = 0; opNum < numOps; opNum++) { final Op op = mOps.get(opNum); if (op.fragment != null) { op.fragment.mBackStackNesting += amt; if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "Bump nesting of " + op.fragment + " to " + op.fragment.mBackStackNesting); } } } } @Override public FragmentTransaction runOnCommit(Runnable runnable) { if (runnable == null) { throw new IllegalArgumentException("runnable cannot be null"); } disallowAddToBackStack(); if (mCommitRunnables == null) { mCommitRunnables = new ArrayList<>(); } mCommitRunnables.add(runnable); return this; } public void runOnCommitRunnables() { if (mCommitRunnables != null) { for (int i = 0, N = mCommitRunnables.size(); i < N; i++) { mCommitRunnables.get(i).run(); } mCommitRunnables = null; } } public int commit() { return commitInternal(false); } public int commitAllowingStateLoss() { return commitInternal(true); } @Override public void commitNow() { disallowAddToBackStack(); mManager.execSingleAction(this, false); } @Override public void commitNowAllowingStateLoss() { disallowAddToBackStack(); mManager.execSingleAction(this, true); } @Override public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) { mReorderingAllowed = reorderingAllowed; return this; } int commitInternal(boolean allowStateLoss) { if (mCommitted) { throw new IllegalStateException("commit already called"); } if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "Commit: " + this); LogWriter logw = new LogWriter(Log.VERBOSE, TAG); PrintWriter pw = new FastPrintWriter(logw, false, 1024); dump(" ", null, pw, null); pw.flush(); } mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); } else { mIndex = -1; } mManager.enqueueAction(this, allowStateLoss); return mIndex; }
Implementation of OpGenerator. This operation is added to the list of pending actions during commit(), and will be executed on the UI thread to run this FragmentTransaction.
Params:
  • records – Modified to add this BackStackRecord
  • isRecordPop – Modified to add a false (this isn't a pop)
Returns:true always because the records and isRecordPop will always be changed
/** * Implementation of {@link 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) { mManager.addBackStackState(this); } return true; } boolean interactsWith(int containerId) { final int numOps = mOps.size(); for (int opNum = 0; opNum < numOps; opNum++) { final Op op = mOps.get(opNum); final int fragContainer = op.fragment != null ? op.fragment.mContainerId : 0; if (fragContainer != 0 && fragContainer == containerId) { return true; } } return false; } boolean interactsWith(ArrayList<BackStackRecord> records, int startIndex, int endIndex) { if (endIndex == startIndex) { return false; } final int numOps = mOps.size(); int lastContainer = -1; for (int opNum = 0; opNum < numOps; opNum++) { final Op op = mOps.get(opNum); final int container = op.fragment != null ? op.fragment.mContainerId : 0; if (container != 0 && container != lastContainer) { lastContainer = container; for (int i = startIndex; i < endIndex; i++) { BackStackRecord record = records.get(i); final int numThoseOps = record.mOps.size(); for (int thoseOpIndex = 0; thoseOpIndex < numThoseOps; thoseOpIndex++) { final Op thatOp = record.mOps.get(thoseOpIndex); final int thatContainer = thatOp.fragment != null ? thatOp.fragment.mContainerId : 0; if (thatContainer == container) { return true; } } } } } return false; }
Executes the operations contained within this transaction. The Fragment states will only be modified if optimizations are not allowed.
/** * 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); final Fragment f = op.fragment; if (f != null) { f.setNextTransition(mTransition, mTransitionStyle); } switch (op.cmd) { case OP_ADD: f.setNextAnim(op.enterAnim); mManager.addFragment(f, false); break; case OP_REMOVE: f.setNextAnim(op.exitAnim); mManager.removeFragment(f); break; case OP_HIDE: f.setNextAnim(op.exitAnim); mManager.hideFragment(f); break; case OP_SHOW: f.setNextAnim(op.enterAnim); mManager.showFragment(f); break; case OP_DETACH: f.setNextAnim(op.exitAnim); mManager.detachFragment(f); break; case OP_ATTACH: 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 (!mReorderingAllowed && op.cmd != OP_ADD && f != null) { mManager.moveFragmentToExpectedState(f); } } if (!mReorderingAllowed) { // Added fragments are added at the end to comply with prior behavior. mManager.moveToState(mManager.mCurState, true); } }
Reverses the execution of the operations within this transaction. The Fragment states will only be modified if optimizations are not allowed.
Params:
  • moveToState – true if added fragments should be moved to their final state in unoptimized transactions
/** * Reverses the execution of the operations within this transaction. The Fragment states will * only be modified if optimizations are not allowed. * * @param moveToState {@code true} if added fragments should be moved to their final state * in unoptimized transactions */
void executePopOps(boolean moveToState) { for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) { final Op op = mOps.get(opNum); Fragment f = op.fragment; if (f != null) { f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); } switch (op.cmd) { case OP_ADD: f.setNextAnim(op.popExitAnim); mManager.removeFragment(f); break; case OP_REMOVE: f.setNextAnim(op.popEnterAnim); mManager.addFragment(f, false); break; case OP_HIDE: f.setNextAnim(op.popEnterAnim); mManager.showFragment(f); break; case OP_SHOW: f.setNextAnim(op.popExitAnim); mManager.hideFragment(f); break; case OP_DETACH: f.setNextAnim(op.popEnterAnim); mManager.attachFragment(f); break; case OP_ATTACH: 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 (!mReorderingAllowed && op.cmd != OP_REMOVE && f != null) { mManager.moveFragmentToExpectedState(f); } } if (!mReorderingAllowed && moveToState) { mManager.moveToState(mManager.mCurState, true); } }
Expands all meta-ops into their more primitive equivalents. This must be called prior to 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 FragmentManager.getPrimaryNavigationFragment().

Params:
  • 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 (executeOps().
  • oldPrimaryNav – The tracked primary navigation fragment as of the beginning of this set of ops
Returns:the new oldPrimaryNav fragment after this record's ops would be run
/** * 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. * * <p>Removes all OP_REPLACE ops and replaces them with the proper add and remove * operations that are equivalent to the replace.</p> * * <p>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()}.</p> * * @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 */
@SuppressWarnings("ReferenceEquality") Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) { for (int opNum = 0; opNum < mOps.size(); opNum++) { final Op op = mOps.get(opNum); switch (op.cmd) { case OP_ADD: case OP_ATTACH: added.add(op.fragment); break; case OP_REMOVE: case OP_DETACH: { added.remove(op.fragment); if (op.fragment == oldPrimaryNav) { mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.fragment)); opNum++; oldPrimaryNav = null; } } break; case OP_REPLACE: { final Fragment f = op.fragment; final int containerId = f.mContainerId; boolean alreadyAdded = false; for (int i = added.size() - 1; i >= 0; i--) { final Fragment old = added.get(i); if (old.mContainerId == containerId) { if (old == f) { alreadyAdded = true; } else { // 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; removeOp.popExitAnim = op.popExitAnim; mOps.add(opNum, removeOp); added.remove(old); opNum++; } } } if (alreadyAdded) { mOps.remove(opNum); opNum--; } else { op.cmd = OP_ADD; added.add(f); } } 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; }
Removes fragments that are added or removed during a pop operation.
Params:
  • 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 (executeOps().
/** * Removes fragments that are added or removed during a pop operation. * * @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 trackAddedFragmentsInPop(ArrayList<Fragment> added) { for (int opNum = 0; opNum < mOps.size(); opNum++) { final Op op = mOps.get(opNum); switch (op.cmd) { case OP_ADD: case OP_ATTACH: added.remove(op.fragment); break; case OP_REMOVE: case OP_DETACH: added.add(op.fragment); break; } } } boolean isPostponed() { for (int opNum = 0; opNum < mOps.size(); opNum++) { final Op op = mOps.get(opNum); if (isFragmentPostponed(op)) { return true; } } return false; } void setOnStartPostponedListener(Fragment.OnStartEnterTransitionListener listener) { for (int opNum = 0; opNum < mOps.size(); opNum++) { final Op op = mOps.get(opNum); if (isFragmentPostponed(op)) { op.fragment.setOnStartEnterTransitionListener(listener); } } } private static boolean isFragmentPostponed(Op op) { final Fragment fragment = op.fragment; return fragment != null && fragment.mAdded && fragment.mView != null && !fragment.mDetached && !fragment.mHidden && fragment.isPostponed(); } public String getName() { return mName; } public int getTransition() { return mTransition; } public int getTransitionStyle() { return mTransitionStyle; } public boolean isEmpty() { return mOps.isEmpty(); } }