Merge "If conversation list cursor is not loaded, defer mark unread until it's loaded." into jb-ub-mail-ur8
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index e736e28..1adc85d 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -59,10 +59,9 @@
 import com.android.mail.R;
 import com.android.mail.browse.ConfirmDialogFragment;
 import com.android.mail.browse.ConversationCursor;
-import com.android.mail.browse.ConversationItemView;
+import com.android.mail.browse.ConversationCursor.ConversationOperation;
 import com.android.mail.browse.ConversationItemViewModel;
 import com.android.mail.browse.ConversationPagerController;
-import com.android.mail.browse.ConversationCursor.ConversationOperation;
 import com.android.mail.browse.MessageCursor.ConversationMessage;
 import com.android.mail.browse.SelectedConversationsActionMenu;
 import com.android.mail.browse.SyncErrorDialogFragment;
@@ -206,6 +205,19 @@
         }
     };
 
+    /**
+     * Interface for actions that are deferred until after a load completes. This is for handling
+     * user actions which affect cursors (e.g. marking messages read or unread) that happen before
+     * that cursor is loaded.
+     */
+    private interface LoadFinishedCallback {
+        void onLoadFinished();
+    }
+
+    /** The deferred actions to execute when mConversationListCursor load completes. */
+    private final ArrayList<LoadFinishedCallback> mConversationListLoadFinishedCallbacks =
+            new ArrayList<LoadFinishedCallback>();
+
     private RefreshTimerTask mConversationListRefreshTask;
 
     /** Listeners that are interested in changes to the current account. */
@@ -1012,8 +1024,8 @@
     }
 
     @Override
-    public void markConversationMessagesUnread(Conversation conv, Set<Uri> unreadMessageUris,
-            byte[] originalConversationInfo) {
+    public void markConversationMessagesUnread(final Conversation conv,
+            final Set<Uri> unreadMessageUris, final byte[] originalConversationInfo) {
         // The only caller of this method is the conversation view, from where marking unread should
         // *always* take you back to list mode.
         showConversation(null);
@@ -1021,12 +1033,24 @@
         // locally mark conversation unread (the provider is supposed to propagate message unread
         // to conversation unread)
         conv.read = false;
-
         if (mConversationListCursor == null) {
-            LogUtils.e(LOG_TAG, "null ConversationCursor in markConversationMessagesUnread");
-            return;
-        }
+            LogUtils.d(LOG_TAG, "deferring markConversationMessagesUnread for id=%d", conv.id);
 
+            mConversationListLoadFinishedCallbacks.add(new LoadFinishedCallback() {
+                @Override
+                public void onLoadFinished() {
+                    doMarkConversationMessagesUnread(conv, unreadMessageUris,
+                            originalConversationInfo);
+                }
+            });
+        } else {
+            doMarkConversationMessagesUnread(conv, unreadMessageUris, originalConversationInfo);
+        }
+    }
+
+    private void doMarkConversationMessagesUnread(Conversation conv, Set<Uri> unreadMessageUris,
+            byte[] originalConversationInfo) {
+        LogUtils.d(LOG_TAG, "performing markConversationMessagesUnread for id=%d", conv.id);
         // only do a granular 'mark unread' if a subset of messages are unread
         final int unreadCount = (unreadMessageUris == null) ? 0 : unreadMessageUris.size();
         final int numMessages = conv.getNumMessages();
@@ -1068,14 +1092,25 @@
     }
 
     @Override
