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