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