-    public void markConversationsRead(Collection<Conversation> targets, boolean read,
-            boolean viewed) {
-        // We want to show the next conversation if we are marking unread.
-        markConversationsRead(targets, read, viewed, true);
+    public void markConversationsRead(final Collection<Conversation> targets, final boolean read,
+            final boolean viewed) {
+        if (mConversationListCursor == null) {
+            LogUtils.d(LOG_TAG, "deferring markConversationsRead");
+            mConversationListLoadFinishedCallbacks.add(new LoadFinishedCallback() {
+                @Override
+                public void onLoadFinished() {
+                    markConversationsRead(targets, read, viewed, true);
+                }
+            });
+        } else {
+            // We want to show the next conversation if we are marking unread.
+            markConversationsRead(targets, read, viewed, true);
+        }
     }
 
     private void markConversationsRead(final Collection<Conversation> targets, final boolean read,
             final boolean markViewed, final boolean showNext) {
+        LogUtils.d(LOG_TAG, "performing markConversationsRead");
         // Auto-advance if requested and the current conversation is being marked unread
         if (showNext && !read) {
             final Runnable operation = new Runnable() {
@@ -1141,6 +1176,8 @@
      * <p>Does nothing if outside of conversation mode.</p>
      *
      * @param target the set of conversations being deleted/marked unread
+     * @param operation if auto-advance setting is unset, this operation is run after the user
+     *        is prompted to select a setting.
      * @return <code>false</code> if we aborted because the user has not yet specified a default
      *         action, <code>true</code> otherwise
      */
@@ -2765,6 +2802,11 @@
             mConversationListCursor.addListener(AbstractActivityController.this);
             mTracker.onCursorUpdated();
             mConversationListObservable.notifyChanged();
+            // Handle actions that were deferred until after the conversation list was loaded.
+            for (LoadFinishedCallback callback : mConversationListLoadFinishedCallbacks) {
+                callback.onLoadFinished();
+            }
+            mConversationListLoadFinishedCallbacks.clear();
 
             final ConversationListFragment convList = getConversationListFragment();
             if (isFragmentVisible(convList)) {
diff --git a/src/com/android/mail/ui/AbstractConversationViewFragment.java b/src/com/android/mail/ui/AbstractConversationViewFragment.java
index 0c2e355..fd51d2f 100644
--- a/src/com/android/mail/ui/AbstractConversationViewFragment.java
+++ b/src/com/android/mail/ui/AbstractConversationViewFragment.java
@@ -114,13 +114,6 @@
      */
     protected ConversationViewState mViewState;
 
-    /**
-     * Handles a deferred 'mark read' operation, necessary when the conversation view has finished
-     * loading before the conversation cursor. Normally null unless this situation occurs.
-     * When finally able to 'mark read', this observer will also be unregistered and cleaned up.
-     */
-    private MarkReadObserver mMarkReadObserver;
-
     private long mLoadingShownTime = -1;
 
     private final Runnable mDelayedShow = new FragmentRunnable("mDelayedShow") {
@@ -424,11 +417,6 @@
     public void onDestroyView() {
         super.onDestroyView();
         mAccountObserver.unregisterAndDestroy();
-        if (mMarkReadObserver != null) {
-            mActivity.getConversationUpdater().unregisterConversationListObserver(
-                    mMarkReadObserver);
-            mMarkReadObserver = null;
-        }
     }
 
     /**
@@ -558,16 +546,14 @@
         // but we do want future re-renders to mark read (e.g. "New message from X" case)
         MessageCursor cursor = getMessageCursor();
         if (!mConversation.isViewed() || (cursor != null && !cursor.isConversationRead())) {
-            final ConversationUpdater listController = activity.getConversationUpdater();
-            // The conversation cursor may not have finished loading by now (when launched via
-            // notification), so watch for when it finishes and mark it read then.
-            if (listController.getConversationListCursor() == null) {
-                LogUtils.i(LOG_TAG, "deferring conv mark read on open for id=%d",
-                        mConversation.id);
-                mMarkReadObserver = new MarkReadObserver(listController);
-                listController.registerConversationListObserver(mMarkReadObserver);
-            } else {
-                markReadOnSeen(listController);
+            // Mark the conversation viewed and read.
+            activity.getConversationUpdater().markConversationsRead(Arrays.asList(mConversation),
+                    true /* read */, true /* viewed */);
+
+            // and update the Message objects in the cursor so the next time a cursor update happens
+            // with these messages marked read, we know to ignore it
+            if (cursor != null) {
+                cursor.markMessagesRead();
             }
         }
 
@@ -576,19 +562,6 @@
         showAutoFitPrompt();
     }
 
-    protected void markReadOnSeen(ConversationUpdater listController) {
-        // Mark the conversation viewed and read.
-        listController.markConversationsRead(Arrays.asList(mConversation), true /* read */,
-                true /* viewed */);
-
-        // and update the Message objects in the cursor so the next time a cursor update happens
-        // with these messages marked read, we know to ignore it
-        MessageCursor cursor = getMessageCursor();
-        if (cursor != null) {
-            cursor.markMessagesRead();
-        }
-    }
-
     protected ConversationViewState getNewViewState() {
         return new ConversationViewState();
     }
@@ -751,27 +724,6 @@
         }
     }
 
-    private class MarkReadObserver extends DataSetObserver {
-        private final ConversationUpdater mListController;
-
-        private MarkReadObserver(ConversationUpdater listController) {
-            mListController = listController;
-        }
-
-        @Override
-        public void onChanged() {
-            if (mListController.getConversationListCursor() == null) {
-                // nothing yet, keep watching
-                return;
-            }
-            // done loading, safe to mark read now
-            mListController.unregisterConversationListObserver(this);
-            mMarkReadObserver = null;
-            LogUtils.i(LOG_TAG, "running deferred conv mark read on open, id=%d", mConversation.id);
-            markReadOnSeen(mListController);
-        }
-    }
-
     public abstract void onConversationUpdated(Conversation conversation);
 
     /**