Home | History | Annotate | Download | only in app
      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 com.android.internal.util.FastPrintWriter;
     20 
     21 import android.graphics.Rect;
     22 import android.os.Parcel;
     23 import android.os.Parcelable;
     24 import android.text.TextUtils;
     25 import android.transition.Transition;
     26 import android.transition.TransitionManager;
     27 import android.transition.TransitionSet;
     28 import android.transition.TransitionUtils;
     29 import android.util.ArrayMap;
     30 import android.util.Log;
     31 import android.util.LogWriter;
     32 import android.util.Pair;
     33 import android.util.SparseArray;
     34 import android.view.View;
     35 import android.view.ViewGroup;
     36 import android.view.ViewTreeObserver;
     37 
     38 import java.io.FileDescriptor;
     39 import java.io.PrintWriter;
     40 import java.util.ArrayList;
     41 import java.util.List;
     42 
     43 final class BackStackState implements Parcelable {
     44     final int[] mOps;
     45     final int mTransition;
     46     final int mTransitionStyle;
     47     final String mName;
     48     final int mIndex;
     49     final int mBreadCrumbTitleRes;
     50     final CharSequence mBreadCrumbTitleText;
     51     final int mBreadCrumbShortTitleRes;
     52     final CharSequence mBreadCrumbShortTitleText;
     53     final ArrayList<String> mSharedElementSourceNames;
     54     final ArrayList<String> mSharedElementTargetNames;
     55 
     56     public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
     57         int numRemoved = 0;
     58         BackStackRecord.Op op = bse.mHead;
     59         while (op != null) {
     60             if (op.removed != null) {
     61                 numRemoved += op.removed.size();
     62             }
     63             op = op.next;
     64         }
     65         mOps = new int[bse.mNumOp * 7 + numRemoved];
     66 
     67         if (!bse.mAddToBackStack) {
     68             throw new IllegalStateException("Not on back stack");
     69         }
     70 
     71         op = bse.mHead;
     72         int pos = 0;
     73         while (op != null) {
     74             mOps[pos++] = op.cmd;
     75             mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1;
     76             mOps[pos++] = op.enterAnim;
     77             mOps[pos++] = op.exitAnim;
     78             mOps[pos++] = op.popEnterAnim;
     79             mOps[pos++] = op.popExitAnim;
     80             if (op.removed != null) {
     81                 final int N = op.removed.size();
     82                 mOps[pos++] = N;
     83                 for (int i = 0; i < N; i++) {
     84                     mOps[pos++] = op.removed.get(i).mIndex;
     85                 }
     86             } else {
     87                 mOps[pos++] = 0;
     88             }
     89             op = op.next;
     90         }
     91         mTransition = bse.mTransition;
     92         mTransitionStyle = bse.mTransitionStyle;
     93         mName = bse.mName;
     94         mIndex = bse.mIndex;
     95         mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
     96         mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
     97         mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
     98         mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
     99         mSharedElementSourceNames = bse.mSharedElementSourceNames;
    100         mSharedElementTargetNames = bse.mSharedElementTargetNames;
    101     }
    102 
    103     public BackStackState(Parcel in) {
    104         mOps = in.createIntArray();
    105         mTransition = in.readInt();
    106         mTransitionStyle = in.readInt();
    107         mName = in.readString();
    108         mIndex = in.readInt();
    109         mBreadCrumbTitleRes = in.readInt();
    110         mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
    111         mBreadCrumbShortTitleRes = in.readInt();
    112         mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
    113         mSharedElementSourceNames = in.createStringArrayList();
    114         mSharedElementTargetNames = in.createStringArrayList();
    115     }
    116 
    117     public BackStackRecord instantiate(FragmentManagerImpl fm) {
    118         BackStackRecord bse = new BackStackRecord(fm);
    119         int pos = 0;
    120         int num = 0;
    121         while (pos < mOps.length) {
    122             BackStackRecord.Op op = new BackStackRecord.Op();
    123             op.cmd = mOps[pos++];
    124             if (FragmentManagerImpl.DEBUG) {
    125                 Log.v(FragmentManagerImpl.TAG,
    126                         "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
    127             }
    128             int findex = mOps[pos++];
    129             if (findex >= 0) {
    130                 Fragment f = fm.mActive.get(findex);
    131                 op.fragment = f;
    132             } else {
    133                 op.fragment = null;
    134             }
    135             op.enterAnim = mOps[pos++];
    136             op.exitAnim = mOps[pos++];
    137             op.popEnterAnim = mOps[pos++];
    138             op.popExitAnim = mOps[pos++];
    139             final int N = mOps[pos++];
    140             if (N > 0) {
    141                 op.removed = new ArrayList<Fragment>(N);
    142                 for (int i = 0; i < N; i++) {
    143                     if (FragmentManagerImpl.DEBUG) {
    144                         Log.v(FragmentManagerImpl.TAG,
    145                                 "Instantiate " + bse + " set remove fragment #" + mOps[pos]);
    146                     }
    147                     Fragment r = fm.mActive.get(mOps[pos++]);
    148                     op.removed.add(r);
    149                 }
    150             }
    151             bse.addOp(op);
    152             num++;
    153         }
    154         bse.mTransition = mTransition;
    155         bse.mTransitionStyle = mTransitionStyle;
    156         bse.mName = mName;
    157         bse.mIndex = mIndex;
    158         bse.mAddToBackStack = true;
    159         bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes;
    160         bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
    161         bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
    162         bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
    163         bse.mSharedElementSourceNames = mSharedElementSourceNames;
    164         bse.mSharedElementTargetNames = mSharedElementTargetNames;
    165         bse.bumpBackStackNesting(1);
    166         return bse;
    167     }
    168 
    169     public int describeContents() {
    170         return 0;
    171     }
    172 
    173     public void writeToParcel(Parcel dest, int flags) {
    174         dest.writeIntArray(mOps);
    175         dest.writeInt(mTransition);
    176         dest.writeInt(mTransitionStyle);
    177         dest.writeString(mName);
    178         dest.writeInt(mIndex);
    179         dest.writeInt(mBreadCrumbTitleRes);
    180         TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
    181         dest.writeInt(mBreadCrumbShortTitleRes);
    182         TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
    183         dest.writeStringList(mSharedElementSourceNames);
    184         dest.writeStringList(mSharedElementTargetNames);
    185     }
    186 
    187     public static final Parcelable.Creator<BackStackState> CREATOR
    188             = new Parcelable.Creator<BackStackState>() {
    189         public BackStackState createFromParcel(Parcel in) {
    190             return new BackStackState(in);
    191         }
    192 
    193         public BackStackState[] newArray(int size) {
    194             return new BackStackState[size];
    195         }
    196     };
    197 }
    198 
    199 /**
    200  * @hide Entry of an operation on the fragment back stack.
    201  */
    202 final class BackStackRecord extends FragmentTransaction implements
    203         FragmentManager.BackStackEntry, Runnable {
    204     static final String TAG = FragmentManagerImpl.TAG;
    205 
    206     final FragmentManagerImpl mManager;
    207 
    208     static final int OP_NULL = 0;
    209     static final int OP_ADD = 1;
    210     static final int OP_REPLACE = 2;
    211     static final int OP_REMOVE = 3;
    212     static final int OP_HIDE = 4;
    213     static final int OP_SHOW = 5;
    214     static final int OP_DETACH = 6;
    215     static final int OP_ATTACH = 7;
    216 
    217     static final class Op {
    218         Op next;
    219         Op prev;
    220         int cmd;
    221         Fragment fragment;
    222         int enterAnim;
    223         int exitAnim;
    224         int popEnterAnim;
    225         int popExitAnim;
    226         ArrayList<Fragment> removed;
    227     }
    228 
    229     Op mHead;
    230     Op mTail;
    231     int mNumOp;
    232     int mEnterAnim;
    233     int mExitAnim;
    234     int mPopEnterAnim;
    235     int mPopExitAnim;
    236     int mTransition;
    237     int mTransitionStyle;
    238     boolean mAddToBackStack;
    239     boolean mAllowAddToBackStack = true;
    240     String mName;
    241     boolean mCommitted;
    242     int mIndex = -1;
    243 
    244     int mBreadCrumbTitleRes;
    245     CharSequence mBreadCrumbTitleText;
    246     int mBreadCrumbShortTitleRes;
    247     CharSequence mBreadCrumbShortTitleText;
    248 
    249     ArrayList<String> mSharedElementSourceNames;
    250     ArrayList<String> mSharedElementTargetNames;
    251 
    252     @Override
    253     public String toString() {
    254         StringBuilder sb = new StringBuilder(128);
    255         sb.append("BackStackEntry{");
    256         sb.append(Integer.toHexString(System.identityHashCode(this)));
    257         if (mIndex >= 0) {
    258             sb.append(" #");
    259             sb.append(mIndex);
    260         }
    261         if (mName != null) {
    262             sb.append(" ");
    263             sb.append(mName);
    264         }
    265         sb.append("}");
    266         return sb.toString();
    267     }
    268 
    269     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
    270         dump(prefix, writer, true);
    271     }
    272 
    273     void dump(String prefix, PrintWriter writer, boolean full) {
    274         if (full) {
    275             writer.print(prefix);
    276             writer.print("mName=");
    277             writer.print(mName);
    278             writer.print(" mIndex=");
    279             writer.print(mIndex);
    280             writer.print(" mCommitted=");
    281             writer.println(mCommitted);
    282             if (mTransition != FragmentTransaction.TRANSIT_NONE) {
    283                 writer.print(prefix);
    284                 writer.print("mTransition=#");
    285                 writer.print(Integer.toHexString(mTransition));
    286                 writer.print(" mTransitionStyle=#");
    287                 writer.println(Integer.toHexString(mTransitionStyle));
    288             }
    289             if (mEnterAnim != 0 || mExitAnim != 0) {
    290                 writer.print(prefix);
    291                 writer.print("mEnterAnim=#");
    292                 writer.print(Integer.toHexString(mEnterAnim));
    293                 writer.print(" mExitAnim=#");
    294                 writer.println(Integer.toHexString(mExitAnim));
    295             }
    296             if (mPopEnterAnim != 0 || mPopExitAnim != 0) {
    297                 writer.print(prefix);
    298                 writer.print("mPopEnterAnim=#");
    299                 writer.print(Integer.toHexString(mPopEnterAnim));
    300                 writer.print(" mPopExitAnim=#");
    301                 writer.println(Integer.toHexString(mPopExitAnim));
    302             }
    303             if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
    304                 writer.print(prefix);
    305                 writer.print("mBreadCrumbTitleRes=#");
    306                 writer.print(Integer.toHexString(mBreadCrumbTitleRes));
    307                 writer.print(" mBreadCrumbTitleText=");
    308                 writer.println(mBreadCrumbTitleText);
    309             }
    310             if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
    311                 writer.print(prefix);
    312                 writer.print("mBreadCrumbShortTitleRes=#");
    313                 writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
    314                 writer.print(" mBreadCrumbShortTitleText=");
    315                 writer.println(mBreadCrumbShortTitleText);
    316             }
    317         }
    318 
    319         if (mHead != null) {
    320             writer.print(prefix);
    321             writer.println("Operations:");
    322             String innerPrefix = prefix + "    ";
    323             Op op = mHead;
    324             int num = 0;
    325             while (op != null) {
    326                 String cmdStr;
    327                 switch (op.cmd) {
    328                     case OP_NULL:
    329                         cmdStr = "NULL";
    330                         break;
    331                     case OP_ADD:
    332                         cmdStr = "ADD";
    333                         break;
    334                     case OP_REPLACE:
    335                         cmdStr = "REPLACE";
    336                         break;
    337                     case OP_REMOVE:
    338                         cmdStr = "REMOVE";
    339                         break;
    340                     case OP_HIDE:
    341                         cmdStr = "HIDE";
    342                         break;
    343                     case OP_SHOW:
    344                         cmdStr = "SHOW";
    345                         break;
    346                     case OP_DETACH:
    347                         cmdStr = "DETACH";
    348                         break;
    349                     case OP_ATTACH:
    350                         cmdStr = "ATTACH";
    351                         break;
    352                     default:
    353                         cmdStr = "cmd=" + op.cmd;
    354                         break;
    355                 }
    356                 writer.print(prefix);
    357                 writer.print("  Op #");
    358                 writer.print(num);
    359                 writer.print(": ");
    360                 writer.print(cmdStr);
    361                 writer.print(" ");
    362                 writer.println(op.fragment);
    363                 if (full) {
    364                     if (op.enterAnim != 0 || op.exitAnim != 0) {
    365                         writer.print(innerPrefix);
    366                         writer.print("enterAnim=#");
    367                         writer.print(Integer.toHexString(op.enterAnim));
    368                         writer.print(" exitAnim=#");
    369                         writer.println(Integer.toHexString(op.exitAnim));
    370                     }
    371                     if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
    372                         writer.print(innerPrefix);
    373                         writer.print("popEnterAnim=#");
    374                         writer.print(Integer.toHexString(op.popEnterAnim));
    375                         writer.print(" popExitAnim=#");
    376                         writer.println(Integer.toHexString(op.popExitAnim));
    377                     }
    378                 }
    379                 if (op.removed != null && op.removed.size() > 0) {
    380                     for (int i = 0; i < op.removed.size(); i++) {
    381                         writer.print(innerPrefix);
    382                         if (op.removed.size() == 1) {
    383                             writer.print("Removed: ");
    384                         } else {
    385                             if (i == 0) {
    386                                 writer.println("Removed:");
    387                             }
    388                             writer.print(innerPrefix);
    389                             writer.print("  #");
    390                             writer.print(i);
    391                             writer.print(": ");
    392                         }
    393                         writer.println(op.removed.get(i));
    394                     }
    395                 }
    396                 op = op.next;
    397                 num++;
    398             }
    399         }
    400     }
    401 
    402     public BackStackRecord(FragmentManagerImpl manager) {
    403         mManager = manager;
    404     }
    405 
    406     public int getId() {
    407         return mIndex;
    408     }
    409 
    410     public int getBreadCrumbTitleRes() {
    411         return mBreadCrumbTitleRes;
    412     }
    413 
    414     public int getBreadCrumbShortTitleRes() {
    415         return mBreadCrumbShortTitleRes;
    416     }
    417 
    418     public CharSequence getBreadCrumbTitle() {
    419         if (mBreadCrumbTitleRes != 0) {
    420             return mManager.mActivity.getText(mBreadCrumbTitleRes);
    421         }
    422         return mBreadCrumbTitleText;
    423     }
    424 
    425     public CharSequence getBreadCrumbShortTitle() {
    426         if (mBreadCrumbShortTitleRes != 0) {
    427             return mManager.mActivity.getText(mBreadCrumbShortTitleRes);
    428         }
    429         return mBreadCrumbShortTitleText;
    430     }
    431 
    432     void addOp(Op op) {
    433         if (mHead == null) {
    434             mHead = mTail = op;
    435         } else {
    436             op.prev = mTail;
    437             mTail.next = op;
    438             mTail = op;
    439         }
    440         op.enterAnim = mEnterAnim;
    441         op.exitAnim = mExitAnim;
    442         op.popEnterAnim = mPopEnterAnim;
    443         op.popExitAnim = mPopExitAnim;
    444         mNumOp++;
    445     }
    446 
    447     public FragmentTransaction add(Fragment fragment, String tag) {
    448         doAddOp(0, fragment, tag, OP_ADD);
    449         return this;
    450     }
    451 
    452     public FragmentTransaction add(int containerViewId, Fragment fragment) {
    453         doAddOp(containerViewId, fragment, null, OP_ADD);
    454         return this;
    455     }
    456 
    457     public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
    458         doAddOp(containerViewId, fragment, tag, OP_ADD);
    459         return this;
    460     }
    461 
    462     private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
    463         fragment.mFragmentManager = mManager;
    464 
    465         if (tag != null) {
    466             if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
    467                 throw new IllegalStateException("Can't change tag of fragment "
    468                         + fragment + ": was " + fragment.mTag
    469                         + " now " + tag);
    470             }
    471             fragment.mTag = tag;
    472         }
    473 
    474         if (containerViewId != 0) {
    475             if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
    476                 throw new IllegalStateException("Can't change container ID of fragment "
    477                         + fragment + ": was " + fragment.mFragmentId
    478                         + " now " + containerViewId);
    479             }
    480             fragment.mContainerId = fragment.mFragmentId = containerViewId;
    481         }
    482 
    483         Op op = new Op();
    484         op.cmd = opcmd;
    485         op.fragment = fragment;
    486         addOp(op);
    487     }
    488 
    489     public FragmentTransaction replace(int containerViewId, Fragment fragment) {
    490         return replace(containerViewId, fragment, null);
    491     }
    492 
    493     public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
    494         if (containerViewId == 0) {
    495             throw new IllegalArgumentException("Must use non-zero containerViewId");
    496         }
    497 
    498         doAddOp(containerViewId, fragment, tag, OP_REPLACE);
    499         return this;
    500     }
    501 
    502     public FragmentTransaction remove(Fragment fragment) {
    503         Op op = new Op();
    504         op.cmd = OP_REMOVE;
    505         op.fragment = fragment;
    506         addOp(op);
    507 
    508         return this;
    509     }
    510 
    511     public FragmentTransaction hide(Fragment fragment) {
    512         Op op = new Op();
    513         op.cmd = OP_HIDE;
    514         op.fragment = fragment;
    515         addOp(op);
    516 
    517         return this;
    518     }
    519 
    520     public FragmentTransaction show(Fragment fragment) {
    521         Op op = new Op();
    522         op.cmd = OP_SHOW;
    523         op.fragment = fragment;
    524         addOp(op);
    525 
    526         return this;
    527     }
    528 
    529     public FragmentTransaction detach(Fragment fragment) {
    530         Op op = new Op();
    531         op.cmd = OP_DETACH;
    532         op.fragment = fragment;
    533         addOp(op);
    534 
    535         return this;
    536     }
    537 
    538     public FragmentTransaction attach(Fragment fragment) {
    539         Op op = new Op();
    540         op.cmd = OP_ATTACH;
    541         op.fragment = fragment;
    542         addOp(op);
    543 
    544         return this;
    545     }
    546 
    547     public FragmentTransaction setCustomAnimations(int enter, int exit) {
    548         return setCustomAnimations(enter, exit, 0, 0);
    549     }
    550 
    551     public FragmentTransaction setCustomAnimations(int enter, int exit,
    552             int popEnter, int popExit) {
    553         mEnterAnim = enter;
    554         mExitAnim = exit;
    555         mPopEnterAnim = popEnter;
    556         mPopExitAnim = popExit;
    557         return this;
    558     }
    559 
    560     public FragmentTransaction setTransition(int transition) {
    561         mTransition = transition;
    562         return this;
    563     }
    564 
    565     @Override
    566     public FragmentTransaction addSharedElement(View sharedElement, String name) {
    567         String transitionName = sharedElement.getTransitionName();
    568         if (transitionName == null) {
    569             throw new IllegalArgumentException("Unique transitionNames are required for all" +
    570                     " sharedElements");
    571         }
    572         if (mSharedElementSourceNames == null) {
    573             mSharedElementSourceNames = new ArrayList<String>();
    574             mSharedElementTargetNames = new ArrayList<String>();
    575         }
    576         mSharedElementSourceNames.add(transitionName);
    577         mSharedElementTargetNames.add(name);
    578         return this;
    579     }
    580 
    581     /** TODO: remove this */
    582     @Override
    583     public FragmentTransaction setSharedElement(View sharedElement, String name) {
    584         String transitionName = sharedElement.getTransitionName();
    585         if (transitionName == null) {
    586             throw new IllegalArgumentException("Unique transitionNames are required for all" +
    587                     " sharedElements");
    588         }
    589         mSharedElementSourceNames = new ArrayList<String>(1);
    590         mSharedElementSourceNames.add(transitionName);
    591 
    592         mSharedElementTargetNames = new ArrayList<String>(1);
    593         mSharedElementTargetNames.add(name);
    594         return this;
    595     }
    596 
    597     /** TODO: remove this */
    598     @Override
    599     public FragmentTransaction setSharedElements(Pair<View, String>... sharedElements) {
    600         if (sharedElements == null || sharedElements.length == 0) {
    601             mSharedElementSourceNames = null;
    602             mSharedElementTargetNames = null;
    603         } else {
    604             ArrayList<String> sourceNames = new ArrayList<String>(sharedElements.length);
    605             ArrayList<String> targetNames = new ArrayList<String>(sharedElements.length);
    606             for (int i = 0; i < sharedElements.length; i++) {
    607                 String transitionName = sharedElements[i].first.getTransitionName();
    608                 if (transitionName == null) {
    609                     throw new IllegalArgumentException("Unique transitionNames are required for all"
    610                             + " sharedElements");
    611                 }
    612                 sourceNames.add(transitionName);
    613                 targetNames.add(sharedElements[i].second);
    614             }
    615             mSharedElementSourceNames = sourceNames;
    616             mSharedElementTargetNames = targetNames;
    617         }
    618         return this;
    619     }
    620 
    621     public FragmentTransaction setTransitionStyle(int styleRes) {
    622         mTransitionStyle = styleRes;
    623         return this;
    624     }
    625 
    626     public FragmentTransaction addToBackStack(String name) {
    627         if (!mAllowAddToBackStack) {
    628             throw new IllegalStateException(
    629                     "This FragmentTransaction is not allowed to be added to the back stack.");
    630         }
    631         mAddToBackStack = true;
    632         mName = name;
    633         return this;
    634     }
    635 
    636     public boolean isAddToBackStackAllowed() {
    637         return mAllowAddToBackStack;
    638     }
    639 
    640     public FragmentTransaction disallowAddToBackStack() {
    641         if (mAddToBackStack) {
    642             throw new IllegalStateException(
    643                     "This transaction is already being added to the back stack");
    644         }
    645         mAllowAddToBackStack = false;
    646         return this;
    647     }
    648 
    649     public FragmentTransaction setBreadCrumbTitle(int res) {
    650         mBreadCrumbTitleRes = res;
    651         mBreadCrumbTitleText = null;
    652         return this;
    653     }
    654 
    655     public FragmentTransaction setBreadCrumbTitle(CharSequence text) {
    656         mBreadCrumbTitleRes = 0;
    657         mBreadCrumbTitleText = text;
    658         return this;
    659     }
    660 
    661     public FragmentTransaction setBreadCrumbShortTitle(int res) {
    662         mBreadCrumbShortTitleRes = res;
    663         mBreadCrumbShortTitleText = null;
    664         return this;
    665     }
    666 
    667     public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) {
    668         mBreadCrumbShortTitleRes = 0;
    669         mBreadCrumbShortTitleText = text;
    670         return this;
    671     }
    672 
    673     void bumpBackStackNesting(int amt) {
    674         if (!mAddToBackStack) {
    675             return;
    676         }
    677         if (FragmentManagerImpl.DEBUG) {
    678             Log.v(TAG, "Bump nesting in " + this
    679                     + " by " + amt);
    680         }
    681         Op op = mHead;
    682         while (op != null) {
    683             if (op.fragment != null) {
    684                 op.fragment.mBackStackNesting += amt;
    685                 if (FragmentManagerImpl.DEBUG) {
    686                     Log.v(TAG, "Bump nesting of "
    687                             + op.fragment + " to " + op.fragment.mBackStackNesting);
    688                 }
    689             }
    690             if (op.removed != null) {
    691                 for (int i = op.removed.size() - 1; i >= 0; i--) {
    692                     Fragment r = op.removed.get(i);
    693                     r.mBackStackNesting += amt;
    694                     if (FragmentManagerImpl.DEBUG) {
    695                         Log.v(TAG, "Bump nesting of "
    696                                 + r + " to " + r.mBackStackNesting);
    697                     }
    698                 }
    699             }
    700             op = op.next;
    701         }
    702     }
    703 
    704     public int commit() {
    705         return commitInternal(false);
    706     }
    707 
    708     public int commitAllowingStateLoss() {
    709         return commitInternal(true);
    710     }
    711 
    712     int commitInternal(boolean allowStateLoss) {
    713         if (mCommitted) {
    714             throw new IllegalStateException("commit already called");
    715         }
    716         if (FragmentManagerImpl.DEBUG) {
    717             Log.v(TAG, "Commit: " + this);
    718             LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
    719             PrintWriter pw = new FastPrintWriter(logw, false, 1024);
    720             dump("  ", null, pw, null);
    721             pw.flush();
    722         }
    723         mCommitted = true;
    724         if (mAddToBackStack) {
    725             mIndex = mManager.allocBackStackIndex(this);
    726         } else {
    727             mIndex = -1;
    728         }
    729         mManager.enqueueAction(this, allowStateLoss);
    730         return mIndex;
    731     }
    732 
    733     public void run() {
    734         if (FragmentManagerImpl.DEBUG) {
    735             Log.v(TAG, "Run: " + this);
    736         }
    737 
    738         if (mAddToBackStack) {
    739             if (mIndex < 0) {
    740                 throw new IllegalStateException("addToBackStack() called after commit()");
    741             }
    742         }
    743 
    744         bumpBackStackNesting(1);
    745 
    746         SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
    747         SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
    748         calculateFragments(firstOutFragments, lastInFragments);
    749         beginTransition(firstOutFragments, lastInFragments, false);
    750 
    751         Op op = mHead;
    752         while (op != null) {
    753             switch (op.cmd) {
    754                 case OP_ADD: {
    755                     Fragment f = op.fragment;
    756                     f.mNextAnim = op.enterAnim;
    757                     mManager.addFragment(f, false);
    758                 }
    759                 break;
    760                 case OP_REPLACE: {
    761                     Fragment f = op.fragment;
    762                     if (mManager.mAdded != null) {
    763                         for (int i = 0; i < mManager.mAdded.size(); i++) {
    764                             Fragment old = mManager.mAdded.get(i);
    765                             if (FragmentManagerImpl.DEBUG) {
    766                                 Log.v(TAG,
    767                                         "OP_REPLACE: adding=" + f + " old=" + old);
    768                             }
    769                             if (f == null || old.mContainerId == f.mContainerId) {
    770                                 if (old == f) {
    771                                     op.fragment = f = null;
    772                                 } else {
    773                                     if (op.removed == null) {
    774                                         op.removed = new ArrayList<Fragment>();
    775                                     }
    776                                     op.removed.add(old);
    777                                     old.mNextAnim = op.exitAnim;
    778                                     if (mAddToBackStack) {
    779                                         old.mBackStackNesting += 1;
    780                                         if (FragmentManagerImpl.DEBUG) {
    781                                             Log.v(TAG, "Bump nesting of "
    782                                                     + old + " to " + old.mBackStackNesting);
    783                                         }
    784                                     }
    785                                     mManager.removeFragment(old, mTransition, mTransitionStyle);
    786                                 }
    787                             }
    788                         }
    789                     }
    790                     if (f != null) {
    791                         f.mNextAnim = op.enterAnim;
    792                         mManager.addFragment(f, false);
    793                     }
    794                 }
    795                 break;
    796                 case OP_REMOVE: {
    797                     Fragment f = op.fragment;
    798                     f.mNextAnim = op.exitAnim;
    799                     mManager.removeFragment(f, mTransition, mTransitionStyle);
    800                 }
    801                 break;
    802                 case OP_HIDE: {
    803                     Fragment f = op.fragment;
    804                     f.mNextAnim = op.exitAnim;
    805                     mManager.hideFragment(f, mTransition, mTransitionStyle);
    806                 }
    807                 break;
    808                 case OP_SHOW: {
    809                     Fragment f = op.fragment;
    810                     f.mNextAnim = op.enterAnim;
    811                     mManager.showFragment(f, mTransition, mTransitionStyle);
    812                 }
    813                 break;
    814                 case OP_DETACH: {
    815                     Fragment f = op.fragment;
    816                     f.mNextAnim = op.exitAnim;
    817                     mManager.detachFragment(f, mTransition, mTransitionStyle);
    818                 }
    819                 break;
    820                 case OP_ATTACH: {
    821                     Fragment f = op.fragment;
    822                     f.mNextAnim = op.enterAnim;
    823                     mManager.attachFragment(f, mTransition, mTransitionStyle);
    824                 }
    825                 break;
    826                 default: {
    827                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
    828                 }
    829             }
    830 
    831             op = op.next;
    832         }
    833 
    834         mManager.moveToState(mManager.mCurState, mTransition,
    835                 mTransitionStyle, true);
    836 
    837         if (mAddToBackStack) {
    838             mManager.addBackStackState(this);
    839         }
    840     }
    841 
    842     private static void setFirstOut(SparseArray<Fragment> fragments, Fragment fragment) {
    843         if (fragment != null) {
    844             int containerId = fragment.mContainerId;
    845             if (containerId != 0 && !fragment.isHidden() && fragment.isAdded() &&
    846                     fragment.getView() != null && fragments.get(containerId) == null) {
    847                 fragments.put(containerId, fragment);
    848             }
    849         }
    850     }
    851 
    852     private void setLastIn(SparseArray<Fragment> fragments, Fragment fragment) {
    853         if (fragment != null) {
    854             int containerId = fragment.mContainerId;
    855             if (containerId != 0) {
    856                 fragments.put(containerId, fragment);
    857             }
    858         }
    859     }
    860 
    861     /**
    862      * Finds the first removed fragment and last added fragments when going forward.
    863      * If none of the fragments have transitions, then both lists will be empty.
    864      *
    865      * @param firstOutFragments The list of first fragments to be removed, keyed on the
    866      *                          container ID. This list will be modified by the method.
    867      * @param lastInFragments The list of last fragments to be added, keyed on the
    868      *                        container ID. This list will be modified by the method.
    869      */
    870     private void calculateFragments(SparseArray<Fragment> firstOutFragments,
    871             SparseArray<Fragment> lastInFragments) {
    872         if (!mManager.mContainer.hasView()) {
    873             return; // nothing to see, so no transitions
    874         }
    875         Op op = mHead;
    876         while (op != null) {
    877             switch (op.cmd) {
    878                 case OP_ADD:
    879                     setLastIn(lastInFragments, op.fragment);
    880                     break;
    881                 case OP_REPLACE: {
    882                     Fragment f = op.fragment;
    883                     if (mManager.mAdded != null) {
    884                         for (int i = 0; i < mManager.mAdded.size(); i++) {
    885                             Fragment old = mManager.mAdded.get(i);
    886                             if (f == null || old.mContainerId == f.mContainerId) {
    887                                 if (old == f) {
    888                                     f = null;
    889                                 } else {
    890                                     setFirstOut(firstOutFragments, old);
    891                                 }
    892                             }
    893                         }
    894                     }
    895                     setLastIn(lastInFragments, f);
    896                     break;
    897                 }
    898                 case OP_REMOVE:
    899                     setFirstOut(firstOutFragments, op.fragment);
    900                     break;
    901                 case OP_HIDE:
    902                     setFirstOut(firstOutFragments, op.fragment);
    903                     break;
    904                 case OP_SHOW:
    905                     setLastIn(lastInFragments, op.fragment);
    906                     break;
    907                 case OP_DETACH:
    908                     setFirstOut(firstOutFragments, op.fragment);
    909                     break;
    910                 case OP_ATTACH:
    911                     setLastIn(lastInFragments, op.fragment);
    912                     break;
    913             }
    914 
    915             op = op.next;
    916         }
    917     }
    918 
    919     /**
    920      * Finds the first removed fragment and last added fragments when popping the back stack.
    921      * If none of the fragments have transitions, then both lists will be empty.
    922      *
    923      * @param firstOutFragments The list of first fragments to be removed, keyed on the
    924      *                          container ID. This list will be modified by the method.
    925      * @param lastInFragments The list of last fragments to be added, keyed on the
    926      *                        container ID. This list will be modified by the method.
    927      */
    928     public void calculateBackFragments(SparseArray<Fragment> firstOutFragments,
    929             SparseArray<Fragment> lastInFragments) {
    930         if (!mManager.mContainer.hasView()) {
    931             return; // nothing to see, so no transitions
    932         }
    933         Op op = mHead;
    934         while (op != null) {
    935             switch (op.cmd) {
    936                 case OP_ADD:
    937                     setFirstOut(firstOutFragments, op.fragment);
    938                     break;
    939                 case OP_REPLACE:
    940                     if (op.removed != null) {
    941                         for (int i = op.removed.size() - 1; i >= 0; i--) {
    942                             setLastIn(lastInFragments, op.removed.get(i));
    943                         }
    944                     }
    945                     setFirstOut(firstOutFragments, op.fragment);
    946                     break;
    947                 case OP_REMOVE:
    948                     setLastIn(lastInFragments, op.fragment);
    949                     break;
    950                 case OP_HIDE:
    951                     setLastIn(lastInFragments, op.fragment);
    952                     break;
    953                 case OP_SHOW:
    954                     setFirstOut(firstOutFragments, op.fragment);
    955                     break;
    956                 case OP_DETACH:
    957                     setLastIn(lastInFragments, op.fragment);
    958                     break;
    959                 case OP_ATTACH:
    960                     setFirstOut(firstOutFragments, op.fragment);
    961                     break;
    962             }
    963 
    964             op = op.next;
    965         }
    966     }
    967 
    968     /**
    969      * When custom fragment transitions are used, this sets up the state for each transition
    970      * and begins the transition. A different transition is started for each fragment container
    971      * and consists of up to 3 different transitions: the exit transition, a shared element
    972      * transition and an enter transition.
    973      *
    974      * <p>The exit transition operates against the leaf nodes of the first fragment
    975      * with a view that was removed. If no such fragment was removed, then no exit
    976      * transition is executed. The exit transition comes from the outgoing fragment.</p>
    977      *
    978      * <p>The enter transition operates against the last fragment that was added. If
    979      * that fragment does not have a view or no fragment was added, then no enter
    980      * transition is executed. The enter transition comes from the incoming fragment.</p>
    981      *
    982      * <p>The shared element transition operates against all views and comes either
    983      * from the outgoing fragment or the incoming fragment, depending on whether this
    984      * is going forward or popping the back stack. When going forward, the incoming
    985      * fragment's enter shared element transition is used, but when going back, the
    986      * outgoing fragment's return shared element transition is used. Shared element
    987      * transitions only operate if there is both an incoming and outgoing fragment.</p>
    988      *
    989      * @param firstOutFragments The list of first fragments to be removed, keyed on the
    990      *                          container ID.
    991      * @param lastInFragments The list of last fragments to be added, keyed on the
    992      *                        container ID.
    993      * @param isBack true if this is popping the back stack or false if this is a
    994      *               forward operation.
    995      * @return The TransitionState used to complete the operation of the transition
    996      * in {@link #setNameOverrides(android.app.BackStackRecord.TransitionState, java.util.ArrayList,
    997      * java.util.ArrayList)}.
    998      */
    999     private TransitionState beginTransition(SparseArray<Fragment> firstOutFragments,
   1000             SparseArray<Fragment> lastInFragments, boolean isBack) {
   1001         TransitionState state = new TransitionState();
   1002 
   1003         // Adding a non-existent target view makes sure that the transitions don't target
   1004         // any views by default. They'll only target the views we tell add. If we don't
   1005         // add any, then no views will be targeted.
   1006         state.nonExistentView = new View(mManager.mActivity);
   1007 
   1008         // Go over all leaving fragments.
   1009         for (int i = 0; i < firstOutFragments.size(); i++) {
   1010             int containerId = firstOutFragments.keyAt(i);
   1011             configureTransitions(containerId, state, isBack, firstOutFragments,
   1012                     lastInFragments);
   1013         }
   1014 
   1015         // Now go over all entering fragments that didn't have a leaving fragment.
   1016         for (int i = 0; i < lastInFragments.size(); i++) {
   1017             int containerId = lastInFragments.keyAt(i);
   1018             if (firstOutFragments.get(containerId) == null) {
   1019                 configureTransitions(containerId, state, isBack, firstOutFragments,
   1020                         lastInFragments);
   1021             }
   1022         }
   1023         return state;
   1024     }
   1025 
   1026     private static Transition cloneTransition(Transition transition) {
   1027         if (transition != null) {
   1028             transition = transition.clone();
   1029         }
   1030         return transition;
   1031     }
   1032 
   1033     private static Transition getEnterTransition(Fragment inFragment, boolean isBack) {
   1034         if (inFragment == null) {
   1035             return null;
   1036         }
   1037         return cloneTransition(isBack ? inFragment.getReenterTransition() :
   1038                 inFragment.getEnterTransition());
   1039     }
   1040 
   1041     private static Transition getExitTransition(Fragment outFragment, boolean isBack) {
   1042         if (outFragment == null) {
   1043             return null;
   1044         }
   1045         return cloneTransition(isBack ? outFragment.getReturnTransition() :
   1046                 outFragment.getExitTransition());
   1047     }
   1048 
   1049     private static Transition getSharedElementTransition(Fragment inFragment, Fragment outFragment,
   1050             boolean isBack) {
   1051         if (inFragment == null || outFragment == null) {
   1052             return null;
   1053         }
   1054         return cloneTransition(isBack ? outFragment.getSharedElementReturnTransition() :
   1055                 inFragment.getSharedElementEnterTransition());
   1056     }
   1057 
   1058     private static ArrayList<View> captureExitingViews(Transition exitTransition,
   1059             Fragment outFragment, ArrayMap<String, View> namedViews, View nonExistentView) {
   1060         ArrayList<View> viewList = null;
   1061         if (exitTransition != null) {
   1062             viewList = new ArrayList<View>();
   1063             View root = outFragment.getView();
   1064             root.captureTransitioningViews(viewList);
   1065             if (namedViews != null) {
   1066                 viewList.removeAll(namedViews.values());
   1067             }
   1068             if (!viewList.isEmpty()) {
   1069                 viewList.add(nonExistentView);
   1070                 addTargets(exitTransition, viewList);
   1071             }
   1072         }
   1073         return viewList;
   1074     }
   1075 
   1076     private ArrayMap<String, View> remapSharedElements(TransitionState state, Fragment outFragment,
   1077             boolean isBack) {
   1078         ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
   1079         if (mSharedElementSourceNames != null) {
   1080             outFragment.getView().findNamedViews(namedViews);
   1081             if (isBack) {
   1082                 namedViews.retainAll(mSharedElementTargetNames);
   1083             } else {
   1084                 namedViews = remapNames(mSharedElementSourceNames, mSharedElementTargetNames,
   1085                         namedViews);
   1086             }
   1087         }
   1088 
   1089         if (isBack) {
   1090             outFragment.mEnterTransitionCallback.onMapSharedElements(
   1091                     mSharedElementTargetNames, namedViews);
   1092             setBackNameOverrides(state, namedViews, false);
   1093         } else {
   1094             outFragment.mExitTransitionCallback.onMapSharedElements(
   1095                     mSharedElementTargetNames, namedViews);
   1096             setNameOverrides(state, namedViews, false);
   1097         }
   1098 
   1099         return namedViews;
   1100     }
   1101 
   1102     /**
   1103      * Prepares the enter transition by adding a non-existent view to the transition's target list
   1104      * and setting it epicenter callback. By adding a non-existent view to the target list,
   1105      * we can prevent any view from being targeted at the beginning of the transition.
   1106      * We will add to the views before the end state of the transition is captured so that the
   1107      * views will appear. At the start of the transition, we clear the list of targets so that
   1108      * we can restore the state of the transition and use it again.
   1109      *
   1110      * <p>The shared element transition maps its shared elements immediately prior to
   1111      * capturing the final state of the Transition.</p>
   1112      */
   1113     private ArrayList<View> addTransitionTargets(final TransitionState state,
   1114             final Transition enterTransition, final Transition sharedElementTransition,
   1115             final Transition overallTransition, final View container,
   1116             final Fragment inFragment, final Fragment outFragment,
   1117             final ArrayList<View> hiddenFragmentViews, final boolean isBack,
   1118             final ArrayList<View> sharedElementTargets) {
   1119         if (enterTransition == null && sharedElementTransition == null &&
   1120                 overallTransition == null) {
   1121             return null;
   1122         }
   1123         final ArrayList<View> enteringViews = new ArrayList<View>();
   1124         container.getViewTreeObserver().addOnPreDrawListener(
   1125                 new ViewTreeObserver.OnPreDrawListener() {
   1126                     @Override
   1127                     public boolean onPreDraw() {
   1128                         container.getViewTreeObserver().removeOnPreDrawListener(this);
   1129 
   1130                         // Don't include any newly-hidden fragments in the transition.
   1131                         excludeHiddenFragments(hiddenFragmentViews, inFragment.mContainerId,
   1132                                 overallTransition);
   1133 
   1134                         ArrayMap<String, View> namedViews = null;
   1135                         if (sharedElementTransition != null) {
   1136                             namedViews = mapSharedElementsIn(state, isBack, inFragment);
   1137                             removeTargets(sharedElementTransition, sharedElementTargets);
   1138                             sharedElementTargets.clear();
   1139                             sharedElementTargets.add(state.nonExistentView);
   1140                             sharedElementTargets.addAll(namedViews.values());
   1141 
   1142                             addTargets(sharedElementTransition, sharedElementTargets);
   1143 
   1144                             setEpicenterIn(namedViews, state);
   1145 
   1146                             callSharedElementEnd(state, inFragment, outFragment, isBack,
   1147                                     namedViews);
   1148                         }
   1149 
   1150                         if (enterTransition != null) {
   1151                             View view = inFragment.getView();
   1152                             if (view != null) {
   1153                                 view.captureTransitioningViews(enteringViews);
   1154                                 if (namedViews != null) {
   1155                                     enteringViews.removeAll(namedViews.values());
   1156                                 }
   1157                                 enteringViews.add(state.nonExistentView);
   1158                                 // We added this earlier to prevent any views being targeted.
   1159                                 enterTransition.removeTarget(state.nonExistentView);
   1160                                 addTargets(enterTransition, enteringViews);
   1161                             }
   1162                             setSharedElementEpicenter(enterTransition, state);
   1163                         }
   1164                         return true;
   1165                     }
   1166                 });
   1167         return enteringViews;
   1168     }
   1169 
   1170     private void callSharedElementEnd(TransitionState state, Fragment inFragment,
   1171             Fragment outFragment, boolean isBack, ArrayMap<String, View> namedViews) {
   1172         SharedElementCallback sharedElementCallback = isBack ?
   1173                 outFragment.mEnterTransitionCallback :
   1174                 inFragment.mEnterTransitionCallback;
   1175         ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
   1176         ArrayList<View> views = new ArrayList<View>(namedViews.values());
   1177         sharedElementCallback.onSharedElementEnd(names, views, null);
   1178     }
   1179 
   1180     private void setEpicenterIn(ArrayMap<String, View> namedViews, TransitionState state) {
   1181         if (mSharedElementTargetNames != null && !namedViews.isEmpty()) {
   1182             // now we know the epicenter of the entering transition.
   1183             View epicenter = namedViews
   1184                     .get(mSharedElementTargetNames.get(0));
   1185             if (epicenter != null) {
   1186                 state.enteringEpicenterView = epicenter;
   1187             }
   1188         }
   1189     }
   1190 
   1191     private ArrayMap<String, View> mapSharedElementsIn(TransitionState state,
   1192             boolean isBack, Fragment inFragment) {
   1193         // Now map the shared elements in the incoming fragment
   1194         ArrayMap<String, View> namedViews = mapEnteringSharedElements(state, inFragment, isBack);
   1195 
   1196         // remap shared elements and set the name mapping used
   1197         // in the shared element transition.
   1198         if (isBack) {
   1199             inFragment.mExitTransitionCallback.onMapSharedElements(
   1200                     mSharedElementTargetNames, namedViews);
   1201             setBackNameOverrides(state, namedViews, true);
   1202         } else {
   1203             inFragment.mEnterTransitionCallback.onMapSharedElements(
   1204                     mSharedElementTargetNames, namedViews);
   1205             setNameOverrides(state, namedViews, true);
   1206         }
   1207         return namedViews;
   1208     }
   1209 
   1210     private static Transition mergeTransitions(Transition enterTransition,
   1211             Transition exitTransition, Transition sharedElementTransition, Fragment inFragment,
   1212             boolean isBack) {
   1213         boolean overlap = true;
   1214         if (enterTransition != null && exitTransition != null) {
   1215             overlap = isBack ? inFragment.getAllowReturnTransitionOverlap() :
   1216                     inFragment.getAllowEnterTransitionOverlap();
   1217         }
   1218 
   1219         // Wrap the transitions. Explicit targets like in enter and exit will cause the
   1220         // views to be targeted regardless of excluded views. If that happens, then the
   1221         // excluded fragments views (hidden fragments) will still be in the transition.
   1222 
   1223         Transition transition;
   1224         if (overlap) {
   1225             // Regular transition -- do it all together
   1226             TransitionSet transitionSet = new TransitionSet();
   1227             if (enterTransition != null) {
   1228                 transitionSet.addTransition(enterTransition);
   1229             }
   1230             if (exitTransition != null) {
   1231                 transitionSet.addTransition(exitTransition);
   1232             }
   1233             if (sharedElementTransition != null) {
   1234                 transitionSet.addTransition(sharedElementTransition);
   1235             }
   1236             transition = transitionSet;
   1237         } else {
   1238             // First do exit, then enter, but allow shared element transition to happen
   1239             // during both.
   1240             Transition staggered = null;
   1241             if (exitTransition != null && enterTransition != null) {
   1242                 staggered = new TransitionSet()
   1243                         .addTransition(exitTransition)
   1244                         .addTransition(enterTransition)
   1245                         .setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
   1246             } else if (exitTransition != null) {
   1247                 staggered = exitTransition;
   1248             } else if (enterTransition != null) {
   1249                 staggered = enterTransition;
   1250             }
   1251             if (sharedElementTransition != null) {
   1252                 TransitionSet together = new TransitionSet();
   1253                 if (staggered != null) {
   1254                     together.addTransition(staggered);
   1255                 }
   1256                 together.addTransition(sharedElementTransition);
   1257                 transition = together;
   1258             } else {
   1259                 transition = staggered;
   1260             }
   1261         }
   1262         return transition;
   1263     }
   1264 
   1265     /**
   1266      * Configures custom transitions for a specific fragment container.
   1267      *
   1268      * @param containerId The container ID of the fragments to configure the transition for.
   1269      * @param state The Transition State keeping track of the executing transitions.
   1270      * @param firstOutFragments The list of first fragments to be removed, keyed on the
   1271      *                          container ID.
   1272      * @param lastInFragments The list of last fragments to be added, keyed on the
   1273      *                        container ID.
   1274      * @param isBack true if this is popping the back stack or false if this is a
   1275      *               forward operation.
   1276      */
   1277     private void configureTransitions(int containerId, TransitionState state, boolean isBack,
   1278             SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
   1279         ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.findViewById(containerId);
   1280         if (sceneRoot != null) {
   1281             Fragment inFragment = lastInFragments.get(containerId);
   1282             Fragment outFragment = firstOutFragments.get(containerId);
   1283 
   1284             Transition enterTransition = getEnterTransition(inFragment, isBack);
   1285             Transition sharedElementTransition = getSharedElementTransition(inFragment, outFragment,
   1286                     isBack);
   1287             Transition exitTransition = getExitTransition(outFragment, isBack);
   1288 
   1289             if (enterTransition == null && sharedElementTransition == null &&
   1290                     exitTransition == null) {
   1291                 return; // no transitions!
   1292             }
   1293             if (enterTransition != null) {
   1294                 enterTransition.addTarget(state.nonExistentView);
   1295             }
   1296             ArrayMap<String, View> namedViews = null;
   1297             ArrayList<View> sharedElementTargets = new ArrayList<View>();
   1298             if (sharedElementTransition != null) {
   1299                 namedViews = remapSharedElements(state, outFragment, isBack);
   1300                 sharedElementTargets.add(state.nonExistentView);
   1301                 sharedElementTargets.addAll(namedViews.values());
   1302                 addTargets(sharedElementTransition, sharedElementTargets);
   1303 
   1304                 // Notify the start of the transition.
   1305                 SharedElementCallback callback = isBack ?
   1306                         outFragment.mEnterTransitionCallback :
   1307                         inFragment.mEnterTransitionCallback;
   1308                 ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
   1309                 ArrayList<View> views = new ArrayList<View>(namedViews.values());
   1310                 callback.onSharedElementStart(names, views, null);
   1311             }
   1312 
   1313             ArrayList<View> exitingViews = captureExitingViews(exitTransition, outFragment,
   1314                     namedViews, state.nonExistentView);
   1315             if (exitingViews == null || exitingViews.isEmpty()) {
   1316                 exitTransition = null;
   1317             }
   1318 
   1319             // Set the epicenter of the exit transition
   1320             if (mSharedElementTargetNames != null && namedViews != null) {
   1321                 View epicenterView = namedViews.get(mSharedElementTargetNames.get(0));
   1322                 if (epicenterView != null) {
   1323                     if (exitTransition != null) {
   1324                         setEpicenter(exitTransition, epicenterView);
   1325                     }
   1326                     if (sharedElementTransition != null) {
   1327                         setEpicenter(sharedElementTransition, epicenterView);
   1328                     }
   1329                 }
   1330             }
   1331 
   1332             Transition transition = mergeTransitions(enterTransition, exitTransition,
   1333                     sharedElementTransition, inFragment, isBack);
   1334 
   1335             if (transition != null) {
   1336                 ArrayList<View> hiddenFragments = new ArrayList<View>();
   1337                 ArrayList<View> enteringViews = addTransitionTargets(state, enterTransition,
   1338                         sharedElementTransition, transition, sceneRoot, inFragment, outFragment,
   1339                         hiddenFragments, isBack, sharedElementTargets);
   1340 
   1341                 transition.setNameOverrides(state.nameOverrides);
   1342                 // We want to exclude hidden views later, so we need a non-null list in the
   1343                 // transition now.
   1344                 transition.excludeTarget(state.nonExistentView, true);
   1345                 // Now exclude all currently hidden fragments.
   1346                 excludeHiddenFragments(hiddenFragments, containerId, transition);
   1347                 TransitionManager.beginDelayedTransition(sceneRoot, transition);
   1348                 // Remove the view targeting after the transition starts
   1349                 removeTargetedViewsFromTransitions(sceneRoot, state.nonExistentView,
   1350                         enterTransition, enteringViews, exitTransition, exitingViews,
   1351                         sharedElementTransition, sharedElementTargets, transition, hiddenFragments);
   1352             }
   1353         }
   1354     }
   1355 
   1356     /**
   1357      * After the transition has started, remove all targets that we added to the transitions
   1358      * so that the transitions are left in a clean state.
   1359      */
   1360     private void removeTargetedViewsFromTransitions(
   1361             final ViewGroup sceneRoot, final View nonExistingView,
   1362             final Transition enterTransition, final ArrayList<View> enteringViews,
   1363             final Transition exitTransition, final ArrayList<View> exitingViews,
   1364             final Transition sharedElementTransition, final ArrayList<View> sharedElementTargets,
   1365             final Transition overallTransition, final ArrayList<View> hiddenViews) {
   1366         if (overallTransition != null) {
   1367             sceneRoot.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
   1368                 @Override
   1369                 public boolean onPreDraw() {
   1370                     sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
   1371                     if (enterTransition != null) {
   1372                         enterTransition.removeTarget(nonExistingView);
   1373                         removeTargets(enterTransition, enteringViews);
   1374                     }
   1375                     if (exitTransition != null) {
   1376                         removeTargets(exitTransition, exitingViews);
   1377                     }
   1378                     if (sharedElementTransition != null) {
   1379                         removeTargets(sharedElementTransition, sharedElementTargets);
   1380                     }
   1381                     int numViews = hiddenViews.size();
   1382                     for (int i = 0; i < numViews; i++) {
   1383                         overallTransition.excludeTarget(hiddenViews.get(i), false);
   1384                     }
   1385                     overallTransition.excludeTarget(nonExistingView, false);
   1386                     return true;
   1387                 }
   1388             });
   1389         }
   1390     }
   1391 
   1392     /**
   1393      * This method removes the views from transitions that target ONLY those views.
   1394      * The views list should match those added in addTargets and should contain
   1395      * one view that is not in the view hierarchy (state.nonExistentView).
   1396      */
   1397     public static void removeTargets(Transition transition, ArrayList<View> views) {
   1398         if (transition instanceof TransitionSet) {
   1399             TransitionSet set = (TransitionSet) transition;
   1400             int numTransitions = set.getTransitionCount();
   1401             for (int i = 0; i < numTransitions; i++) {
   1402                 Transition child = set.getTransitionAt(i);
   1403                 removeTargets(child, views);
   1404             }
   1405         } else if (!hasSimpleTarget(transition)) {
   1406             List<View> targets = transition.getTargets();
   1407             if (targets != null && targets.size() == views.size() &&
   1408                     targets.containsAll(views)) {
   1409                 // We have an exact match. We must have added these earlier in addTargets
   1410                 for (int i = views.size() - 1; i >= 0; i--) {
   1411                     transition.removeTarget(views.get(i));
   1412                 }
   1413             }
   1414         }
   1415     }
   1416 
   1417     /**
   1418      * This method adds views as targets to the transition, but only if the transition
   1419      * doesn't already have a target. It is best for views to contain one View object
   1420      * that does not exist in the view hierarchy (state.nonExistentView) so that
   1421      * when they are removed later, a list match will suffice to remove the targets.
   1422      * Otherwise, if you happened to have targeted the exact views for the transition,
   1423      * the removeTargets call will remove them unexpectedly.
   1424      */
   1425     public static void addTargets(Transition transition, ArrayList<View> views) {
   1426         if (transition instanceof TransitionSet) {
   1427             TransitionSet set = (TransitionSet) transition;
   1428             int numTransitions = set.getTransitionCount();
   1429             for (int i = 0; i < numTransitions; i++) {
   1430                 Transition child = set.getTransitionAt(i);
   1431                 addTargets(child, views);
   1432             }
   1433         } else if (!hasSimpleTarget(transition)) {
   1434             List<View> targets = transition.getTargets();
   1435             if (isNullOrEmpty(targets)) {
   1436                 // We can just add the target views
   1437                 int numViews = views.size();
   1438                 for (int i = 0; i < numViews; i++) {
   1439                     transition.addTarget(views.get(i));
   1440                 }
   1441             }
   1442         }
   1443     }
   1444 
   1445     private static boolean hasSimpleTarget(Transition transition) {
   1446         return !isNullOrEmpty(transition.getTargetIds()) ||
   1447                 !isNullOrEmpty(transition.getTargetNames()) ||
   1448                 !isNullOrEmpty(transition.getTargetTypes());
   1449     }
   1450 
   1451     private static boolean isNullOrEmpty(List list) {
   1452         return list == null || list.isEmpty();
   1453     }
   1454 
   1455     /**
   1456      * Remaps a name-to-View map, substituting different names for keys.
   1457      *
   1458      * @param inMap A list of keys found in the map, in the order in toGoInMap
   1459      * @param toGoInMap A list of keys to use for the new map, in the order of inMap
   1460      * @param namedViews The current mapping
   1461      * @return a new Map after it has been mapped with the new names as keys.
   1462      */
   1463     private static ArrayMap<String, View> remapNames(ArrayList<String> inMap,
   1464             ArrayList<String> toGoInMap, ArrayMap<String, View> namedViews) {
   1465         ArrayMap<String, View> remappedViews = new ArrayMap<String, View>();
   1466         if (!namedViews.isEmpty()) {
   1467             int numKeys = inMap.size();
   1468             for (int i = 0; i < numKeys; i++) {
   1469                 View view = namedViews.get(inMap.get(i));
   1470 
   1471                 if (view != null) {
   1472                     remappedViews.put(toGoInMap.get(i), view);
   1473                 }
   1474             }
   1475         }
   1476         return remappedViews;
   1477     }
   1478 
   1479     /**
   1480      * Maps shared elements to views in the entering fragment.
   1481      *
   1482      * @param state The transition State as returned from {@link #beginTransition(
   1483      * android.util.SparseArray, android.util.SparseArray, boolean)}.
   1484      * @param inFragment The last fragment to be added.
   1485      * @param isBack true if this is popping the back stack or false if this is a
   1486      *               forward operation.
   1487      */
   1488     private ArrayMap<String, View> mapEnteringSharedElements(TransitionState state,
   1489             Fragment inFragment, boolean isBack) {
   1490         ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
   1491         View root = inFragment.getView();
   1492         if (root != null) {
   1493             if (mSharedElementSourceNames != null) {
   1494                 root.findNamedViews(namedViews);
   1495                 if (isBack) {
   1496                     namedViews = remapNames(mSharedElementSourceNames,
   1497                             mSharedElementTargetNames, namedViews);
   1498                 } else {
   1499                     namedViews.retainAll(mSharedElementTargetNames);
   1500                 }
   1501             }
   1502         }
   1503         return namedViews;
   1504     }
   1505 
   1506     private void excludeHiddenFragments(final ArrayList<View> hiddenFragmentViews, int containerId,
   1507             Transition transition) {
   1508         if (mManager.mAdded != null) {
   1509             for (int i = 0; i < mManager.mAdded.size(); i++) {
   1510                 Fragment fragment = mManager.mAdded.get(i);
   1511                 if (fragment.mView != null && fragment.mContainer != null &&
   1512                         fragment.mContainerId == containerId) {
   1513                     if (fragment.mHidden) {
   1514                         if (!hiddenFragmentViews.contains(fragment.mView)) {
   1515                             transition.excludeTarget(fragment.mView, true);
   1516                             hiddenFragmentViews.add(fragment.mView);
   1517                         }
   1518                     } else {
   1519                         transition.excludeTarget(fragment.mView, false);
   1520                         hiddenFragmentViews.remove(fragment.mView);
   1521                     }
   1522                 }
   1523             }
   1524         }
   1525     }
   1526 
   1527     private static void setEpicenter(Transition transition, View view) {
   1528         final Rect epicenter = new Rect();
   1529         view.getBoundsOnScreen(epicenter);
   1530 
   1531         transition.setEpicenterCallback(new Transition.EpicenterCallback() {
   1532             @Override
   1533             public Rect onGetEpicenter(Transition transition) {
   1534                 return epicenter;
   1535             }
   1536         });
   1537     }
   1538 
   1539     private void setSharedElementEpicenter(Transition transition, final TransitionState state) {
   1540         transition.setEpicenterCallback(new Transition.EpicenterCallback() {
   1541             private Rect mEpicenter;
   1542 
   1543             @Override
   1544             public Rect onGetEpicenter(Transition transition) {
   1545                 if (mEpicenter == null && state.enteringEpicenterView != null) {
   1546                     mEpicenter = new Rect();
   1547                     state.enteringEpicenterView.getBoundsOnScreen(mEpicenter);
   1548                 }
   1549                 return mEpicenter;
   1550             }
   1551         });
   1552     }
   1553 
   1554     public TransitionState popFromBackStack(boolean doStateMove, TransitionState state,
   1555             SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
   1556         if (FragmentManagerImpl.DEBUG) {
   1557             Log.v(TAG, "popFromBackStack: " + this);
   1558             LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
   1559             PrintWriter pw = new FastPrintWriter(logw, false, 1024);
   1560             dump("  ", null, pw, null);
   1561             pw.flush();
   1562         }
   1563 
   1564         if (state == null) {
   1565             if (firstOutFragments.size() != 0 || lastInFragments.size() != 0) {
   1566                 state = beginTransition(firstOutFragments, lastInFragments, true);
   1567             }
   1568         } else if (!doStateMove) {
   1569             setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames);
   1570         }
   1571 
   1572         bumpBackStackNesting(-1);
   1573 
   1574         Op op = mTail;
   1575         while (op != null) {
   1576             switch (op.cmd) {
   1577                 case OP_ADD: {
   1578                     Fragment f = op.fragment;
   1579                     f.mNextAnim = op.popExitAnim;
   1580                     mManager.removeFragment(f,
   1581                             FragmentManagerImpl.reverseTransit(mTransition),
   1582                             mTransitionStyle);
   1583                 }
   1584                 break;
   1585                 case OP_REPLACE: {
   1586                     Fragment f = op.fragment;
   1587                     if (f != null) {
   1588                         f.mNextAnim = op.popExitAnim;
   1589                         mManager.removeFragment(f,
   1590                                 FragmentManagerImpl.reverseTransit(mTransition),
   1591                                 mTransitionStyle);
   1592                     }
   1593                     if (op.removed != null) {
   1594                         for (int i = 0; i < op.removed.size(); i++) {
   1595                             Fragment old = op.removed.get(i);
   1596                             old.mNextAnim = op.popEnterAnim;
   1597                             mManager.addFragment(old, false);
   1598                         }
   1599                     }
   1600                 }
   1601                 break;
   1602                 case OP_REMOVE: {
   1603                     Fragment f = op.fragment;
   1604                     f.mNextAnim = op.popEnterAnim;
   1605                     mManager.addFragment(f, false);
   1606                 }
   1607                 break;
   1608                 case OP_HIDE: {
   1609                     Fragment f = op.fragment;
   1610                     f.mNextAnim = op.popEnterAnim;
   1611                     mManager.showFragment(f,
   1612                             FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
   1613                 }
   1614                 break;
   1615                 case OP_SHOW: {
   1616                     Fragment f = op.fragment;
   1617                     f.mNextAnim = op.popExitAnim;
   1618                     mManager.hideFragment(f,
   1619                             FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
   1620                 }
   1621                 break;
   1622                 case OP_DETACH: {
   1623                     Fragment f = op.fragment;
   1624                     f.mNextAnim = op.popEnterAnim;
   1625                     mManager.attachFragment(f,
   1626                             FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
   1627                 }
   1628                 break;
   1629                 case OP_ATTACH: {
   1630                     Fragment f = op.fragment;
   1631                     f.mNextAnim = op.popExitAnim;
   1632                     mManager.detachFragment(f,
   1633                             FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
   1634                 }
   1635                 break;
   1636                 default: {
   1637                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
   1638                 }
   1639             }
   1640 
   1641             op = op.prev;
   1642         }
   1643 
   1644         if (doStateMove) {
   1645             mManager.moveToState(mManager.mCurState,
   1646                     FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
   1647             state = null;
   1648         }
   1649 
   1650         if (mIndex >= 0) {
   1651             mManager.freeBackStackIndex(mIndex);
   1652             mIndex = -1;
   1653         }
   1654         return state;
   1655     }
   1656 
   1657     private static void setNameOverride(ArrayMap<String, String> overrides,
   1658             String source, String target) {
   1659         if (source != null && target != null && !source.equals(target)) {
   1660             for (int index = 0; index < overrides.size(); index++) {
   1661                 if (source.equals(overrides.valueAt(index))) {
   1662                     overrides.setValueAt(index, target);
   1663                     return;
   1664                 }
   1665             }
   1666             overrides.put(source, target);
   1667         }
   1668     }
   1669 
   1670     private static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames,
   1671             ArrayList<String> targetNames) {
   1672         if (sourceNames != null) {
   1673             for (int i = 0; i < sourceNames.size(); i++) {
   1674                 String source = sourceNames.get(i);
   1675                 String target = targetNames.get(i);
   1676                 setNameOverride(state.nameOverrides, source, target);
   1677             }
   1678         }
   1679     }
   1680 
   1681     private void setBackNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
   1682             boolean isEnd) {
   1683         int count = mSharedElementTargetNames.size();
   1684         for (int i = 0; i < count; i++) {
   1685             String source = mSharedElementSourceNames.get(i);
   1686             String originalTarget = mSharedElementTargetNames.get(i);
   1687             View view = namedViews.get(originalTarget);
   1688             if (view != null) {
   1689                 String target = view.getTransitionName();
   1690                 if (isEnd) {
   1691                     setNameOverride(state.nameOverrides, source, target);
   1692                 } else {
   1693                     setNameOverride(state.nameOverrides, target, source);
   1694                 }
   1695             }
   1696         }
   1697     }
   1698 
   1699     private void setNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
   1700             boolean isEnd) {
   1701         int count = namedViews.size();
   1702         for (int i = 0; i < count; i++) {
   1703             String source = namedViews.keyAt(i);
   1704             String target = namedViews.valueAt(i).getTransitionName();
   1705             if (isEnd) {
   1706                 setNameOverride(state.nameOverrides, source, target);
   1707             } else {
   1708                 setNameOverride(state.nameOverrides, target, source);
   1709             }
   1710         }
   1711     }
   1712 
   1713     public String getName() {
   1714         return mName;
   1715     }
   1716 
   1717     public int getTransition() {
   1718         return mTransition;
   1719     }
   1720 
   1721     public int getTransitionStyle() {
   1722         return mTransitionStyle;
   1723     }
   1724 
   1725     public boolean isEmpty() {
   1726         return mNumOp == 0;
   1727     }
   1728 
   1729     public class TransitionState {
   1730         public ArrayMap<String, String> nameOverrides = new ArrayMap<String, String>();
   1731         public View enteringEpicenterView;
   1732         public View nonExistentView;
   1733     }
   1734 }
   1735