UIControllerTwoPane.java revision ea56ccf6ddbbcd6cbbc000822328f2fad1d3ff98
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.email.activity.setup.AccountSettingsXL;
26import com.android.emailcommon.Logging;
27import com.android.emailcommon.provider.EmailContent.Account;
28import com.android.emailcommon.provider.EmailContent.Mailbox;
29import com.android.emailcommon.utility.EmailAsyncTask;
30import com.android.emailcommon.utility.Utility;
31
32import android.app.ActionBar;
33import android.app.Activity;
34import android.app.Fragment;
35import android.app.FragmentManager;
36import android.app.FragmentTransaction;
37import android.app.LoaderManager.LoaderCallbacks;
38import android.content.Context;
39import android.content.Loader;
40import android.database.Cursor;
41import android.os.Bundle;
42import android.util.Log;
43import android.view.LayoutInflater;
44import android.view.Menu;
45import android.view.MenuInflater;
46import android.view.MenuItem;
47import android.view.View;
48import android.widget.TextView;
49
50import java.security.InvalidParameterException;
51import java.util.ArrayList;
52import java.util.Set;
53import java.util.Stack;
54
55/**
56 * UI Controller for x-large devices.  Supports a multi-pane layout.
57 *
58 * Note: Always use {@link #commitFragmentTransaction} to commit fragment transactions.  Currently
59 * we use synchronous transactions only, but we may want to switch back to asynchronous later.
60 *
61 * TODO: Test it.  It's testable if we implement MockFragmentTransaction, which may be too early
62 * to do so at this point.  (API may not be stable enough yet.)
63 *
64 * TODO Consider extracting out a separate class to manage the action bar
65 */
66class UIControllerTwoPane implements
67        MailboxFinder.Callback,
68        ThreePaneLayout.Callback,
69        MailboxListFragment.Callback,
70        MessageListFragment.Callback,
71        MessageViewFragment.Callback {
72    private static final String BUNDLE_KEY_ACCOUNT_ID = "UIControllerTwoPane.state.account_id";
73    private static final String BUNDLE_KEY_MAILBOX_ID = "UIControllerTwoPane.state.mailbox_id";
74    private static final String BUNDLE_KEY_MESSAGE_ID = "UIControllerTwoPane.state.message_id";
75    private static final String BUNDLE_KEY_MAILBOX_STACK
76            = "UIControllerTwoPane.state.mailbox_stack";
77
78    /* package */ static final int MAILBOX_REFRESH_MIN_INTERVAL = 30 * 1000; // in milliseconds
79    /* package */ static final int INBOX_AUTO_REFRESH_MIN_INTERVAL = 10 * 1000; // in milliseconds
80
81    private static final int LOADER_ID_ACCOUNT_LIST
82            = EmailActivity.UI_CONTROLLER_LOADER_ID_BASE + 0;
83
84    /** No account selected */
85    static final long NO_ACCOUNT = -1;
86    /** No mailbox selected */
87    static final long NO_MAILBOX = -1;
88    /** No message selected */
89    static final long NO_MESSAGE = -1;
90    /** Current account id */
91    private long mAccountId = NO_ACCOUNT;
92
93    // TODO Remove this instance variable and replace it with a call to mMessageListFragment to
94    // retrieve it's mailbox ID. There's no reason we should be duplicating data
95    /**
96     * The id of the currently viewed mailbox in the mailbox list fragment.
97     * IMPORTANT: Do not confuse this with the value returned by {@link #getMessageListMailboxId()}
98     * which is the mailbox id associated with the message list fragment. The two may be different.
99     */
100    private long mMailboxListMailboxId = NO_MAILBOX;
101
102    /** Current message id */
103    private long mMessageId = NO_MESSAGE;
104
105    // Action bar
106    private ActionBar mActionBar;
107    private AccountSelectorAdapter mAccountsSelectorAdapter;
108    private final ActionBarNavigationCallback mActionBarNavigationCallback =
109        new ActionBarNavigationCallback();
110    private View mActionBarMailboxNameView;
111    private TextView mActionBarMailboxName;
112    private TextView mActionBarUnreadCount;
113
114    // Other UI elements
115    private ThreePaneLayout mThreePane;
116
117    /**
118     * Fragments that are installed.
119     *
120     * A fragment is installed when:
121     * - it is attached to the activity
122     * - the parent activity is created
123     * - and it is not scheduled to be removed.
124     *
125     * We set callbacks to fragments only when they are installed.
126     */
127    private MailboxListFragment mMailboxListFragment;
128    private MessageListFragment mMessageListFragment;
129    private MessageViewFragment mMessageViewFragment;
130
131    private MessageCommandButtonView mMessageCommandButtons;
132
133    private MailboxFinder mMailboxFinder;
134
135    private final RefreshManager mRefreshManager;
136    private final RefreshListener mRefreshListener = new RefreshListener();
137    private MessageOrderManager mOrderManager;
138    private final MessageOrderManagerCallback mMessageOrderManagerCallback =
139        new MessageOrderManagerCallback();
140    /** Mailbox IDs that the user has navigated away from; used to provide "back" functionality */
141    private final Stack<Long> mMailboxStack = new Stack<Long>();
142
143    /**
144     * List of fragments that are restored by the framework while the activity is being re-created
145     * for configuration changes (e.g. screen rotation).  We'll install them later when the activity
146     * is created in {@link #installRestoredFragments()}.
147     */
148    private final ArrayList<Fragment> mRestoredFragments = new ArrayList<Fragment>();
149
150    /**
151     * Whether fragment installation should be hold.
152     * We hold installing fragments until {@link #installRestoredFragments()} is called.
153     */
154    private boolean mHoldFragmentInstallation = true;
155
156    /** The owner activity */
157    private final EmailActivity mActivity;
158
159    private final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker();
160
161    public UIControllerTwoPane(EmailActivity activity) {
162        mActivity = activity;
163        mRefreshManager = RefreshManager.getInstance(mActivity);
164    }
165
166    // MailboxFinder$Callback
167    @Override
168    public void onAccountNotFound() {
169        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
170            Log.d(Logging.LOG_TAG, "" + this + " onAccountNotFound()");
171        }
172        // Shouldn't happen
173    }
174
175    @Override
176    public void onAccountSecurityHold(long accountId) {
177        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
178            Log.d(Logging.LOG_TAG, "" + this + " onAccountSecurityHold()");
179        }
180        mActivity.startActivity(AccountSecurity.actionUpdateSecurityIntent(mActivity, accountId,
181                true));
182    }
183
184    @Override
185    public void onMailboxFound(long accountId, long mailboxId) {
186        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
187            Log.d(Logging.LOG_TAG, "" + this + " onMailboxFound()");
188        }
189        updateMessageList(mailboxId, true, true);
190    }
191
192    @Override
193    public void onMailboxNotFound(long accountId) {
194        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
195            Log.d(Logging.LOG_TAG, "" + this + " onMailboxNotFound()");
196        }
197        // TODO: handle more gracefully.
198        Log.e(Logging.LOG_TAG, "unable to find mailbox for account " + accountId);
199    }
200
201    @Override
202    public void onMailboxNotFound() {
203        // TODO: handle more gracefully.
204        Log.e(Logging.LOG_TAG, "unable to find mailbox");
205    }
206
207    // ThreePaneLayoutCallback
208    @Override
209    public void onVisiblePanesChanged(int previousVisiblePanes) {
210
211        updateActionBar();
212
213        // If the right pane is gone, remove the message view.
214        final int visiblePanes = mThreePane.getVisiblePanes();
215        if (((visiblePanes & ThreePaneLayout.PANE_RIGHT) == 0) &&
216                ((previousVisiblePanes & ThreePaneLayout.PANE_RIGHT) != 0)) {
217            // Message view just got hidden
218            mMessageId = NO_MESSAGE;
219            if (mMessageListFragment != null) {
220                mMessageListFragment.setSelectedMessage(NO_MESSAGE);
221            }
222            uninstallMessageViewFragment(mActivity.getFragmentManager().beginTransaction())
223                    .commit();
224        }
225        // Disable CAB when the message list is not visible.
226        if (mMessageListFragment != null) {
227            mMessageListFragment.onHidden((visiblePanes & ThreePaneLayout.PANE_MIDDLE) == 0);
228        }
229    }
230
231    /**
232     * Update the action bar according to the current state.
233     *
234     * - Show/hide the "back" button next to the "Home" icon.
235     * - Show/hide the current mailbox name.
236     */
237    private void updateActionBar() {
238        final int visiblePanes = mThreePane.getVisiblePanes();
239
240        // If the left pane (mailbox list pane) is hidden, the back action on action bar will be
241        // enabled, and we also show the current mailbox name.
242        final boolean leftPaneHidden = ((visiblePanes & ThreePaneLayout.PANE_LEFT) == 0);
243        boolean displayUp = leftPaneHidden || !mMailboxStack.isEmpty();
244        mActionBar.setDisplayOptions(displayUp ? ActionBar.DISPLAY_HOME_AS_UP : 0,
245                ActionBar.DISPLAY_HOME_AS_UP);
246        mActionBarMailboxNameView.setVisibility(leftPaneHidden ? View.VISIBLE : View.GONE);
247    }
248
249    // MailboxListFragment$Callback
250    @Override
251    public void onMailboxSelected(long accountId, long mailboxId, boolean navigate,
252            boolean dragDrop) {
253        if (dragDrop) {
254            // We don't want to change the message list for D&D.
255
256            // STOPSHIP fixit: the new mailbox list created here doesn't know D&D is in progress.
257
258            updateMailboxList(accountId, mailboxId, true,
259                    false /* don't clear message list and message view */);
260        } else if (mailboxId == NO_MAILBOX) {
261            // reload the top-level message list.  Always implies navigate.
262            openAccount(accountId);
263        } else if (navigate) {
264            if (mMailboxStack.isEmpty() || mailboxId != mMailboxListMailboxId) {
265                // Don't navigate to the same mailbox id twice in a row
266                mMailboxStack.push(mMailboxListMailboxId);
267                openMailbox(accountId, mailboxId);
268            }
269        } else {
270            updateMessageList(mailboxId, true, true);
271        }
272    }
273
274    @Override
275    public void onAccountSelected(long accountId) {
276        // TODO openAccount should do the check eventually, but it's necessary for now.
277        if (accountId != getUIAccountId()) {
278            openAccount(accountId);
279            loadAccounts(); // update account spinner
280        }
281    }
282
283    @Override
284    public void onCurrentMailboxUpdated(long mailboxId, String mailboxName, int unreadCount) {
285        mActionBarMailboxName.setText(mailboxName);
286
287        // Note on action bar, we show only "unread count".  Some mailboxes such as Outbox don't
288        // have the idea of "unread count", in which case we just omit the count.
289        mActionBarUnreadCount.setText(
290                UiUtilities.getMessageCountForUi(mActivity, unreadCount, true));
291    }
292
293    // MessageListFragment$Callback
294    @Override
295    public void onMessageOpen(long messageId, long messageMailboxId, long listMailboxId,
296            int type) {
297        if (type == MessageListFragment.Callback.TYPE_DRAFT) {
298            MessageCompose.actionEditDraft(mActivity, messageId);
299        } else {
300            updateMessageView(messageId);
301        }
302    }
303
304    @Override
305    public void onEnterSelectionMode(boolean enter) {
306    }
307
308    /**
309     * Apply the auto-advance policy upon initation of a batch command that could potentially
310     * affect the currently selected conversation.
311     */
312    @Override
313    public void onAdvancingOpAccepted(Set<Long> affectedMessages) {
314        if (!isMessageSelected()) {
315            // Do nothing if message view is not visible.
316            return;
317        }
318
319        int autoAdvanceDir = Preferences.getPreferences(mActivity).getAutoAdvanceDirection();
320        if ((autoAdvanceDir == Preferences.AUTO_ADVANCE_MESSAGE_LIST) || (mOrderManager == null)) {
321            if (affectedMessages.contains(getMessageId())) {
322                goBackToMailbox();
323            }
324            return;
325        }
326
327        // Navigate to the first unselected item in the appropriate direction.
328        switch (autoAdvanceDir) {
329            case Preferences.AUTO_ADVANCE_NEWER:
330                while (affectedMessages.contains(mOrderManager.getCurrentMessageId())) {
331                    if (!mOrderManager.moveToNewer()) {
332                        goBackToMailbox();
333                        return;
334                    }
335                }
336                updateMessageView(mOrderManager.getCurrentMessageId());
337                break;
338
339            case Preferences.AUTO_ADVANCE_OLDER:
340                while (affectedMessages.contains(mOrderManager.getCurrentMessageId())) {
341                    if (!mOrderManager.moveToOlder()) {
342                        goBackToMailbox();
343                        return;
344                    }
345                }
346                updateMessageView(mOrderManager.getCurrentMessageId());
347                break;
348        }
349    }
350
351    @Override
352    public void onListLoaded() {
353    }
354
355    // MessageViewFragment$Callback
356    @Override
357    public void onMessageViewShown(int mailboxType) {
358        updateMessageOrderManager();
359        updateNavigationArrows();
360    }
361
362    @Override
363    public void onMessageViewGone() {
364        stopMessageOrderManager();
365    }
366
367    @Override
368    public boolean onUrlInMessageClicked(String url) {
369        return ActivityHelper.openUrlInMessage(mActivity, url, getActualAccountId());
370    }
371
372    @Override
373    public void onMessageSetUnread() {
374        goBackToMailbox();
375    }
376
377    @Override
378    public void onMessageNotExists() {
379        goBackToMailbox();
380    }
381
382    @Override
383    public void onLoadMessageStarted() {
384        // TODO Any nice UI for this?
385    }
386
387    @Override
388    public void onLoadMessageFinished() {
389        // TODO Any nice UI for this?
390    }
391
392    @Override
393    public void onLoadMessageError(String errorMessage) {
394    }
395
396    @Override
397    public void onRespondedToInvite(int response) {
398        onCurrentMessageGone();
399    }
400
401    @Override
402    public void onCalendarLinkClicked(long epochEventStartTime) {
403        ActivityHelper.openCalendar(mActivity, epochEventStartTime);
404    }
405
406    @Override
407    public void onBeforeMessageGone() {
408        onCurrentMessageGone();
409    }
410
411    @Override
412    public void onForward() {
413        MessageCompose.actionForward(mActivity, getMessageId());
414    }
415
416    @Override
417    public void onReply() {
418        MessageCompose.actionReply(mActivity, getMessageId(), false);
419    }
420
421    @Override
422    public void onReplyAll() {
423        MessageCompose.actionReply(mActivity, getMessageId(), true);
424    }
425
426    /**
427     * Must be called just after the activity sets up the content view.
428     *
429     * (Due to the complexity regarding class/activity initialization order, we can't do this in
430     * the constructor.)  TODO this should no longer be true when we merge activities.
431     */
432    public void onActivityViewReady() {
433        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
434            Log.d(Logging.LOG_TAG, "" + this + " onActivityViewReady");
435        }
436        // Set up action bar
437        mActionBar = mActivity.getActionBar();
438        mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME);
439
440        // Set a view for the current mailbox to the action bar.
441        final LayoutInflater inflater = LayoutInflater.from(mActivity);
442        mActionBarMailboxNameView = inflater.inflate(R.layout.action_bar_current_mailbox, null);
443        mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, ActionBar.DISPLAY_SHOW_CUSTOM);
444        final ActionBar.LayoutParams customViewLayout = new ActionBar.LayoutParams(
445                ActionBar.LayoutParams.WRAP_CONTENT,
446                ActionBar.LayoutParams.MATCH_PARENT);
447        customViewLayout.setMargins(mActivity.getResources().getDimensionPixelSize(
448                        R.dimen.action_bar_mailbox_name_left_margin) , 0, 0, 0);
449        mActionBar.setCustomView(mActionBarMailboxNameView, customViewLayout);
450
451        mActionBarMailboxName =
452                (TextView) mActionBarMailboxNameView.findViewById(R.id.mailbox_name);
453        mActionBarUnreadCount =
454                (TextView) mActionBarMailboxNameView.findViewById(R.id.unread_count);
455
456
457        // Set up content
458        mThreePane = (ThreePaneLayout) mActivity.findViewById(R.id.three_pane);
459        mThreePane.setCallback(this);
460
461        mMessageCommandButtons = mThreePane.getMessageCommandButtons();
462        mMessageCommandButtons.setCallback(new CommandButtonCallback());
463    }
464
465    /**
466     * @return the currently selected account ID, *or* {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
467     *
468     * @see #getActualAccountId()
469     */
470    public long getUIAccountId() {
471        return mAccountId;
472    }
473
474    /**
475     * @return the currently selected account ID.  If the current view is the combined view,
476     * it'll return {@link #NO_ACCOUNT}.
477     *
478     * @see #getUIAccountId()
479     */
480    public long getActualAccountId() {
481        return mAccountId == Account.ACCOUNT_ID_COMBINED_VIEW ? NO_ACCOUNT : mAccountId;
482    }
483
484    /**
485     * Returns the id of the mailbox used for the message list fragment.
486     * IMPORTANT: Do not confuse this with {@link #mMailboxListMailboxId} which is the id used
487     * for the mailbox list. The two may be different.
488     */
489    public long getMessageListMailboxId() {
490        return (mMessageListFragment == null)
491                ? Mailbox.NO_MAILBOX
492                : mMessageListFragment.getMailboxId();
493    }
494
495    public long getMessageId() {
496        return mMessageId;
497    }
498
499    /**
500     * @return true if an account is selected, or the current view is the combined view.
501     */
502    public boolean isAccountSelected() {
503        return getUIAccountId() != NO_ACCOUNT;
504    }
505
506    public boolean isMailboxSelected() {
507        return getMessageListMailboxId() != NO_MAILBOX;
508    }
509
510    public boolean isMessageSelected() {
511        return getMessageId() != NO_MESSAGE;
512    }
513
514    /**
515     * @return true if refresh is in progress for the current mailbox.
516     */
517    public boolean isRefreshInProgress() {
518        long messageListMailboxId = getMessageListMailboxId();
519        return (messageListMailboxId >= 0)
520                && mRefreshManager.isMessageListRefreshing(messageListMailboxId);
521    }
522
523    /**
524     * @return true if the UI should enable the "refresh" command.
525     */
526    public boolean isRefreshEnabled() {
527        // - Don't show for combined inboxes, but
528        // - Show even for non-refreshable mailboxes, in which case we refresh the mailbox list
529        return -1 != getActualAccountId();
530    }
531
532    /**
533     * Called by the host activity at the end of {@link Activity#onCreate}.
534     */
535    public void onActivityCreated() {
536        mRefreshManager.registerListener(mRefreshListener);
537        loadAccounts();
538    }
539
540    /**
541     * Install all the fragments kept in {@link #mRestoredFragments}.
542     *
543     * Must be called at the end of {@link EmailActivity#onCreate}.
544     */
545    public void installRestoredFragments() {
546        mHoldFragmentInstallation = false;
547
548        // Install all the fragments restored by the framework.
549        for (Fragment fragment : mRestoredFragments) {
550            installFragment(fragment);
551        }
552        mRestoredFragments.clear();
553    }
554
555    /**
556     * Called by {@link EmailActivity} when a {@link Fragment} is attached.
557     *
558     * If the activity has already been created, we initialize the fragment here.  Otherwise we
559     * keep the fragment in {@link #mRestoredFragments} and initialize it after the activity's
560     * onCreate.
561     */
562    public void onAttachFragment(Fragment fragment) {
563        if (mHoldFragmentInstallation) {
564            // Fragment being restored by the framework during the activity recreation.
565            mRestoredFragments.add(fragment);
566            return;
567        }
568        installFragment(fragment);
569    }
570
571    /**
572     * Called from {@link EmailActivity#onStart}.
573     */
574    public void onStart() {
575        if (isMessageSelected()) {
576            updateMessageOrderManager();
577        }
578    }
579
580    /**
581     * Called from {@link EmailActivity#onResume}.
582     */
583    public void onResume() {
584        updateActionBar();
585    }
586
587    /**
588     * Called from {@link EmailActivity#onPause}.
589     */
590    public void onPause() {
591    }
592
593    /**
594     * Called from {@link EmailActivity#onStop}.
595     */
596    public void onStop() {
597        stopMessageOrderManager();
598    }
599
600    /**
601     * Called from {@link EmailActivity#onDestroy}.
602     */
603    public void onDestroy() {
604        mHoldFragmentInstallation = true; // No more fragment installation.
605        mTaskTracker.cancellAllInterrupt();
606        closeMailboxFinder();
607        mRefreshManager.unregisterListener(mRefreshListener);
608    }
609
610    public void onSaveInstanceState(Bundle outState) {
611        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
612            Log.d(Logging.LOG_TAG, "" + this + " onSaveInstanceState");
613        }
614        outState.putLong(BUNDLE_KEY_ACCOUNT_ID, mAccountId);
615        outState.putLong(BUNDLE_KEY_MAILBOX_ID, mMailboxListMailboxId);
616        outState.putLong(BUNDLE_KEY_MESSAGE_ID, mMessageId);
617        if (!mMailboxStack.isEmpty()) {
618            // Save the mailbox stack
619            long[] mailboxIds = Utility.toPrimitiveLongArray(mMailboxStack);
620            outState.putLongArray(BUNDLE_KEY_MAILBOX_STACK, mailboxIds);
621        }
622    }
623
624    public void restoreInstanceState(Bundle savedInstanceState) {
625        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
626            Log.d(Logging.LOG_TAG, "" + this + " restoreInstanceState");
627        }
628        mAccountId = savedInstanceState.getLong(BUNDLE_KEY_ACCOUNT_ID, NO_ACCOUNT);
629        mMailboxListMailboxId = savedInstanceState.getLong(BUNDLE_KEY_MAILBOX_ID, NO_MAILBOX);
630        mMessageId = savedInstanceState.getLong(BUNDLE_KEY_MESSAGE_ID, NO_MESSAGE);
631        long[] mailboxIds = savedInstanceState.getLongArray(BUNDLE_KEY_MAILBOX_STACK);
632        if (mailboxIds != null) {
633            // Restore the mailbox stack; ugly hack to get around 'Long' versus 'long'
634            mMailboxStack.clear();
635            for (long id : mailboxIds) {
636                mMailboxStack.push(id);
637            }
638        }
639
640        // STOPSHIP If MailboxFinder is still running, it needs restarting after loadState().
641        // This probably means we need to start MailboxFinder if mMailboxId == -1.
642    }
643
644    private void installFragment(Fragment fragment) {
645        if (fragment instanceof MailboxListFragment) {
646            mMailboxListFragment = (MailboxListFragment) fragment;
647            mMailboxListFragment.setCallback(this);
648        } else if (fragment instanceof MessageListFragment) {
649            mMessageListFragment = (MessageListFragment) fragment;
650            mMessageListFragment.setCallback(this);
651        } else if (fragment instanceof MessageViewFragment) {
652            mMessageViewFragment = (MessageViewFragment) fragment;
653            mMessageViewFragment.setCallback(this);
654        } else {
655            // Ignore -- uninteresting fragments such as dialogs.
656        }
657    }
658
659    private FragmentTransaction uninstallMailboxListFragment(FragmentTransaction ft) {
660        if (mMailboxListFragment != null) {
661            ft.remove(mMailboxListFragment);
662            mMailboxListFragment.setCallback(null);
663            mMailboxListFragment = null;
664        }
665        return ft;
666    }
667
668    private FragmentTransaction uninstallMessageListFragment(FragmentTransaction ft) {
669        if (mMessageListFragment != null) {
670            ft.remove(mMessageListFragment);
671            mMessageListFragment.setCallback(null);
672            mMessageListFragment = null;
673        }
674        return ft;
675    }
676
677    private FragmentTransaction uninstallMessageViewFragment(FragmentTransaction ft) {
678        if (mMessageViewFragment != null) {
679            ft.remove(mMessageViewFragment);
680            mMessageViewFragment.setCallback(null);
681            mMessageViewFragment = null;
682        }
683        return ft;
684    }
685
686    private void commitFragmentTransaction(FragmentTransaction ft) {
687        ft.commit();
688        mActivity.getFragmentManager().executePendingTransactions();
689    }
690
691    /**
692     * Show the default view for the account.
693     *
694     * On two-pane, it's the account's root mailboxes on the left pane with Inbox on the right pane.
695     *
696     * @param accountId ID of the account to load.  Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
697     *     Must never be {@link #NO_ACCOUNT}.
698     */
699    public void openAccount(long accountId) {
700        mMailboxStack.clear();
701        updateActionBar();
702        open(accountId, NO_MAILBOX, NO_MESSAGE);
703    }
704
705    /**
706     * Opens the given mailbox. on two-pane, this will update both the mailbox list and the
707     * message list.
708     *
709     * NOTE: It's assumed that the mailbox is associated with the specified account. If the
710     * mailbox is not associated with the account, the behaviour is undefined.
711     */
712    private void openMailbox(long accountId, long mailboxId) {
713        updateActionBar();
714        updateMailboxList(accountId, mailboxId, true, true);
715        updateMessageList(mailboxId, true, true);
716    }
717
718    /**
719     * Loads the given account and optionally selects the given mailbox and message.  Used to open
720     * a particular view at a request from outside of the activity, such as the widget.
721     *
722     * @param accountId ID of the account to load.  Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
723     *     Must never be {@link #NO_ACCOUNT}.
724     * @param mailboxId ID of the mailbox to load. If {@link #NO_MAILBOX}, load the account's inbox.
725     * @param messageId ID of the message to load. If {@link #NO_MESSAGE}, do not open a message.
726     */
727    public void open(long accountId, long mailboxId, long messageId) {
728        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
729            Log.d(Logging.LOG_TAG, "" + this + " open accountId=" + accountId
730                    + " mailboxId=" + mailboxId + " messageId=" + messageId);
731        }
732        if (accountId == NO_ACCOUNT) {
733            throw new IllegalArgumentException();
734        } else if (mailboxId == NO_MAILBOX) {
735            updateMailboxList(accountId, NO_MAILBOX, true, true);
736
737            // Show the appropriate message list
738            if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
739                // When opening the Combined view, the right pane will be "combined inbox".
740                updateMessageList(Mailbox.QUERY_ALL_INBOXES, true, true);
741            } else {
742                // Try to find the inbox for the account
743                closeMailboxFinder();
744                mMailboxFinder = new MailboxFinder(mActivity, mAccountId, Mailbox.TYPE_INBOX, this);
745                mMailboxFinder.startLookup();
746            }
747        } else if (messageId == NO_MESSAGE) {
748            // STOPSHIP Use the appropriate parent mailbox ID
749            updateMailboxList(accountId, mailboxId, true, true);
750            updateMessageList(mailboxId, true, true);
751        } else {
752            // STOPSHIP Use the appropriate parent mailbox ID
753            updateMailboxList(accountId, mailboxId, false, true);
754            updateMessageList(mailboxId, false, true);
755            updateMessageView(messageId);
756        }
757    }
758
759    /**
760     * Pre-fragment transaction check.
761     *
762     * @throw IllegalStateException if updateXxx methods can't be called in the current state.
763     */
764    private void preFragmentTransactionCheck() {
765        if (mHoldFragmentInstallation) {
766            // Code assumes mMailboxListFragment/etc are set right within the
767            // commitFragmentTransaction() call (because we use synchronous transaction),
768            // so updateXxx() can't be called if fragments are not installable yet.
769            throw new IllegalStateException();
770        }
771    }
772
773    /**
774     * Loads the given account and optionally selects the given mailbox and message. If the
775     * specified account is already selected, no actions will be performed unless
776     * <code>forceReload</code> is <code>true</code>.
777     *
778     * @param accountId ID of the account to load. Must never be {@link #NO_ACCOUNT}.
779     * @param parentMailboxId ID of the mailbox to use as the parent mailbox.  Pass
780     *     {@link #NO_MAILBOX} to show the root mailboxes.
781     * @param changeVisiblePane if true, the message view will be hidden.
782     * @param clearDependentPane if true, the message list and the message view will be cleared
783     */
784
785    // TODO The name "updateMailboxList" is misleading, as it also updates members such as
786    // mAccountId.  We need better structure but let's do that after refactoring
787    // MailboxListFragment.onMailboxSelected, and removed the UI callbacks such as
788    // TargetActivity.onAccountChanged.
789
790    private void updateMailboxList(long accountId, long parentMailboxId,
791            boolean changeVisiblePane, boolean clearDependentPane) {
792        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
793            Log.d(Logging.LOG_TAG, "" + this + " updateMailboxList accountId=" + accountId
794                    + " parentMailboxId=" + parentMailboxId);
795        }
796        preFragmentTransactionCheck();
797        if (accountId == NO_ACCOUNT) {
798            throw new InvalidParameterException();
799        }
800
801        // TODO Check if the current fragment has been initialized with the same parameters, and
802        // then return.
803
804        mAccountId = accountId;
805        mMailboxListMailboxId = parentMailboxId;
806
807        // Open mailbox list, remove message list / message view
808        final FragmentManager fm = mActivity.getFragmentManager();
809        final FragmentTransaction ft = fm.beginTransaction();
810        uninstallMailboxListFragment(ft);
811        if (clearDependentPane) {
812            mMessageId = NO_MESSAGE;
813            uninstallMessageListFragment(ft);
814            uninstallMessageViewFragment(ft);
815        }
816        ft.add(mThreePane.getLeftPaneId(),
817                MailboxListFragment.newInstance(getUIAccountId(), parentMailboxId));
818        commitFragmentTransaction(ft);
819
820        if (changeVisiblePane) {
821            mThreePane.showLeftPane();
822        }
823        updateRefreshProgress();
824    }
825
826    /**
827     * Go back to a mailbox list view. If a message view is currently active, it will
828     * be hidden.
829     */
830    private void goBackToMailbox() {
831        if (isMessageSelected()) {
832            mThreePane.showLeftPane(); // Show mailbox list
833        }
834    }
835
836    /**
837     * Selects the specified mailbox and optionally loads a message within it. If a message is
838     * not loaded, a list of the messages contained within the mailbox is shown. Otherwise the
839     * given message is shown. If <code>navigateToMailbox<code> is <code>true</code>, the
840     * mailbox is navigated to and any contained mailboxes are shown.
841     *
842     * @param mailboxId ID of the mailbox to load. Must never be <code>0</code> or <code>-1</code>.
843     * @param changeVisiblePane if true, the message view will be hidden.
844     * @param clearDependentPane if true, the message view will be cleared
845     */
846    private void updateMessageList(long mailboxId, boolean changeVisiblePane,
847            boolean clearDependentPane) {
848        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
849            Log.d(Logging.LOG_TAG, "" + this + " updateMessageList mMailboxId=" + mailboxId);
850        }
851        preFragmentTransactionCheck();
852        if (mailboxId == 0 || mailboxId == -1) {
853            throw new InvalidParameterException();
854        }
855
856        // TODO Check if the current fragment has been initialized with the same parameters, and
857        // then return.
858
859        final FragmentManager fm = mActivity.getFragmentManager();
860        final FragmentTransaction ft = fm.beginTransaction();
861        uninstallMessageListFragment(ft);
862        if (clearDependentPane) {
863            uninstallMessageViewFragment(ft);
864            mMessageId = NO_MESSAGE;
865        }
866        ft.add(mThreePane.getMiddlePaneId(), MessageListFragment.newInstance(mailboxId));
867        commitFragmentTransaction(ft);
868
869        if (changeVisiblePane) {
870            mThreePane.showLeftPane();
871        }
872
873        // TODO We shouldn't select the mailbox when we're updating the message list. These two
874        // functions should be done separately. Find a better location for this call to be done.
875        mMailboxListFragment.setSelectedMailbox(mailboxId);
876        updateRefreshProgress();
877    }
878
879    /**
880     * Show a message on the message view.
881     *
882     * @param messageId ID of the mailbox to load. Must never be {@link #NO_MESSAGE}.
883     */
884    private void updateMessageView(long messageId) {
885        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
886            Log.d(Logging.LOG_TAG, "" + this + " updateMessageView messageId=" + messageId);
887        }
888        preFragmentTransactionCheck();
889        if (messageId == NO_MESSAGE) {
890            throw new InvalidParameterException();
891        }
892
893        // TODO Check if the current fragment has been initialized with the same parameters, and
894        // then return.
895
896        // Update member
897        mMessageId = messageId;
898
899        // Open message
900        final FragmentManager fm = mActivity.getFragmentManager();
901        final FragmentTransaction ft = fm.beginTransaction();
902        uninstallMessageViewFragment(ft);
903        ft.add(mThreePane.getRightPaneId(), MessageViewFragment.newInstance(messageId));
904        commitFragmentTransaction(ft);
905
906        mThreePane.showRightPane(); // Show message view
907
908        mMessageListFragment.setSelectedMessage(mMessageId);
909    }
910
911    private void closeMailboxFinder() {
912        if (mMailboxFinder != null) {
913            mMailboxFinder.cancel();
914            mMailboxFinder = null;
915        }
916    }
917
918    private class CommandButtonCallback implements MessageCommandButtonView.Callback {
919        @Override
920        public void onMoveToNewer() {
921            moveToNewer();
922        }
923
924        @Override
925        public void onMoveToOlder() {
926            moveToOlder();
927        }
928    }
929
930    private void onCurrentMessageGone() {
931        switch (Preferences.getPreferences(mActivity).getAutoAdvanceDirection()) {
932            case Preferences.AUTO_ADVANCE_NEWER:
933                if (moveToNewer()) return;
934                break;
935            case Preferences.AUTO_ADVANCE_OLDER:
936                if (moveToOlder()) return;
937                break;
938        }
939        // Last message in the box or AUTO_ADVANCE_MESSAGE_LIST.  Go back to message list.
940        goBackToMailbox();
941    }
942
943    /**
944     * Potentially create a new {@link MessageOrderManager}; if it's not already started or if
945     * the account has changed, and sync it to the current message.
946     */
947    private void updateMessageOrderManager() {
948        if (!isMailboxSelected()) {
949            return;
950        }
951        final long mailboxId = getMessageListMailboxId();
952        if (mOrderManager == null || mOrderManager.getMailboxId() != mailboxId) {
953            stopMessageOrderManager();
954            mOrderManager =
955                new MessageOrderManager(mActivity, mailboxId, mMessageOrderManagerCallback);
956        }
957        if (isMessageSelected()) {
958            mOrderManager.moveTo(getMessageId());
959        }
960    }
961
962    private class MessageOrderManagerCallback implements MessageOrderManager.Callback {
963        @Override
964        public void onMessagesChanged() {
965            updateNavigationArrows();
966        }
967
968        @Override
969        public void onMessageNotFound() {
970            // Current message gone.
971            goBackToMailbox();
972        }
973    }
974
975    /**
976     * Stop {@link MessageOrderManager}.
977     */
978    private void stopMessageOrderManager() {
979        if (mOrderManager != null) {
980            mOrderManager.close();
981            mOrderManager = null;
982        }
983    }
984
985    /**
986     * Disable/enable the move-to-newer/older buttons.
987     */
988    private void updateNavigationArrows() {
989        if (mOrderManager == null) {
990            // shouldn't happen, but just in case
991            mMessageCommandButtons.enableNavigationButtons(false, false, 0, 0);
992        } else {
993            mMessageCommandButtons.enableNavigationButtons(
994                    mOrderManager.canMoveToNewer(), mOrderManager.canMoveToOlder(),
995                    mOrderManager.getCurrentPosition(), mOrderManager.getTotalMessageCount());
996        }
997    }
998
999    private boolean moveToOlder() {
1000        if ((mOrderManager != null) && mOrderManager.moveToOlder()) {
1001            updateMessageView(mOrderManager.getCurrentMessageId());
1002            return true;
1003        }
1004        return false;
1005    }
1006
1007    private boolean moveToNewer() {
1008        if ((mOrderManager != null) && mOrderManager.moveToNewer()) {
1009            updateMessageView(mOrderManager.getCurrentMessageId());
1010            return true;
1011        }
1012        return false;
1013    }
1014
1015    /**
1016     * Load account list for the action bar.
1017     *
1018     * If there's only one account configured, show the account name in the action bar.
1019     * If more than one account are configured, show a spinner in the action bar, and select the
1020     * current account.
1021     */
1022    private void loadAccounts() {
1023        if (mAccountsSelectorAdapter == null) {
1024            mAccountsSelectorAdapter = new AccountSelectorAdapter(mActivity);
1025        }
1026        mActivity.getLoaderManager().initLoader(LOADER_ID_ACCOUNT_LIST, null,
1027                new LoaderCallbacks<Cursor>() {
1028            @Override
1029            public Loader<Cursor> onCreateLoader(int id, Bundle args) {
1030                return AccountSelectorAdapter.createLoader(mActivity);
1031            }
1032
1033            @Override
1034            public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
1035                updateAccountList(data);
1036            }
1037
1038            @Override
1039            public void onLoaderReset(Loader<Cursor> loader) {
1040                mAccountsSelectorAdapter.swapCursor(null);
1041            }
1042        });
1043    }
1044
1045    /**
1046     * Called when the LOADER_ID_ACCOUNT_LIST loader loads the data.  Update the account spinner
1047     * on the action bar.
1048     */
1049    private void updateAccountList(Cursor accountsCursor) {
1050        final int count = accountsCursor.getCount();
1051        if (count == 0) {
1052            // Open Welcome, which in turn shows the adding a new account screen.
1053            Welcome.actionStart(mActivity);
1054            mActivity.finish();
1055            return;
1056        }
1057
1058        // If ony one acount, don't show dropdown.
1059        final ActionBar ab = mActionBar;
1060        if (count == 1) {
1061            accountsCursor.moveToFirst();
1062
1063            // Show the account name as the title.
1064            ab.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE);
1065            ab.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
1066            ab.setTitle(AccountSelectorAdapter.getAccountDisplayName(accountsCursor));
1067            return;
1068        }
1069
1070        // Find the currently selected account, and select it.
1071        int defaultSelection = 0;
1072        if (isAccountSelected()) {
1073            accountsCursor.moveToPosition(-1);
1074            int i = 0;
1075            while (accountsCursor.moveToNext()) {
1076                final long accountId = AccountSelectorAdapter.getAccountId(accountsCursor);
1077                if (accountId == getUIAccountId()) {
1078                    defaultSelection = i;
1079                    break;
1080                }
1081                i++;
1082            }
1083        }
1084
1085        // Update the dropdown list.
1086        mAccountsSelectorAdapter.swapCursor(accountsCursor);
1087
1088        // Don't show the title.
1089        ab.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
1090        ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
1091        ab.setListNavigationCallbacks(mAccountsSelectorAdapter, mActionBarNavigationCallback);
1092        ab.setSelectedNavigationItem(defaultSelection);
1093    }
1094
1095    private class ActionBarNavigationCallback implements ActionBar.OnNavigationListener {
1096        @Override
1097        public boolean onNavigationItemSelected(int itemPosition, long accountId) {
1098            // TODO openAccount should do the check eventually, but it's necessary for now.
1099            if (accountId != getUIAccountId()) {
1100                openAccount(accountId);
1101            }
1102            return true;
1103        }
1104    }
1105
1106    /**
1107     * Handles {@link android.app.Activity#onCreateOptionsMenu} callback.
1108     */
1109    public boolean onCreateOptionsMenu(MenuInflater inflater, Menu menu) {
1110        inflater.inflate(R.menu.message_list_xl_option, menu);
1111        return true;
1112    }
1113
1114    /**
1115     * Handles {@link android.app.Activity#onPrepareOptionsMenu} callback.
1116     */
1117    public boolean onPrepareOptionsMenu(MenuInflater inflater, Menu menu) {
1118        ActivityHelper.updateRefreshMenuIcon(menu.findItem(R.id.refresh),
1119                isRefreshEnabled(),
1120                isRefreshInProgress());
1121        return true;
1122    }
1123
1124    /**
1125     * Handles {@link android.app.Activity#onOptionsItemSelected} callback.
1126     *
1127     * @return true if the option item is handled.
1128     */
1129    public boolean onOptionsItemSelected(MenuItem item) {
1130        switch (item.getItemId()) {
1131            case android.R.id.home:
1132                // Comes from the action bar when the app icon on the left is pressed.
1133                // It works like a back press, but it won't close the activity.
1134                return onBackPressed(false);
1135            case R.id.compose:
1136                return onCompose();
1137            case R.id.refresh:
1138                onRefresh();
1139                return true;
1140            case R.id.account_settings:
1141                return onAccountSettings();
1142        }
1143        return false;
1144    }
1145
1146    /**
1147     * Performs the back action.
1148     *
1149     * @param isSystemBackKey <code>true</code> if the system back key was pressed.
1150     * <code>true</code> if it's caused by the "home" icon click on the action bar.
1151     */
1152    public boolean onBackPressed(boolean isSystemBackKey) {
1153        if (mThreePane.onBackPressed(isSystemBackKey)) {
1154            return true;
1155        } else if (!mMailboxStack.isEmpty()) {
1156            long mailboxId = mMailboxStack.pop();
1157            if (mailboxId == NO_MAILBOX) {
1158                // No mailbox; reload the top-level message list
1159                openAccount(mAccountId);
1160            } else {
1161                openMailbox(mAccountId, mailboxId);
1162            }
1163            return true;
1164        }
1165        return false;
1166    }
1167
1168    /**
1169     * Handles the "Compose" option item.  Opens the message compose activity.
1170     */
1171    private boolean onCompose() {
1172        if (!isAccountSelected()) {
1173            return false; // this shouldn't really happen
1174        }
1175        MessageCompose.actionCompose(mActivity, getActualAccountId());
1176        return true;
1177    }
1178
1179    /**
1180     * Handles the "Compose" option item.  Opens the settings activity.
1181     */
1182    private boolean onAccountSettings() {
1183        AccountSettingsXL.actionSettings(mActivity, getActualAccountId());
1184        return true;
1185    }
1186
1187    /**
1188     * Handles the "refresh" option item.  Opens the settings activity.
1189     */
1190    // TODO used by experimental code in the activity -- otherwise can be private.
1191    public void onRefresh() {
1192        // Cancel previously running instance if any.
1193        new RefreshTask(mTaskTracker, mActivity, getActualAccountId(),
1194                getMessageListMailboxId()).cancelPreviousAndExecuteParallel();
1195    }
1196
1197    /**
1198     * Start/stop the "refresh" animation on the action bar according to the current refresh state.
1199     *
1200     * (We start the animation if {@link UIControllerTwoPane#isRefreshInProgress} returns true,
1201     * and stop otherwise.)
1202     */
1203    private void updateRefreshProgress() {
1204        mActivity.invalidateOptionsMenu();
1205    }
1206
1207    private class RefreshListener
1208            implements RefreshManager.Listener {
1209        @Override
1210        public void onMessagingError(final long accountId, long mailboxId, final String message) {
1211            updateRefreshProgress();
1212        }
1213
1214        @Override
1215        public void onRefreshStatusChanged(long accountId, long mailboxId) {
1216            updateRefreshProgress();
1217        }
1218    }
1219
1220    /**
1221     * Class to handle refresh.
1222     *
1223     * When the user press "refresh",
1224     * <ul>
1225     *   <li>Refresh the current mailbox, if it's refreshable.  (e.g. don't refresh combined inbox,
1226     *       drafts, etc.
1227     *   <li>Refresh the mailbox list, if it hasn't been refreshed in the last
1228     *       {@link #MAILBOX_REFRESH_MIN_INTERVAL}.
1229     *   <li>Refresh inbox, if it's not the current mailbox and it hasn't been refreshed in the last
1230     *       {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}.
1231     * </ul>
1232     */
1233    /* package */ static class RefreshTask extends EmailAsyncTask<Void, Void, Boolean> {
1234        private final Clock mClock;
1235        private final Context mContext;
1236        private final long mAccountId;
1237        private final long mMailboxId;
1238        private final RefreshManager mRefreshManager;
1239        /* package */ long mInboxId;
1240
1241        public RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId,
1242                long mailboxId) {
1243            this(tracker, context, accountId, mailboxId, Clock.INSTANCE,
1244                    RefreshManager.getInstance(context));
1245        }
1246
1247        /* package */ RefreshTask(EmailAsyncTask.Tracker tracker, Context context, long accountId,
1248                long mailboxId, Clock clock, RefreshManager refreshManager) {
1249            super(tracker);
1250            mClock = clock;
1251            mContext = context;
1252            mRefreshManager = refreshManager;
1253            mAccountId = accountId;
1254            mMailboxId = mailboxId;
1255        }
1256
1257        /**
1258         * Do DB access on a worker thread.
1259         */
1260        @Override
1261        protected Boolean doInBackground(Void... params) {
1262            mInboxId = Account.getInboxId(mContext, mAccountId);
1263            return Mailbox.isRefreshable(mContext, mMailboxId);
1264        }
1265
1266        /**
1267         * Do the actual refresh.
1268         */
1269        @Override
1270        protected void onPostExecute(Boolean isCurrentMailboxRefreshable) {
1271            if (isCancelled() || isCurrentMailboxRefreshable == null) {
1272                return;
1273            }
1274            if (isCurrentMailboxRefreshable) {
1275                mRefreshManager.refreshMessageList(mAccountId, mMailboxId, false);
1276            }
1277            // Refresh mailbox list
1278            if (mAccountId != -1) {
1279                if (shouldRefreshMailboxList()) {
1280                    mRefreshManager.refreshMailboxList(mAccountId);
1281                }
1282            }
1283            // Refresh inbox
1284            if (shouldAutoRefreshInbox()) {
1285                mRefreshManager.refreshMessageList(mAccountId, mInboxId, false);
1286            }
1287        }
1288
1289        /**
1290         * @return true if the mailbox list of the current account hasn't been refreshed
1291         * in the last {@link #MAILBOX_REFRESH_MIN_INTERVAL}.
1292         */
1293        /* package */ boolean shouldRefreshMailboxList() {
1294            if (mRefreshManager.isMailboxListRefreshing(mAccountId)) {
1295                return false;
1296            }
1297            final long nextRefreshTime = mRefreshManager.getLastMailboxListRefreshTime(mAccountId)
1298                    + MAILBOX_REFRESH_MIN_INTERVAL;
1299            if (nextRefreshTime > mClock.getTime()) {
1300                return false;
1301            }
1302            return true;
1303        }
1304
1305        /**
1306         * @return true if the inbox of the current account hasn't been refreshed
1307         * in the last {@link #INBOX_AUTO_REFRESH_MIN_INTERVAL}.
1308         */
1309        /* package */ boolean shouldAutoRefreshInbox() {
1310            if (mInboxId == mMailboxId) {
1311                return false; // Current ID == inbox.  No need to auto-refresh.
1312            }
1313            if (mRefreshManager.isMessageListRefreshing(mInboxId)) {
1314                return false;
1315            }
1316            final long nextRefreshTime = mRefreshManager.getLastMessageListRefreshTime(mInboxId)
1317                    + INBOX_AUTO_REFRESH_MIN_INTERVAL;
1318            if (nextRefreshTime > mClock.getTime()) {
1319                return false;
1320            }
1321            return true;
1322        }
1323    }
1324}
1325