Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2012 Google Inc.
      3  * Licensed to The Android Open Source Project.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.mail.ui;
     19 
     20 import android.app.SearchManager;
     21 import android.app.SearchableInfo;
     22 import android.content.ContentResolver;
     23 import android.content.Context;
     24 import android.database.Cursor;
     25 import android.net.Uri;
     26 import android.os.AsyncTask;
     27 import android.os.Bundle;
     28 import android.support.v4.view.MenuItemCompat;
     29 import android.support.v7.app.ActionBar;
     30 import android.support.v7.widget.SearchView;
     31 import android.support.v7.widget.SearchView.OnQueryTextListener;
     32 import android.support.v7.widget.SearchView.OnSuggestionListener;
     33 import android.text.TextUtils;
     34 import android.view.Menu;
     35 import android.view.MenuItem;
     36 
     37 import com.android.mail.ConversationListContext;
     38 import com.android.mail.R;
     39 import com.android.mail.providers.Account;
     40 import com.android.mail.providers.AccountObserver;
     41 import com.android.mail.providers.Conversation;
     42 import com.android.mail.providers.Folder;
     43 import com.android.mail.providers.FolderObserver;
     44 import com.android.mail.providers.SearchRecentSuggestionsProvider;
     45 import com.android.mail.providers.UIProvider;
     46 import com.android.mail.providers.UIProvider.AccountCapabilities;
     47 import com.android.mail.providers.UIProvider.FolderCapabilities;
     48 import com.android.mail.providers.UIProvider.FolderType;
     49 import com.android.mail.utils.LogTag;
     50 import com.android.mail.utils.LogUtils;
     51 import com.android.mail.utils.Utils;
     52 
     53 /**
     54  * Controller to manage the various states of the {@link android.app.ActionBar}.
     55  */
     56 public class ActionBarController implements ViewMode.ModeChangeListener,
     57         OnQueryTextListener, OnSuggestionListener, MenuItemCompat.OnActionExpandListener {
     58 
     59     private final Context mContext;
     60 
     61     protected ActionBar mActionBar;
     62     protected ControllableActivity mActivity;
     63     protected ActivityController mController;
     64     /**
     65      * The current mode of the ActionBar and Activity
     66      */
     67     private ViewMode mViewModeController;
     68 
     69     /**
     70      * The account currently being shown
     71      */
     72     private Account mAccount;
     73     /**
     74      * The folder currently being shown
     75      */
     76     private Folder mFolder;
     77 
     78     private SearchView mSearchWidget;
     79     private MenuItem mSearch;
     80     private MenuItem mEmptyTrashItem;
     81     private MenuItem mEmptySpamItem;
     82 
     83     /** True if the current device is a tablet, false otherwise. */
     84     protected final boolean mIsOnTablet;
     85     private Conversation mCurrentConversation;
     86 
     87     public static final String LOG_TAG = LogTag.getLogTag();
     88 
     89     private FolderObserver mFolderObserver;
     90 
     91     /** Updates the resolver and tells it the most recent account. */
     92     private final class UpdateProvider extends AsyncTask<Bundle, Void, Void> {
     93         final Uri mAccount;
     94         final ContentResolver mResolver;
     95         public UpdateProvider(Uri account, ContentResolver resolver) {
     96             mAccount = account;
     97             mResolver = resolver;
     98         }
     99 
    100         @Override
    101         protected Void doInBackground(Bundle... params) {
    102             mResolver.call(mAccount, UIProvider.AccountCallMethods.SET_CURRENT_ACCOUNT,
    103                     mAccount.toString(), params[0]);
    104             return null;
    105         }
    106     }
    107 
    108     private final AccountObserver mAccountObserver = new AccountObserver() {
    109         @Override
    110         public void onChanged(Account newAccount) {
    111             updateAccount(newAccount);
    112         }
    113     };
    114 
    115     public ActionBarController(Context context) {
    116         mContext = context;
    117         mIsOnTablet = Utils.useTabletUI(context.getResources());
    118     }
    119 
    120     public void expandSearch() {
    121         if (mSearch != null) {
    122             MenuItemCompat.expandActionView(mSearch);
    123         }
    124     }
    125 
    126     /**
    127      * Close the search view if it is expanded.
    128      */
    129     public void collapseSearch() {
    130         if (mSearch != null) {
    131             MenuItemCompat.collapseActionView(mSearch);
    132         }
    133     }
    134 
    135     /**
    136      * Get the search menu item.
    137      */
    138     protected MenuItem getSearch() {
    139         return mSearch;
    140     }
    141 
    142     public boolean onCreateOptionsMenu(Menu menu) {
    143         mEmptyTrashItem = menu.findItem(R.id.empty_trash);
    144         mEmptySpamItem = menu.findItem(R.id.empty_spam);
    145         mSearch = menu.findItem(R.id.search);
    146 
    147         if (mSearch != null) {
    148             mSearchWidget = (SearchView) MenuItemCompat.getActionView(mSearch);
    149             MenuItemCompat.setOnActionExpandListener(mSearch, this);
    150             SearchManager searchManager = (SearchManager) mActivity.getActivityContext()
    151                     .getSystemService(Context.SEARCH_SERVICE);
    152             if (searchManager != null && mSearchWidget != null) {
    153                 SearchableInfo info = searchManager.getSearchableInfo(mActivity.getComponentName());
    154                 mSearchWidget.setSearchableInfo(info);
    155                 mSearchWidget.setOnQueryTextListener(this);
    156                 mSearchWidget.setOnSuggestionListener(this);
    157                 mSearchWidget.setIconifiedByDefault(true);
    158             }
    159         }
    160 
    161         // the menu should be displayed if the mode is known
    162         return getMode() != ViewMode.UNKNOWN;
    163     }
    164 
    165     public int getOptionsMenuId() {
    166         switch (getMode()) {
    167             case ViewMode.UNKNOWN:
    168                 return R.menu.conversation_list_menu;
    169             case ViewMode.CONVERSATION:
    170                 return R.menu.conversation_actions;
    171             case ViewMode.CONVERSATION_LIST:
    172                 return R.menu.conversation_list_menu;
    173             case ViewMode.SEARCH_RESULTS_LIST:
    174                 return R.menu.conversation_list_search_results_actions;
    175             case ViewMode.SEARCH_RESULTS_CONVERSATION:
    176                 return R.menu.conversation_actions;
    177             case ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION:
    178                 return R.menu.wait_mode_actions;
    179         }
    180         LogUtils.wtf(LOG_TAG, "Menu requested for unknown view mode");
    181         return R.menu.conversation_list_menu;
    182     }
    183 
    184     public void initialize(ControllableActivity activity, ActivityController callback,
    185             ActionBar actionBar) {
    186         mActionBar = actionBar;
    187         mController = callback;
    188         mActivity = activity;
    189 
    190         mFolderObserver = new FolderObserver() {
    191             @Override
    192             public void onChanged(Folder newFolder) {
    193                 onFolderUpdated(newFolder);
    194             }
    195         };
    196         // Return values are purposely discarded. Initialization happens quite early, and we don't
    197         // have a valid folder, or a valid list of accounts.
    198         mFolderObserver.initialize(mController);
    199         updateAccount(mAccountObserver.initialize(activity.getAccountController()));
    200     }
    201 
    202     private void updateAccount(Account account) {
    203         final boolean accountChanged = mAccount == null || !mAccount.uri.equals(account.uri);
    204         mAccount = account;
    205         if (mAccount != null && accountChanged) {
    206             final ContentResolver resolver = mActivity.getActivityContext().getContentResolver();
    207             final Bundle bundle = new Bundle(1);
    208             bundle.putParcelable(UIProvider.SetCurrentAccountColumns.ACCOUNT, account);
    209             final UpdateProvider updater = new UpdateProvider(mAccount.uri, resolver);
    210             updater.execute(bundle);
    211             setFolderAndAccount();
    212         }
    213     }
    214 
    215     /**
    216      * Called by the owner of the ActionBar to change the current folder.
    217      */
    218     public void setFolder(Folder folder) {
    219         mFolder = folder;
    220         setFolderAndAccount();
    221     }
    222 
    223     public void onDestroy() {
    224         if (mFolderObserver != null) {
    225             mFolderObserver.unregisterAndDestroy();
    226             mFolderObserver = null;
    227         }
    228         mAccountObserver.unregisterAndDestroy();
    229     }
    230 
    231     @Override
    232     public void onViewModeChanged(int newMode) {
    233         mActivity.supportInvalidateOptionsMenu();
    234         // Check if we are either on a phone, or in Conversation mode on tablet. For these, the
    235         // recent folders is enabled.
    236         switch (getMode()) {
    237             case ViewMode.UNKNOWN:
    238                 break;
    239             case ViewMode.CONVERSATION_LIST:
    240                 showNavList();
    241                 break;
    242             case ViewMode.SEARCH_RESULTS_CONVERSATION:
    243                 mActionBar.setDisplayHomeAsUpEnabled(true);
    244                 setEmptyMode();
    245                 break;
    246             case ViewMode.CONVERSATION:
    247             case ViewMode.AD:
    248                 closeSearchField();
    249                 mActionBar.setDisplayHomeAsUpEnabled(true);
    250                 setEmptyMode();
    251                 break;
    252             case ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION:
    253                 // We want the user to be able to switch accounts while waiting for an account
    254                 // to sync.
    255                 showNavList();
    256                 break;
    257         }
    258     }
    259 
    260     /**
    261      * Close the search query entry field to avoid keyboard events, and to restore the actionbar
    262      * to non-search mode.
    263      */
    264     private void closeSearchField() {
    265         if (mSearch == null) {
    266             return;
    267         }
    268         mSearch.collapseActionView();
    269     }
    270 
    271     protected int getMode() {
    272         if (mViewModeController != null) {
    273             return mViewModeController.getMode();
    274         } else {
    275             return ViewMode.UNKNOWN;
    276         }
    277     }
    278 
    279     /**
    280      * Helper function to ensure that the menu items that are prone to variable changes and race
    281      * conditions are properly set to the correct visibility
    282      */
    283     public void validateVolatileMenuOptionVisibility() {
    284         if (mEmptyTrashItem != null) {
    285             mEmptyTrashItem.setVisible(mAccount != null && mFolder != null
    286                     && mAccount.supportsCapability(AccountCapabilities.EMPTY_TRASH)
    287                     && mFolder.isTrash() && mFolder.totalCount > 0
    288                     && (mController.getConversationListCursor() == null
    289                     || mController.getConversationListCursor().getCount() > 0));
    290         }
    291         if (mEmptySpamItem != null) {
    292             mEmptySpamItem.setVisible(mAccount != null && mFolder != null
    293                     && mAccount.supportsCapability(AccountCapabilities.EMPTY_SPAM)
    294                     && mFolder.isType(FolderType.SPAM) && mFolder.totalCount > 0
    295                     && (mController.getConversationListCursor() == null
    296                     || mController.getConversationListCursor().getCount() > 0));
    297         }
    298     }
    299 
    300     public boolean onPrepareOptionsMenu(Menu menu) {
    301         // We start out with every option enabled. Based on the current view, we disable actions
    302         // that are possible.
    303         LogUtils.d(LOG_TAG, "ActionBarView.onPrepareOptionsMenu().");
    304 
    305         if (mController.shouldHideMenuItems()) {
    306             // Shortcut: hide all menu items if the drawer is shown
    307             final int size = menu.size();
    308 
    309             for (int i = 0; i < size; i++) {
    310                 final MenuItem item = menu.getItem(i);
    311                 item.setVisible(false);
    312             }
    313             return false;
    314         }
    315         validateVolatileMenuOptionVisibility();
    316 
    317         switch (getMode()) {
    318             case ViewMode.CONVERSATION:
    319             case ViewMode.SEARCH_RESULTS_CONVERSATION:
    320                 // We update the ActionBar options when we are entering conversation view because
    321                 // waiting for the AbstractConversationViewFragment to do it causes duplicate icons
    322                 // to show up during the time between the conversation is selected and the fragment
    323                 // is added.
    324                 setConversationModeOptions(menu);
    325                 break;
    326             case ViewMode.CONVERSATION_LIST:
    327                 // Show search if the account supports it
    328                 Utils.setMenuItemVisibility(menu, R.id.search, mAccount.supportsSearch());
    329                 break;
    330             case ViewMode.SEARCH_RESULTS_LIST:
    331                 // Hide compose and search
    332                 Utils.setMenuItemVisibility(menu, R.id.compose, false);
    333                 Utils.setMenuItemVisibility(menu, R.id.search, false);
    334                 break;
    335         }
    336 
    337         return false;
    338     }
    339 
    340     /**
    341      * Put the ActionBar in List navigation mode.
    342      */
    343     private void showNavList() {
    344         setTitleModeFlags(ActionBar.DISPLAY_SHOW_TITLE);
    345         setFolderAndAccount();
    346     }
    347 
    348     private void setTitle(String title) {
    349         if (!TextUtils.equals(title, mActionBar.getTitle())) {
    350             mActionBar.setTitle(title);
    351         }
    352     }
    353 
    354     /**
    355      * Set the actionbar mode to empty: no title, no subtitle, no custom view.
    356      */
    357     protected void setEmptyMode() {
    358         // Disable title/subtitle and the custom view by setting the bitmask to all off.
    359         setTitleModeFlags(0);
    360     }
    361 
    362     /**
    363      * Removes the back button from being shown
    364      */
    365     public void removeBackButton() {
    366         if (mActionBar == null) {
    367             return;
    368         }
    369         // Remove the back button but continue showing an icon.
    370         final int mask = ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME;
    371         mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME, mask);
    372         mActionBar.setHomeButtonEnabled(false);
    373     }
    374 
    375     public void setBackButton() {
    376         if (mActionBar == null) {
    377             return;
    378         }
    379         // Show home as up, and show an icon.
    380         final int mask = ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME;
    381         mActionBar.setDisplayOptions(mask, mask);
    382         mActionBar.setHomeButtonEnabled(true);
    383     }
    384 
    385     @Override
    386     public boolean onQueryTextSubmit(String query) {
    387         if (mSearch != null) {
    388             MenuItemCompat.collapseActionView(mSearch);
    389             mSearchWidget.setQuery("", false);
    390         }
    391         mController.executeSearch(query.trim());
    392         return true;
    393     }
    394 
    395     @Override
    396     public boolean onQueryTextChange(String newText) {
    397         return false;
    398     }
    399 
    400     // Next two methods are called when search suggestions are clicked.
    401     @Override
    402     public boolean onSuggestionSelect(int position) {
    403         return onSuggestionClick(position);
    404     }
    405 
    406     @Override
    407     public boolean onSuggestionClick(int position) {
    408         final Cursor c = mSearchWidget.getSuggestionsAdapter().getCursor();
    409         final boolean haveValidQuery = (c != null) && c.moveToPosition(position);
    410         if (!haveValidQuery) {
    411             LogUtils.d(LOG_TAG, "onSuggestionClick: Couldn't get a search query");
    412             // We haven't handled this query, but the default behavior will
    413             // leave EXTRA_ACCOUNT un-populated, leading to a crash. So claim
    414             // that we have handled the event.
    415             return true;
    416         }
    417         collapseSearch();
    418         // what is in the text field
    419         String queryText = mSearchWidget.getQuery().toString();
    420         // What the suggested query is
    421         String query = c.getString(c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1));
    422         // If the text the user typed in is a prefix of what is in the search
    423         // widget suggestion query, just take the search widget suggestion
    424         // query. Otherwise, it is a suffix and we want to remove matching
    425         // prefix portions.
    426         if (!TextUtils.isEmpty(queryText) && query.indexOf(queryText) != 0) {
    427             final int queryTokenIndex = queryText
    428                     .lastIndexOf(SearchRecentSuggestionsProvider.QUERY_TOKEN_SEPARATOR);
    429             if (queryTokenIndex > -1) {
    430                 queryText = queryText.substring(0, queryTokenIndex);
    431             }
    432             // Since we auto-complete on each token in a query, if the query the
    433             // user typed up until the last token is a substring of the
    434             // suggestion they click, make sure we don't double include the
    435             // query text. For example:
    436             // user types john, that matches john palo alto
    437             // User types john p, that matches john john palo alto
    438             // Remove the first john
    439             // Only do this if we have multiple query tokens.
    440             if (queryTokenIndex > -1 && !TextUtils.isEmpty(query) && query.contains(queryText)
    441                     && queryText.length() < query.length()) {
    442                 int start = query.indexOf(queryText);
    443                 query = query.substring(0, start) + query.substring(start + queryText.length());
    444             }
    445         }
    446         mController.executeSearch(query.trim());
    447         return true;
    448     }
    449 
    450     /**
    451      * Uses the current state to update the current folder {@link #mFolder} and the current
    452      * account {@link #mAccount} shown in the actionbar. Also updates the actionbar subtitle to
    453      * momentarily display the unread count if it has changed.
    454      */
    455     private void setFolderAndAccount() {
    456         // Very little can be done if the actionbar or activity is null.
    457         if (mActionBar == null || mActivity == null) {
    458             return;
    459         }
    460         if (ViewMode.isWaitingForSync(getMode())) {
    461             // Account is not synced: clear title and update the subtitle.
    462             setTitle("");
    463             return;
    464         }
    465         // Check if we should be changing the actionbar at all, and back off if not.
    466         final boolean isShowingFolder = mIsOnTablet || ViewMode.isListMode(getMode());
    467         if (!isShowingFolder) {
    468             // It isn't necessary to set the title in this case, as the title view will
    469             // be hidden
    470             return;
    471         }
    472         if (mFolder == null) {
    473             // Clear the action bar title.  We don't want the app name to be shown while
    474             // waiting for the folder query to finish
    475             setTitle("");
    476             return;
    477         }
    478         setTitle(mFolder.name);
    479     }
    480 
    481 
    482     /**
    483      * Notify that the folder has changed.
    484      */
    485     public void onFolderUpdated(Folder folder) {
    486         if (folder == null) {
    487             return;
    488         }
    489         /** True if we are changing folders. */
    490         final boolean changingFolders = (mFolder == null || !mFolder.equals(folder));
    491         mFolder = folder;
    492         setFolderAndAccount();
    493         final ConversationListContext listContext = mController == null ? null :
    494                 mController.getCurrentListContext();
    495         if (changingFolders && !ConversationListContext.isSearchResult(listContext)) {
    496             closeSearchField();
    497         }
    498         // make sure that we re-validate the optional menu items
    499         validateVolatileMenuOptionVisibility();
    500     }
    501 
    502     @Override
    503     public boolean onMenuItemActionExpand(MenuItem item) {
    504         // Do nothing. Required as part of the interface, we ar only interested in
    505         // onMenuItemActionCollapse(MenuItem).
    506         // Have to return true here. Unlike other callbacks, the return value here is whether
    507         // we want to suppress the action (rather than consume the action). We don't want to
    508         // suppress the action.
    509         return true;
    510     }
    511 
    512     @Override
    513     public boolean onMenuItemActionCollapse(MenuItem item) {
    514         // Have to return true here. Unlike other callbacks, the return value
    515         // here is whether we want to suppress the action (rather than consume the action). We
    516         // don't want to suppress the action.
    517         return true;
    518     }
    519 
    520     /**
    521      * Sets the actionbar mode: Pass it an integer which contains each of these values, perhaps
    522      * OR'd together: {@link ActionBar#DISPLAY_SHOW_CUSTOM} and
    523      * {@link ActionBar#DISPLAY_SHOW_TITLE}. To disable all, pass a zero.
    524      * @param enabledFlags
    525      */
    526     private void setTitleModeFlags(int enabledFlags) {
    527         final int mask = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM;
    528         mActionBar.setDisplayOptions(enabledFlags, mask);
    529     }
    530 
    531     public void setCurrentConversation(Conversation conversation) {
    532         mCurrentConversation = conversation;
    533     }
    534 
    535     //We need to do this here instead of in the fragment
    536     public void setConversationModeOptions(Menu menu) {
    537         if (mCurrentConversation == null) {
    538             return;
    539         }
    540         final boolean showMarkImportant = !mCurrentConversation.isImportant();
    541         Utils.setMenuItemVisibility(menu, R.id.mark_important, showMarkImportant
    542                 && mAccount.supportsCapability(UIProvider.AccountCapabilities.MARK_IMPORTANT));
    543         Utils.setMenuItemVisibility(menu, R.id.mark_not_important, !showMarkImportant
    544                 && mAccount.supportsCapability(UIProvider.AccountCapabilities.MARK_IMPORTANT));
    545         final boolean isOutbox = mFolder.isType(FolderType.OUTBOX);
    546         final boolean showDiscardOutbox = mFolder != null && isOutbox &&
    547                 mCurrentConversation.sendingState == UIProvider.ConversationSendingState.SEND_ERROR;
    548         Utils.setMenuItemVisibility(menu, R.id.discard_outbox, showDiscardOutbox);
    549         final boolean showDelete = !isOutbox && mFolder != null &&
    550                 mFolder.supportsCapability(UIProvider.FolderCapabilities.DELETE);
    551         Utils.setMenuItemVisibility(menu, R.id.delete, showDelete);
    552         // We only want to show the discard drafts menu item if we are not showing the delete menu
    553         // item, and the current folder is a draft folder and the account supports discarding
    554         // drafts for a conversation
    555         final boolean showDiscardDrafts = !showDelete && mFolder != null && mFolder.isDraft() &&
    556                 mAccount.supportsCapability(AccountCapabilities.DISCARD_CONVERSATION_DRAFTS);
    557         Utils.setMenuItemVisibility(menu, R.id.discard_drafts, showDiscardDrafts);
    558         final boolean archiveVisible = mAccount.supportsCapability(AccountCapabilities.ARCHIVE)
    559                 && mFolder != null && mFolder.supportsCapability(FolderCapabilities.ARCHIVE)
    560                 && !mFolder.isTrash();
    561         Utils.setMenuItemVisibility(menu, R.id.archive, archiveVisible);
    562         Utils.setMenuItemVisibility(menu, R.id.remove_folder, !archiveVisible && mFolder != null
    563                 && mFolder.supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
    564                 && !mFolder.isProviderFolder()
    565                 && mAccount.supportsCapability(AccountCapabilities.ARCHIVE));
    566         Utils.setMenuItemVisibility(menu, R.id.move_to, mFolder != null
    567                 && mFolder.supportsCapability(FolderCapabilities.ALLOWS_REMOVE_CONVERSATION));
    568         Utils.setMenuItemVisibility(menu, R.id.move_to_inbox, mFolder != null
    569                 && mFolder.supportsCapability(FolderCapabilities.ALLOWS_MOVE_TO_INBOX));
    570         Utils.setMenuItemVisibility(menu, R.id.change_folders, mAccount.supportsCapability(
    571                 UIProvider.AccountCapabilities.MULTIPLE_FOLDERS_PER_CONV));
    572 
    573         final MenuItem removeFolder = menu.findItem(R.id.remove_folder);
    574         if (mFolder != null && removeFolder != null) {
    575             removeFolder.setTitle(mActivity.getApplicationContext().getString(
    576                     R.string.remove_folder, mFolder.name));
    577         }
    578         Utils.setMenuItemVisibility(menu, R.id.report_spam,
    579                 mAccount.supportsCapability(AccountCapabilities.REPORT_SPAM) && mFolder != null
    580                         && mFolder.supportsCapability(FolderCapabilities.REPORT_SPAM)
    581                         && !mCurrentConversation.spam);
    582         Utils.setMenuItemVisibility(menu, R.id.mark_not_spam,
    583                 mAccount.supportsCapability(AccountCapabilities.REPORT_SPAM) && mFolder != null
    584                         && mFolder.supportsCapability(FolderCapabilities.MARK_NOT_SPAM)
    585                         && mCurrentConversation.spam);
    586         Utils.setMenuItemVisibility(menu, R.id.report_phishing,
    587                 mAccount.supportsCapability(AccountCapabilities.REPORT_PHISHING) && mFolder != null
    588                         && mFolder.supportsCapability(FolderCapabilities.REPORT_PHISHING)
    589                         && !mCurrentConversation.phishing);
    590         Utils.setMenuItemVisibility(menu, R.id.mute,
    591                         mAccount.supportsCapability(AccountCapabilities.MUTE) && mFolder != null
    592                         && mFolder.supportsCapability(FolderCapabilities.DESTRUCTIVE_MUTE)
    593                         && !mCurrentConversation.muted);
    594     }
    595 
    596     public void setViewModeController(ViewMode viewModeController) {
    597         mViewModeController = viewModeController;
    598         mViewModeController.addListener(this);
    599     }
    600 
    601     public Context getContext() {
    602         return mContext;
    603     }
    604 }
    605