UIControllerTwoPane.java revision 2a292e235cbe7ce33367ba549d4da6cb4764ffe9
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email.activity;
18
19import com.android.email.Clock;
20import com.android.email.Email;
21import com.android.email.Preferences;
22import com.android.email.R;
23import com.android.email.RefreshManager;
24import com.android.email.activity.MailboxFinder.Callback;
25import com.android.email.activity.setup.AccountSecurity;
26import com.android.emailcommon.Logging;
27import com.android.emailcommon.provider.Account;
28import com.android.emailcommon.provider.EmailContent.Message;
29import com.android.emailcommon.provider.Mailbox;
30import com.android.emailcommon.utility.EmailAsyncTask;
31import com.google.common.annotations.VisibleForTesting;
32
33import android.app.Activity;
34import android.app.FragmentTransaction;
35import android.content.Context;
36import android.os.Bundle;
37import android.util.Log;
38
39import java.util.Set;
40
41/**
42 * UI Controller for x-large devices.  Supports a multi-pane layout.
43 *
44 * Note: Always use {@link #commitFragmentTransaction} to operate fragment transactions,
45 * so that we can easily switch between synchronous and asynchronous transactions.
46 */
47class UIControllerTwoPane extends UIControllerBase implements
48        MailboxFinder.Callback,
49        ThreePaneLayout.Callback,
50        MailboxListFragment.Callback,
51        MessageListFragment.Callback,
52        MessageViewFragment.Callback {
53    @VisibleForTesting
54    static final int MAILBOX_REFRESH_MIN_INTERVAL = 30 * 1000; // in milliseconds
55
56    @VisibleForTesting
57    static final int INBOX_AUTO_REFRESH_MIN_INTERVAL = 10 * 1000; // in milliseconds
58
59    // Other UI elements
60    private ThreePaneLayout mThreePane;
61
62    private MessageCommandButtonView mMessageCommandButtons;
63
64    private MessageOrderManager mOrderManager;
65    private final MessageOrderManagerCallback mMessageOrderManagerCallback =
66        new MessageOrderManagerCallback();
67
68    /**
69     * The mailbox name selected on the mailbox list.
70     * Passed via {@link #onCurrentMailboxUpdated}.
71     */
72    private String mCurrentMailboxName;
73
74    /**
75     * The unread count for the mailbox selected on the mailbox list.
76     * Passed via {@link #onCurrentMailboxUpdated}.
77     *
78     * 0 if the mailbox doesn't have the concept of "unread".  e.g. Drafts.
79     */
80    private int mCurrentMailboxUnreadCount;
81
82    public UIControllerTwoPane(EmailActivity activity) {
83        super(activity);
84    }
85
86    @Override
87    public int getLayoutId() {
88        return R.layout.email_activity_two_pane;
89    }
90
91    // MailboxFinder$Callback
92    @Override
93    public void onAccountNotFound() {
94        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
95            Log.d(Logging.LOG_TAG, this + " onAccountNotFound()");
96        }
97        // Shouldn't happen
98    }
99
100    // MailboxFinder$Callback
101    @Override
102    public void onAccountSecurityHold(long accountId) {
103        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
104            Log.d(Logging.LOG_TAG, this + " onAccountSecurityHold()");
105        }
106        mActivity.startActivity(AccountSecurity.actionUpdateSecurityIntent(mActivity, accountId,
107                true));
108    }
109
110    // MailboxFinder$Callback
111    @Override
112    public void onMailboxFound(long accountId, long mailboxId) {
113        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
114            Log.d(Logging.LOG_TAG, this + " onMailboxFound()");
115        }
116        updateMessageList(accountId, mailboxId, true);
117    }
118
119    // MailboxFinder$Callback
120    @Override
121    public void onMailboxNotFound(long accountId) {
122        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
123            Log.d(Logging.LOG_TAG, this + " onMailboxNotFound()");
124        }
125        Log.e(Logging.LOG_TAG, "unable to find mailbox for account " + accountId);
126    }
127
128    // ThreePaneLayoutCallback
129    @Override
130    public void onVisiblePanesChanged(int previousVisiblePanes) {
131        refreshActionBar();
132
133        // If the right pane is gone, remove the message view.
134        final int visiblePanes = mThreePane.getVisiblePanes();
135
136        if (((visiblePanes & ThreePaneLayout.PANE_RIGHT) == 0) &&
137                ((previousVisiblePanes & ThreePaneLayout.PANE_RIGHT) != 0)) {
138            // Message view just got hidden
139            unselectMessage();
140        }
141        // Disable CAB when the message list is not visible.
142        if (isMessageListInstalled()) {
143            getMessageListFragment().onHidden((visiblePanes & ThreePaneLayout.PANE_MIDDLE) == 0);
144        }
145    }
146
147    // MailboxListFragment$Callback
148    @Override
149    public void onMailboxSelected(long accountId, long mailboxId, boolean nestedNavigation) {
150        if ((accountId == Account.NO_ACCOUNT) || (mailboxId == Mailbox.NO_MAILBOX)) {
151            throw new IllegalArgumentException();
152        }
153        if (getMessageListMailboxId() != mailboxId) {
154            updateMessageList(accountId, mailboxId, true);
155        }
156    }
157
158    // MailboxListFragment$Callback
159    @Override
160    public void onAccountSelected(long accountId) {
161        switchAccount(accountId);
162    }
163
164    // MailboxListFragment$Callback
165    @Override
166    public void onCurrentMailboxUpdated(long mailboxId, String mailboxName, int unreadCount) {
167        mCurrentMailboxName = mailboxName;
168        mCurrentMailboxUnreadCount = unreadCount;
169        refreshActionBar();
170    }
171
172    // MailboxListFragment$Callback
173    @Override
174    public void onParentMailboxChanged() {
175        refreshActionBar();
176    }
177
178    // MessageListFragment$Callback
179    @Override
180    public void onMessageOpen(long messageId, long messageMailboxId, long listMailboxId,
181            int type) {
182        if (type == MessageListFragment.Callback.TYPE_DRAFT) {
183            MessageCompose.actionEditDraft(mActivity, messageId);
184        } else {
185            if (getMessageId() != messageId) {
186                updateMessageView(messageId);
187                mThreePane.showRightPane();
188            }
189        }
190    }
191
192    // MessageListFragment$Callback
193    @Override
194    public void onMailboxNotFound() {
195        Log.e(Logging.LOG_TAG, "unable to find mailbox");
196    }
197
198    // MessageListFragment$Callback
199    @Override
200    public void onEnterSelectionMode(boolean enter) {
201    }
202
203    // MessageListFragment$Callback
204    /**
205     * Apply the auto-advance policy upon initation of a batch command that could potentially
206     * affect the currently selected conversation.
207     */
208    @Override
209    public void onAdvancingOpAccepted(Set<Long> affectedMessages) {
210        if (!isMessageViewInstalled()) {
211            // Do nothing if message view is not visible.
212            return;
213        }
214
215        int autoAdvanceDir = Preferences.getPreferences(mActivity).getAutoAdvanceDirection();
216        if ((autoAdvanceDir == Preferences.AUTO_ADVANCE_MESSAGE_LIST) || (mOrderManager == null)) {
217            if (affectedMessages.contains(getMessageId())) {
218                goBackToMailbox();
219            }
220            return;
221        }
222
223        // Navigate to the first unselected item in the appropriate direction.
224        switch (autoAdvanceDir) {
225            case Preferences.AUTO_ADVANCE_NEWER:
226                while (affectedMessages.contains(mOrderManager.getCurrentMessageId())) {
227                    if (!mOrderManager.moveToNewer()) {
228                        goBackToMailbox();
229                        return;
230                    }
231                }
232                updateMessageView(mOrderManager.getCurrentMessageId());
233                break;
234
235            case Preferences.AUTO_ADVANCE_OLDER:
236                while (affectedMessages.contains(mOrderManager.getCurrentMessageId())) {
237                    if (!mOrderManager.moveToOlder()) {
238                        goBackToMailbox();
239                        return;
240                    }
241                }
242                updateMessageView(mOrderManager.getCurrentMessageId());
243                break;
244        }
245    }
246
247    // MessageListFragment$Callback
248    @Override
249    public void onListLoaded() {
250    }
251
252    // MessageListFragment$Callback
253    @Override
254    public boolean onDragStarted() {
255        Log.w(Logging.LOG_TAG, "Drag started");
256
257        if ((mThreePane.getVisiblePanes() & ThreePaneLayout.PANE_LEFT) == 0) {
258            // Mailbox list hidden.  D&D not allowed.
259            return false;
260        }
261
262        // STOPSHIP Save the current mailbox list
263
264        return true;
265    }
266
267    // MessageListFragment$Callback
268    @Override
269    public void onDragEnded() {
270        Log.w(Logging.LOG_TAG, "Drag ended");
271
272        // STOPSHIP Restore the saved mailbox list
273    }
274
275    // MessageViewFragment$Callback
276    @Override
277    public void onMessageShown() {
278        updateMessageOrderManager();
279        updateNavigationArrows();
280    }
281
282    // MessageViewFragment$Callback
283    @Override
284    public boolean onUrlInMessageClicked(String url) {
285        return ActivityHelper.openUrlInMessage(mActivity, url, getActualAccountId());
286    }
287
288    // MessageViewFragment$Callback
289    @Override
290    public void onMessageSetUnread() {
291        goBackToMailbox();
292    }
293
294    // MessageViewFragment$Callback
295    @Override
296    public void onMessageNotExists() {
297        goBackToMailbox();
298    }
299
300    // MessageViewFragment$Callback
301    @Override
302    public void onLoadMessageStarted() {
303    }
304
305    // MessageViewFragment$Callback
306    @Override
307    public void onLoadMessageFinished() {
308    }
309
310    // MessageViewFragment$Callback
311    @Override
312    public void onLoadMessageError(String errorMessage) {
313    }
314
315    // MessageViewFragment$Callback
316    @Override
317    public void onRespondedToInvite(int response) {
318        onCurrentMessageGone();
319    }
320
321    // MessageViewFragment$Callback
322    @Override
323    public void onCalendarLinkClicked(long epochEventStartTime) {
324        ActivityHelper.openCalendar(mActivity, epochEventStartTime);
325    }
326
327    // MessageViewFragment$Callback
328    @Override
329    public void onBeforeMessageGone() {
330        onCurrentMessageGone();
331    }
332
333    // MessageViewFragment$Callback
334    @Override
335    public void onForward() {
336        MessageCompose.actionForward(mActivity, getMessageId());
337    }
338
339    // MessageViewFragment$Callback
340    @Override
341    public void onReply() {
342        MessageCompose.actionReply(mActivity, getMessageId(), false);
343    }
344
345    // MessageViewFragment$Callback
346    @Override
347    public void onReplyAll() {
348        MessageCompose.actionReply(mActivity, getMessageId(), true);
349    }
350
351    /**
352     * Must be called just after the activity sets up the content view.
353     */
354    @Override
355    public void onActivityViewReady() {
356        super.onActivityViewReady();
357
358        // Set up content
359        mThreePane = (ThreePaneLayout) mActivity.findViewById(R.id.three_pane);
360        mThreePane.setCallback(this);
361
362        mMessageCommandButtons = mThreePane.getMessageCommandButtons();
363        mMessageCommandButtons.setCallback(new CommandButtonCallback());
364    }
365
366    @Override
367    protected ActionBarController createActionBarController(Activity activity) {
368        return new ActionBarController(activity, activity.getLoaderManager(),
369                activity.getActionBar(), new ActionBarControllerCallback());
370    }
371
372    /**
373     * @return the currently selected account ID, *or* {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
374     *
375     * @see #getActualAccountId()
376     */
377    @Override
378    public long getUIAccountId() {
379        return isMailboxListInstalled() ? getMailboxListFragment().getAccountId()
380                :Account.NO_ACCOUNT;
381    }
382
383    /**
384     * Returns the id of the parent mailbox used for the mailbox list fragment.
385     *
386     * IMPORTANT: Do not confuse {@link #getMailboxListMailboxId()} with
387     *     {@link #getMessageListMailboxId()}
388     */
389    private long getMailboxListMailboxId() {
390        return isMailboxListInstalled() ? getMailboxListFragment().getSelectedMailboxId()
391                : Mailbox.NO_MAILBOX;
392    }
393
394    /**
395     * Returns the id of the mailbox used for the message list fragment.
396     *
397     * IMPORTANT: Do not confuse {@link #getMailboxListMailboxId()} with
398     *     {@link #getMessageListMailboxId()}
399     */
400    private long getMessageListMailboxId() {
401        return isMessageListInstalled() ? getMessageListFragment().getMailboxId()
402                : Message.NO_MESSAGE;
403    }
404
405    /*
406     * STOPSHIP Remove this -- see the base class method.
407     */
408    @Override
409    public long getMailboxSettingsMailboxId() {
410        return getMessageListMailboxId();
411    }
412
413    /*
414     * STOPSHIP Remove this -- see the base class method.
415     */
416    @Override
417    public long getSearchMailboxId() {
418        return getMessageListMailboxId();
419    }
420
421    private long getMessageId() {
422        return isMessageViewInstalled() ? getMessageViewFragment().getMessageId()
423                : Message.NO_MESSAGE;
424    }
425
426    /**
427     * @return true if refresh is in progress for the current mailbox.
428     */
429    @Override
430    protected boolean isRefreshInProgress() {
431        long messageListMailboxId = getMessageListMailboxId();
432        return (messageListMailboxId >= 0)
433                && mRefreshManager.isMessageListRefreshing(messageListMailboxId);
434    }
435
436    /**
437     * @return true if the UI should enable the "refresh" command.
438     */
439    @Override
440    protected boolean isRefreshEnabled() {
441        // - Don't show for combined inboxes, but
442        // - Show even for non-refreshable mailboxes, in which case we refresh the mailbox list
443        return getActualAccountId() != Account.NO_ACCOUNT;
444    }
445
446    /**
447     * Called by the host activity at the end of {@link Activity#onCreate}.
448     */
449    @Override
450    public void onActivityCreated() {
451        super.onActivityCreated();
452    }
453
454    /** {@inheritDoc} */
455    @Override
456    public void onActivityStart() {
457        super.onActivityStart();
458        if (isMessageViewInstalled()) {
459            updateMessageOrderManager();
460        }
461    }
462
463    /** {@inheritDoc} */
464    @Override
465    public void onActivityResume() {
466        super.onActivityResume();
467        refreshActionBar();
468    }
469
470    /** {@inheritDoc} */
471    @Override
472    public void onActivityPause() {
473        super.onActivityPause();
474    }
475
476    /** {@inheritDoc} */
477    @Override
478    public void onActivityStop() {
479        stopMessageOrderManager();
480        super.onActivityStop();
481    }
482
483    /** {@inheritDoc} */
484    @Override
485    public void onActivityDestroy() {
486        super.onActivityDestroy();
487    }
488
489    /** {@inheritDoc} */
490    @Override
491    public void onSaveInstanceState(Bundle outState) {
492        super.onSaveInstanceState(outState);
493    }
494
495    /** {@inheritDoc} */
496    @Override
497    public void restoreInstanceState(Bundle savedInstanceState) {
498        super.restoreInstanceState(savedInstanceState);
499    }
500
501    @Override
502    protected Callback getInboxLookupCallback() {
503        return this;
504    }
505
506    @Override
507    protected void installMessageListFragment(MessageListFragment fragment) {
508        super.installMessageListFragment(fragment);
509
510        if (isMailboxListInstalled()) {
511            getMailboxListFragment().setHighlightedMailbox(fragment.getMailboxId());
512        }
513    }
514
515    @Override
516    protected void installMessageViewFragment(MessageViewFragment fragment) {
517        super.installMessageViewFragment(fragment);
518
519        if (isMessageListInstalled()) {
520            getMessageListFragment().setSelectedMessage(fragment.getMessageId());
521        }
522    }
523
524    @Override
525    protected void uninstallMessageViewFragment() {
526        // Don't need it when there's no message view.
527        stopMessageOrderManager();
528        super.uninstallMessageViewFragment();
529    }
530
531    /**
532     * Commit a {@link FragmentTransaction}.
533     */
534    private void commitFragmentTransaction(FragmentTransaction ft) {
535        if (DEBUG_FRAGMENTS) {
536            Log.d(Logging.LOG_TAG, this + " commitFragmentTransaction: " + ft);
537        }
538        if (!ft.isEmpty()) {
539            ft.commit();
540            mActivity.getFragmentManager().executePendingTransactions();
541        }
542    }
543
544    /**
545     * {@inheritDoc}
546     */
547    @Override
548    public void open(long accountId, long mailboxId, long messageId) {
549        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
550            Log.d(Logging.LOG_TAG, this + " open accountId=" + accountId
551                    + " mailboxId=" + mailboxId + " messageId=" + messageId);
552        }
553        final FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
554        if (accountId == Account.NO_ACCOUNT) {
555            throw new IllegalArgumentException();
556        } else if (mailboxId == Mailbox.NO_MAILBOX) {
557            updateMailboxList(ft, accountId, Mailbox.NO_MAILBOX, true);
558
559            // Show the appropriate message list
560            if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
561                // When opening the Combined view, the right pane will be "combined inbox".
562                updateMessageList(ft, accountId, Mailbox.QUERY_ALL_INBOXES, true);
563            } else {
564                // Try to find the inbox for the account
565                startInboxLookup(accountId);
566            }
567            mThreePane.showLeftPane();
568        } else if (messageId == Message.NO_MESSAGE) {
569            updateMailboxList(ft, accountId, mailboxId, true);
570            updateMessageList(ft, accountId, mailboxId, true);
571
572            mThreePane.showLeftPane();
573        } else {
574            updateMailboxList(ft, accountId, mailboxId, true);
575            updateMessageList(ft, accountId, mailboxId, true);
576            updateMessageView(ft, messageId);
577
578            mThreePane.showRightPane();
579        }
580        commitFragmentTransaction(ft);
581    }
582
583    /**
584     * Loads the given account and optionally selects the given mailbox and message. If the
585     * specified account is already selected, no actions will be performed unless
586     * <code>forceReload</code> is <code>true</code>.
587     *
588     * @param ft {@link FragmentTransaction} to use.
589     * @param accountId ID of the account to load. Must never be {@link Account#NO_ACCOUNT}.
590     * @param mailboxId ID of the mailbox to use as the "selected".  Pass
591     *     {@link Mailbox#NO_MAILBOX} to show the root mailboxes.
592     * @param clearDependentPane if true, the message list and the message view will be cleared
593     */
594    private void updateMailboxList(FragmentTransaction ft,
595            long accountId, long mailboxId, boolean clearDependentPane) {
596        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
597            Log.d(Logging.LOG_TAG, this + " updateMailboxList accountId=" + accountId
598                    + " mailboxId=" + mailboxId);
599        }
600        if (accountId == Account.NO_ACCOUNT) {
601            throw new IllegalArgumentException();
602        }
603
604        if ((getUIAccountId() != accountId) || (getMailboxListMailboxId() != mailboxId)) {
605            removeMailboxListFragment(ft);
606            ft.add(mThreePane.getLeftPaneId(),
607                    MailboxListFragment.newInstance(accountId, mailboxId, true));
608        }
609        if (clearDependentPane) {
610            removeMessageListFragment(ft);
611            removeMessageViewFragment(ft);
612        }
613    }
614
615    /**
616     * Shortcut to call {@link #updateMailboxList(FragmentTransaction, long, long, boolean)} and
617     * commit.
618     */
619    @SuppressWarnings("unused")
620    private void updateMailboxList(long accountId, long mailboxId, boolean clearDependentPane) {
621        FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
622        updateMailboxList(ft, accountId, mailboxId, clearDependentPane);
623        commitFragmentTransaction(ft);
624    }
625
626    /**
627     * Go back to a mailbox list view. If a message view is currently active, it will
628     * be hidden.
629     */
630    private void goBackToMailbox() {
631        if (isMessageViewInstalled()) {
632            mThreePane.showLeftPane(); // Show mailbox list
633        }
634    }
635
636    /**
637     * Show the message list fragment for the given mailbox.
638     *
639     * @param ft {@link FragmentTransaction} to use.
640     * @param accountId ID of the owner account for the mailbox.  Must never be
641     *     {@link Account#NO_ACCOUNT}.
642     * @param mailboxId ID of the mailbox to load. Must never be {@link Mailbox#NO_MAILBOX}.
643     * @param clearDependentPane if true, the message view will be cleared
644     */
645    private void updateMessageList(FragmentTransaction ft,
646            long accountId, long mailboxId, boolean clearDependentPane) {
647        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
648            Log.d(Logging.LOG_TAG, this + " updateMessageList mMailboxId=" + mailboxId);
649        }
650        if (mailboxId == Mailbox.NO_MAILBOX) {
651            throw new IllegalArgumentException();
652        }
653
654        stopInboxLookup();
655
656        if (mailboxId != getMessageListMailboxId()) {
657            removeMessageListFragment(ft);
658            ft.add(mThreePane.getMiddlePaneId(), MessageListFragment.newInstance(
659                    accountId, mailboxId));
660        }
661        if (clearDependentPane) {
662            removeMessageViewFragment(ft);
663        }
664    }
665
666    /**
667     * Shortcut to call {@link #updateMessageList(FragmentTransaction, long, long, boolean)} and
668     * commit.
669     */
670    private void updateMessageList(long accountId, long mailboxId, boolean clearDependentPane) {
671        FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
672        updateMessageList(ft, accountId, mailboxId, clearDependentPane);
673        commitFragmentTransaction(ft);
674    }
675
676    /**
677     * Show a message on the message view.
678     *
679     * @param ft {@link FragmentTransaction} to use.
680     * @param messageId ID of the mailbox to load. Must never be {@link Message#NO_MESSAGE}.
681     */
682    private void updateMessageView(FragmentTransaction ft, long messageId) {
683        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
684            Log.d(Logging.LOG_TAG, this + " updateMessageView messageId=" + messageId);
685        }
686        if (messageId == Message.NO_MESSAGE) {
687            throw new IllegalArgumentException();
688        }
689
690        if (messageId == getMessageId()) {
691            return; // nothing to do.
692        }
693
694        removeMessageViewFragment(ft);
695
696        ft.add(mThreePane.getRightPaneId(), MessageViewFragment.newInstance(
697                getUIAccountId(), getMessageListMailboxId(), messageId));
698    }
699
700    /**
701     * Shortcut to call {@link #updateMessageView(FragmentTransaction, long)} and commit.
702     */
703    private void updateMessageView(long messageId) {
704        FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
705        updateMessageView(ft, messageId);
706        commitFragmentTransaction(ft);
707    }
708
709    /**
710     * Remove the message view if shown.
711     */
712    private void unselectMessage() {
713        commitFragmentTransaction(removeMessageViewFragment(
714                mActivity.getFragmentManager().beginTransaction()));
715        if (isMessageListInstalled()) {
716            getMessageListFragment().setSelectedMessage(Message.NO_MESSAGE);
717        }
718    }
719
720    private class CommandButtonCallback implements MessageCommandButtonView.Callback {
721        @Override
722        public void onMoveToNewer() {
723            moveToNewer();
724        }
725
726        @Override
727        public void onMoveToOlder() {
728            moveToOlder();
729        }
730    }
731
732    private void onCurrentMessageGone() {
733        switch (Preferences.getPreferences(mActivity).getAutoAdvanceDirection()) {
734            case Preferences.AUTO_ADVANCE_NEWER:
735                if (moveToNewer()) return;
736                break;
737            case Preferences.AUTO_ADVANCE_OLDER:
738                if (moveToOlder()) return;
739                break;
740        }
741        // Last message in the box or AUTO_ADVANCE_MESSAGE_LIST.  Go back to message list.
742        goBackToMailbox();
743    }
744
745    /**
746     * Potentially create a new {@link MessageOrderManager}; if it's not already started or if
747     * the account has changed, and sync it to the current message.
748     */
749    private void updateMessageOrderManager() {
750        if (!isMessageViewInstalled()) {
751            return;
752        }
753        final long mailboxId = getMessageListMailboxId();
754        if (mOrderManager == null || mOrderManager.getMailboxId() != mailboxId) {
755            stopMessageOrderManager();
756            mOrderManager =
757                new MessageOrderManager(mActivity, mailboxId, mMessageOrderManagerCallback);
758        }
759        mOrderManager.moveTo(getMessageId());
760    }
761
762    private class MessageOrderManagerCallback implements MessageOrderManager.Callback {
763        @Override
764        public void onMessagesChanged() {
765            updateNavigationArrows();
766        }
767
768        @Override
769        public void onMessageNotFound() {
770            // Current message gone.
771            goBackToMailbox();
772        }
773    }
774
775    /**
776     * Stop {@link MessageOrderManager}.
777     */
778    private void stopMessageOrderManager() {
779        if (mOrderManager != null) {
780            mOrderManager.close();
781            mOrderManager = null;
782        }
783    }
784
785    /**
786     * Disable/enable the move-to-newer/older buttons.
787     */
788    private void updateNavigationArrows() {
789        if (mOrderManager == null) {
790            // shouldn't happen, but just in case
791            mMessageCommandButtons.enableNavigationButtons(false, false, 0, 0);
792        } else {
793            mMessageCommandButtons.enableNavigationButtons(
794                    mOrderManager.canMoveToNewer(), mOrderManager.canMoveToOlder(),
795                    mOrderManager.getCurrentPosition(), mOrderManager.getTotalMessageCount());
796        }
797    }
798
799    private boolean moveToOlder() {
800        if ((mOrderManager != null) && mOrderManager.moveToOlder()) {
801            updateMessageView(mOrderManager.getCurrentMessageId());
802            return true;
803        }
804        return false;
805    }
806
807    private boolean moveToNewer() {
808        if ((mOrderManager != null) && mOrderManager.moveToNewer()) {
809            updateMessageView(mOrderManager.getCurrentMessageId());
810            return true;
811        }
812        return false;
813    }
814
815    /** {@inheritDoc} */
816    @Override
817    public boolean onBackPressed(boolean isSystemBackKey) {
818        if (mThreePane.onBackPressed(isSystemBackKey)) {
819            return true;
820        } else if (isMailboxListInstalled() && getMailboxListFragment().navigateUp()) {
821            return true;
822        }
823        return false;
824    }
825
826    /**
827     * Handles the "refresh" option item.  Opens the settings activity.
828     * TODO used by experimental code in the activity -- otherwise can be private.
829     */
830    @Override
831    public void onRefresh() {
832        // Cancel previously running instance if any.
833        new RefreshTask(mTaskTracker, mActivity, getActualAccountId(),
834                getMessageListMailboxId()).cancelPreviousAndExecuteParallel();
835    }
836
837    /**
838     * Class to handle refresh.
839     *
840     * When the user press "refresh",
841     * <ul>
842     *   <li>Refresh the current mailbox, if it's refreshable.  (e.g. don't refresh combined inbox,
843     *       drafts, etc.
844     *   <li>Refresh the mailbox list, if it hasn't been refreshed in the last
845     *       {@link #MAILBOX_REFRESH_MIN_INTERVAL}.
846     *   <li>Refresh inbox, if it's not the current mailbox and it hasn't been refreshed in the last
847     *       {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}.
848     * </ul>
849     */
850    @VisibleForTesting
851    static class RefreshTask extends EmailAsyncTask<Void, Void, Boolean> {
852        private final Clock mClock;
853        private final Context mContext;
854        private final long mAccountId;
855        private final long mMailboxId;
856        private final RefreshManager mRefreshManager;
857        @VisibleForTesting
858        long mInboxId;
859
860        public RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId,
861                long mailboxId) {
862            this(tracker, context, accountId, mailboxId, Clock.INSTANCE,
863                    RefreshManager.getInstance(context));
864        }
865
866        @VisibleForTesting
867        RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId,
868                long mailboxId, Clock clock, RefreshManager refreshManager) {
869            super(tracker);
870            mClock = clock;
871            mContext = context;
872            mRefreshManager = refreshManager;
873            mAccountId = accountId;
874            mMailboxId = mailboxId;
875        }
876
877        /**
878         * Do DB access on a worker thread.
879         */
880        @Override
881        protected Boolean doInBackground(Void... params) {
882            mInboxId = Account.getInboxId(mContext, mAccountId);
883            return Mailbox.isRefreshable(mContext, mMailboxId);
884        }
885
886        /**
887         * Do the actual refresh.
888         */
889        @Override
890        protected void onPostExecute(Boolean isCurrentMailboxRefreshable) {
891            if (isCancelled() || isCurrentMailboxRefreshable == null) {
892                return;
893            }
894            if (isCurrentMailboxRefreshable) {
895                mRefreshManager.refreshMessageList(mAccountId, mMailboxId, false);
896            }
897            // Refresh mailbox list
898            if (mAccountId != Account.NO_ACCOUNT) {
899                if (shouldRefreshMailboxList()) {
900                    mRefreshManager.refreshMailboxList(mAccountId);
901                }
902            }
903            // Refresh inbox
904            if (shouldAutoRefreshInbox()) {
905                mRefreshManager.refreshMessageList(mAccountId, mInboxId, false);
906            }
907        }
908
909        /**
910         * @return true if the mailbox list of the current account hasn't been refreshed
911         * in the last {@link #MAILBOX_REFRESH_MIN_INTERVAL}.
912         */
913        @VisibleForTesting
914        boolean shouldRefreshMailboxList() {
915            if (mRefreshManager.isMailboxListRefreshing(mAccountId)) {
916                return false;
917            }
918            final long nextRefreshTime = mRefreshManager.getLastMailboxListRefreshTime(mAccountId)
919                    + MAILBOX_REFRESH_MIN_INTERVAL;
920            if (nextRefreshTime > mClock.getTime()) {
921                return false;
922            }
923            return true;
924        }
925
926        /**
927         * @return true if the inbox of the current account hasn't been refreshed
928         * in the last {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}.
929         */
930        @VisibleForTesting
931        boolean shouldAutoRefreshInbox() {
932            if (mInboxId == mMailboxId) {
933                return false; // Current ID == inbox.  No need to auto-refresh.
934            }
935            if (mRefreshManager.isMessageListRefreshing(mInboxId)) {
936                return false;
937            }
938            final long nextRefreshTime = mRefreshManager.getLastMessageListRefreshTime(mInboxId)
939                    + INBOX_AUTO_REFRESH_MIN_INTERVAL;
940            if (nextRefreshTime > mClock.getTime()) {
941                return false;
942            }
943            return true;
944        }
945    }
946
947    private class ActionBarControllerCallback implements ActionBarController.Callback {
948        @Override
949        public String getCurrentMailboxName() {
950            return mCurrentMailboxName;
951        }
952
953        @Override
954        public int getCurrentMailboxUnreadCount() {
955            return mCurrentMailboxUnreadCount;
956        }
957
958        @Override
959        public long getUIAccountId() {
960            return UIControllerTwoPane.this.getUIAccountId();
961        }
962
963        @Override
964        public boolean isAccountSelected() {
965            return UIControllerTwoPane.this.isAccountSelected();
966        }
967
968        @Override
969        public void onAccountSelected(long accountId) {
970            switchAccount(accountId);
971        }
972
973        @Override
974        public void onMailboxSelected(long mailboxId) {
975            UIControllerTwoPane.this.openMailbox(getUIAccountId(), mailboxId);
976        }
977
978        @Override
979        public void onNoAccountsFound() {
980            Welcome.actionStart(mActivity);
981            mActivity.finish();
982        }
983
984        @Override
985        public boolean shouldShowMailboxName() {
986            // Show when the left pane is hidden.
987            return (mThreePane.getVisiblePanes() & ThreePaneLayout.PANE_LEFT) == 0;
988        }
989
990        @Override
991        public boolean shouldShowUp() {
992            final int visiblePanes = mThreePane.getVisiblePanes();
993            final boolean leftPaneHidden = ((visiblePanes & ThreePaneLayout.PANE_LEFT) == 0);
994            return leftPaneHidden
995                    || (isMailboxListInstalled() && !getMailboxListFragment().isRoot());
996        }
997    }
998}
999