UIControllerBase.java revision 4689cb7100a8b1cfa188499a9910912d15b3dad3
1/*
2 * Copyright (C) 2011 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.Email;
20import com.android.email.MessageListContext;
21import com.android.email.R;
22import com.android.email.RefreshManager;
23import com.android.email.activity.setup.AccountSettings;
24import com.android.emailcommon.Logging;
25import com.android.emailcommon.provider.Account;
26import com.android.emailcommon.provider.EmailContent.Message;
27import com.android.emailcommon.provider.HostAuth;
28import com.android.emailcommon.provider.Mailbox;
29import com.android.emailcommon.utility.EmailAsyncTask;
30import com.android.emailcommon.utility.Utility;
31import com.google.common.base.Objects;
32
33import android.app.Activity;
34import android.app.Fragment;
35import android.app.FragmentManager;
36import android.app.FragmentTransaction;
37import android.os.Bundle;
38import android.util.Log;
39import android.view.Menu;
40import android.view.MenuInflater;
41import android.view.MenuItem;
42
43import java.util.LinkedList;
44import java.util.List;
45
46/**
47 * Base class for the UI controller.
48 */
49abstract class UIControllerBase implements MailboxListFragment.Callback,
50        MessageListFragment.Callback, MessageViewFragment.Callback  {
51    static final boolean DEBUG_FRAGMENTS = false; // DO NOT SUBMIT WITH TRUE
52
53    static final String KEY_LIST_CONTEXT = "UIControllerBase.listContext";
54
55    /** The owner activity */
56    final EmailActivity mActivity;
57    final FragmentManager mFragmentManager;
58
59    protected final ActionBarController mActionBarController;
60
61    final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker();
62
63    final RefreshManager mRefreshManager;
64
65    /**
66     * Fragments that are installed.
67     *
68     * A fragment is installed in {@link Fragment#onActivityCreated} and uninstalled in
69     * {@link Fragment#onDestroyView}, using {@link FragmentInstallable} callbacks.
70     *
71     * This means fragments in the back stack are *not* installed.
72     *
73     * We set callbacks to fragments only when they are installed.
74     *
75     * @see FragmentInstallable
76     */
77    private MailboxListFragment mMailboxListFragment;
78    private MessageListFragment mMessageListFragment;
79    private MessageViewFragment mMessageViewFragment;
80
81    /**
82     * To avoid double-deleting a fragment (which will cause a runtime exception),
83     * we put a fragment in this list when we {@link FragmentTransaction#remove(Fragment)} it,
84     * and remove from the list when we actually uninstall it.
85     */
86    private final List<Fragment> mRemovedFragments = new LinkedList<Fragment>();
87
88    /**
89     * The active context for the current MessageList.
90     * In some UI layouts such as the one-pane view, the message list may not be visible, but is
91     * on the backstack. This list context will still be accessible in those cases.
92     *
93     * Should be set using {@link #setListContext(MessageListContext)}.
94     */
95    protected MessageListContext mListContext;
96
97    private final RefreshManager.Listener mRefreshListener
98            = new RefreshManager.Listener() {
99        @Override
100        public void onMessagingError(final long accountId, long mailboxId, final String message) {
101            refreshActionBar();
102        }
103
104        @Override
105        public void onRefreshStatusChanged(long accountId, long mailboxId) {
106            refreshActionBar();
107        }
108    };
109
110    public UIControllerBase(EmailActivity activity) {
111        mActivity = activity;
112        mFragmentManager = activity.getFragmentManager();
113        mRefreshManager = RefreshManager.getInstance(mActivity);
114        mActionBarController = createActionBarController(activity);
115        if (DEBUG_FRAGMENTS) {
116            FragmentManager.enableDebugLogging(true);
117        }
118    }
119
120    /**
121     * Called by the base class to let a subclass create an {@link ActionBarController}.
122     */
123    protected abstract ActionBarController createActionBarController(Activity activity);
124
125    /** @return the layout ID for the activity. */
126    public abstract int getLayoutId();
127
128    /**
129     * Must be called just after the activity sets up the content view.  Used to initialize views.
130     *
131     * (Due to the complexity regarding class/activity initialization order, we can't do this in
132     * the constructor.)
133     */
134    public void onActivityViewReady() {
135        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
136            Log.d(Logging.LOG_TAG, this + " onActivityViewReady");
137        }
138    }
139
140    /**
141     * Called at the end of {@link EmailActivity#onCreate}.
142     */
143    public void onActivityCreated() {
144        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
145            Log.d(Logging.LOG_TAG, this + " onActivityCreated");
146        }
147        mRefreshManager.registerListener(mRefreshListener);
148        mActionBarController.onActivityCreated();
149    }
150
151    /**
152     * Handles the {@link android.app.Activity#onStart} callback.
153     */
154    public void onActivityStart() {
155        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
156            Log.d(Logging.LOG_TAG, this + " onActivityStart");
157        }
158    }
159
160    /**
161     * Handles the {@link android.app.Activity#onResume} callback.
162     */
163    public void onActivityResume() {
164        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
165            Log.d(Logging.LOG_TAG, this + " onActivityResume");
166        }
167        refreshActionBar();
168    }
169
170    /**
171     * Handles the {@link android.app.Activity#onPause} callback.
172     */
173    public void onActivityPause() {
174        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
175            Log.d(Logging.LOG_TAG, this + " onActivityPause");
176        }
177    }
178
179    /**
180     * Handles the {@link android.app.Activity#onStop} callback.
181     */
182    public void onActivityStop() {
183        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
184            Log.d(Logging.LOG_TAG, this + " onActivityStop");
185        }
186    }
187
188    /**
189     * Handles the {@link android.app.Activity#onDestroy} callback.
190     */
191    public void onActivityDestroy() {
192        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
193            Log.d(Logging.LOG_TAG, this + " onActivityDestroy");
194        }
195        mActionBarController.onActivityDestroy();
196        mRefreshManager.unregisterListener(mRefreshListener);
197        mTaskTracker.cancellAllInterrupt();
198    }
199
200    /**
201     * Handles the {@link android.app.Activity#onSaveInstanceState} callback.
202     */
203    public void onSaveInstanceState(Bundle outState) {
204        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
205            Log.d(Logging.LOG_TAG, this + " onSaveInstanceState");
206        }
207        mActionBarController.onSaveInstanceState(outState);
208        outState.putParcelable(KEY_LIST_CONTEXT, mListContext);
209    }
210
211    /**
212     * Handles the {@link android.app.Activity#onRestoreInstanceState} callback.
213     */
214    public void onRestoreInstanceState(Bundle savedInstanceState) {
215        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
216            Log.d(Logging.LOG_TAG, this + " restoreInstanceState");
217        }
218        mActionBarController.onRestoreInstanceState(savedInstanceState);
219        mListContext = savedInstanceState.getParcelable(KEY_LIST_CONTEXT);
220    }
221
222    /**
223     * Install a fragment.  Must be caleld from the host activity's
224     * {@link FragmentInstallable#onInstallFragment}.
225     */
226    public final void onInstallFragment(Fragment fragment) {
227        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
228            Log.d(Logging.LOG_TAG, this + " onInstallFragment  fragment=" + fragment);
229        }
230        if (fragment instanceof MailboxListFragment) {
231            installMailboxListFragment((MailboxListFragment) fragment);
232        } else if (fragment instanceof MessageListFragment) {
233            installMessageListFragment((MessageListFragment) fragment);
234        } else if (fragment instanceof MessageViewFragment) {
235            installMessageViewFragment((MessageViewFragment) fragment);
236        } else {
237            throw new IllegalArgumentException("Tried to install unknown fragment");
238        }
239    }
240
241    /** Install fragment */
242    protected void installMailboxListFragment(MailboxListFragment fragment) {
243        mMailboxListFragment = fragment;
244        mMailboxListFragment.setCallback(this);
245        refreshActionBar();
246    }
247
248    /** Install fragment */
249    protected void installMessageListFragment(MessageListFragment fragment) {
250        mMessageListFragment = fragment;
251        mMessageListFragment.setCallback(this);
252        refreshActionBar();
253    }
254
255    /** Install fragment */
256    protected void installMessageViewFragment(MessageViewFragment fragment) {
257        mMessageViewFragment = fragment;
258        mMessageViewFragment.setCallback(this);
259        refreshActionBar();
260    }
261
262    /**
263     * Uninstall a fragment.  Must be caleld from the host activity's
264     * {@link FragmentInstallable#onUninstallFragment}.
265     */
266    public final void onUninstallFragment(Fragment fragment) {
267        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
268            Log.d(Logging.LOG_TAG, this + " onUninstallFragment  fragment=" + fragment);
269        }
270        mRemovedFragments.remove(fragment);
271        if (fragment == mMailboxListFragment) {
272            uninstallMailboxListFragment();
273        } else if (fragment == mMessageListFragment) {
274            uninstallMessageListFragment();
275        } else if (fragment == mMessageViewFragment) {
276            uninstallMessageViewFragment();
277        } else {
278            throw new IllegalArgumentException("Tried to uninstall unknown fragment");
279        }
280    }
281
282    /** Uninstall {@link MailboxListFragment} */
283    protected void uninstallMailboxListFragment() {
284        mMailboxListFragment.setCallback(null);
285        mMailboxListFragment = null;
286    }
287
288    /** Uninstall {@link MessageListFragment} */
289    protected void uninstallMessageListFragment() {
290        mMessageListFragment.setCallback(null);
291        mMessageListFragment = null;
292    }
293
294    /** Uninstall {@link MessageViewFragment} */
295    protected void uninstallMessageViewFragment() {
296        mMessageViewFragment.setCallback(null);
297        mMessageViewFragment = null;
298    }
299
300    /**
301     * If a {@link Fragment} is not already in {@link #mRemovedFragments},
302     * {@link FragmentTransaction#remove} it and add to the list.
303     *
304     * Do nothing if {@code fragment} is null.
305     */
306    protected final void removeFragment(FragmentTransaction ft, Fragment fragment) {
307        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
308            Log.d(Logging.LOG_TAG, this + " removeFragment fragment=" + fragment);
309        }
310        if (fragment == null) {
311            return;
312        }
313        if (!mRemovedFragments.contains(fragment)) {
314            // STOPSHIP Remove log/catch.  b/4905749.
315            Log.d(Logging.LOG_TAG, "Removing " + fragment);
316            try {
317                ft.remove(fragment);
318            } catch (RuntimeException ex) {
319                Log.e(Logging.LOG_TAG, "Got RuntimeException trying to remove fragment: "
320                        + fragment, ex);
321                Log.e(Logging.LOG_TAG, Utility.dumpFragment(fragment));
322                throw ex;
323            }
324            addFragmentToRemovalList(fragment);
325        }
326    }
327
328    /**
329     * Remove a {@link Fragment} from {@link #mRemovedFragments}.  No-op if {@code fragment} is
330     * null.
331     *
332     * {@link #removeMailboxListFragment}, {@link #removeMessageListFragment} and
333     * {@link #removeMessageViewFragment} all call this, so subclasses don't have to do this when
334     * using them.
335     *
336     * However, unfortunately, subclasses have to call this manually when popping from the
337     * back stack to avoid double-delete.
338     */
339    protected void addFragmentToRemovalList(Fragment fragment) {
340        if (fragment != null) {
341            mRemovedFragments.add(fragment);
342        }
343    }
344
345    /**
346     * Remove the fragment if it's installed.
347     */
348    protected FragmentTransaction removeMailboxListFragment(FragmentTransaction ft) {
349        removeFragment(ft, mMailboxListFragment);
350        return ft;
351    }
352
353    /**
354     * Remove the fragment if it's installed.
355     */
356    protected FragmentTransaction removeMessageListFragment(FragmentTransaction ft) {
357        removeFragment(ft, mMessageListFragment);
358        return ft;
359    }
360
361    /**
362     * Remove the fragment if it's installed.
363     */
364    protected FragmentTransaction removeMessageViewFragment(FragmentTransaction ft) {
365        removeFragment(ft, mMessageViewFragment);
366        return ft;
367    }
368
369    /** @return true if a {@link MailboxListFragment} is installed. */
370    protected final boolean isMailboxListInstalled() {
371        return mMailboxListFragment != null;
372    }
373
374    /** @return true if a {@link MessageListFragment} is installed. */
375    protected final boolean isMessageListInstalled() {
376        return mMessageListFragment != null;
377    }
378
379    /** @return true if a {@link MessageViewFragment} is installed. */
380    protected final boolean isMessageViewInstalled() {
381        return mMessageViewFragment != null;
382    }
383
384    /** @return the installed {@link MailboxListFragment} or null. */
385    protected final MailboxListFragment getMailboxListFragment() {
386        return mMailboxListFragment;
387    }
388
389    /** @return the installed {@link MessageListFragment} or null. */
390    protected final MessageListFragment getMessageListFragment() {
391        return mMessageListFragment;
392    }
393
394    /** @return the installed {@link MessageViewFragment} or null. */
395    protected final MessageViewFragment getMessageViewFragment() {
396        return mMessageViewFragment;
397    }
398
399    /**
400     * @return the currently selected account ID, *or* {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
401     *
402     * @see #getActualAccountId()
403     */
404    public abstract long getUIAccountId();
405
406    /**
407     * @return true if an account is selected, or the current view is the combined view.
408     */
409    public final boolean isAccountSelected() {
410        return getUIAccountId() != Account.NO_ACCOUNT;
411    }
412
413    /**
414     * @return if an actual account is selected.  (i.e. {@link Account#ACCOUNT_ID_COMBINED_VIEW}
415     * is not considered "actual".s)
416     */
417    public final boolean isActualAccountSelected() {
418        return isAccountSelected() && (getUIAccountId() != Account.ACCOUNT_ID_COMBINED_VIEW);
419    }
420
421    /**
422     * @return the currently selected account ID.  If the current view is the combined view,
423     * it'll return {@link Account#NO_ACCOUNT}.
424     *
425     * @see #getUIAccountId()
426     */
427    public final long getActualAccountId() {
428        return isActualAccountSelected() ? getUIAccountId() : Account.NO_ACCOUNT;
429    }
430
431    /**
432     * Show the default view for the given account.
433     *
434     * @param accountId ID of the account to load.  Can be {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
435     *     Must never be {@link Account#NO_ACCOUNT}.
436     * @param forceShowInbox If {@code false} and the given account is already selected, do nothing.
437     *        If {@code false}, we always change the view even if the account is selected.
438     */
439    public final void switchAccount(long accountId, boolean forceShowInbox) {
440
441        if (Account.isSecurityHold(mActivity, accountId)) {
442            ActivityHelper.showSecurityHoldDialog(mActivity, accountId);
443            mActivity.finish();
444            return;
445        }
446
447        if (accountId == getUIAccountId() && !forceShowInbox) {
448            // Do nothing if the account is already selected.  Not even going back to the inbox.
449            return;
450        }
451
452        if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
453            openMailbox(accountId, Mailbox.QUERY_ALL_INBOXES);
454        } else {
455            long inboxId = Mailbox.findMailboxOfType(mActivity, accountId, Mailbox.TYPE_INBOX);
456            if (inboxId == Mailbox.NO_MAILBOX) {
457                // The account doesn't have Inbox yet... Redirect to Welcome and let it wait for
458                // the initial sync...
459                Log.w(Logging.LOG_TAG, "Account " + accountId +" doesn't have Inbox.  Redirecting"
460                        + " to Welcome...");
461                Welcome.actionOpenAccountInbox(mActivity, accountId);
462                mActivity.finish();
463                return;
464            } else {
465                openMailbox(accountId, inboxId);
466            }
467        }
468    }
469
470    /**
471     * Returns the id of the parent mailbox used for the mailbox list fragment.
472     *
473     * IMPORTANT: Do not confuse {@link #getMailboxListMailboxId()} with
474     *     {@link #getMessageListMailboxId()}
475     */
476    protected long getMailboxListMailboxId() {
477        return isMailboxListInstalled() ? getMailboxListFragment().getSelectedMailboxId()
478                : Mailbox.NO_MAILBOX;
479    }
480
481    /**
482     * Returns the id of the mailbox used for the message list fragment.
483     *
484     * IMPORTANT: Do not confuse {@link #getMailboxListMailboxId()} with
485     *     {@link #getMessageListMailboxId()}
486     */
487    protected long getMessageListMailboxId() {
488        return isMessageListInstalled() ? getMessageListFragment().getMailboxId()
489                : Mailbox.NO_MAILBOX;
490    }
491
492    /**
493     * Shortcut for {@link #open} with {@link Message#NO_MESSAGE}.
494     */
495    protected final void openMailbox(long accountId, long mailboxId) {
496        open(MessageListContext.forMailbox(accountId, mailboxId), Message.NO_MESSAGE);
497    }
498
499    /**
500     * Opens a given list
501     * @param listContext the list context for the message list to open
502     * @param messageId if specified and not {@link Message#NO_MESSAGE}, will open the message
503     *     in the message list.
504     */
505    public final void open(final MessageListContext listContext, final long messageId) {
506        setListContext(listContext);
507        openInternal(listContext, messageId);
508
509        if (listContext.isSearch()) {
510            mActionBarController.enterSearchMode(listContext.getSearchParams().mFilter);
511        }
512    }
513
514    /**
515     * Sets the internal value of the list context for the message list.
516     */
517    protected void setListContext(MessageListContext listContext) {
518        if (Objects.equal(listContext, mListContext)) {
519            return;
520        }
521
522        // TODO: remove this when the search mailbox no longer shows up on the list
523        // Special case search. Since the search mailbox shows up in the mailbox list, the mailbox
524        // list can give us a callback to open that mailbox, and it will look like a normal
525        // mailbox open instead of a search, blowing away a perfectly good list context.
526        if (mListContext != null
527                && mListContext.isSearch()
528                && mListContext.getMailboxId() == listContext.getMailboxId()) {
529            return;
530        }
531
532        mListContext = listContext;
533    }
534
535    protected abstract void openInternal(
536            final MessageListContext listContext, final long messageId);
537
538    /**
539     * Performs the back action.
540     *
541     * NOTE The method in the base class has precedence.  Subclasses overriding this method MUST
542     * call super's method first.
543     *
544     * @param isSystemBackKey <code>true</code> if the system back key was pressed.
545     * <code>false</code> if it's caused by the "home" icon click on the action bar.
546     */
547    public boolean onBackPressed(boolean isSystemBackKey) {
548        if (mActionBarController.onBackPressed(isSystemBackKey)) {
549            return true;
550        }
551        return false;
552    }
553
554    /**
555     * Must be called from {@link Activity#onSearchRequested()}.
556     * This initiates the search entry mode - see {@link #onSearchSubmit} for when the search
557     * is actually submitted.
558     */
559    public void onSearchRequested() {
560        mActionBarController.enterSearchMode(null);
561    }
562
563    /** @return true if the search menu option should be enabled based on the current UI. */
564    protected boolean canSearch() {
565        return false;
566    }
567
568    /**
569     * Kicks off a search query, if the UI is in a state where a search is possible.
570     */
571    protected void onSearchSubmit(final String queryTerm) {
572        final long accountId = getUIAccountId();
573        if (!Account.isNormalAccount(accountId)) {
574            return; // Invalid account to search from.
575        }
576
577        // TODO: do a global search for EAS inbox.
578        // TODO: handle doing another search from a search result, in which case we should
579        //       search the original mailbox that was searched, and not search in the search mailbox
580        final long mailboxId = getMessageListMailboxId();
581
582        if (Email.DEBUG) {
583            Log.d(Logging.LOG_TAG, "Submitting search: " + queryTerm);
584        }
585
586        mActivity.startActivity(EmailActivity.createSearchIntent(
587                mActivity, accountId, mailboxId, queryTerm));
588
589
590        // TODO: this causes a slight flicker.
591        // A new instance of the activity will sit on top. When the user exits search and
592        // returns to this activity, the search box should not be open then.
593        mActionBarController.exitSearchMode();
594    }
595
596    /**
597     * Handles exiting of search entry mode.
598     */
599    protected void onSearchExit() {
600        if ((mListContext != null) && mListContext.isSearch()) {
601            mActivity.finish();
602        }
603    }
604
605    /**
606     * Handles the {@link android.app.Activity#onCreateOptionsMenu} callback.
607     */
608    public boolean onCreateOptionsMenu(MenuInflater inflater, Menu menu) {
609        inflater.inflate(R.menu.email_activity_options, menu);
610        return true;
611    }
612
613    /**
614     * Handles the {@link android.app.Activity#onPrepareOptionsMenu} callback.
615     */
616    public boolean onPrepareOptionsMenu(MenuInflater inflater, Menu menu) {
617
618        // Update the refresh button.
619        MenuItem item = menu.findItem(R.id.refresh);
620        if (isRefreshEnabled()) {
621            item.setVisible(true);
622            if (isRefreshInProgress()) {
623                item.setActionView(R.layout.action_bar_indeterminate_progress);
624            } else {
625                item.setActionView(null);
626            }
627        } else {
628            item.setVisible(false);
629        }
630
631        // Deal with protocol-specific menu options.
632        boolean isEas = false;
633        boolean accountSearchable = false;
634        long accountId = getActualAccountId();
635        if (accountId > 0) {
636            Account account = Account.restoreAccountWithId(mActivity, accountId);
637            if (account != null) {
638                String protocol = account.getProtocol(mActivity);
639                if (HostAuth.SCHEME_EAS.equals(protocol)) {
640                    isEas = true;
641                }
642                accountSearchable = (account.mFlags & Account.FLAGS_SUPPORTS_SEARCH) != 0;
643            }
644        }
645
646        // TODO: Should use an isSyncable call to prevent drafts/outbox from allowing this
647        menu.findItem(R.id.search).setVisible(accountSearchable && canSearch());
648        menu.findItem(R.id.sync_lookback).setVisible(isEas);
649        menu.findItem(R.id.sync_frequency).setVisible(isEas);
650
651        return true;
652    }
653
654    /**
655     * Handles the {@link android.app.Activity#onOptionsItemSelected} callback.
656     *
657     * @return true if the option item is handled.
658     */
659    public boolean onOptionsItemSelected(MenuItem item) {
660        switch (item.getItemId()) {
661            case android.R.id.home:
662                // Comes from the action bar when the app icon on the left is pressed.
663                // It works like a back press, but it won't close the activity.
664                return onBackPressed(false);
665            case R.id.compose:
666                return onCompose();
667            case R.id.refresh:
668                onRefresh();
669                return true;
670            case R.id.account_settings:
671                return onAccountSettings();
672            case R.id.search:
673                onSearchRequested();
674                return true;
675        }
676        return false;
677    }
678
679    /**
680     * Opens the message compose activity.
681     */
682    private boolean onCompose() {
683        if (!isAccountSelected()) {
684            return false; // this shouldn't really happen
685        }
686        MessageCompose.actionCompose(mActivity, getActualAccountId());
687        return true;
688    }
689
690    /**
691     * Handles the "Settings" option item.  Opens the settings activity.
692     */
693    private boolean onAccountSettings() {
694        AccountSettings.actionSettings(mActivity, getActualAccountId());
695        return true;
696    }
697
698    /**
699     * @return the ID of the message in focus and visible, if any. Returns
700     *     {@link Message#NO_MESSAGE} if no message is opened.
701     */
702    protected long getMessageId() {
703        return isMessageViewInstalled()
704                ? getMessageViewFragment().getMessageId()
705                : Message.NO_MESSAGE;
706    }
707
708
709    /**
710     * STOPSHIP For experimental UI.  Remove this.
711     *
712     * @return mailbox ID for "mailbox settings" option.
713     */
714    public abstract long getMailboxSettingsMailboxId();
715
716    /**
717     * STOPSHIP For experimental UI.  Make it abstract protected.
718     *
719     * Performs "refesh".
720     */
721    public abstract void onRefresh();
722
723    /**
724     * @return true if refresh is in progress for the current mailbox.
725     */
726    protected abstract boolean isRefreshInProgress();
727
728    /**
729     * @return true if the UI should enable the "refresh" command.
730     */
731    protected abstract boolean isRefreshEnabled();
732
733    /**
734     * Refresh the action bar and menu items, including the "refreshing" icon.
735     */
736    protected void refreshActionBar() {
737        if (mActionBarController != null) {
738            mActionBarController.refresh();
739        }
740        mActivity.invalidateOptionsMenu();
741    }
742
743
744    @Override
745    public String toString() {
746        return getClass().getSimpleName(); // Shown on logcat
747    }
748}
749