Fix issue #3191573: PreferenceFragment.onActivityResult cannot
launch a new fragment on the same call.

There were some problems with the API design where you could do
things in such a way that a back stack entry that was not at the
top would get popped.  Ouch.  Hopefully this change prevents that
from being able to happen.

Change-Id: I8cbc952e12ddd231ff6c84b6e9bbf5125f449f04
diff --git a/api/current.xml b/api/current.xml
index d2bd8e5..357daae 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -21911,47 +21911,6 @@
 <parameter name="exitAnim" type="int">
 </parameter>
 </method>
-<method name="popBackStack"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="deprecated"
- visibility="public"
->
-</method>
-<method name="popBackStack"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="deprecated"
- visibility="public"
->
-<parameter name="name" type="java.lang.String">
-</parameter>
-<parameter name="flags" type="int">
-</parameter>
-</method>
-<method name="popBackStack"
- return="boolean"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="deprecated"
- visibility="public"
->
-<parameter name="id" type="int">
-</parameter>
-<parameter name="flags" type="int">
-</parameter>
-</method>
 <method name="registerForContextMenu"
  return="void"
  abstract="false"
@@ -22637,17 +22596,6 @@
  visibility="protected"
 >
 </field>
-<field name="POP_BACK_STACK_INCLUSIVE"
- type="int"
- transient="false"
- volatile="false"
- value="1"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="RESULT_CANCELED"
  type="int"
  transient="false"
@@ -28360,6 +28308,17 @@
 <parameter name="args" type="java.lang.String[]">
 </parameter>
 </method>
+<method name="executePendingTransactions"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="findFragmentById"
  return="android.app.Fragment"
  abstract="true"
@@ -28426,7 +28385,7 @@
 >
 </method>
 <method name="popBackStack"
- return="boolean"
+ return="void"
  abstract="true"
  native="false"
  synchronized="false"
@@ -28437,7 +28396,7 @@
 >
 </method>
 <method name="popBackStack"
- return="boolean"
+ return="void"
  abstract="true"
  native="false"
  synchronized="false"
