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();
}
}