Work on issue #7232641: ISE crash when rotating phone in label list mode
This doesn't fix the problem; I think it is an app problem.  It does
improve a bunch of the debugging to help better identify what is going
on, and introduces some checks when adding a fragment to fail
immediately if we are getting into a state when a fragment is going to
be in the added list multiple times (which is pretty much guaranteed
to lead to a failure at some point in the future).
Change-Id: If3a8700763facd61c4505c6ff872ae66875afc8d
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index 96814b7..1b1d341 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -20,6 +20,7 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.LogWriter;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -94,11 +95,12 @@
     public BackStackRecord instantiate(FragmentManagerImpl fm) {
         BackStackRecord bse = new BackStackRecord(fm);
         int pos = 0;
+        int num = 0;
         while (pos < mOps.length) {
             BackStackRecord.Op op = new BackStackRecord.Op();
             op.cmd = mOps[pos++];
             if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
-                    "BSE " + bse + " set base fragment #" + mOps[pos]);
+                    "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
             int findex = mOps[pos++];
             if (findex >= 0) {
                 Fragment f = fm.mActive.get(findex);
@@ -115,12 +117,13 @@
                 op.removed = new ArrayList<Fragment>(N);
                 for (int i=0; i<N; i++) {
                     if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
-                            "BSE " + bse + " set remove fragment #" + mOps[pos]);
+                            "Instantiate " + bse + " set remove fragment #" + mOps[pos]);
                     Fragment r = fm.mActive.get(mOps[pos++]);
                     op.removed.add(r);
                 }
             }
             bse.addOp(op);
+            num++;
         }
         bse.mTransition = mTransition;
         bse.mTransitionStyle = mTransitionStyle;
