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