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