@@ -28452,6 +28411,47 @@
 </parameter>
 </method>
 <method name="popBackStack"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="int">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<method name="popBackStackImmediate"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="popBackStackImmediate"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<method name="popBackStackImmediate"
  return="boolean"
  abstract="true"
  native="false"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 07d21fb..5174f19 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2086,63 +2086,12 @@
     }
     
     /**
-     * Flag for {@link #popBackStack(String, int)}
-     * and {@link #popBackStack(int, int)}: If set, and the name or ID of
-     * a back stack entry has been supplied, then all matching entries will
-     * be consumed until one that doesn't match is found or the bottom of
-     * the stack is reached.  Otherwise, all entries up to but not including that entry
-     * will be removed.
-     */
-    public static final int POP_BACK_STACK_INCLUSIVE = 1<<0;
-
-    /**
-     * Pop the top state off the back stack.  Returns true if there was one
-     * to pop, else false.
-     * @deprecated use {@link #getFragmentManager}.
-     */
-    @Deprecated
-    public boolean popBackStack() {
-        return mFragments.popBackStack();
-    }
-
-    /**
-     * Pop the last fragment transition from the local activity's fragment
-     * back stack.  If there is nothing to pop, false is returned.
-     * @param name If non-null, this is the name of a previous back state
-     * to look for; if found, all states up to that state will be popped.  The
-     * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
-     * the named state itself is popped. If null, only the top state is popped.
-     * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
-     * @deprecated use {@link #getFragmentManager}.
-     */
-    @Deprecated
-    public boolean popBackStack(String name, int flags) {
-        return mFragments.popBackStack(name, flags);
-    }
-
-    /**
-     * Pop all back stack states up to the one with the given identifier.
-     * @param id Identifier of the stated to be popped. If no identifier exists,
-     * false is returned.
-     * The identifier is the number returned by
-     * {@link FragmentTransaction#commit() FragmentTransaction.commit()}.  The
-     * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
-     * the named state itself is popped.
-     * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
-     * @deprecated use {@link #getFragmentManager}.
-     */
-    @Deprecated
-    public boolean popBackStack(int id, int flags) {
-        return mFragments.popBackStack(id, flags);
-    }
-    
-    /**
      * Called when the activity has detected the user's press of the back
      * key.  The default implementation simply finishes the current activity,
      * but you can override this to do whatever you want.
      */
     public void onBackPressed() {
-        if (!mFragments.popBackStack()) {
+        if (!mFragments.popBackStackImmediate()) {
             finish();
         }
     }
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 9970418..d3a4f33 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -22,6 +22,7 @@
 import android.content.res.TypedArray;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
@@ -99,6 +100,20 @@
     public abstract FragmentTransaction openTransaction();
 
     /**
+     * After a {@link FragmentTransaction} is committed with
+     * {@link FragmentTransaction#commit FragmentTransaction.commit()}, it
+     * is scheduled to be executed asynchronously on the process's main thread.
+     * If you want to immediately executing any such pending operations, you
+     * can call this function (only from the main thread) to do so.  Note that
+     * all callbacks and other related behavior will be done from within this
+     * call, so be careful about where this is called from.
+     *
+     * @return Returns true if there were any pending transactions to be
+     * executed.
+     */
+    public abstract boolean executePendingTransactions();
+
+    /**
      * Finds a fragment that was identified by the given id either when inflated
      * from XML or as the container ID when added in a transaction.  This first
      * searches through fragments that are currently added to the manager's
@@ -132,7 +147,15 @@
      * Pop the top state off the back stack.  Returns true if there was one
      * to pop, else false.
      */
-    public abstract boolean popBackStack();
+    public abstract void popBackStack();
+
+    /**
+     * Like {@link #popBackStack()}, but performs the operation immediately
+     * inside of the call.  This is like calling {@link #executePendingTransactions()}
+     * afterwards.
+     * @return Returns true if there was something popped, else false.
+     */
+    public abstract boolean popBackStackImmediate();
 
     /**
      * Pop the last fragment transition from the manager's fragment
@@ -143,7 +166,15 @@
      * the named state itself is popped. If null, only the top state is popped.
      * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
      */
-    public abstract boolean popBackStack(String name, int flags);
+    public abstract void popBackStack(String name, int flags);
+
+    /**
+     * Like {@link #popBackStack(String, int)}, but performs the operation immediately
+     * inside of the call.  This is like calling {@link #executePendingTransactions()}
+     * afterwards.
+     * @return Returns true if there was something popped, else false.
+     */
+    public abstract boolean popBackStackImmediate(String name, int flags);
 
     /**
      * Pop all back stack states up to the one with the given identifier.
@@ -155,7 +186,15 @@
      * the named state itself is popped.
      * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
      */
-    public abstract boolean popBackStack(int id, int flags);
+    public abstract void popBackStack(int id, int flags);
+
+    /**
+     * Like {@link #popBackStack(int, int)}, but performs the operation immediately
+     * inside of the call.  This is like calling {@link #executePendingTransactions()}
+     * afterwards.
+     * @return Returns true if there was something popped, else false.
+     */
+    public abstract boolean popBackStackImmediate(int id, int flags);
 
     /**
      * Return the number of entries currently in the back stack.
@@ -300,17 +339,58 @@
     }
 
     @Override
-    public boolean popBackStack() {
+    public boolean executePendingTransactions() {
+        return execPendingActions();
+    }
+
+    @Override
+    public void popBackStack() {
+        enqueueAction(new Runnable() {
+            @Override public void run() {
+                popBackStackState(mActivity.mHandler, null, -1, 0);
+            }
+        }, false);
+    }
+
+    @Override
+    public boolean popBackStackImmediate() {
+        checkStateLoss();
+        executePendingTransactions();
         return popBackStackState(mActivity.mHandler, null, -1, 0);
     }
 
     @Override
-    public boolean popBackStack(String name, int flags) {
+    public void popBackStack(final String name, final int flags) {
+        enqueueAction(new Runnable() {
+            @Override public void run() {
+                popBackStackState(mActivity.mHandler, name, -1, flags);
+            }
+        }, false);
+    }
+
+    @Override
+    public boolean popBackStackImmediate(String name, int flags) {
+        checkStateLoss();
+        executePendingTransactions();
         return popBackStackState(mActivity.mHandler, name, -1, flags);
     }
 
     @Override
-    public boolean popBackStack(int id, int flags) {
+    public void popBackStack(final int id, final int flags) {
+        if (id < 0) {
+            throw new IllegalArgumentException("Bad id: " + id);
+        }
+        enqueueAction(new Runnable() {
+            @Override public void run() {
+                popBackStackState(mActivity.mHandler, null, id, flags);
+            }
+        }, false);
+    }
+
+    @Override
+    public boolean popBackStackImmediate(int id, int flags) {
+        checkStateLoss();
+        executePendingTransactions();
         if (id < 0) {
             throw new IllegalArgumentException("Bad id: " + id);
         }
@@ -849,16 +929,20 @@
         return null;
     }
     
+    private void checkStateLoss() {
+        if (mStateSaved) {
+            throw new IllegalStateException(
+                    "Can not perform this action after onSaveInstanceState");
+        }
+        if (mNoTransactionsBecause != null) {
+            throw new IllegalStateException(
+                    "Can not perform this action inside of " + mNoTransactionsBecause);
+        }
+    }
+
     public void enqueueAction(Runnable action, boolean allowStateLoss) {
         if (!allowStateLoss) {
-            if (mStateSaved) {
-                throw new IllegalStateException(
-                        "Can not perform this action after onSaveInstanceState");
-            }
-            if (mNoTransactionsBecause != null) {
-                throw new IllegalStateException(
-                        "Can not perform this action inside of " + mNoTransactionsBecause);
-            }
+            checkStateLoss();
         }
         synchronized (this) {
             if (mActivity == null) {
@@ -934,17 +1018,23 @@
     /**
      * Only call from main thread!
      */
-    public void execPendingActions() {
+    public boolean execPendingActions() {
         if (mExecutingActions) {
-            throw new IllegalStateException("Recursive entry to execPendingActions");
+            throw new IllegalStateException("Recursive entry to executePendingTransactions");
         }
         
+        if (Looper.myLooper() != Looper.getMainLooper()) {
+            throw new IllegalStateException("Must be called from main thread of process");
+        }
+
+        boolean didSomething = false;
+
         while (true) {
             int numActions;
             
             synchronized (this) {
                 if (mPendingActions == null || mPendingActions.size() == 0) {
-                    return;
+                    return didSomething;
                 }
                 
                 numActions = mPendingActions.size();
@@ -961,6 +1051,7 @@
                 mTmpActions[i].run();
             }
             mExecutingActions = false;
+            didSomething = true;
         }
     }
     
@@ -984,19 +1075,14 @@
         if (mBackStack == null) {
             return false;
         }
-        if (name == null && id < 0 && (flags&Activity.POP_BACK_STACK_INCLUSIVE) == 0) {
+        if (name == null && id < 0 && (flags&POP_BACK_STACK_INCLUSIVE) == 0) {
             int last = mBackStack.size()-1;
             if (last < 0) {
                 return false;
             }
             final BackStackRecord bss = mBackStack.remove(last);
-            enqueueAction(new Runnable() {
-                public void run() {
-                    if (DEBUG) Log.v(TAG, "Popping back stack state: " + bss);
-                    bss.popFromBackStack(true);
-                    reportBackStackChanged();
-                }
-            }, false);
+            bss.popFromBackStack(true);
+            reportBackStackChanged();
         } else {
             int index = -1;
             if (name != null || id >= 0) {
@@ -1016,7 +1102,7 @@
                 if (index < 0) {
                     return false;
                 }
-                if ((flags&Activity.POP_BACK_STACK_INCLUSIVE) != 0) {
+                if ((flags&POP_BACK_STACK_INCLUSIVE) != 0) {
                     index--;
                     // Consume all following entries that match.
                     while (index >= 0) {
@@ -1038,16 +1124,12 @@
             for (int i=mBackStack.size()-1; i>index; i--) {
                 states.add(mBackStack.remove(i));
             }
-            enqueueAction(new Runnable() {
-                public void run() {
-                    final int LAST = states.size()-1;
-                    for (int i=0; i<=LAST; i++) {
-                        if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
-                        states.get(i).popFromBackStack(i == LAST);
-                    }
-                    reportBackStackChanged();
-                }
-            }, false);
+            final int LAST = states.size()-1;
+            for (int i=0; i<=LAST; i++) {
+                if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
+                states.get(i).popFromBackStack(i == LAST);
+            }
+            reportBackStackChanged();
         }
         return true;
     }
@@ -1084,6 +1166,10 @@
     }
     
     Parcelable saveAllState() {
+        // Make sure all pending operations have now been executed to get
+        // our state update-to-date.
+        execPendingActions();
+
         mStateSaved = true;
 
         if (mActive == null || mActive.size() <= 0) {
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 0ce69ad..39f3cee 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -23,6 +23,7 @@
 
 import android.app.Fragment;
 import android.app.FragmentBreadCrumbs;
+import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.app.ListActivity;
 import android.content.Context;
@@ -902,7 +903,8 @@
     }
 
     private void switchToHeaderInner(String fragmentName, Bundle args, int direction) {
-        getFragmentManager().popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE);
+        getFragmentManager().popBackStack(BACK_STACK_PREFS,
+                FragmentManager.POP_BACK_STACK_INCLUSIVE);
         Fragment f = Fragment.instantiate(this, fragmentName, args);
         FragmentTransaction transaction = getFragmentManager().openTransaction();
         transaction.setTransition(direction == 0 ? FragmentTransaction.TRANSIT_NONE
@@ -934,7 +936,8 @@
         if (mCurHeader == header) {
             // This is the header we are currently displaying.  Just make sure
             // to pop the stack up to its root state.
-            getFragmentManager().popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE);
+            getFragmentManager().popBackStack(BACK_STACK_PREFS,
+                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
         } else {
             int direction = mHeaders.indexOf(header) - mHeaders.indexOf(mCurHeader);
             switchToHeaderInner(header.fragment, header.fragmentArguments, direction);
@@ -1061,14 +1064,14 @@
             setResult(resultCode, resultData);
             finish();
         } else {
+            // XXX be smarter about popping the stack.
+            onBackPressed();
             if (caller != null) {
                 if (caller.getTargetFragment() != null) {
                     caller.getTargetFragment().onActivityResult(caller.getTargetRequestCode(),
                             resultCode, resultData);
                 }
             }
-            // XXX be smarter about popping the stack.
-            onBackPressed();
         }
     }