Fix bug 4982804 / nested fragment transaction

Post MessageOrderManager callbacks instead of calling them directly
to break the loop.

Change-Id: I033121f7bdbadf6edd7a0fab87b960b737da820e
This commit is contained in:
Makoto Onuki 2011-06-30 16:59:42 -07:00
parent 8067daaa0f
commit 2ac164f609
2 changed files with 65 additions and 2 deletions

View File

@ -19,8 +19,10 @@ package com.android.email.activity;
import com.android.emailcommon.provider.EmailContent; import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Message; import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.provider.Mailbox; import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.utility.DelayedOperations;
import com.android.emailcommon.utility.EmailAsyncTask; import com.android.emailcommon.utility.EmailAsyncTask;
import com.android.emailcommon.utility.Utility; import com.android.emailcommon.utility.Utility;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import android.content.ContentResolver; import android.content.ContentResolver;
@ -56,6 +58,7 @@ public class MessageOrderManager {
private final long mMailboxId; private final long mMailboxId;
private final ContentObserver mObserver; private final ContentObserver mObserver;
private final Callback mCallback; private final Callback mCallback;
private final DelayedOperations mDelayedOperations;
private LoadMessageListTask mLoadMessageListTask; private LoadMessageListTask mLoadMessageListTask;
private Cursor mCursor; private Cursor mCursor;
@ -81,12 +84,56 @@ public class MessageOrderManager {
public void onMessageNotFound(); public void onMessageNotFound();
} }
/**
* Wrapper for {@link Callback}, which uses {@link DelayedOperations#post(Runnable)} to
* kick callbacks rather than calling them directly. This is used to avoid the "nested fragment
* transaction" exception. e.g. {@link #moveTo} is often called during a fragment transaction,
* and if the message no longer exists we call {@link #onMessageNotFound}, which most probably
* triggers another fragment transaction.
*/
private class PostingCallback implements Callback {
private final Callback mOriginal;
private PostingCallback(Callback original) {
mOriginal = original;
}
private final Runnable mOnMessagesChangedRunnable = new Runnable() {
@Override public void run() {
mOriginal.onMessagesChanged();
}
};
@Override
public void onMessagesChanged() {
mDelayedOperations.post(mOnMessagesChangedRunnable);
}
private final Runnable mOnMessageNotFoundRunnable = new Runnable() {
@Override public void run() {
mOriginal.onMessageNotFound();
}
};
@Override
public void onMessageNotFound() {
mDelayedOperations.post(mOnMessageNotFoundRunnable);
}
}
public MessageOrderManager(Context context, long mailboxId, Callback callback) { public MessageOrderManager(Context context, long mailboxId, Callback callback) {
this(context, mailboxId, callback, new DelayedOperations(Utility.getMainThreadHandler()));
}
@VisibleForTesting
MessageOrderManager(Context context, long mailboxId, Callback callback,
DelayedOperations delayedOperations) {
Preconditions.checkArgument(mailboxId != Mailbox.NO_MAILBOX); Preconditions.checkArgument(mailboxId != Mailbox.NO_MAILBOX);
mContext = context.getApplicationContext(); mContext = context.getApplicationContext();
mContentResolver = mContext.getContentResolver(); mContentResolver = mContext.getContentResolver();
mDelayedOperations = delayedOperations;
mMailboxId = mailboxId; mMailboxId = mailboxId;
mCallback = callback; mCallback = new PostingCallback(callback);
mObserver = new ContentObserver(getHandlerForContentObserver()) { mObserver = new ContentObserver(getHandlerForContentObserver()) {
@Override public void onChange(boolean selfChange) { @Override public void onChange(boolean selfChange) {
if (mClosed) { if (mClosed) {
@ -173,6 +220,7 @@ public class MessageOrderManager {
*/ */
public void close() { public void close() {
mClosed = true; mClosed = true;
mDelayedOperations.removeCallbacks();
cancelTask(); cancelTask();
closeCursor(); closeCursor();
} }

View File

@ -18,6 +18,7 @@ package com.android.email.activity;
import com.android.email.provider.EmailProvider; import com.android.email.provider.EmailProvider;
import com.android.emailcommon.provider.EmailContent; import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.utility.DelayedOperations;
import android.content.Context; import android.content.Context;
import android.database.AbstractCursor; import android.database.AbstractCursor;
@ -284,6 +285,20 @@ public class MessageOrderManagerTest extends ProviderTestCase2<EmailProvider> {
} }
} }
/**
* "Non" delayed operation -- runs the runnable immediately
*/
private static final class NonDelayedOperations extends DelayedOperations {
public NonDelayedOperations() {
super(new Handler());
}
@Override
public void post(Runnable r) {
r.run();
}
}
/** /**
* MessageOrderManager for test. Overrides {@link #startQuery} * MessageOrderManager for test. Overrides {@link #startQuery}
*/ */
@ -292,7 +307,7 @@ public class MessageOrderManagerTest extends ProviderTestCase2<EmailProvider> {
public boolean mStartQueryCalled; public boolean mStartQueryCalled;
public MessageOrderManagerForTest(Context context, long mailboxId, Callback callback) { public MessageOrderManagerForTest(Context context, long mailboxId, Callback callback) {
super(context, mailboxId, callback); super(context, mailboxId, callback, new NonDelayedOperations());
} }
@Override void startQuery() { @Override void startQuery() {