1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app;
18 
19 import android.os.Build;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 import android.text.TextUtils;
23 import android.util.Log;
24 import android.util.LogWriter;
25 import android.view.View;
26 
27 import com.android.internal.util.FastPrintWriter;
28 
29 import java.io.FileDescriptor;
30 import java.io.PrintWriter;
31 import java.lang.reflect.Modifier;
32 import java.util.ArrayList;
33 
34 final class BackStackState implements Parcelable {
35     final int[] mOps;
36     final int mTransition;
37     final int mTransitionStyle;
38     final String mName;
39     final int mIndex;
40     final int mBreadCrumbTitleRes;
41     final CharSequence mBreadCrumbTitleText;
42     final int mBreadCrumbShortTitleRes;
43     final CharSequence mBreadCrumbShortTitleText;
44     final ArrayList<String> mSharedElementSourceNames;
45     final ArrayList<String> mSharedElementTargetNames;
46     final boolean mReorderingAllowed;
47 
BackStackState(FragmentManagerImpl fm, BackStackRecord bse)48     public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
49         final int numOps = bse.mOps.size();
50         mOps = new int[numOps * 6];
51 
52         if (!bse.mAddToBackStack) {
53             throw new IllegalStateException("Not on back stack");
54         }
55 
56         int pos = 0;
57         for (int opNum = 0; opNum < numOps; opNum++) {
58             final BackStackRecord.Op op = bse.mOps.get(opNum);
59             mOps[pos++] = op.cmd;
60             mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1;
61             mOps[pos++] = op.enterAnim;
62             mOps[pos++] = op.exitAnim;
63             mOps[pos++] = op.popEnterAnim;
64             mOps[pos++] = op.popExitAnim;
65         }
66         mTransition = bse.mTransition;
67         mTransitionStyle = bse.mTransitionStyle;
68         mName = bse.mName;
69         mIndex = bse.mIndex;
70         mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
71         mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
72         mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
73         mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
74         mSharedElementSourceNames = bse.mSharedElementSourceNames;
75         mSharedElementTargetNames = bse.mSharedElementTargetNames;
76         mReorderingAllowed = bse.mReorderingAllowed;
77     }
78 
BackStackState(Parcel in)79     public BackStackState(Parcel in) {
80         mOps = in.createIntArray();
81         mTransition = in.readInt();
82         mTransitionStyle = in.readInt();
83         mName = in.readString();
84         mIndex = in.readInt();
85         mBreadCrumbTitleRes = in.readInt();
86         mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
87         mBreadCrumbShortTitleRes = in.readInt();
88         mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
89         mSharedElementSourceNames = in.createStringArrayList();
90         mSharedElementTargetNames = in.createStringArrayList();
91         mReorderingAllowed = in.readInt() != 0;
92     }
93 
instantiate(FragmentManagerImpl fm)94     public BackStackRecord instantiate(FragmentManagerImpl fm) {
95         BackStackRecord bse = new BackStackRecord(fm);
96         int pos = 0;
97         int num = 0;
98         while (pos < mOps.length) {
99             BackStackRecord.Op op = new BackStackRecord.Op();
100             op.cmd = mOps[pos++];
101             if (FragmentManagerImpl.DEBUG) {
102                 Log.v(FragmentManagerImpl.TAG,
103                         "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
104             }
105             int findex = mOps[pos++];
106             if (findex >= 0) {
107                 Fragment f = fm.mActive.get(findex);
108                 op.fragment = f;
109             } else {
110                 op.fragment = null;
111             }
112             op.enterAnim = mOps[pos++];
113             op.exitAnim = mOps[pos++];
114             op.popEnterAnim = mOps[pos++];
115             op.popExitAnim = mOps[pos++];
116             bse.mEnterAnim = op.enterAnim;
117             bse.mExitAnim = op.exitAnim;
118             bse.mPopEnterAnim = op.popEnterAnim;
119             bse.mPopExitAnim = op.popExitAnim;
120             bse.addOp(op);
121             num++;
122         }
123         bse.mTransition = mTransition;
124         bse.mTransitionStyle = mTransitionStyle;
125         bse.mName = mName;
126         bse.mIndex = mIndex;
127         bse.mAddToBackStack = true;
128         bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes;
129         bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
130         bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
131         bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
132         bse.mSharedElementSourceNames = mSharedElementSourceNames;
133         bse.mSharedElementTargetNames = mSharedElementTargetNames;
134         bse.mReorderingAllowed = mReorderingAllowed;
135         bse.bumpBackStackNesting(1);
136         return bse;
137     }
138 
describeContents()139     public int describeContents() {
140         return 0;
141     }
142 
writeToParcel(Parcel dest, int flags)143     public void writeToParcel(Parcel dest, int flags) {
144         dest.writeIntArray(mOps);
145         dest.writeInt(mTransition);
146         dest.writeInt(mTransitionStyle);
147         dest.writeString(mName);
148         dest.writeInt(mIndex);
149         dest.writeInt(mBreadCrumbTitleRes);
150         TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
151         dest.writeInt(mBreadCrumbShortTitleRes);
152         TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
153         dest.writeStringList(mSharedElementSourceNames);
154         dest.writeStringList(mSharedElementTargetNames);
155         dest.writeInt(mReorderingAllowed ? 1 : 0);
156     }
157 
158     public static final @android.annotation.NonNull Parcelable.Creator<BackStackState> CREATOR
159             = new Parcelable.Creator<BackStackState>() {
160         public BackStackState createFromParcel(Parcel in) {
161             return new BackStackState(in);
162         }
163 
164         public BackStackState[] newArray(int size) {
165             return new BackStackState[size];
166         }
167     };
168 }
169 
170 /**
171  * @hide Entry of an operation on the fragment back stack.
172  */
173 final class BackStackRecord extends FragmentTransaction implements
174         FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {
175     static final String TAG = FragmentManagerImpl.TAG;
176 
177     final FragmentManagerImpl mManager;
178 
179     static final int OP_NULL = 0;
180     static final int OP_ADD = 1;
181     static final int OP_REPLACE = 2;
182     static final int OP_REMOVE = 3;
183     static final int OP_HIDE = 4;
184     static final int OP_SHOW = 5;
185     static final int OP_DETACH = 6;
186     static final int OP_ATTACH = 7;
187     static final int OP_SET_PRIMARY_NAV = 8;
188     static final int OP_UNSET_PRIMARY_NAV = 9;
189 
190     static final class Op {
191         int cmd;
192         Fragment fragment;
193         int enterAnim;
194         int exitAnim;
195         int popEnterAnim;
196         int popExitAnim;
197 
Op()198         Op() {
199         }
200 
Op(int cmd, Fragment fragment)201         Op(int cmd, Fragment fragment) {
202             this.cmd = cmd;
203             this.fragment = fragment;
204         }
205     }
206 
207     ArrayList<Op> mOps = new ArrayList<>();
208     int mEnterAnim;
209     int mExitAnim;
210     int mPopEnterAnim;
211     int mPopExitAnim;
212     int mTransition;
213     int mTransitionStyle;
214     boolean mAddToBackStack;
215     boolean mAllowAddToBackStack = true;
216     String mName;
217     boolean mCommitted;
218     int mIndex = -1;
219     boolean mReorderingAllowed;
220 
221     ArrayList<Runnable> mCommitRunnables;
222 
223     int mBreadCrumbTitleRes;
224     CharSequence mBreadCrumbTitleText;
225     int mBreadCrumbShortTitleRes;
226     CharSequence mBreadCrumbShortTitleText;
227 
228     ArrayList<String> mSharedElementSourceNames;
229     ArrayList<String> mSharedElementTargetNames;
230 
231     @Override
toString()232     public String toString() {
233         StringBuilder sb = new StringBuilder(128);
234         sb.append("BackStackEntry{");
235         sb.append(Integer.toHexString(System.identityHashCode(this)));
236         if (mIndex >= 0) {
237             sb.append(" #");
238             sb.append(mIndex);
239         }
240         if (mName != null) {
241             sb.append(" ");
242             sb.append(mName);
243         }
244         sb.append("}");
245         return sb.toString();
246     }
247 
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)248     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
249         dump(prefix, writer, true);
250     }
251 
dump(String prefix, PrintWriter writer, boolean full)252     void dump(String prefix, PrintWriter writer, boolean full) {
253         if (full) {
254             writer.print(prefix);
255             writer.print("mName=");
256             writer.print(mName);
257             writer.print(" mIndex=");
258             writer.print(mIndex);
259             writer.print(" mCommitted=");
260             writer.println(mCommitted);
261             if (mTransition != FragmentTransaction.TRANSIT_NONE) {
262                 writer.print(prefix);
263                 writer.print("mTransition=#");
264                 writer.print(Integer.toHexString(mTransition));
265                 writer.print(" mTransitionStyle=#");
266                 writer.println(Integer.toHexString(mTransitionStyle));
267             }
268             if (mEnterAnim != 0 || mExitAnim != 0) {
269                 writer.print(prefix);
270                 writer.print("mEnterAnim=#");
271                 writer.print(Integer.toHexString(mEnterAnim));
272                 writer.print(" mExitAnim=#");
273                 writer.println(Integer.toHexString(mExitAnim));
274             }
275             if (mPopEnterAnim != 0 || mPopExitAnim != 0) {
276                 writer.print(prefix);
277                 writer.print("mPopEnterAnim=#");
278                 writer.print(Integer.toHexString(mPopEnterAnim));
279                 writer.print(" mPopExitAnim=#");
280                 writer.println(Integer.toHexString(mPopExitAnim));
281             }
282             if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
283                 writer.print(prefix);
284                 writer.print("mBreadCrumbTitleRes=#");
285                 writer.print(Integer.toHexString(mBreadCrumbTitleRes));
286                 writer.print(" mBreadCrumbTitleText=");
287                 writer.println(mBreadCrumbTitleText);
288             }
289             if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
290                 writer.print(prefix);
291                 writer.print("mBreadCrumbShortTitleRes=#");
292                 writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
293                 writer.print(" mBreadCrumbShortTitleText=");
294                 writer.println(mBreadCrumbShortTitleText);
295             }
296         }
297 
298         if (!mOps.isEmpty()) {
299             writer.print(prefix);
300             writer.println("Operations:");
301             String innerPrefix = prefix + "    ";
302             final int numOps = mOps.size();
303             for (int opNum = 0; opNum < numOps; opNum++) {
304                 final Op op = mOps.get(opNum);
305                 String cmdStr;
306                 switch (op.cmd) {
307                     case OP_NULL:
308                         cmdStr = "NULL";
309                         break;
310                     case OP_ADD:
311                         cmdStr = "ADD";
312                         break;
313                     case OP_REPLACE:
314                         cmdStr = "REPLACE";
315                         break;
316                     case OP_REMOVE:
317                         cmdStr = "REMOVE";
318                         break;
319                     case OP_HIDE:
320                         cmdStr = "HIDE";
321                         break;
322                     case OP_SHOW:
323                         cmdStr = "SHOW";
324                         break;
325                     case OP_DETACH:
326                         cmdStr = "DETACH";
327                         break;
328                     case OP_ATTACH:
329                         cmdStr = "ATTACH";
330                         break;
331                     case OP_SET_PRIMARY_NAV:
332                         cmdStr="SET_PRIMARY_NAV";
333                         break;
334                     case OP_UNSET_PRIMARY_NAV:
335                         cmdStr="UNSET_PRIMARY_NAV";
336                         break;
337 
338                     default:
339                         cmdStr = "cmd=" + op.cmd;
340                         break;
341                 }
342                 writer.print(prefix);
343                 writer.print("  Op #");
344                 writer.print(opNum);
345                 writer.print(": ");
346                 writer.print(cmdStr);
347                 writer.print(" ");
348                 writer.println(op.fragment);
349                 if (full) {
350                     if (op.enterAnim != 0 || op.exitAnim != 0) {
351                         writer.print(innerPrefix);
352                         writer.print("enterAnim=#");
353                         writer.print(Integer.toHexString(op.enterAnim));
354                         writer.print(" exitAnim=#");
355                         writer.println(Integer.toHexString(op.exitAnim));
356                     }
357                     if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
358                         writer.print(innerPrefix);
359                         writer.print("popEnterAnim=#");
360                         writer.print(Integer.toHexString(op.popEnterAnim));
361                         writer.print(" popExitAnim=#");
362                         writer.println(Integer.toHexString(op.popExitAnim));
363                     }
364                 }
365             }
366         }
367     }
368 
BackStackRecord(FragmentManagerImpl manager)369     public BackStackRecord(FragmentManagerImpl manager) {
370         mManager = manager;
371         mReorderingAllowed = mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1;
372     }
373 
getId()374     public int getId() {
375         return mIndex;
376     }
377 
getBreadCrumbTitleRes()378     public int getBreadCrumbTitleRes() {
379         return mBreadCrumbTitleRes;
380     }
381 
getBreadCrumbShortTitleRes()382     public int getBreadCrumbShortTitleRes() {
383         return mBreadCrumbShortTitleRes;
384     }
385 
getBreadCrumbTitle()386     public CharSequence getBreadCrumbTitle() {
387         if (mBreadCrumbTitleRes != 0 && mManager.mHost != null) {
388             return mManager.mHost.getContext().getText(mBreadCrumbTitleRes);
389         }
390         return mBreadCrumbTitleText;
391     }
392 
getBreadCrumbShortTitle()393     public CharSequence getBreadCrumbShortTitle() {
394         if (mBreadCrumbShortTitleRes != 0 && mManager.mHost != null) {
395             return mManager.mHost.getContext().getText(mBreadCrumbShortTitleRes);
396         }
397         return mBreadCrumbShortTitleText;
398     }
399 
addOp(Op op)400     void addOp(Op op) {
401         mOps.add(op);
402         op.enterAnim = mEnterAnim;
403         op.exitAnim = mExitAnim;
404         op.popEnterAnim = mPopEnterAnim;
405         op.popExitAnim = mPopExitAnim;
406     }
407 
add(Fragment fragment, String tag)408     public FragmentTransaction add(Fragment fragment, String tag) {
409         doAddOp(0, fragment, tag, OP_ADD);
410         return this;
411     }
412 
add(int containerViewId, Fragment fragment)413     public FragmentTransaction add(int containerViewId, Fragment fragment) {
414         doAddOp(containerViewId, fragment, null, OP_ADD);
415         return this;
416     }
417 
add(int containerViewId, Fragment fragment, String tag)418     public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
419         doAddOp(containerViewId, fragment, tag, OP_ADD);
420         return this;
421     }
422 
doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd)423     private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
424         if (mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1) {
425             final Class fragmentClass = fragment.getClass();
426             final int modifiers = fragmentClass.getModifiers();
427             if ((fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
428                     || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers)))) {
429                 throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
430                         + " must be a public static class to be  properly recreated from"
431                         + " instance state.");
432             }
433         }
434         fragment.mFragmentManager = mManager;
435 
436         if (tag != null) {
437             if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
438                 throw new IllegalStateException("Can't change tag of fragment "
439                         + fragment + ": was " + fragment.mTag
440                         + " now " + tag);
441             }
442             fragment.mTag = tag;
443         }
444 
445         if (containerViewId != 0) {
446             if (containerViewId == View.NO_ID) {
447                 throw new IllegalArgumentException("Can't add fragment "
448                         + fragment + " with tag " + tag + " to container view with no id");
449             }
450             if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
451                 throw new IllegalStateException("Can't change container ID of fragment "
452                         + fragment + ": was " + fragment.mFragmentId
453                         + " now " + containerViewId);
454             }
455             fragment.mContainerId = fragment.mFragmentId = containerViewId;
456         }
457 
458         addOp(new Op(opcmd, fragment));
459     }
460 
replace(int containerViewId, Fragment fragment)461     public FragmentTransaction replace(int containerViewId, Fragment fragment) {
462         return replace(containerViewId, fragment, null);
463     }
464 
replace(int containerViewId, Fragment fragment, String tag)465     public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
466         if (containerViewId == 0) {
467             throw new IllegalArgumentException("Must use non-zero containerViewId");
468         }
469 
470         doAddOp(containerViewId, fragment, tag, OP_REPLACE);
471         return this;
472     }
473 
remove(Fragment fragment)474     public FragmentTransaction remove(Fragment fragment) {
475         addOp(new Op(OP_REMOVE, fragment));
476 
477         return this;
478     }
479 
hide(Fragment fragment)480     public FragmentTransaction hide(Fragment fragment) {
481         addOp(new Op(OP_HIDE, fragment));
482 
483         return this;
484     }
485 
show(Fragment fragment)486     public FragmentTransaction show(Fragment fragment) {
487         addOp(new Op(OP_SHOW, fragment));
488 
489         return this;
490     }
491 
detach(Fragment fragment)492     public FragmentTransaction detach(Fragment fragment) {
493         addOp(new Op(OP_DETACH, fragment));
494 
495         return this;
496     }
497 
attach(Fragment fragment)498     public FragmentTransaction attach(Fragment fragment) {
499         addOp(new Op(OP_ATTACH, fragment));
500 
501         return this;
502     }
503 
setPrimaryNavigationFragment(Fragment fragment)504     public FragmentTransaction setPrimaryNavigationFragment(Fragment fragment) {
505         addOp(new Op(OP_SET_PRIMARY_NAV, fragment));
506 
507         return this;
508     }
509 
setCustomAnimations(int enter, int exit)510     public FragmentTransaction setCustomAnimations(int enter, int exit) {
511         return setCustomAnimations(enter, exit, 0, 0);
512     }
513 
setCustomAnimations(int enter, int exit, int popEnter, int popExit)514     public FragmentTransaction setCustomAnimations(int enter, int exit,
515             int popEnter, int popExit) {
516         mEnterAnim = enter;
517         mExitAnim = exit;
518         mPopEnterAnim = popEnter;
519         mPopExitAnim = popExit;
520         return this;
521     }
522 
setTransition(int transition)523     public FragmentTransaction setTransition(int transition) {
524         mTransition = transition;
525         return this;
526     }
527 
528     @Override
addSharedElement(View sharedElement, String name)529     public FragmentTransaction addSharedElement(View sharedElement, String name) {
530         String transitionName = sharedElement.getTransitionName();
531         if (transitionName == null) {
532             throw new IllegalArgumentException("Unique transitionNames are required for all" +
533                     " sharedElements");
534         }
535         if (mSharedElementSourceNames == null) {
536             mSharedElementSourceNames = new ArrayList<String>();
537             mSharedElementTargetNames = new ArrayList<String>();
538         } else if (mSharedElementTargetNames.contains(name)) {
539             throw new IllegalArgumentException("A shared element with the target name '"
540                     + name + "' has already been added to the transaction.");
541         } else if (mSharedElementSourceNames.contains(transitionName)) {
542             throw new IllegalArgumentException("A shared element with the source name '"
543                     + transitionName + " has already been added to the transaction.");
544         }
545         mSharedElementSourceNames.add(transitionName);
546         mSharedElementTargetNames.add(name);
547         return this;
548     }
549 
setTransitionStyle(int styleRes)550     public FragmentTransaction setTransitionStyle(int styleRes) {
551         mTransitionStyle = styleRes;
552         return this;
553     }
554 
addToBackStack(String name)555     public FragmentTransaction addToBackStack(String name) {
556         if (!mAllowAddToBackStack) {
557             throw new IllegalStateException(
558                     "This FragmentTransaction is not allowed to be added to the back stack.");
559         }
560         mAddToBackStack = true;
561         mName = name;
562         return this;
563     }
564 
isAddToBackStackAllowed()565     public boolean isAddToBackStackAllowed() {
566         return mAllowAddToBackStack;
567     }
568 
disallowAddToBackStack()569     public FragmentTransaction disallowAddToBackStack() {
570         if (mAddToBackStack) {
571             throw new IllegalStateException(
572                     "This transaction is already being added to the back stack");
573         }
574         mAllowAddToBackStack = false;
575         return this;
576     }
577 
setBreadCrumbTitle(int res)578     public FragmentTransaction setBreadCrumbTitle(int res) {
579         mBreadCrumbTitleRes = res;
580         mBreadCrumbTitleText = null;
581         return this;
582     }
583 
setBreadCrumbTitle(CharSequence text)584     public FragmentTransaction setBreadCrumbTitle(CharSequence text) {
585         mBreadCrumbTitleRes = 0;
586         mBreadCrumbTitleText = text;
587         return this;
588     }
589 
setBreadCrumbShortTitle(int res)590     public FragmentTransaction setBreadCrumbShortTitle(int res) {
591         mBreadCrumbShortTitleRes = res;
592         mBreadCrumbShortTitleText = null;
593         return this;
594     }
595 
setBreadCrumbShortTitle(CharSequence text)596     public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) {
597         mBreadCrumbShortTitleRes = 0;
598         mBreadCrumbShortTitleText = text;
599         return this;
600     }
601 
bumpBackStackNesting(int amt)602     void bumpBackStackNesting(int amt) {
603         if (!mAddToBackStack) {
604             return;
605         }
606         if (FragmentManagerImpl.DEBUG) {
607             Log.v(TAG, "Bump nesting in " + this
608                     + " by " + amt);
609         }
610         final int numOps = mOps.size();
611         for (int opNum = 0; opNum < numOps; opNum++) {
612             final Op op = mOps.get(opNum);
613             if (op.fragment != null) {
614                 op.fragment.mBackStackNesting += amt;
615                 if (FragmentManagerImpl.DEBUG) {
616                     Log.v(TAG, "Bump nesting of "
617                             + op.fragment + " to " + op.fragment.mBackStackNesting);
618                 }
619             }
620         }
621     }
622 
623     @Override
runOnCommit(Runnable runnable)624     public FragmentTransaction runOnCommit(Runnable runnable) {
625         if (runnable == null) {
626             throw new IllegalArgumentException("runnable cannot be null");
627         }
628         disallowAddToBackStack();
629         if (mCommitRunnables == null) {
630             mCommitRunnables = new ArrayList<>();
631         }
632         mCommitRunnables.add(runnable);
633         return this;
634     }
635 
runOnCommitRunnables()636     public void runOnCommitRunnables() {
637         if (mCommitRunnables != null) {
638             for (int i = 0, N = mCommitRunnables.size(); i < N; i++) {
639                 mCommitRunnables.get(i).run();
640             }
641             mCommitRunnables = null;
642         }
643     }
644 
commit()645     public int commit() {
646         return commitInternal(false);
647     }
648 
commitAllowingStateLoss()649     public int commitAllowingStateLoss() {
650         return commitInternal(true);
651     }
652 
653     @Override
commitNow()654     public void commitNow() {
655         disallowAddToBackStack();
656         mManager.execSingleAction(this, false);
657     }
658 
659     @Override
commitNowAllowingStateLoss()660     public void commitNowAllowingStateLoss() {
661         disallowAddToBackStack();
662         mManager.execSingleAction(this, true);
663     }
664 
665     @Override
setReorderingAllowed(boolean reorderingAllowed)666     public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) {
667         mReorderingAllowed = reorderingAllowed;
668         return this;
669     }
670 
commitInternal(boolean allowStateLoss)671     int commitInternal(boolean allowStateLoss) {
672         if (mCommitted) {
673             throw new IllegalStateException("commit already called");
674         }
675         if (FragmentManagerImpl.DEBUG) {
676             Log.v(TAG, "Commit: " + this);
677             LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
678             PrintWriter pw = new FastPrintWriter(logw, false, 1024);
679             dump("  ", null, pw, null);
680             pw.flush();
681         }
682         mCommitted = true;
683         if (mAddToBackStack) {
684             mIndex = mManager.allocBackStackIndex(this);
685         } else {
686             mIndex = -1;
687         }
688         mManager.enqueueAction(this, allowStateLoss);
689         return mIndex;
690     }
691 
692     /**
693      * Implementation of {@link android.app.FragmentManagerImpl.OpGenerator}.
694      * This operation is added to the list of pending actions during {@link #commit()}, and
695      * will be executed on the UI thread to run this FragmentTransaction.
696      *
697      * @param records Modified to add this BackStackRecord
698      * @param isRecordPop Modified to add a false (this isn't a pop)
699      * @return true always because the records and isRecordPop will always be changed
700      */
701     @Override
generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop)702     public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) {
703         if (FragmentManagerImpl.DEBUG) {
704             Log.v(TAG, "Run: " + this);
705         }
706 
707         records.add(this);
708         isRecordPop.add(false);
709         if (mAddToBackStack) {
710             mManager.addBackStackState(this);
711         }
712         return true;
713     }
714 
interactsWith(int containerId)715     boolean interactsWith(int containerId) {
716         final int numOps = mOps.size();
717         for (int opNum = 0; opNum < numOps; opNum++) {
718             final Op op = mOps.get(opNum);
719             final int fragContainer = op.fragment != null ? op.fragment.mContainerId : 0;
720             if (fragContainer != 0 && fragContainer == containerId) {
721                 return true;
722             }
723         }
724         return false;
725     }
726 
interactsWith(ArrayList<BackStackRecord> records, int startIndex, int endIndex)727     boolean interactsWith(ArrayList<BackStackRecord> records, int startIndex, int endIndex) {
728         if (endIndex == startIndex) {
729             return false;
730         }
731         final int numOps = mOps.size();
732         int lastContainer = -1;
733         for (int opNum = 0; opNum < numOps; opNum++) {
734             final Op op = mOps.get(opNum);
735             final int container = op.fragment != null ? op.fragment.mContainerId : 0;
736             if (container != 0 && container != lastContainer) {
737                 lastContainer = container;
738                 for (int i = startIndex; i < endIndex; i++) {
739                     BackStackRecord record = records.get(i);
740                     final int numThoseOps = record.mOps.size();
741                     for (int thoseOpIndex = 0; thoseOpIndex < numThoseOps; thoseOpIndex++) {
742                         final Op thatOp = record.mOps.get(thoseOpIndex);
743                         final int thatContainer = thatOp.fragment != null
744                                 ? thatOp.fragment.mContainerId : 0;
745                         if (thatContainer == container) {
746                             return true;
747                         }
748                     }
749                 }
750             }
751         }
752         return false;
753     }
754 
755     /**
756      * Executes the operations contained within this transaction. The Fragment states will only
757      * be modified if optimizations are not allowed.
758      */
executeOps()759     void executeOps() {
760         final int numOps = mOps.size();
761         for (int opNum = 0; opNum < numOps; opNum++) {
762             final Op op = mOps.get(opNum);
763             final Fragment f = op.fragment;
764             if (f != null) {
765                 f.setNextTransition(mTransition, mTransitionStyle);
766             }
767             switch (op.cmd) {
768                 case OP_ADD:
769                     f.setNextAnim(op.enterAnim);
770                     mManager.addFragment(f, false);
771                     break;
772                 case OP_REMOVE:
773                     f.setNextAnim(op.exitAnim);
774                     mManager.removeFragment(f);
775                     break;
776                 case OP_HIDE:
777                     f.setNextAnim(op.exitAnim);
778                     mManager.hideFragment(f);
779                     break;
780                 case OP_SHOW:
781                     f.setNextAnim(op.enterAnim);
782                     mManager.showFragment(f);
783                     break;
784                 case OP_DETACH:
785                     f.setNextAnim(op.exitAnim);
786                     mManager.detachFragment(f);
787                     break;
788                 case OP_ATTACH:
789                     f.setNextAnim(op.enterAnim);
790                     mManager.attachFragment(f);
791                     break;
792                 case OP_SET_PRIMARY_NAV:
793                     mManager.setPrimaryNavigationFragment(f);
794                     break;
795                 case OP_UNSET_PRIMARY_NAV:
796                     mManager.setPrimaryNavigationFragment(null);
797                     break;
798                 default:
799                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
800             }
801             if (!mReorderingAllowed && op.cmd != OP_ADD && f != null) {
802                 mManager.moveFragmentToExpectedState(f);
803             }
804         }
805         if (!mReorderingAllowed) {
806             // Added fragments are added at the end to comply with prior behavior.
807             mManager.moveToState(mManager.mCurState, true);
808         }
809     }
810 
811     /**
812      * Reverses the execution of the operations within this transaction. The Fragment states will
813      * only be modified if optimizations are not allowed.
814      *
815      * @param moveToState {@code true} if added fragments should be moved to their final state
816      *                    in unoptimized transactions
817      */
executePopOps(boolean moveToState)818     void executePopOps(boolean moveToState) {
819         for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
820             final Op op = mOps.get(opNum);
821             Fragment f = op.fragment;
822             if (f != null) {
823                 f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition),
824                         mTransitionStyle);
825             }
826             switch (op.cmd) {
827                 case OP_ADD:
828                     f.setNextAnim(op.popExitAnim);
829                     mManager.removeFragment(f);
830                     break;
831                 case OP_REMOVE:
832                     f.setNextAnim(op.popEnterAnim);
833                     mManager.addFragment(f, false);
834                     break;
835                 case OP_HIDE:
836                     f.setNextAnim(op.popEnterAnim);
837                     mManager.showFragment(f);
838                     break;
839                 case OP_SHOW:
840                     f.setNextAnim(op.popExitAnim);
841                     mManager.hideFragment(f);
842                     break;
843                 case OP_DETACH:
844                     f.setNextAnim(op.popEnterAnim);
845                     mManager.attachFragment(f);
846                     break;
847                 case OP_ATTACH:
848                     f.setNextAnim(op.popExitAnim);
849                     mManager.detachFragment(f);
850                     break;
851                 case OP_SET_PRIMARY_NAV:
852                     mManager.setPrimaryNavigationFragment(null);
853                     break;
854                 case OP_UNSET_PRIMARY_NAV:
855                     mManager.setPrimaryNavigationFragment(f);
856                     break;
857                 default:
858                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
859             }
860             if (!mReorderingAllowed && op.cmd != OP_REMOVE && f != null) {
861                 mManager.moveFragmentToExpectedState(f);
862             }
863         }
864         if (!mReorderingAllowed && moveToState) {
865             mManager.moveToState(mManager.mCurState, true);
866         }
867     }
868 
869     /**
870      * Expands all meta-ops into their more primitive equivalents. This must be called prior to
871      * {@link #executeOps()} or any other call that operations on mOps for forward navigation.
872      * It should not be called for pop/reverse navigation operations.
873      *
874      * <p>Removes all OP_REPLACE ops and replaces them with the proper add and remove
875      * operations that are equivalent to the replace.</p>
876      *
877      * <p>Adds OP_UNSET_PRIMARY_NAV ops to match OP_SET_PRIMARY_NAV, OP_REMOVE and OP_DETACH
878      * ops so that we can restore the old primary nav fragment later. Since callers call this
879      * method in a loop before running ops from several transactions at once, the caller should
880      * pass the return value from this method as the oldPrimaryNav parameter for the next call.
881      * The first call in such a loop should pass the value of
882      * {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
883      *
884      * @param added Initialized to the fragments that are in the mManager.mAdded, this
885      *              will be modified to contain the fragments that will be in mAdded
886      *              after the execution ({@link #executeOps()}.
887      * @param oldPrimaryNav The tracked primary navigation fragment as of the beginning of
888      *                      this set of ops
889      * @return the new oldPrimaryNav fragment after this record's ops would be run
890      */
891     @SuppressWarnings("ReferenceEquality")
expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav)892     Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
893         for (int opNum = 0; opNum < mOps.size(); opNum++) {
894             final Op op = mOps.get(opNum);
895             switch (op.cmd) {
896                 case OP_ADD:
897                 case OP_ATTACH:
898                     added.add(op.fragment);
899                     break;
900                 case OP_REMOVE:
901                 case OP_DETACH: {
902                     added.remove(op.fragment);
903                     if (op.fragment == oldPrimaryNav) {
904                         mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.fragment));
905                         opNum++;
906                         oldPrimaryNav = null;
907                     }
908                 }
909                 break;
910                 case OP_REPLACE: {
911                     final Fragment f = op.fragment;
912                     final int containerId = f.mContainerId;
913                     boolean alreadyAdded = false;
914                     for (int i = added.size() - 1; i >= 0; i--) {
915                         final Fragment old = added.get(i);
916                         if (old.mContainerId == containerId) {
917                             if (old == f) {
918                                 alreadyAdded = true;
919                             } else {
920                                 // This is duplicated from above since we only make
921                                 // a single pass for expanding ops. Unset any outgoing primary nav.
922                                 if (old == oldPrimaryNav) {
923                                     mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old));
924                                     opNum++;
925                                     oldPrimaryNav = null;
926                                 }
927                                 final Op removeOp = new Op(OP_REMOVE, old);
928                                 removeOp.enterAnim = op.enterAnim;
929                                 removeOp.popEnterAnim = op.popEnterAnim;
930                                 removeOp.exitAnim = op.exitAnim;
931                                 removeOp.popExitAnim = op.popExitAnim;
932                                 mOps.add(opNum, removeOp);
933                                 added.remove(old);
934                                 opNum++;
935                             }
936                         }
937                     }
938                     if (alreadyAdded) {
939                         mOps.remove(opNum);
940                         opNum--;
941                     } else {
942                         op.cmd = OP_ADD;
943                         added.add(f);
944                     }
945                 }
946                 break;
947                 case OP_SET_PRIMARY_NAV: {
948                     // It's ok if this is null, that means we will restore to no active
949                     // primary navigation fragment on a pop.
950                     mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav));
951                     opNum++;
952                     // Will be set by the OP_SET_PRIMARY_NAV we inserted before when run
953                     oldPrimaryNav = op.fragment;
954                 }
955                 break;
956             }
957         }
958         return oldPrimaryNav;
959     }
960 
961     /**
962      * Removes fragments that are added or removed during a pop operation.
963      *
964      * @param added Initialized to the fragments that are in the mManager.mAdded, this
965      *              will be modified to contain the fragments that will be in mAdded
966      *              after the execution ({@link #executeOps()}.
967      */
trackAddedFragmentsInPop(ArrayList<Fragment> added)968     void trackAddedFragmentsInPop(ArrayList<Fragment> added) {
969         for (int opNum = 0; opNum < mOps.size(); opNum++) {
970             final Op op = mOps.get(opNum);
971             switch (op.cmd) {
972                 case OP_ADD:
973                 case OP_ATTACH:
974                     added.remove(op.fragment);
975                     break;
976                 case OP_REMOVE:
977                 case OP_DETACH:
978                     added.add(op.fragment);
979                     break;
980             }
981         }
982     }
983 
isPostponed()984     boolean isPostponed() {
985         for (int opNum = 0; opNum < mOps.size(); opNum++) {
986             final Op op = mOps.get(opNum);
987             if (isFragmentPostponed(op)) {
988                 return true;
989             }
990         }
991         return false;
992     }
993 
setOnStartPostponedListener(Fragment.OnStartEnterTransitionListener listener)994     void setOnStartPostponedListener(Fragment.OnStartEnterTransitionListener listener) {
995         for (int opNum = 0; opNum < mOps.size(); opNum++) {
996             final Op op = mOps.get(opNum);
997             if (isFragmentPostponed(op)) {
998                 op.fragment.setOnStartEnterTransitionListener(listener);
999             }
1000         }
1001     }
1002 
isFragmentPostponed(Op op)1003     private static boolean isFragmentPostponed(Op op) {
1004         final Fragment fragment = op.fragment;
1005         return fragment != null && fragment.mAdded && fragment.mView != null && !fragment.mDetached
1006                 && !fragment.mHidden && fragment.isPostponed();
1007     }
1008 
getName()1009     public String getName() {
1010         return mName;
1011     }
1012 
getTransition()1013     public int getTransition() {
1014         return mTransition;
1015     }
1016 
getTransitionStyle()1017     public int getTransitionStyle() {
1018         return mTransitionStyle;
1019     }
1020 
isEmpty()1021     public boolean isEmpty() {
1022         return mOps.isEmpty();
1023     }
1024 }
1025