@@ -168,7 +171,7 @@
  */
 final class BackStackRecord extends FragmentTransaction implements
         FragmentManager.BackStackEntry, Runnable {
-    static final String TAG = "BackStackEntry";
+    static final String TAG = FragmentManagerImpl.TAG;
 
     final FragmentManagerImpl mManager;
 
@@ -206,46 +209,69 @@
     boolean mAllowAddToBackStack = true;
     String mName;
     boolean mCommitted;
-    int mIndex;
+    int mIndex = -1;
 
     int mBreadCrumbTitleRes;
     CharSequence mBreadCrumbTitleText;
     int mBreadCrumbShortTitleRes;
     CharSequence mBreadCrumbShortTitleText;
 
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("BackStackEntry{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        if (mIndex >= 0) {
+            sb.append(" #");
+            sb.append(mIndex);
+        }
+        if (mName != null) {
+            sb.append(" ");
+            sb.append(mName);
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+
     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
-        writer.print(prefix); writer.print("mName="); writer.print(mName);
-                writer.print(" mIndex="); writer.print(mIndex);
-                writer.print(" mCommitted="); writer.println(mCommitted);
-        if (mTransition != FragmentTransaction.TRANSIT_NONE) {
-            writer.print(prefix); writer.print("mTransition=#");
-                    writer.print(Integer.toHexString(mTransition));
-                    writer.print(" mTransitionStyle=#");
-                    writer.println(Integer.toHexString(mTransitionStyle));
-        }
-        if (mEnterAnim != 0 || mExitAnim !=0) {
-            writer.print(prefix); writer.print("mEnterAnim=#");
-                    writer.print(Integer.toHexString(mEnterAnim));
-                    writer.print(" mExitAnim=#");
-                    writer.println(Integer.toHexString(mExitAnim));
-        }
-        if (mPopEnterAnim != 0 || mPopExitAnim !=0) {
-            writer.print(prefix); writer.print("mPopEnterAnim=#");
-                    writer.print(Integer.toHexString(mPopEnterAnim));
-                    writer.print(" mPopExitAnim=#");
-                    writer.println(Integer.toHexString(mPopExitAnim));
-        }
-        if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
-            writer.print(prefix); writer.print("mBreadCrumbTitleRes=#");
-                    writer.print(Integer.toHexString(mBreadCrumbTitleRes));
-                    writer.print(" mBreadCrumbTitleText=");
-                    writer.println(mBreadCrumbTitleText);
-        }
-        if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
-            writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#");
-                    writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
-                    writer.print(" mBreadCrumbShortTitleText=");
-                    writer.println(mBreadCrumbShortTitleText);
+        dump(prefix, writer, true);
+    }
+
+    void dump(String prefix, PrintWriter writer, boolean full) {
+        if (full) {
+            writer.print(prefix); writer.print("mName="); writer.print(mName);
+                    writer.print(" mIndex="); writer.print(mIndex);
+                    writer.print(" mCommitted="); writer.println(mCommitted);
+            if (mTransition != FragmentTransaction.TRANSIT_NONE) {
+                writer.print(prefix); writer.print("mTransition=#");
+                        writer.print(Integer.toHexString(mTransition));
+                        writer.print(" mTransitionStyle=#");
+                        writer.println(Integer.toHexString(mTransitionStyle));
+            }
+            if (mEnterAnim != 0 || mExitAnim !=0) {
+                writer.print(prefix); writer.print("mEnterAnim=#");
+                        writer.print(Integer.toHexString(mEnterAnim));
+                        writer.print(" mExitAnim=#");
+                        writer.println(Integer.toHexString(mExitAnim));
+            }
+            if (mPopEnterAnim != 0 || mPopExitAnim !=0) {
+                writer.print(prefix); writer.print("mPopEnterAnim=#");
+                        writer.print(Integer.toHexString(mPopEnterAnim));
+                        writer.print(" mPopExitAnim=#");
+                        writer.println(Integer.toHexString(mPopExitAnim));
+            }
+            if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
+                writer.print(prefix); writer.print("mBreadCrumbTitleRes=#");
+                        writer.print(Integer.toHexString(mBreadCrumbTitleRes));
+                        writer.print(" mBreadCrumbTitleText=");
+                        writer.println(mBreadCrumbTitleText);
+            }
+            if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
+                writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#");
+                        writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
+                        writer.print(" mBreadCrumbShortTitleText=");
+                        writer.println(mBreadCrumbShortTitleText);
+            }
         }
 
         if (mHead != null) {
@@ -254,21 +280,34 @@
             Op op = mHead;
             int num = 0;
             while (op != null) {
-                writer.print(prefix); writer.print("  Op #"); writer.print(num);
-                        writer.println(":");
-                writer.print(innerPrefix); writer.print("cmd="); writer.print(op.cmd);
-                        writer.print(" fragment="); writer.println(op.fragment);
-                if (op.enterAnim != 0 || op.exitAnim != 0) {
-                    writer.print(prefix); writer.print("enterAnim=#");
-                            writer.print(Integer.toHexString(op.enterAnim));
-                            writer.print(" exitAnim=#");
-                            writer.println(Integer.toHexString(op.exitAnim));
+                String cmdStr;
+                switch (op.cmd) {
+                    case OP_NULL: cmdStr="NULL"; break;
+                    case OP_ADD: cmdStr="ADD"; break;
+                    case OP_REPLACE: cmdStr="REPLACE"; break;
+                    case OP_REMOVE: cmdStr="REMOVE"; break;
+                    case OP_HIDE: cmdStr="HIDE"; break;
+                    case OP_SHOW: cmdStr="SHOW"; break;
+                    case OP_DETACH: cmdStr="DETACH"; break;
+                    case OP_ATTACH: cmdStr="ATTACH"; break;
+                    default: cmdStr="cmd=" + op.cmd; break;
                 }
-                if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
-                    writer.print(prefix); writer.print("popEnterAnim=#");
-                            writer.print(Integer.toHexString(op.popEnterAnim));
-                            writer.print(" popExitAnim=#");
-                            writer.println(Integer.toHexString(op.popExitAnim));
+                writer.print(prefix); writer.print("  Op #"); writer.print(num);
+                        writer.print(": "); writer.print(cmdStr);
+                        writer.print(" "); writer.println(op.fragment);
+                if (full) {
+                    if (op.enterAnim != 0 || op.exitAnim != 0) {
+                        writer.print(innerPrefix); writer.print("enterAnim=#");
+                                writer.print(Integer.toHexString(op.enterAnim));
+                                writer.print(" exitAnim=#");
+                                writer.println(Integer.toHexString(op.exitAnim));
+                    }
+                    if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
+                        writer.print(innerPrefix); writer.print("popEnterAnim=#");
+                                writer.print(Integer.toHexString(op.popEnterAnim));
+                                writer.print(" popExitAnim=#");
+                                writer.println(Integer.toHexString(op.popExitAnim));
+                    }
                 }
                 if (op.removed != null && op.removed.size() > 0) {
                     for (int i=0; i<op.removed.size(); i++) {
@@ -276,14 +315,17 @@
                         if (op.removed.size() == 1) {
                             writer.print("Removed: ");
                         } else {
-                            writer.println("Removed:");
-                            writer.print(innerPrefix); writer.print("  #"); writer.print(num);
+                            if (i == 0) {
+                                writer.println("Removed:");
+                            }
+                            writer.print(innerPrefix); writer.print("  #"); writer.print(i);
                                     writer.print(": "); 
                         }
                         writer.println(op.removed.get(i));
                     }
                 }
                 op = op.next;
+                num++;
             }
         }
     }
