Home | History | Annotate | Download | only in browse
      1 /*
      2  * Copyright (C) 2010 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.browse;
     19 
     20 import android.content.Context;
     21 import android.net.Uri;
     22 import android.os.AsyncTask;
     23 import android.view.ActionMode;
     24 import android.view.Menu;
     25 import android.view.MenuInflater;
     26 import android.view.MenuItem;
     27 import android.widget.Toast;
     28 
     29 import com.android.mail.R;
     30 import com.android.mail.analytics.Analytics;
     31 import com.android.mail.providers.Account;
     32 import com.android.mail.providers.AccountObserver;
     33 import com.android.mail.providers.Conversation;
     34 import com.android.mail.providers.Folder;
     35 import com.android.mail.providers.MailAppProvider;
     36 import com.android.mail.providers.Settings;
     37 import com.android.mail.providers.UIProvider;
     38 import com.android.mail.providers.UIProvider.AccountCapabilities;
     39 import com.android.mail.providers.UIProvider.ConversationColumns;
     40 import com.android.mail.providers.UIProvider.FolderCapabilities;
     41 import com.android.mail.providers.UIProvider.FolderType;
     42 import com.android.mail.ui.ControllableActivity;
     43 import com.android.mail.ui.ConversationListCallbacks;
     44 import com.android.mail.ui.ConversationSelectionSet;
     45 import com.android.mail.ui.ConversationSetObserver;
     46 import com.android.mail.ui.ConversationUpdater;
     47 import com.android.mail.ui.DestructiveAction;
     48 import com.android.mail.ui.FolderOperation;
     49 import com.android.mail.ui.FolderSelectionDialog;
     50 import com.android.mail.ui.MailActionBarView;
     51 import com.android.mail.utils.LogTag;
     52 import com.android.mail.utils.LogUtils;
     53 import com.android.mail.utils.Utils;
     54 import com.google.common.annotations.VisibleForTesting;
     55 import com.google.common.collect.Lists;
     56 
     57 import java.util.Collection;
     58 import java.util.List;
     59 
     60 /**
     61  * A component that displays a custom view for an {@code ActionBar}'s {@code
     62  * ContextMode} specific to operating on a set of conversations.
     63  */
     64 public class SelectedConversationsActionMenu implements ActionMode.Callback,
     65         ConversationSetObserver {
     66 
     67     private static final String LOG_TAG = LogTag.getLogTag();
     68 
     69     /**
     70      * The set of conversations to display the menu for.
     71      */
     72     protected final ConversationSelectionSet mSelectionSet;
     73 
     74     private final ControllableActivity mActivity;
     75     private final ConversationListCallbacks mListController;
     76     /**
     77      * Context of the activity. A dialog requires the context of an activity rather than the global
     78      * root context of the process. So mContext = mActivity.getApplicationContext() will fail.
     79      */
     80     private final Context mContext;
     81 
     82     @VisibleForTesting
     83     private ActionMode mActionMode;
     84 
     85     private boolean mActivated = false;
     86 
     87     private Menu mMenu;
     88 
     89     /** Object that can update conversation state on our behalf. */
     90     private final ConversationUpdater mUpdater;
     91 
     92     private Account mAccount;
     93 
     94     private final Folder mFolder;
     95 
     96     private AccountObserver mAccountObserver;
     97 
     98     public SelectedConversationsActionMenu(
     99             ControllableActivity activity, ConversationSelectionSet selectionSet, Folder folder) {
    100         mActivity = activity;
    101         mListController = activity.getListHandler();
    102         mSelectionSet = selectionSet;
    103         mAccountObserver = new AccountObserver() {
    104             @Override
    105             public void onChanged(Account newAccount) {
    106                 mAccount = newAccount;
    107             }
    108         };
    109         mAccount = mAccountObserver.initialize(activity.getAccountController());
    110         mFolder = folder;
    111         mContext = mActivity.getActivityContext();
    112         mUpdater = activity.getConversationUpdater();
    113         FolderSelectionDialog.setDialogDismissed();
    114     }
    115 
    116     @Override
    117     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    118         boolean handled = true;
    119         // If the user taps a new menu item, commit any existing destructive actions.
    120         mListController.commitDestructiveActions(true);
    121         final int itemId = item.getItemId();
    122 
    123         Analytics.getInstance().sendMenuItemEvent(Analytics.EVENT_CATEGORY_MENU_ITEM, itemId,
    124                 "cab_mode", 0);
    125 
    126         if (itemId == R.id.delete) {
    127             LogUtils.i(LOG_TAG, "Delete selected from CAB menu");
    128             performDestructiveAction(R.id.delete);
    129         } else if (itemId == R.id.discard_drafts) {
    130             performDestructiveAction(R.id.discard_drafts);
    131         } else if (itemId == R.id.archive) {
    132             LogUtils.i(LOG_TAG, "Archive selected from CAB menu");
    133             performDestructiveAction(R.id.archive);
    134         } else if (itemId == R.id.remove_folder) {
    135             destroy(R.id.remove_folder, mSelectionSet.values(),
    136                     mUpdater.getDeferredRemoveFolder(mSelectionSet.values(), mFolder, true,
    137                             true, true));
    138         } else if (itemId == R.id.mute) {
    139             destroy(R.id.mute, mSelectionSet.values(), mUpdater.getBatchAction(R.id.mute));
    140         } else if (itemId == R.id.report_spam) {
    141             destroy(R.id.report_spam, mSelectionSet.values(),
    142                     mUpdater.getBatchAction(R.id.report_spam));
    143         } else if (itemId == R.id.mark_not_spam) {
    144             // Currently, since spam messages are only shown in list with other spam messages,
    145             // marking a message not as spam is a destructive action
    146             destroy (R.id.mark_not_spam,
    147                     mSelectionSet.values(), mUpdater.getBatchAction(R.id.mark_not_spam)) ;
    148         } else if (itemId == R.id.report_phishing) {
    149             destroy(R.id.report_phishing,
    150                     mSelectionSet.values(), mUpdater.getBatchAction(R.id.report_phishing));
    151         } else if (itemId == R.id.read) {
    152             markConversationsRead(true);
    153         } else if (itemId == R.id.unread) {
    154             markConversationsRead(false);
    155         } else if (itemId == R.id.star) {
    156             starConversations(true);
    157         } else if (itemId == R.id.remove_star) {
    158             if (mFolder.isType(UIProvider.FolderType.STARRED)) {
    159                 LogUtils.d(LOG_TAG, "We are in a starred folder, removing the star");
    160                 performDestructiveAction(R.id.remove_star);
    161             } else {
    162                 LogUtils.d(LOG_TAG, "Not in a starred folder.");
    163                 starConversations(false);
    164             }
    165         } else if (itemId == R.id.move_to || itemId == R.id.change_folders) {
    166             boolean cantMove = false;
    167             Account acct = mAccount;
    168             // Special handling for virtual folders
    169             if (mFolder.supportsCapability(FolderCapabilities.IS_VIRTUAL)) {
    170                 Uri accountUri = null;
    171                 for (Conversation conv: mSelectionSet.values()) {
    172                     if (accountUri == null) {
    173                         accountUri = conv.accountUri;
    174                     } else if (!accountUri.equals(conv.accountUri)) {
    175                         // Tell the user why we can't do this
    176                         Toast.makeText(mContext, R.string.cant_move_or_change_labels,
    177                                 Toast.LENGTH_LONG).show();
    178                         cantMove = true;
    179                         return handled;
    180                     }
    181                 }
    182                 if (!cantMove) {
    183                     // Get the actual account here, so that we display its folders in the dialog
    184                     acct = MailAppProvider.getAccountFromAccountUri(accountUri);
    185                 }
    186             }
    187             if (!cantMove) {
    188                 final FolderSelectionDialog dialog = FolderSelectionDialog.getInstance(
    189                         mContext, acct, mUpdater, mSelectionSet.values(), true, mFolder,
    190                         item.getItemId() == R.id.move_to);
    191                 if (dialog != null) {
    192                     dialog.show();
    193                 }
    194             }
    195         } else if (itemId == R.id.move_to_inbox) {
    196             new AsyncTask<Void, Void, Folder>() {
    197                 @Override
    198                 protected Folder doInBackground(final Void... params) {
    199                     // Get the "move to" inbox
    200                     return Utils.getFolder(mContext, mAccount.settings.moveToInbox,
    201                             true /* allowHidden */);
    202                 }
    203 
    204                 @Override
    205                 protected void onPostExecute(final Folder moveToInbox) {
    206                     final List<FolderOperation> ops = Lists.newArrayListWithCapacity(1);
    207                     // Add inbox
    208                     ops.add(new FolderOperation(moveToInbox, true));
    209                     mUpdater.assignFolder(ops, mSelectionSet.values(), true,
    210                             true /* showUndo */, false /* isMoveTo */);
    211                 }
    212             }.execute((Void[]) null);
    213         } else if (itemId == R.id.mark_important) {
    214             markConversationsImportant(true);
    215         } else if (itemId == R.id.mark_not_important) {
    216             if (mFolder.supportsCapability(UIProvider.FolderCapabilities.ONLY_IMPORTANT)) {
    217                 performDestructiveAction(R.id.mark_not_important);
    218             } else {
    219                 markConversationsImportant(false);
    220             }
    221         } else {
    222             handled = false;
    223         }
    224         return handled;
    225     }
    226 
    227     /**
    228      * Clear the selection and perform related UI changes to keep the state consistent.
    229      */
    230     private void clearSelection() {
    231         mSelectionSet.clear();
    232     }
    233 
    234     /**
    235      * Update the underlying list adapter and redraw the menus if necessary.
    236      */
    237     private void updateSelection() {
    238         mUpdater.refreshConversationList();
    239         if (mActionMode != null) {
    240             // Calling mActivity.invalidateOptionsMenu doesn't have the correct behavior, since
    241             // the action mode is not refreshed when activity's options menu is invalidated.
    242             // Since we need to refresh our own menu, it is easy to call onPrepareActionMode
    243             // directly.
    244             onPrepareActionMode(mActionMode, mActionMode.getMenu());
    245         }
    246     }
    247 
    248     private void performDestructiveAction(final int action) {
    249         final Collection<Conversation> conversations = mSelectionSet.values();
    250         final Settings settings = mAccount.settings;
    251         final boolean showDialog;
    252         // no confirmation dialog by default unless user preference or common sense dictates one
    253         if (action == R.id.discard_drafts) {
    254             // drafts are lost forever, so always confirm
    255             showDialog = true;
    256         } else if (settings != null && (action == R.id.archive || action == R.id.delete)) {
    257             showDialog = (action == R.id.delete) ? settings.confirmDelete : settings.confirmArchive;
    258         } else {
    259             showDialog = false;
    260         }
    261         if (showDialog) {
    262             mUpdater.makeDialogListener(action, true /* fromSelectedSet */);
    263             final int resId;
    264             if (action == R.id.delete) {
    265                 resId = R.plurals.confirm_delete_conversation;
    266             } else if (action == R.id.discard_drafts) {
    267                 resId = R.plurals.confirm_discard_drafts_conversation;
    268             } else {
    269                 resId = R.plurals.confirm_archive_conversation;
    270             }
    271             final CharSequence message = Utils.formatPlural(mContext, resId, conversations.size());
    272             final ConfirmDialogFragment c = ConfirmDialogFragment.newInstance(message);
    273             c.displayDialog(mActivity.getFragmentManager());
    274         } else {
    275             // No need to show the dialog, just make a destructive action and destroy the
    276             // selected set immediately.
    277             // TODO(viki): Stop using the deferred action here. Use the registered action.
    278             destroy(action, conversations, mUpdater.getDeferredBatchAction(action));
    279         }
    280     }
    281 
    282     /**
    283      * Destroy these conversations through the conversation updater
    284      * @param actionId the ID of the action: R.id.archive, R.id.delete, ...
    285      * @param target conversations to destroy
    286      * @param action the action that performs the destruction
    287      */
    288     private void destroy(int actionId, final Collection<Conversation> target,
    289             final DestructiveAction action) {
    290         LogUtils.i(LOG_TAG, "About to remove %d converations", target.size());
    291         mUpdater.delete(actionId, target, action, true);
    292     }
    293 
    294     /**
    295      * Marks the read state of currently selected conversations (<b>and</b> the backing storage)
    296      * to the value provided here.
    297      * @param read is true if the conversations are to be marked as read, false if they are to be
    298      * marked unread.
    299      */
    300     private void markConversationsRead(boolean read) {
    301         final Collection<Conversation> targets = mSelectionSet.values();
    302         // The conversations are marked read but not viewed.
    303         mUpdater.markConversationsRead(targets, read, false);
    304         updateSelection();
    305     }
    306 
    307     /**
    308      * Marks the important state of currently selected conversations (<b>and</b> the backing
    309      * storage) to the value provided here.
    310      * @param important is true if the conversations are to be marked as important, false if they
    311      * are to be marked not important.
    312      */
    313     private void markConversationsImportant(boolean important) {
    314         final Collection<Conversation> target = mSelectionSet.values();
    315         final int priority = important ? UIProvider.ConversationPriority.HIGH
    316                 : UIProvider.ConversationPriority.LOW;
    317         mUpdater.updateConversation(target, ConversationColumns.PRIORITY, priority);
    318         // Update the conversations in the selection too.
    319         for (final Conversation c : target) {
    320             c.priority = priority;
    321         }
    322         updateSelection();
    323     }
    324 
    325     /**
    326      * Marks the selected conversations with the star setting provided here.
    327      * @param star true if you want all the conversations to have stars, false if you want to remove
    328      * stars from all conversations
    329      */
    330     private void starConversations(boolean star) {
    331         final Collection<Conversation> target = mSelectionSet.values();
    332         mUpdater.updateConversation(target, ConversationColumns.STARRED, star);
    333         // Update the conversations in the selection too.
    334         for (final Conversation c : target) {
    335             c.starred = star;
    336         }
    337         updateSelection();
    338     }
    339 
    340     @Override
    341     public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    342         mSelectionSet.addObserver(this);
    343         final MenuInflater inflater = mActivity.getMenuInflater();
    344         inflater.inflate(R.menu.conversation_list_selection_actions_menu, menu);
    345         mActionMode = mode;
    346         mMenu = menu;
    347         updateCount();
    348         return true;
    349     }
    350 
    351     @Override
    352     public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    353         // Update the actionbar to select operations available on the current conversation.
    354         final Collection<Conversation> conversations = mSelectionSet.values();
    355         boolean showStar = false;
    356         boolean showMarkUnread = false;
    357         boolean showMarkImportant = false;
    358         boolean showMarkNotSpam = false;
    359         boolean showMarkAsPhishing = false;
    360 
    361         for (Conversation conversation : conversations) {
    362             if (!conversation.starred) {
    363                 showStar = true;
    364             }
    365             if (conversation.read) {
    366                 showMarkUnread = true;
    367             }
    368             if (!conversation.isImportant()) {
    369                 showMarkImportant = true;
    370             }
    371             if (conversation.spam) {
    372                 showMarkNotSpam = true;
    373             }
    374             if (!conversation.phishing) {
    375                 showMarkAsPhishing = true;
    376             }
    377             if (showStar && showMarkUnread && showMarkImportant && showMarkNotSpam &&
    378                     showMarkAsPhishing) {
    379                 break;
    380             }
    381         }
    382         final MenuItem star = menu.findItem(R.id.star);
    383         star.setVisible(showStar);
    384         final MenuItem unstar = menu.findItem(R.id.remove_star);
    385         unstar.setVisible(!showStar);
    386         final MenuItem read = menu.findItem(R.id.read);
    387         read.setVisible(!showMarkUnread);
    388         final MenuItem unread = menu.findItem(R.id.unread);
    389         unread.setVisible(showMarkUnread);
    390         // We only ever show one of:
    391         // 1) remove folder
    392         // 2) archive
    393         // 3) If we show neither archive or remove folder, then show a disabled
    394         // archive icon if the setting for that is true.
    395         final MenuItem removeFolder = menu.findItem(R.id.remove_folder);
    396         final MenuItem moveTo = menu.findItem(R.id.move_to);
    397         final MenuItem moveToInbox = menu.findItem(R.id.move_to_inbox);
    398         final boolean showRemoveFolder = mFolder != null && mFolder.isType(FolderType.DEFAULT)
    399                 && mFolder.supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
    400                 && !mFolder.isProviderFolder()
    401                 && mAccount.supportsCapability(AccountCapabilities.ARCHIVE);
    402         final boolean showMoveTo = mFolder != null
    403                 && mFolder.supportsCapability(FolderCapabilities.ALLOWS_REMOVE_CONVERSATION);
    404         final boolean showMoveToInbox = mFolder != null
    405                 && mFolder.supportsCapability(FolderCapabilities.ALLOWS_MOVE_TO_INBOX);
    406         removeFolder.setVisible(showRemoveFolder);
    407         moveTo.setVisible(showMoveTo);
    408         moveToInbox.setVisible(showMoveToInbox);
    409 
    410         if (mFolder != null && showRemoveFolder) {
    411             removeFolder.setTitle(mActivity.getActivityContext().getString(R.string.remove_folder,
    412                     mFolder.name));
    413         }
    414         final MenuItem archive = menu.findItem(R.id.archive);
    415         final boolean accountSupportsArchive =
    416                 mAccount.supportsCapability(UIProvider.AccountCapabilities.ARCHIVE);
    417         boolean showArchive = accountSupportsArchive
    418                 && mFolder.supportsCapability(FolderCapabilities.ARCHIVE);
    419         if (archive == null) {
    420             showArchive = false;
    421         } else {
    422             archive.setVisible(showArchive);
    423         }
    424         // We may want to reshow the archive menu item, but only if the account supports archiving
    425         if (!showArchive && accountSupportsArchive) {
    426             if (!showRemoveFolder &&
    427                     Utils.shouldShowDisabledArchiveIcon(mActivity.getActivityContext())) {
    428                 archive.setEnabled(false);
    429                 archive.setVisible(true);
    430             }
    431         }
    432         final MenuItem spam = menu.findItem(R.id.report_spam);
    433         spam.setVisible(!showMarkNotSpam
    434                 && mAccount.supportsCapability(UIProvider.AccountCapabilities.REPORT_SPAM)
    435                 && mFolder.supportsCapability(FolderCapabilities.REPORT_SPAM));
    436         final MenuItem notSpam = menu.findItem(R.id.mark_not_spam);
    437         notSpam.setVisible(showMarkNotSpam &&
    438                 mAccount.supportsCapability(UIProvider.AccountCapabilities.REPORT_SPAM) &&
    439                 mFolder.supportsCapability(FolderCapabilities.MARK_NOT_SPAM));
    440         final MenuItem phishing = menu.findItem(R.id.report_phishing);
    441         phishing.setVisible(showMarkAsPhishing &&
    442                 mAccount.supportsCapability(UIProvider.AccountCapabilities.REPORT_PHISHING) &&
    443                 mFolder.supportsCapability(FolderCapabilities.REPORT_PHISHING));
    444 
    445         final MenuItem mute = menu.findItem(R.id.mute);
    446         if (mute != null) {
    447             mute.setVisible(mAccount.supportsCapability(UIProvider.AccountCapabilities.MUTE)
    448                     && (mFolder != null && mFolder.isInbox()));
    449         }
    450         final MenuItem markImportant = menu.findItem(R.id.mark_important);
    451         markImportant.setVisible(showMarkImportant
    452                 && mAccount.supportsCapability(UIProvider.AccountCapabilities.MARK_IMPORTANT));
    453         final MenuItem markNotImportant = menu.findItem(R.id.mark_not_important);
    454         markNotImportant.setVisible(!showMarkImportant
    455                 && mAccount.supportsCapability(UIProvider.AccountCapabilities.MARK_IMPORTANT));
    456         final boolean showDelete = mFolder != null
    457                 && mFolder.supportsCapability(UIProvider.FolderCapabilities.DELETE);
    458         final MenuItem trash = menu.findItem(R.id.delete);
    459         trash.setVisible(showDelete);
    460         // We only want to show the discard drafts menu item if we are not showing the delete menu
    461         // item, and the current folder is a draft folder and the account supports discarding
    462         // drafts for a conversation
    463         final boolean showDiscardDrafts = !showDelete && mFolder != null && mFolder.isDraft() &&
    464                 mAccount.supportsCapability(AccountCapabilities.DISCARD_CONVERSATION_DRAFTS);
    465         final MenuItem discardDrafts = menu.findItem(R.id.discard_drafts);
    466         if (discardDrafts != null) {
    467             discardDrafts.setVisible(showDiscardDrafts);
    468         }
    469 
    470         MailActionBarView.reorderMenu(mContext, mAccount, menu,
    471                 mContext.getResources().getInteger(R.integer.actionbar_max_items));
    472 
    473         return true;
    474     }
    475 
    476     @Override
    477     public void onDestroyActionMode(ActionMode mode) {
    478         mActionMode = null;
    479         // The action mode may have been destroyed due to this menu being deactivated, in which
    480         // case resources need not be cleaned up. However, if it was destroyed while this menu is
    481         // active, that implies the user hit "Done" in the top right, and resources need cleaning.
    482         if (mActivated) {
    483             destroy();
    484             // Only commit destructive actions if the user actually pressed
    485             // done; otherwise, this was handled when we toggled conversation
    486             // selection state.
    487             mActivity.getListHandler().commitDestructiveActions(true);
    488         }
    489         mMenu = null;
    490     }
    491 
    492     @Override
    493     public void onSetPopulated(ConversationSelectionSet set) {
    494         // Noop. This object can only exist while the set is non-empty.
    495     }
    496 
    497     @Override
    498     public void onSetEmpty() {
    499         LogUtils.d(LOG_TAG, "onSetEmpty called.");
    500         destroy();
    501     }
    502 
    503     @Override
    504     public void onSetChanged(ConversationSelectionSet set) {
    505         // If the set is empty, the menu buttons are invalid and most like the menu will be cleaned
    506         // up. Avoid making any changes to stop flickering ("Add Star" -> "Remove Star") just
    507         // before hiding the menu.
    508         if (set.isEmpty()) {
    509             return;
    510         }
    511         updateCount();
    512     }
    513 
    514     /**
    515      * Updates the visible count of how many conversations are selected.
    516      */
    517     private void updateCount() {
    518         if (mActionMode != null) {
    519             mActionMode.setTitle(mContext.getString(R.string.num_selected, mSelectionSet.size()));
    520         }
    521     }
    522 
    523     /**
    524      * Activates and shows this menu (essentially starting an {@link ActionMode}) if the selected
    525      * set is non-empty.
    526      */
    527     public void activate() {
    528         if (mSelectionSet.isEmpty()) {
    529             return;
    530         }
    531         mListController.onCabModeEntered();
    532         mActivated = true;
    533         if (mActionMode == null) {
    534             mActivity.startActionMode(this);
    535         }
    536     }
    537 
    538     /**
    539      * De-activates and hides the menu (essentially disabling the {@link ActionMode}), but maintains
    540      * the selection conversation set, and internally updates state as necessary.
    541      */
    542     public void deactivate() {
    543         mListController.onCabModeExited();
    544 
    545         if (mActionMode != null) {
    546             mActivated = false;
    547             mActionMode.finish();
    548         }
    549     }
    550 
    551     @VisibleForTesting
    552     public boolean isActivated() {
    553         return mActivated;
    554     }
    555 
    556     /**
    557      * Destroys and cleans up the resources associated with this menu.
    558      */
    559     private void destroy() {
    560         deactivate();
    561         mSelectionSet.removeObserver(this);
    562         clearSelection();
    563         mUpdater.refreshConversationList();
    564         if (mAccountObserver != null) {
    565             mAccountObserver.unregisterAndDestroy();
    566             mAccountObserver = null;
    567         }
    568     }
    569 
    570     /**
    571      * Disable the selected conversations menu item associated with a command
    572      * id.
    573      */
    574     public void disableCommand(int id) {
    575         enableMenuItem(id, false);
    576     }
    577 
    578     /**
    579      * Enable the selected conversations menu item associated with a command
    580      * id.
    581      */
    582     public void enableCommand(int id) {
    583         enableMenuItem(id, true);
    584     }
    585 
    586     private void enableMenuItem(int id, boolean enable) {
    587         if (mActivated) {
    588             MenuItem item = mMenu.findItem(id);
    589             if (item != null) {
    590                 item.setEnabled(enable);
    591             }
    592         }
    593     }
    594 }
    595