UIControllerTwoPane.java revision 0f2763274922e15d4baba7e7228f5765034b9c0d
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    private long getMessageId() {
414        return isMessageViewInstalled() ? getMessageViewFragment().getMessageId()
415                : Message.NO_MESSAGE;
416    }
417
418    /**
419     * @return true if refresh is in progress for the current mailbox.
420     */
421    @Override
422    protected boolean isRefreshInProgress() {
423        long messageListMailboxId = getMessageListMailboxId();
424        return (messageListMailboxId >= 0)
425                && mRefreshManager.isMessageListRefreshing(messageListMailboxId);
426    }
427
428    /**
429     * @return true if the UI should enable the "refresh" command.
430     */
431    @Override
432    protected boolean isRefreshEnabled() {
433        // - Don't show for combined inboxes, but
434        // - Show even for non-refreshable mailboxes, in which case we refresh the mailbox list
435        return getActualAccountId() != Account.NO_ACCOUNT;
436    }
437
438    /**
439     * Called by the host activity at the end of {@link Activity#onCreate}.
440     */
441    @Override
442    public void onActivityCreated() {
443        super.onActivityCreated();
444    }
445
446    /** {@inheritDoc} */
447    @Override
448    public void onActivityStart() {
449        super.onActivityStart();
450        if (isMessageViewInstalled()) {
451            updateMessageOrderManager();
452        }
453    }
454
455    /** {@inheritDoc} */
456    @Override
457    public void onActivityResume() {
458        super.onActivityResume();
459        refreshActionBar();
460    }
461
462    /** {@inheritDoc} */
463    @Override
464    public void onActivityPause() {
465        super.onActivityPause();
466    }
467
468    /** {@inheritDoc} */
469    @Override
470    public void onActivityStop() {
471        stopMessageOrderManager();
472        super.onActivityStop();
473    }
474
475    /** {@inheritDoc} */
476    @Override
477    public void onActivityDestroy() {
478        super.onActivityDestroy();
479    }
480
481    /** {@inheritDoc} */
482    @Override
483    public void onSaveInstanceState(Bundle outState) {
484        super.onSaveInstanceState(outState);
485    }
486
487    /** {@inheritDoc} */
488    @Override
489    public void onRestoreInstanceState(Bundle savedInstanceState) {
490        super.onRestoreInstanceState(savedInstanceState);
491    }
492
493    @Override
494    protected Callback getInboxLookupCallback() {
495        return this;
496    }
497
498    @Override
499    protected void installMessageListFragment(MessageListFragment fragment) {
500        super.installMessageListFragment(fragment);
501
502        if (isMailboxListInstalled()) {
503            getMailboxListFragment().setHighlightedMailbox(fragment.getMailboxId());
504        }
505    }
506
507    @Override
508    protected void installMessageViewFragment(MessageViewFragment fragment) {
509        super.installMessageViewFragment(fragment);
510
511        if (isMessageListInstalled()) {
512            getMessageListFragment().setSelectedMessage(fragment.getMessageId());
513        }
514    }
515
516    @Override
517    protected void uninstallMessageViewFragment() {
518        // Don't need it when there's no message view.
519        stopMessageOrderManager();
520        super.uninstallMessageViewFragment();
521    }
522
523    /**
524     * Commit a {@link FragmentTransaction}.
525     */
526    private void commitFragmentTransaction(FragmentTransaction ft) {
527        if (DEBUG_FRAGMENTS) {
528            Log.d(Logging.LOG_TAG, this + " commitFragmentTransaction: " + ft);
529        }
530        ft.commit();
531    }
532
533    /**
534     * {@inheritDoc}
535     */
536    @Override
537    public void open(long accountId, long mailboxId, long messageId) {
538        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
539            Log.d(Logging.LOG_TAG, this + " open accountId=" + accountId
540                    + " mailboxId=" + mailboxId + " messageId=" + messageId);
541        }
542        final FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
543        if (accountId == Account.NO_ACCOUNT) {
544            throw new IllegalArgumentException();
545        } else if (mailboxId == Mailbox.NO_MAILBOX) {
546            updateMailboxList(ft, accountId, Mailbox.NO_MAILBOX, true);
547
548            // Show the appropriate message list
549            if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
550                // When opening the Combined view, the right pane will be "combined inbox".
551                updateMessageList(ft, accountId, Mailbox.QUERY_ALL_INBOXES, true);
552            } else {
553                // Try to find the inbox for the account
554                startInboxLookup(accountId);
555            }
556            mThreePane.showLeftPane();
557        } else if (messageId == Message.NO_MESSAGE) {
558            updateMailboxList(ft, accountId, mailboxId, true);
559            updateMessageList(ft, accountId, mailboxId, true);
560
561            mThreePane.showLeftPane();
562        } else {
563            updateMailboxList(ft, accountId, mailboxId, true);
564            updateMessageList(ft, accountId, mailboxId, true);
565            updateMessageView(ft, messageId);
566
567            mThreePane.showRightPane();
568        }
569        commitFragmentTransaction(ft);
570    }
571
572    /**
573     * Loads the given account and optionally selects the given mailbox and message. If the
574     * specified account is already selected, no actions will be performed unless
575     * <code>forceReload</code> is <code>true</code>.
576     *
577     * @param ft {@link FragmentTransaction} to use.
578     * @param accountId ID of the account to load. Must never be {@link Account#NO_ACCOUNT}.
579     * @param mailboxId ID of the mailbox to use as the "selected".  Pass
580     *     {@link Mailbox#NO_MAILBOX} to show the root mailboxes.
581     * @param clearDependentPane if true, the message list and the message view will be cleared
582     */
583    private void updateMailboxList(FragmentTransaction ft,
584            long accountId, long mailboxId, boolean clearDependentPane) {
585        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
586            Log.d(Logging.LOG_TAG, this + " updateMailboxList accountId=" + accountId
587                    + " mailboxId=" + mailboxId);
588        }
589        if (accountId == Account.NO_ACCOUNT) {
590            throw new IllegalArgumentException();
591        }
592
593        if ((getUIAccountId() != accountId) || (getMailboxListMailboxId() != mailboxId)) {
594            removeMailboxListFragment(ft);
595            ft.add(mThreePane.getLeftPaneId(),
596                    MailboxListFragment.newInstance(accountId, mailboxId, true));
597        }
598        if (clearDependentPane) {
599            removeMessageListFragment(ft);
600            removeMessageViewFragment(ft);
601        }
602    }
603
604    /**
605     * Shortcut to call {@link #updateMailboxList(FragmentTransaction, long, long, boolean)} and
606     * commit.
607     */
608    @SuppressWarnings("unused")
609    private void updateMailboxList(long accountId, long mailboxId, boolean clearDependentPane) {
610        FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
611        updateMailboxList(ft, accountId, mailboxId, clearDependentPane);
612        commitFragmentTransaction(ft);
613    }
614
615    /**
616     * Go back to a mailbox list view. If a message view is currently active, it will
617     * be hidden.
618     */
619    private void goBackToMailbox() {
620        if (isMessageViewInstalled()) {
621            mThreePane.showLeftPane(); // Show mailbox list
622        }
623    }
624
625    /**
626     * Show the message list fragment for the given mailbox.
627     *
628     * @param ft {@link FragmentTransaction} to use.
629     * @param accountId ID of the owner account for the mailbox.  Must never be
630     *     {@link Account#NO_ACCOUNT}.
631     * @param mailboxId ID of the mailbox to load. Must never be {@link Mailbox#NO_MAILBOX}.
632     * @param clearDependentPane if true, the message view will be cleared
633     */
634    private void updateMessageList(FragmentTransaction ft,
635            long accountId, long mailboxId, boolean clearDependentPane) {
636        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
637            Log.d(Logging.LOG_TAG, this + " updateMessageList mMailboxId=" + mailboxId);
638        }
639        if (mailboxId == Mailbox.NO_MAILBOX) {
640            throw new IllegalArgumentException();
641        }
642
643        stopInboxLookup();
644
645        if (mailboxId != getMessageListMailboxId()) {
646            removeMessageListFragment(ft);
647            ft.add(mThreePane.getMiddlePaneId(), MessageListFragment.newInstance(
648                    accountId, mailboxId));
649        }
650        if (clearDependentPane) {
651            removeMessageViewFragment(ft);
652        }
653    }
654
655    /**
656     * Shortcut to call {@link #updateMessageList(FragmentTransaction, long, long, boolean)} and
657     * commit.
658     */
659    private void updateMessageList(long accountId, long mailboxId, boolean clearDependentPane) {
660        FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
661        updateMessageList(ft, accountId, mailboxId, clearDependentPane);
662        commitFragmentTransaction(ft);
663    }
664
665    /**
666     * Show a message on the message view.
667     *
668     * @param ft {@link FragmentTransaction} to use.
669     * @param messageId ID of the mailbox to load. Must never be {@link Message#NO_MESSAGE}.
670     */
671    private void updateMessageView(FragmentTransaction ft, long messageId) {
672        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
673            Log.d(Logging.LOG_TAG, this + " updateMessageView messageId=" + messageId);
674        }
675        if (messageId == Message.NO_MESSAGE) {
676            throw new IllegalArgumentException();
677        }
678
679        if (messageId == getMessageId()) {
680            return; // nothing to do.
681        }
682
683        removeMessageViewFragment(ft);
684
685        ft.add(mThreePane.getRightPaneId(), MessageViewFragment.newInstance(
686                getUIAccountId(), getMessageListMailboxId(), messageId));
687    }
688
689    /**
690     * Shortcut to call {@link #updateMessageView(FragmentTransaction, long)} and commit.
691     */
692    private void updateMessageView(long messageId) {
693        FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction();
694        updateMessageView(ft, messageId);
695        commitFragmentTransaction(ft);
696    }
697
698    /**
699     * Remove the message view if shown.
700     */
701    private void unselectMessage() {
702        commitFragmentTransaction(removeMessageViewFragment(
703                mActivity.getFragmentManager().beginTransaction()));
704        if (isMessageListInstalled()) {
705            getMessageListFragment().setSelectedMessage(Message.NO_MESSAGE);
706        }
707    }
708
709    private class CommandButtonCallback implements MessageCommandButtonView.Callback {
710        @Override
711        public void onMoveToNewer() {
712            moveToNewer();
713        }
714
715        @Override
716        public void onMoveToOlder() {
717            moveToOlder();
718        }
719    }
720
721    private void onCurrentMessageGone() {
722        switch (Preferences.getPreferences(mActivity).getAutoAdvanceDirection()) {
723            case Preferences.AUTO_ADVANCE_NEWER:
724                if (moveToNewer()) return;
725                break;
726            case Preferences.AUTO_ADVANCE_OLDER:
727                if (moveToOlder()) return;
728                break;
729        }
730        // Last message in the box or AUTO_ADVANCE_MESSAGE_LIST.  Go back to message list.
731        goBackToMailbox();
732    }
733
734    /**
735     * Potentially create a new {@link MessageOrderManager}; if it's not already started or if
736     * the account has changed, and sync it to the current message.
737     */
738    private void updateMessageOrderManager() {
739        if (!isMessageViewInstalled()) {
740            return;
741        }
742        final long mailboxId = getMessageListMailboxId();
743        if (mOrderManager == null || mOrderManager.getMailboxId() != mailboxId) {
744            stopMessageOrderManager();
745            mOrderManager =
746                new MessageOrderManager(mActivity, mailboxId, mMessageOrderManagerCallback);
747        }
748        mOrderManager.moveTo(getMessageId());
749    }
750
751    private class MessageOrderManagerCallback implements MessageOrderManager.Callback {
752        @Override
753        public void onMessagesChanged() {
754            updateNavigationArrows();
755        }
756
757        @Override
758        public void onMessageNotFound() {
759            // Current message gone.
760            goBackToMailbox();
761        }
762    }
763
764    /**
765     * Stop {@link MessageOrderManager}.
766     */
767    private void stopMessageOrderManager() {
768        if (mOrderManager != null) {
769            mOrderManager.close();
770            mOrderManager = null;
771        }
772    }
773
774    /**
775     * Disable/enable the move-to-newer/older buttons.
776     */
777    private void updateNavigationArrows() {
778        if (mOrderManager == null) {
779            // shouldn't happen, but just in case
780            mMessageCommandButtons.enableNavigationButtons(false, false, 0, 0);
781        } else {
782            mMessageCommandButtons.enableNavigationButtons(
783                    mOrderManager.canMoveToNewer(), mOrderManager.canMoveToOlder(),
784                    mOrderManager.getCurrentPosition(), mOrderManager.getTotalMessageCount());
785        }
786    }
787
788    private boolean moveToOlder() {
789        if ((mOrderManager != null) && mOrderManager.moveToOlder()) {
790            updateMessageView(mOrderManager.getCurrentMessageId());
791            return true;
792        }
793        return false;
794    }
795
796    private boolean moveToNewer() {
797        if ((mOrderManager != null) && mOrderManager.moveToNewer()) {
798            updateMessageView(mOrderManager.getCurrentMessageId());
799            return true;
800        }
801        return false;
802    }
803
804    /** {@inheritDoc} */
805    @Override
806    public boolean onBackPressed(boolean isSystemBackKey) {
807        // Super's method has precedence.  Must call it first.
808        if (super.onBackPressed(isSystemBackKey)) {
809            return true;
810        }
811        if (mThreePane.onBackPressed(isSystemBackKey)) {
812            return true;
813        }
814        if (isMailboxListInstalled() && getMailboxListFragment().navigateUp()) {
815            return true;
816        }
817        return false;
818    }
819
820    @Override protected boolean canSearch() {
821        // Search is always enabled on two-pane. (if the account supports it)
822        return true;
823    }
824
825    /**
826     * Handles the "refresh" option item.  Opens the settings activity.
827     * TODO used by experimental code in the activity -- otherwise can be private.
828     */
829    @Override
830    public void onRefresh() {
831        // Cancel previously running instance if any.
832        new RefreshTask(mTaskTracker, mActivity, getActualAccountId(),
833                getMessageListMailboxId()).cancelPreviousAndExecuteParallel();
834    }
835
836    /**
837     * Class to handle refresh.
838     *
839     * When the user press "refresh",
840     * <ul>
841     *   <li>Refresh the current mailbox, if it's refreshable.  (e.g. don't refresh combined inbox,
842     *       drafts, etc.
843     *   <li>Refresh the mailbox list, if it hasn't been refreshed in the last
844     *       {@link #MAILBOX_REFRESH_MIN_INTERVAL}.
845     *   <li>Refresh inbox, if it's not the current mailbox and it hasn't been refreshed in the last
846     *       {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}.
847     * </ul>
848     */
849    @VisibleForTesting
850    static class RefreshTask extends EmailAsyncTask<Void, Void, Boolean> {
851        private final Clock mClock;
852        private final Context mContext;
853        private final long mAccountId;
854        private final long mMailboxId;
855        private final RefreshManager mRefreshManager;
856        @VisibleForTesting
857        long mInboxId;
858
859        public RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId,
860                long mailboxId) {
861            this(tracker, context, accountId, mailboxId, Clock.INSTANCE,
862                    RefreshManager.getInstance(context));
863        }
864
865        @VisibleForTesting
866        RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId,
867                long mailboxId, Clock clock, RefreshManager refreshManager) {
868            super(tracker);
869            mClock = clock;
870            mContext = context;
871            mRefreshManager = refreshManager;
872            mAccountId = accountId;
873            mMailboxId = mailboxId;
874        }
875
876        /**
877         * Do DB access on a worker thread.
878         */
879        @Override
880        protected Boolean doInBackground(Void... params) {
881            mInboxId = Account.getInboxId(mContext, mAccountId);
882            return Mailbox.isRefreshable(mContext, mMailboxId);
883        }
884
885        /**
886         * Do the actual refresh.
887         */
888        @Override
889        protected void onPostExecute(Boolean isCurrentMailboxRefreshable) {
890            if (isCancelled() || isCurrentMailboxRefreshable == null) {
891                return;
892            }
893            if (isCurrentMailboxRefreshable) {
894                mRefreshManager.refreshMessageList(mAccountId, mMailboxId, false);
895            }
896            // Refresh mailbox list
897            if (mAccountId != Account.NO_ACCOUNT) {
898                if (shouldRefreshMailboxList()) {
899                    mRefreshManager.refreshMailboxList(mAccountId);
900                }
901            }
902            // Refresh inbox
903            if (shouldAutoRefreshInbox()) {
904                mRefreshManager.refreshMessageList(mAccountId, mInboxId, false);
905            }
906        }
907
908        /**
909         * @return true if the mailbox list of the current account hasn't been refreshed
910         * in the last {@link #MAILBOX_REFRESH_MIN_INTERVAL}.
911         */
912        @VisibleForTesting
913        boolean shouldRefreshMailboxList() {
914            if (mRefreshManager.isMailboxListRefreshing(mAccountId)) {
915                return false;
916            }
917            final long nextRefreshTime = mRefreshManager.getLastMailboxListRefreshTime(mAccountId)
918                    + MAILBOX_REFRESH_MIN_INTERVAL;
919            if (nextRefreshTime > mClock.getTime()) {
920                return false;
921            }
922            return true;
923        }
924
925        /**
926         * @return true if the inbox of the current account hasn't been refreshed
927         * in the last {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}.
928         */
929        @VisibleForTesting
930        boolean shouldAutoRefreshInbox() {
931            if (mInboxId == mMailboxId) {
932                return false; // Current ID == inbox.  No need to auto-refresh.
933            }
934            if (mRefreshManager.isMessageListRefreshing(mInboxId)) {
935                return false;
936            }
937            final long nextRefreshTime = mRefreshManager.getLastMessageListRefreshTime(mInboxId)
938                    + INBOX_AUTO_REFRESH_MIN_INTERVAL;
939            if (nextRefreshTime > mClock.getTime()) {
940                return false;
941            }
942            return true;
943        }
944    }
945
946    private class ActionBarControllerCallback implements ActionBarController.Callback {
947        @Override
948        public String getCurrentMailboxName() {
949            return mCurrentMailboxName;
950        }
951
952        @Override
953        public int getCurrentMailboxUnreadCount() {
954            return mCurrentMailboxUnreadCount;
955        }
956
957        @Override
958        public long getUIAccountId() {
959            return UIControllerTwoPane.this.getUIAccountId();
960        }
961
962        @Override
963        public boolean isAccountSelected() {
964            return UIControllerTwoPane.this.isAccountSelected();
965        }
966
967        @Override
968        public void onAccountSelected(long accountId) {
969            switchAccount(accountId);
970        }
971
972        @Override
973        public void onMailboxSelected(long mailboxId) {
974            UIControllerTwoPane.this.openMailbox(getUIAccountId(), mailboxId);
975        }
976
977        @Override
978        public void onNoAccountsFound() {
979            Welcome.actionStart(mActivity);
980            mActivity.finish();
981        }
982
983        @Override
984        public boolean shouldShowMailboxName() {
985            // Show when the left pane is hidden.
986            return (mThreePane.getVisiblePanes() & ThreePaneLayout.PANE_LEFT) == 0;
987        }
988
989        @Override
990        public boolean shouldShowUp() {
991            final int visiblePanes = mThreePane.getVisiblePanes();
992            final boolean leftPaneHidden = ((visiblePanes & ThreePaneLayout.PANE_LEFT) == 0);
993            return leftPaneHidden
994                    || (isMailboxListInstalled() && !getMailboxListFragment().isRoot());
995        }
996
997        @Override
998        public void onSearchSubmit(String queryTerm) {
999            // STOPSHIP temporary code
1000            final long accountId = getUIAccountId();
1001            if (accountId == Account.NO_ACCOUNT) {
1002                return; // no account selected.
1003            }
1004            final long mailboxId = getMessageListMailboxId();
1005
1006            // TODO global search?
1007
1008            mActivity.startActivity(EmailActivity.createSearchIntent(
1009                    mActivity, accountId, mailboxId, queryTerm));
1010        }
1011
1012        @Override
1013        public void onSearchExit() {
1014            // STOPSHIP If the activity is a "search" instance, finish() it.
1015        }
1016    }
1017}
1018