• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
UpdateProvider(Uri account, ContentResolver resolver)95         public UpdateProvider(Uri account, ContentResolver resolver) {
96             mAccount = account;
97             mResolver = resolver;
98         }
99 
100         @Override
doInBackground(Bundle... params)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 
ActionBarController(Context context)115     public ActionBarController(Context context) {
116         mContext = context;
117         mIsOnTablet = Utils.useTabletUI(context.getResources());
118     }
119 
expandSearch()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      */
collapseSearch()129     public void collapseSearch() {
130         if (mSearch != null) {
131             MenuItemCompat.collapseActionView(mSearch);
132         }
133     }
134 
135     /**
136      * Get the search menu item.
137      */
getSearch()138     protected MenuItem getSearch() {
139         return mSearch;
140     }
141 
onCreateOptionsMenu(Menu menu)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 
getOptionsMenuId()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 
initialize(ControllableActivity activity, ActivityController callback, ActionBar actionBar)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 
updateAccount(Account account)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      */
setFolder(Folder folder)218     public void setFolder(Folder folder) {
219         mFolder = folder;
220         setFolderAndAccount();
221     }
222 
onDestroy()223     public void onDestroy() {
224         if (mFolderObserver != null) {
225             mFolderObserver.unregisterAndDestroy();
226             mFolderObserver = null;
227         }
228         mAccountObserver.unregisterAndDestroy();
229     }
230 
231     @Override
onViewModeChanged(int newMode)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      */
closeSearchField()264     private void closeSearchField() {
265         if (mSearch == null) {
266             return;
267         }
268         mSearch.collapseActionView();
269     }
270 
getMode()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      */
validateVolatileMenuOptionVisibility()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 
onPrepareOptionsMenu(Menu menu)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      */
showNavList()343     private void showNavList() {
344         setTitleModeFlags(ActionBar.DISPLAY_SHOW_TITLE);
345         setFolderAndAccount();
346     }
347 
setTitle(String title)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      */
setEmptyMode()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      */
removeBackButton()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 
setBackButton()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
onQueryTextSubmit(String query)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
onQueryTextChange(String newText)396     public boolean onQueryTextChange(String newText) {
397         return false;
398     }
399 
400     // Next two methods are called when search suggestions are clicked.
401     @Override
onSuggestionSelect(int position)402     public boolean onSuggestionSelect(int position) {
403         return onSuggestionClick(position);
404     }
405 
406     @Override
onSuggestionClick(int position)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      */
setFolderAndAccount()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      */
onFolderUpdated(Folder folder)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
onMenuItemActionExpand(MenuItem item)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
onMenuItemActionCollapse(MenuItem item)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      */
setTitleModeFlags(int enabledFlags)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 
setCurrentConversation(Conversation conversation)531     public void setCurrentConversation(Conversation conversation) {
532         mCurrentConversation = conversation;
533     }
534 
535     //We need to do this here instead of in the fragment
setConversationModeOptions(Menu menu)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 
setViewModeController(ViewMode viewModeController)596     public void setViewModeController(ViewMode viewModeController) {
597         mViewModeController = viewModeController;
598         mViewModeController.addListener(this);
599     }
600 
getContext()601     public Context getContext() {
602         return mContext;
603     }
604 }
605