@@ -538,7 +580,12 @@
     
     int commitInternal(boolean allowStateLoss) {
         if (mCommitted) throw new IllegalStateException("commit already called");
-        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Commit: " + this);
+        if (FragmentManagerImpl.DEBUG) {
+            Log.v(TAG, "Commit: " + this);
+            LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
+            PrintWriter pw = new PrintWriter(logw);
+            dump("  ", null, pw, null);
+        }
         mCommitted = true;
         if (mAddToBackStack) {
             mIndex = mManager.allocBackStackIndex(this);
@@ -641,7 +688,12 @@
     }
 
     public void popFromBackStack(boolean doStateMove) {
-        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "popFromBackStack: " + this);
+        if (FragmentManagerImpl.DEBUG) {
+            Log.v(TAG, "popFromBackStack: " + this);
+            LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
+            PrintWriter pw = new PrintWriter(logw);
+            dump("  ", null, pw, null);
+        }
 
         bumpBackStackNesting(-1);
 
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index aad5487..e983299 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -22,6 +22,7 @@
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.os.Bundle;
+import android.os.Debug;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Parcel;
@@ -771,9 +772,9 @@
 
     void moveToState(Fragment f, int newState, int transit, int transitionStyle,
             boolean keepActive) {
-        //if (DEBUG) Log.v(TAG, "moveToState: " + f
-        //    + " oldState=" + f.mState + " newState=" + newState
-        //    + " mRemoving=" + f.mRemoving + " Callers=" + Debug.getCallers(5));
+        if (DEBUG && false) Log.v(TAG, "moveToState: " + f
+            + " oldState=" + f.mState + " newState=" + newState
+            + " mRemoving=" + f.mRemoving + " Callers=" + Debug.getCallers(5));
 
         // Fragments that are not currently added will sit in the onCreate() state.
         if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {
@@ -1112,6 +1113,9 @@
         if (DEBUG) Log.v(TAG, "add: " + fragment);
         makeActive(fragment);
         if (!fragment.mDetached) {
+            if (mAdded.contains(fragment)) {
+                throw new IllegalStateException("Fragment already added: " + fragment);
+            }
             mAdded.add(fragment);
             fragment.mAdded = true;
             fragment.mRemoving = false;
@@ -1128,6 +1132,14 @@
         if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
         final boolean inactive = !fragment.isInBackStack();
         if (!fragment.mDetached || inactive) {
+            if (false) {
+                // Would be nice to catch a bad remove here, but we need
+                // time to test this to make sure we aren't crashes cases
+                // where it is not a problem.
+                if (!mAdded.contains(fragment)) {
+                    throw new IllegalStateException("Fragment not added: " + fragment);
+                }
+            }
             if (mAdded != null) {
                 mAdded.remove(fragment);
             }
@@ -1200,6 +1212,7 @@
             if (fragment.mAdded) {
                 // We are not already in back stack, so need to remove the fragment.
                 if (mAdded != null) {
+                    if (DEBUG) Log.v(TAG, "remove from detach: " + fragment);
                     mAdded.remove(fragment);
                 }
                 if (fragment.mHasMenu && fragment.mMenuVisible) {
@@ -1219,6 +1232,10 @@
                 if (mAdded == null) {
                     mAdded = new ArrayList<Fragment>();
                 }
+                if (mAdded.contains(fragment)) {
+                    throw new IllegalStateException("Fragment already added: " + fragment);
+                }
+                if (DEBUG) Log.v(TAG, "add from attach: " + fragment);
                 mAdded.add(fragment);
                 fragment.mAdded = true;
                 if (fragment.mHasMenu && fragment.mMenuVisible) {
@@ -1717,19 +1734,18 @@
             FragmentState fs = fms.mActive[i];
             if (fs != null) {
                 Fragment f = fs.instantiate(mActivity, mParent);
-                if (DEBUG) Log.v(TAG, "restoreAllState: adding #" + i + ": " + f);
+                if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
                 mActive.add(f);
                 // Now that the fragment is instantiated (or came from being
                 // retained above), clear mInstance in case we end up re-restoring
                 // from this FragmentState again.
                 fs.mInstance = null;
             } else {
-                if (DEBUG) Log.v(TAG, "restoreAllState: adding #" + i + ": (null)");
                 mActive.add(null);
                 if (mAvailIndices == null) {
                     mAvailIndices = new ArrayList<Integer>();
                 }
-                if (DEBUG) Log.v(TAG, "restoreAllState: adding avail #" + i);
+                if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);
                 mAvailIndices.add(i);
             }
         }
@@ -1760,7 +1776,10 @@
                             "No instantiated fragment for index #" + fms.mAdded[i]));
                 }
                 f.mAdded = true;
-                if (DEBUG) Log.v(TAG, "restoreAllState: making added #" + i + ": " + f);
+                if (DEBUG) Log.v(TAG, "restoreAllState: added #" + i + ": " + f);
+                if (mAdded.contains(f)) {
+                    throw new IllegalStateException("Already added!");
+                }
                 mAdded.add(f);
             }
         } else {
@@ -1772,8 +1791,13 @@
             mBackStack = new ArrayList<BackStackRecord>(fms.mBackStack.length);
             for (int i=0; i<fms.mBackStack.length; i++) {
                 BackStackRecord bse = fms.mBackStack[i].instantiate(this);
-                if (DEBUG) Log.v(TAG, "restoreAllState: adding bse #" + i
+                if (DEBUG) {
+                    Log.v(TAG, "restoreAllState: back stack #" + i
                         + " (index " + bse.mIndex + "): " + bse);
+                    LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
+                    PrintWriter pw = new PrintWriter(logw);
+                    bse.dump("  ", pw, false);
+                }
                 mBackStack.add(bse);
                 if (bse.mIndex >= 0) {
                     setBackStackIndex(bse.mIndex, bse);