blob: 5ebf7cef912e03ab4ceb788a197225f3522c69b1 [file] [log] [blame]
Mindy Pereira11b0bbd2012-01-24 10:06:17 -08001/*
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
18package com.android.mail.browse;
19
Paul Westbrookbf232c32012-04-18 03:17:41 -070020import android.content.Context;
Marc Blanka6b671d2012-05-25 12:52:02 -070021import android.net.Uri;
Scott Kennedydd2ec682013-06-03 19:16:13 -070022import android.os.AsyncTask;
Andrew Sapperstein52882ff2014-07-27 12:30:18 -070023import android.support.v7.view.ActionMode;
Paul Westbrookbf232c32012-04-18 03:17:41 -070024import android.view.Menu;
25import android.view.MenuInflater;
26import android.view.MenuItem;
Marc Blanka6b671d2012-05-25 12:52:02 -070027import android.widget.Toast;
Paul Westbrookbf232c32012-04-18 03:17:41 -070028
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080029import com.android.mail.R;
Andy Huang042a5302013-08-13 12:39:08 -070030import com.android.mail.analytics.Analytics;
Mindy Pereira30d2d322012-02-03 08:40:49 -080031import com.android.mail.providers.Account;
Vikram Aggarwal7c401b72012-08-13 16:43:47 -070032import com.android.mail.providers.AccountObserver;
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080033import com.android.mail.providers.Conversation;
Mindy Pereira4f166de2012-02-14 13:40:58 -080034import com.android.mail.providers.Folder;
Marc Blanka6b671d2012-05-25 12:52:02 -070035import com.android.mail.providers.MailAppProvider;
Mindy Pereiraefe3d252012-03-01 14:20:44 -080036import com.android.mail.providers.Settings;
Mindy Pereiraf98b3182012-02-22 11:07:13 -080037import com.android.mail.providers.UIProvider;
Paul Westbrookef362542012-08-27 14:53:32 -070038import com.android.mail.providers.UIProvider.AccountCapabilities;
Marc Blankcf164d62012-04-20 08:56:17 -070039import com.android.mail.providers.UIProvider.ConversationColumns;
Paul Westbrookbf232c32012-04-18 03:17:41 -070040import com.android.mail.providers.UIProvider.FolderCapabilities;
Mindy Pereira9ba42512012-08-14 11:52:48 -070041import com.android.mail.providers.UIProvider.FolderType;
Vikram Aggarwal531488e2012-05-29 16:36:52 -070042import com.android.mail.ui.ControllableActivity;
Jin Caoec0fa482014-08-28 16:38:08 -070043import com.android.mail.ui.ConversationCheckedSet;
Vikram Aggarwala8e43182012-09-13 12:55:10 -070044import com.android.mail.ui.ConversationListCallbacks;
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -080045import com.android.mail.ui.ConversationSetObserver;
Vikram Aggarwal531488e2012-05-29 16:36:52 -070046import com.android.mail.ui.ConversationUpdater;
Marc Blanka6b671d2012-05-25 12:52:02 -070047import com.android.mail.ui.DestructiveAction;
Scott Kennedydd2ec682013-06-03 19:16:13 -070048import com.android.mail.ui.FolderOperation;
Mark Wei9eb1c9a2012-10-01 12:54:50 -070049import com.android.mail.ui.FolderSelectionDialog;
Paul Westbrookb334c902012-06-25 11:42:46 -070050import com.android.mail.utils.LogTag;
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080051import com.android.mail.utils.LogUtils;
Mindy Pereiraefe3d252012-03-01 14:20:44 -080052import com.android.mail.utils.Utils;
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080053import com.google.common.annotations.VisibleForTesting;
Scott Kennedydd2ec682013-06-03 19:16:13 -070054import com.google.common.collect.Lists;
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080055
56import java.util.Collection;
Scott Kennedydd2ec682013-06-03 19:16:13 -070057import java.util.List;
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080058
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080059/**
60 * A component that displays a custom view for an {@code ActionBar}'s {@code
61 * ContextMode} specific to operating on a set of conversations.
62 */
63public class SelectedConversationsActionMenu implements ActionMode.Callback,
Vikram Aggarwald503df42012-05-11 10:13:35 -070064 ConversationSetObserver {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080065
Paul Westbrookb334c902012-06-25 11:42:46 -070066 private static final String LOG_TAG = LogTag.getLogTag();
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080067
68 /**
69 * The set of conversations to display the menu for.
70 */
Jin Caoec0fa482014-08-28 16:38:08 -070071 protected final ConversationCheckedSet mCheckedSet;
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080072
Vikram Aggarwala8e43182012-09-13 12:55:10 -070073 private final ControllableActivity mActivity;
74 private final ConversationListCallbacks mListController;
Vikram Aggarwalcd8bf0a2012-02-14 13:13:18 -080075 /**
76 * Context of the activity. A dialog requires the context of an activity rather than the global
77 * root context of the process. So mContext = mActivity.getApplicationContext() will fail.
78 */
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080079 private final Context mContext;
80
81 @VisibleForTesting
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -070082 private ActionMode mActionMode;
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080083
84 private boolean mActivated = false;
85
Vikram Aggarwal531488e2012-05-29 16:36:52 -070086 /** Object that can update conversation state on our behalf. */
87 private final ConversationUpdater mUpdater;
Mindy Pereira30d2d322012-02-03 08:40:49 -080088
Vikram Aggarwal7c401b72012-08-13 16:43:47 -070089 private Account mAccount;
Mindy Pereirad92e9f32012-02-03 09:10:58 -080090
Vikram Aggarwal531488e2012-05-29 16:36:52 -070091 private final Folder mFolder;
Mindy Pereira4f166de2012-02-14 13:40:58 -080092
Mindy Pereira14f64ec2012-08-14 17:14:02 -070093 private AccountObserver mAccountObserver;
Mindy Pereira07118a02012-04-02 16:35:01 -070094
Jin Cao512821c2014-05-30 15:54:04 -070095 private MenuItem mDiscardOutboxMenuItem;
96
Vikram Aggarwal7704d792013-01-11 15:48:24 -080097 public SelectedConversationsActionMenu(
Jin Caoec0fa482014-08-28 16:38:08 -070098 ControllableActivity activity, ConversationCheckedSet checkedSet, Folder folder) {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -080099 mActivity = activity;
Vikram Aggarwala8e43182012-09-13 12:55:10 -0700100 mListController = activity.getListHandler();
Jin Caoec0fa482014-08-28 16:38:08 -0700101 mCheckedSet = checkedSet;
Mindy Pereira14f64ec2012-08-14 17:14:02 -0700102 mAccountObserver = new AccountObserver() {
103 @Override
104 public void onChanged(Account newAccount) {
105 mAccount = newAccount;
106 }
107 };
Vikram Aggarwal7c401b72012-08-13 16:43:47 -0700108 mAccount = mAccountObserver.initialize(activity.getAccountController());
Mindy Pereira4f166de2012-02-14 13:40:58 -0800109 mFolder = folder;
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700110 mContext = mActivity.getActivityContext();
mindyp54f120f2012-08-28 13:10:33 -0700111 mUpdater = activity.getConversationUpdater();
Vikram Aggarwale8a85322012-04-24 09:01:38 -0700112 }
113
Jin Caoa6b45212014-09-18 13:46:54 -0700114 public boolean onActionItemClicked(MenuItem item) {
115 return onActionItemClicked(mActionMode, item);
116 }
117
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800118 @Override
119 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
120 boolean handled = true;
Mindy Pereira8937bf12012-07-23 14:05:02 -0700121 // If the user taps a new menu item, commit any existing destructive actions.
Vikram Aggarwala8e43182012-09-13 12:55:10 -0700122 mListController.commitDestructiveActions(true);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700123 final int itemId = item.getItemId();
Andy Huang042a5302013-08-13 12:39:08 -0700124
Andy Huang2b555492013-08-14 21:06:21 -0700125 Analytics.getInstance().sendMenuItemEvent(Analytics.EVENT_CATEGORY_MENU_ITEM, itemId,
126 "cab_mode", 0);
Andy Huang042a5302013-08-13 12:39:08 -0700127
Jin Caof5586372014-04-08 14:28:36 -0700128 UndoCallback undoCallback = null; // not applicable here (yet)
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700129 if (itemId == R.id.delete) {
130 LogUtils.i(LOG_TAG, "Delete selected from CAB menu");
Jin Caof5586372014-04-08 14:28:36 -0700131 performDestructiveAction(R.id.delete, undoCallback);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700132 } else if (itemId == R.id.discard_drafts) {
Jin Cao512821c2014-05-30 15:54:04 -0700133 LogUtils.i(LOG_TAG, "Discard drafts selected from CAB menu");
Jin Caof5586372014-04-08 14:28:36 -0700134 performDestructiveAction(R.id.discard_drafts, undoCallback);
Jin Cao512821c2014-05-30 15:54:04 -0700135 } else if (itemId == R.id.discard_outbox) {
136 LogUtils.i(LOG_TAG, "Discard outbox selected from CAB menu");
137 performDestructiveAction(R.id.discard_outbox, undoCallback);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700138 } else if (itemId == R.id.archive) {
139 LogUtils.i(LOG_TAG, "Archive selected from CAB menu");
Jin Caof5586372014-04-08 14:28:36 -0700140 performDestructiveAction(R.id.archive, undoCallback);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700141 } else if (itemId == R.id.remove_folder) {
Jin Caoec0fa482014-08-28 16:38:08 -0700142 destroy(R.id.remove_folder, mCheckedSet.values(),
143 mUpdater.getDeferredRemoveFolder(mCheckedSet.values(), mFolder, true,
Jin Caof5586372014-04-08 14:28:36 -0700144 true, true, undoCallback));
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700145 } else if (itemId == R.id.mute) {
Jin Caoec0fa482014-08-28 16:38:08 -0700146 destroy(R.id.mute, mCheckedSet.values(), mUpdater.getBatchAction(R.id.mute,
Jin Caof5586372014-04-08 14:28:36 -0700147 undoCallback));
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700148 } else if (itemId == R.id.report_spam) {
Jin Caoec0fa482014-08-28 16:38:08 -0700149 destroy(R.id.report_spam, mCheckedSet.values(),
Jin Caof5586372014-04-08 14:28:36 -0700150 mUpdater.getBatchAction(R.id.report_spam, undoCallback));
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700151 } else if (itemId == R.id.mark_not_spam) {
152 // Currently, since spam messages are only shown in list with other spam messages,
153 // marking a message not as spam is a destructive action
154 destroy (R.id.mark_not_spam,
Jin Caoec0fa482014-08-28 16:38:08 -0700155 mCheckedSet.values(), mUpdater.getBatchAction(R.id.mark_not_spam,
Jin Caof5586372014-04-08 14:28:36 -0700156 undoCallback)) ;
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700157 } else if (itemId == R.id.report_phishing) {
158 destroy(R.id.report_phishing,
Jin Caoec0fa482014-08-28 16:38:08 -0700159 mCheckedSet.values(), mUpdater.getBatchAction(R.id.report_phishing,
Jin Caof5586372014-04-08 14:28:36 -0700160 undoCallback));
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700161 } else if (itemId == R.id.read) {
162 markConversationsRead(true);
163 } else if (itemId == R.id.unread) {
164 markConversationsRead(false);
165 } else if (itemId == R.id.star) {
166 starConversations(true);
Jin Caoa6b45212014-09-18 13:46:54 -0700167 } else if (itemId == R.id.toggle_read_unread) {
168 if (mActionMode != null) {
169 markConversationsRead(mActionMode.getMenu().findItem(R.id.read).isVisible());
170 }
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700171 } else if (itemId == R.id.remove_star) {
172 if (mFolder.isType(UIProvider.FolderType.STARRED)) {
173 LogUtils.d(LOG_TAG, "We are in a starred folder, removing the star");
Jin Caof5586372014-04-08 14:28:36 -0700174 performDestructiveAction(R.id.remove_star, undoCallback);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700175 } else {
176 LogUtils.d(LOG_TAG, "Not in a starred folder.");
177 starConversations(false);
178 }
Andrew Sapperstein6c570db2013-08-06 17:21:36 -0700179 } else if (itemId == R.id.move_to || itemId == R.id.change_folders) {
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700180 boolean cantMove = false;
181 Account acct = mAccount;
182 // Special handling for virtual folders
183 if (mFolder.supportsCapability(FolderCapabilities.IS_VIRTUAL)) {
184 Uri accountUri = null;
Jin Caoec0fa482014-08-28 16:38:08 -0700185 for (Conversation conv: mCheckedSet.values()) {
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700186 if (accountUri == null) {
187 accountUri = conv.accountUri;
188 } else if (!accountUri.equals(conv.accountUri)) {
189 // Tell the user why we can't do this
190 Toast.makeText(mContext, R.string.cant_move_or_change_labels,
191 Toast.LENGTH_LONG).show();
192 cantMove = true;
193 return handled;
Marc Blanka6b671d2012-05-25 12:52:02 -0700194 }
195 }
196 if (!cantMove) {
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700197 // Get the actual account here, so that we display its folders in the dialog
198 acct = MailAppProvider.getAccountFromAccountUri(accountUri);
Marc Blanka6b671d2012-05-25 12:52:02 -0700199 }
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700200 }
201 if (!cantMove) {
202 final FolderSelectionDialog dialog = FolderSelectionDialog.getInstance(
Jin Caoec0fa482014-08-28 16:38:08 -0700203 acct, mCheckedSet.values(), true, mFolder,
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700204 item.getItemId() == R.id.move_to);
205 if (dialog != null) {
Tony Mantler2a4be242013-12-11 15:30:53 -0800206 dialog.show(mActivity.getFragmentManager(), null);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700207 }
208 }
209 } else if (itemId == R.id.move_to_inbox) {
210 new AsyncTask<Void, Void, Folder>() {
211 @Override
212 protected Folder doInBackground(final Void... params) {
213 // Get the "move to" inbox
214 return Utils.getFolder(mContext, mAccount.settings.moveToInbox,
215 true /* allowHidden */);
216 }
Scott Kennedydd2ec682013-06-03 19:16:13 -0700217
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700218 @Override
219 protected void onPostExecute(final Folder moveToInbox) {
220 final List<FolderOperation> ops = Lists.newArrayListWithCapacity(1);
221 // Add inbox
222 ops.add(new FolderOperation(moveToInbox, true));
Jin Caoec0fa482014-08-28 16:38:08 -0700223 mUpdater.assignFolder(ops, mCheckedSet.values(), true,
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700224 true /* showUndo */, false /* isMoveTo */);
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700225 }
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700226 }.execute((Void[]) null);
227 } else if (itemId == R.id.mark_important) {
228 markConversationsImportant(true);
229 } else if (itemId == R.id.mark_not_important) {
230 if (mFolder.supportsCapability(UIProvider.FolderCapabilities.ONLY_IMPORTANT)) {
Jin Caof5586372014-04-08 14:28:36 -0700231 performDestructiveAction(R.id.mark_not_important, undoCallback);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700232 } else {
233 markConversationsImportant(false);
234 }
235 } else {
236 handled = false;
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800237 }
238 return handled;
239 }
240
Vikram Aggarwalad6d6282012-03-28 09:16:14 -0700241 /**
242 * Clear the selection and perform related UI changes to keep the state consistent.
243 */
Jin Caoec0fa482014-08-28 16:38:08 -0700244 private void clearChecked() {
245 mCheckedSet.clear();
Vikram Aggarwalad6d6282012-03-28 09:16:14 -0700246 }
247
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700248 /**
249 * Update the underlying list adapter and redraw the menus if necessary.
250 */
251 private void updateSelection() {
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700252 mUpdater.refreshConversationList();
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700253 if (mActionMode != null) {
254 // Calling mActivity.invalidateOptionsMenu doesn't have the correct behavior, since
255 // the action mode is not refreshed when activity's options menu is invalidated.
256 // Since we need to refresh our own menu, it is easy to call onPrepareActionMode
257 // directly.
258 onPrepareActionMode(mActionMode, mActionMode.getMenu());
259 }
260 }
261
Jin Caof5586372014-04-08 14:28:36 -0700262 private void performDestructiveAction(final int action, UndoCallback undoCallback) {
Jin Caoec0fa482014-08-28 16:38:08 -0700263 final Collection<Conversation> conversations = mCheckedSet.values();
Vikram Aggarwal6cadbfc2012-12-27 09:17:05 -0800264 final Settings settings = mAccount.settings;
Andy Huang121c8b82013-08-05 11:52:55 -0700265 final boolean showDialog;
266 // no confirmation dialog by default unless user preference or common sense dictates one
267 if (action == R.id.discard_drafts) {
268 // drafts are lost forever, so always confirm
269 showDialog = true;
270 } else if (settings != null && (action == R.id.archive || action == R.id.delete)) {
271 showDialog = (action == R.id.delete) ? settings.confirmDelete : settings.confirmArchive;
272 } else {
273 showDialog = false;
274 }
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800275 if (showDialog) {
Jin Caof5586372014-04-08 14:28:36 -0700276 mUpdater.makeDialogListener(action, true /* fromSelectedSet */, null /* undoCallback */);
Paul Westbrookef362542012-08-27 14:53:32 -0700277 final int resId;
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700278 if (action == R.id.delete) {
279 resId = R.plurals.confirm_delete_conversation;
280 } else if (action == R.id.discard_drafts) {
281 resId = R.plurals.confirm_discard_drafts_conversation;
282 } else {
283 resId = R.plurals.confirm_archive_conversation;
Paul Westbrookef362542012-08-27 14:53:32 -0700284 }
Vikram Aggarwal6cadbfc2012-12-27 09:17:05 -0800285 final CharSequence message = Utils.formatPlural(mContext, resId, conversations.size());
286 final ConfirmDialogFragment c = ConfirmDialogFragment.newInstance(message);
Vikram Aggarwalb8c31712013-01-03 17:03:19 -0800287 c.displayDialog(mActivity.getFragmentManager());
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800288 } else {
Vikram Aggarwal6cadbfc2012-12-27 09:17:05 -0800289 // No need to show the dialog, just make a destructive action and destroy the
290 // selected set immediately.
Vikram Aggarwalb8c31712013-01-03 17:03:19 -0800291 // TODO(viki): Stop using the deferred action here. Use the registered action.
Jin Caof5586372014-04-08 14:28:36 -0700292 destroy(action, conversations, mUpdater.getDeferredBatchAction(action, undoCallback));
Mindy Pereira07118a02012-04-02 16:35:01 -0700293 }
294 }
295
Vikram Aggarwal84fe9942013-04-17 11:20:58 -0700296 /**
297 * Destroy these conversations through the conversation updater
298 * @param actionId the ID of the action: R.id.archive, R.id.delete, ...
299 * @param target conversations to destroy
300 * @param action the action that performs the destruction
301 */
302 private void destroy(int actionId, final Collection<Conversation> target,
303 final DestructiveAction action) {
Alice Yang193e05a2013-05-05 14:12:08 -0700304 LogUtils.i(LOG_TAG, "About to remove %d converations", target.size());
Scott Kennedycaaeed32013-06-12 13:39:16 -0700305 mUpdater.delete(actionId, target, action, true);
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800306 }
307
Vikram Aggarwal0e091c52012-05-30 15:07:15 -0700308 /**
309 * Marks the read state of currently selected conversations (<b>and</b> the backing storage)
310 * to the value provided here.
311 * @param read is true if the conversations are to be marked as read, false if they are to be
312 * marked unread.
313 */
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800314 private void markConversationsRead(boolean read) {
Jin Caoec0fa482014-08-28 16:38:08 -0700315 final Collection<Conversation> targets = mCheckedSet.values();
Vikram Aggarwal66bc2aa2012-08-02 10:47:03 -0700316 // The conversations are marked read but not viewed.
317 mUpdater.markConversationsRead(targets, read, false);
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700318 updateSelection();
Mindy Pereira7f0a9622012-02-29 15:00:34 -0800319 }
320
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700321 /**
Vikram Aggarwal0e091c52012-05-30 15:07:15 -0700322 * Marks the important state of currently selected conversations (<b>and</b> the backing
323 * storage) to the value provided here.
324 * @param important is true if the conversations are to be marked as important, false if they
325 * are to be marked not important.
326 */
327 private void markConversationsImportant(boolean important) {
Jin Caoec0fa482014-08-28 16:38:08 -0700328 final Collection<Conversation> target = mCheckedSet.values();
Vikram Aggarwal0e091c52012-05-30 15:07:15 -0700329 final int priority = important ? UIProvider.ConversationPriority.HIGH
330 : UIProvider.ConversationPriority.LOW;
331 mUpdater.updateConversation(target, ConversationColumns.PRIORITY, priority);
332 // Update the conversations in the selection too.
333 for (final Conversation c : target) {
334 c.priority = priority;
335 }
336 updateSelection();
337 }
338
339 /**
340 * Marks the selected conversations with the star setting provided here.
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700341 * @param star true if you want all the conversations to have stars, false if you want to remove
342 * stars from all conversations
343 */
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800344 private void starConversations(boolean star) {
Jin Caoec0fa482014-08-28 16:38:08 -0700345 final Collection<Conversation> target = mCheckedSet.values();
Vikram Aggarwal0e091c52012-05-30 15:07:15 -0700346 mUpdater.updateConversation(target, ConversationColumns.STARRED, star);
347 // Update the conversations in the selection too.
348 for (final Conversation c : target) {
349 c.starred = star;
350 }
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700351 updateSelection();
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800352 }
353
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800354 @Override
355 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Jin Caoec0fa482014-08-28 16:38:08 -0700356 mCheckedSet.addObserver(this);
Vikram Aggarwal7f602f72012-04-30 16:04:06 -0700357 final MenuInflater inflater = mActivity.getMenuInflater();
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800358 inflater.inflate(R.menu.conversation_list_selection_actions_menu, menu);
359 mActionMode = mode;
Andrew Sappersteindb861e92014-09-07 17:54:30 -0700360 updateCount();
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800361 return true;
362 }
363
364 @Override
365 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
Vikram Aggarwale5e917c2012-09-20 16:27:41 -0700366 // Update the actionbar to select operations available on the current conversation.
Jin Caoec0fa482014-08-28 16:38:08 -0700367 final Collection<Conversation> conversations = mCheckedSet.values();
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800368 boolean showStar = false;
369 boolean showMarkUnread = false;
Mindy Pereira7f0a9622012-02-29 15:00:34 -0800370 boolean showMarkImportant = false;
Paul Westbrook77eee622012-07-10 13:41:57 -0700371 boolean showMarkNotSpam = false;
Paul Westbrook76b20622012-07-12 11:45:43 -0700372 boolean showMarkAsPhishing = false;
Mindy Pereira7f0a9622012-02-29 15:00:34 -0800373
Rohan Shah69419bb2014-08-15 15:53:29 -0700374 // TODO(shahrk): Clean up these dirty calls using Utils.setMenuItemVisibility(...) or
375 // in another way
376
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800377 for (Conversation conversation : conversations) {
378 if (!conversation.starred) {
379 showStar = true;
380 }
381 if (conversation.read) {
382 showMarkUnread = true;
383 }
Mindy Pereira7f0a9622012-02-29 15:00:34 -0800384 if (!conversation.isImportant()) {
385 showMarkImportant = true;
386 }
Paul Westbrook77eee622012-07-10 13:41:57 -0700387 if (conversation.spam) {
388 showMarkNotSpam = true;
389 }
Paul Westbrook76b20622012-07-12 11:45:43 -0700390 if (!conversation.phishing) {
391 showMarkAsPhishing = true;
392 }
393 if (showStar && showMarkUnread && showMarkImportant && showMarkNotSpam &&
394 showMarkAsPhishing) {
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800395 break;
396 }
397 }
Tony Mantlerec86c422014-09-24 14:33:20 -0700398 final boolean canStar = mFolder != null && !mFolder.isTrash();
Paul Westbrook334e64a2012-02-23 13:26:35 -0800399 final MenuItem star = menu.findItem(R.id.star);
Tony Mantlerec86c422014-09-24 14:33:20 -0700400 star.setVisible(showStar && canStar);
Paul Westbrook334e64a2012-02-23 13:26:35 -0800401 final MenuItem unstar = menu.findItem(R.id.remove_star);
Tony Mantlerec86c422014-09-24 14:33:20 -0700402 unstar.setVisible(!showStar && canStar);
Paul Westbrook334e64a2012-02-23 13:26:35 -0800403 final MenuItem read = menu.findItem(R.id.read);
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800404 read.setVisible(!showMarkUnread);
Paul Westbrook334e64a2012-02-23 13:26:35 -0800405 final MenuItem unread = menu.findItem(R.id.unread);
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800406 unread.setVisible(showMarkUnread);
Jin Caoa6b45212014-09-18 13:46:54 -0700407
Mindy Pereira9ba42512012-08-14 11:52:48 -0700408 // We only ever show one of:
409 // 1) remove folder
410 // 2) archive
Mindy Pereira01f30502012-08-14 10:30:51 -0700411 final MenuItem removeFolder = menu.findItem(R.id.remove_folder);
Rohan Shahaab9bc72013-02-07 14:18:05 -0800412 final MenuItem moveTo = menu.findItem(R.id.move_to);
Scott Kennedydd2ec682013-06-03 19:16:13 -0700413 final MenuItem moveToInbox = menu.findItem(R.id.move_to_inbox);
Scott Kennedy8c1058e2013-03-20 13:40:20 -0700414 final boolean showRemoveFolder = mFolder != null && mFolder.isType(FolderType.DEFAULT)
Mindy Pereira9ba42512012-08-14 11:52:48 -0700415 && mFolder.supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
Tony Mantler53b34252013-09-25 16:42:25 -0700416 && !mFolder.isProviderFolder()
417 && mAccount.supportsCapability(AccountCapabilities.ARCHIVE);
Rohan Shahaab9bc72013-02-07 14:18:05 -0800418 final boolean showMoveTo = mFolder != null
419 && mFolder.supportsCapability(FolderCapabilities.ALLOWS_REMOVE_CONVERSATION);
Scott Kennedydd2ec682013-06-03 19:16:13 -0700420 final boolean showMoveToInbox = mFolder != null
421 && mFolder.supportsCapability(FolderCapabilities.ALLOWS_MOVE_TO_INBOX);
Mindy Pereira9ba42512012-08-14 11:52:48 -0700422 removeFolder.setVisible(showRemoveFolder);
Scott Kennedy28f3d5c2013-04-08 16:53:50 -0700423 moveTo.setVisible(showMoveTo);
Scott Kennedydd2ec682013-06-03 19:16:13 -0700424 moveToInbox.setVisible(showMoveToInbox);
425
Rohan Shah69419bb2014-08-15 15:53:29 -0700426 final MenuItem changeFolders = menu.findItem(R.id.change_folders);
427 changeFolders.setVisible(mAccount.supportsCapability(
428 UIProvider.AccountCapabilities.MULTIPLE_FOLDERS_PER_CONV));
429
Mindy Pereira9ba42512012-08-14 11:52:48 -0700430 if (mFolder != null && showRemoveFolder) {
Mindy Pereira01f30502012-08-14 10:30:51 -0700431 removeFolder.setTitle(mActivity.getActivityContext().getString(R.string.remove_folder,
432 mFolder.name));
433 }
Mindy Pereira9ba42512012-08-14 11:52:48 -0700434 final MenuItem archive = menu.findItem(R.id.archive);
Andrew Sapperstein3047a9f2014-07-22 18:17:53 -0700435 if (archive != null) {
436 archive.setVisible(
437 mAccount.supportsCapability(UIProvider.AccountCapabilities.ARCHIVE) &&
438 mFolder.supportsCapability(FolderCapabilities.ARCHIVE));
Mindy Pereira50cfeda2012-08-14 13:54:56 -0700439 }
Paul Westbrook334e64a2012-02-23 13:26:35 -0800440 final MenuItem spam = menu.findItem(R.id.report_spam);
Mindy Pereira9ba42512012-08-14 11:52:48 -0700441 spam.setVisible(!showMarkNotSpam
442 && mAccount.supportsCapability(UIProvider.AccountCapabilities.REPORT_SPAM)
443 && mFolder.supportsCapability(FolderCapabilities.REPORT_SPAM));
Paul Westbrook77eee622012-07-10 13:41:57 -0700444 final MenuItem notSpam = menu.findItem(R.id.mark_not_spam);
445 notSpam.setVisible(showMarkNotSpam &&
446 mAccount.supportsCapability(UIProvider.AccountCapabilities.REPORT_SPAM) &&
447 mFolder.supportsCapability(FolderCapabilities.MARK_NOT_SPAM));
Paul Westbrook76b20622012-07-12 11:45:43 -0700448 final MenuItem phishing = menu.findItem(R.id.report_phishing);
449 phishing.setVisible(showMarkAsPhishing &&
450 mAccount.supportsCapability(UIProvider.AccountCapabilities.REPORT_PHISHING) &&
451 mFolder.supportsCapability(FolderCapabilities.REPORT_PHISHING));
452
Paul Westbrook334e64a2012-02-23 13:26:35 -0800453 final MenuItem mute = menu.findItem(R.id.mute);
Mindy Pereira50cfeda2012-08-14 13:54:56 -0700454 if (mute != null) {
455 mute.setVisible(mAccount.supportsCapability(UIProvider.AccountCapabilities.MUTE)
Scott Kennedy962a6922013-03-21 17:43:49 -0700456 && (mFolder != null && mFolder.isInbox()));
Mindy Pereira50cfeda2012-08-14 13:54:56 -0700457 }
Mindy Pereira7f0a9622012-02-29 15:00:34 -0800458 final MenuItem markImportant = menu.findItem(R.id.mark_important);
459 markImportant.setVisible(showMarkImportant
460 && mAccount.supportsCapability(UIProvider.AccountCapabilities.MARK_IMPORTANT));
461 final MenuItem markNotImportant = menu.findItem(R.id.mark_not_important);
462 markNotImportant.setVisible(!showMarkImportant
463 && mAccount.supportsCapability(UIProvider.AccountCapabilities.MARK_IMPORTANT));
Jin Cao512821c2014-05-30 15:54:04 -0700464
465 boolean shouldShowDiscardOutbox = mFolder != null && mFolder.isType(FolderType.OUTBOX);
466 mDiscardOutboxMenuItem = menu.findItem(R.id.discard_outbox);
467 if (mDiscardOutboxMenuItem != null) {
468 mDiscardOutboxMenuItem.setVisible(shouldShowDiscardOutbox);
469 mDiscardOutboxMenuItem.setEnabled(shouldEnableDiscardOutbox(conversations));
470 }
471 final boolean showDelete = mFolder != null && !mFolder.isType(FolderType.OUTBOX)
Paul Westbrookef362542012-08-27 14:53:32 -0700472 && mFolder.supportsCapability(UIProvider.FolderCapabilities.DELETE);
Mindy Pereirab8073372012-08-09 14:00:10 -0700473 final MenuItem trash = menu.findItem(R.id.delete);
Paul Westbrookef362542012-08-27 14:53:32 -0700474 trash.setVisible(showDelete);
475 // We only want to show the discard drafts menu item if we are not showing the delete menu
476 // item, and the current folder is a draft folder and the account supports discarding
477 // drafts for a conversation
478 final boolean showDiscardDrafts = !showDelete && mFolder != null && mFolder.isDraft() &&
479 mAccount.supportsCapability(AccountCapabilities.DISCARD_CONVERSATION_DRAFTS);
480 final MenuItem discardDrafts = menu.findItem(R.id.discard_drafts);
481 if (discardDrafts != null) {
482 discardDrafts.setVisible(showDiscardDrafts);
483 }
484
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800485 return true;
486 }
487
Jin Cao512821c2014-05-30 15:54:04 -0700488 private boolean shouldEnableDiscardOutbox(Collection<Conversation> conversations) {
489 boolean shouldEnableDiscardOutbox = true;
490 // Java should be smart enough to realize that once showDiscardOutbox becomes false it can
491 // just skip everything remaining in the for-loop..
492 for (Conversation conv : conversations) {
493 shouldEnableDiscardOutbox &=
494 conv.sendingState != UIProvider.ConversationSendingState.SENDING &&
495 conv.sendingState != UIProvider.ConversationSendingState.RETRYING;
496 }
497 return shouldEnableDiscardOutbox;
498 }
499
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800500 @Override
501 public void onDestroyActionMode(ActionMode mode) {
502 mActionMode = null;
503 // The action mode may have been destroyed due to this menu being deactivated, in which
504 // case resources need not be cleaned up. However, if it was destroyed while this menu is
505 // active, that implies the user hit "Done" in the top right, and resources need cleaning.
506 if (mActivated) {
507 destroy();
mindyp54f120f2012-08-28 13:10:33 -0700508 // Only commit destructive actions if the user actually pressed
509 // done; otherwise, this was handled when we toggled conversation
510 // selection state.
Scott Kennedyff8553f2013-04-05 20:57:44 -0700511 mActivity.getListHandler().commitDestructiveActions(true);
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800512 }
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800513 }
514
515 @Override
Jin Caoec0fa482014-08-28 16:38:08 -0700516 public void onSetPopulated(ConversationCheckedSet set) {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800517 // Noop. This object can only exist while the set is non-empty.
518 }
519
520 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800521 public void onSetEmpty() {
Vikram Aggarwalad6d6282012-03-28 09:16:14 -0700522 LogUtils.d(LOG_TAG, "onSetEmpty called.");
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800523 destroy();
524 }
525
526 @Override
Jin Caoec0fa482014-08-28 16:38:08 -0700527 public void onSetChanged(ConversationCheckedSet set) {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800528 // If the set is empty, the menu buttons are invalid and most like the menu will be cleaned
529 // up. Avoid making any changes to stop flickering ("Add Star" -> "Remove Star") just
530 // before hiding the menu.
531 if (set.isEmpty()) {
532 return;
533 }
Andrew Sappersteindb861e92014-09-07 17:54:30 -0700534 updateCount();
Jin Cao512821c2014-05-30 15:54:04 -0700535
536 if (mFolder.isType(FolderType.OUTBOX) && mDiscardOutboxMenuItem != null) {
537 mDiscardOutboxMenuItem.setEnabled(shouldEnableDiscardOutbox(set.values()));
538 }
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800539 }
540
541 /**
Andrew Sappersteindb861e92014-09-07 17:54:30 -0700542 * Updates the visible count of how many conversations are selected.
543 */
544 private void updateCount() {
545 if (mActionMode != null) {
546 mActionMode.setTitle(Integer.toString(mCheckedSet.size()));
547 }
548 }
549
550 /**
Vikram Aggarwale128fc22012-04-04 12:33:34 -0700551 * Activates and shows this menu (essentially starting an {@link ActionMode}) if the selected
552 * set is non-empty.
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800553 */
554 public void activate() {
Jin Caoec0fa482014-08-28 16:38:08 -0700555 if (mCheckedSet.isEmpty()) {
Vikram Aggarwale128fc22012-04-04 12:33:34 -0700556 return;
557 }
Andrew Sapperstein2f542872013-06-11 10:48:30 -0700558 mListController.onCabModeEntered();
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800559 mActivated = true;
560 if (mActionMode == null) {
Andrew Sapperstein52882ff2014-07-27 12:30:18 -0700561 mActivity.startSupportActionMode(this);
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800562 }
563 }
564
565 /**
566 * De-activates and hides the menu (essentially disabling the {@link ActionMode}), but maintains
567 * the selection conversation set, and internally updates state as necessary.
568 */
569 public void deactivate() {
Scott Kennedycc139832013-08-19 18:03:54 -0700570 mListController.onCabModeExited();
Andrew Sapperstein4c928742014-08-29 15:34:23 -0700571 mActivated = false;
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800572 if (mActionMode != null) {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800573 mActionMode.finish();
574 }
575 }
576
577 @VisibleForTesting
Andrew Sapperstein4c928742014-08-29 15:34:23 -0700578 /**
579 * Returns true if CAB mode is active.
580 */
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800581 public boolean isActivated() {
582 return mActivated;
583 }
584
585 /**
586 * Destroys and cleans up the resources associated with this menu.
587 */
Vikram Aggarwalad6d6282012-03-28 09:16:14 -0700588 private void destroy() {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800589 deactivate();
Jin Caoec0fa482014-08-28 16:38:08 -0700590 mCheckedSet.removeObserver(this);
591 clearChecked();
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700592 mUpdater.refreshConversationList();
Mindy Pereira14f64ec2012-08-14 17:14:02 -0700593 if (mAccountObserver != null) {
594 mAccountObserver.unregisterAndDestroy();
595 mAccountObserver = null;
596 }
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800597 }
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800598}