blob: 7293a3632a410396cdfb996d1bc651698dc8c0ea [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
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800114 @Override
115 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
116 boolean handled = true;
Mindy Pereira8937bf12012-07-23 14:05:02 -0700117 // If the user taps a new menu item, commit any existing destructive actions.
Vikram Aggarwala8e43182012-09-13 12:55:10 -0700118 mListController.commitDestructiveActions(true);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700119 final int itemId = item.getItemId();
Andy Huang042a5302013-08-13 12:39:08 -0700120
Andy Huang2b555492013-08-14 21:06:21 -0700121 Analytics.getInstance().sendMenuItemEvent(Analytics.EVENT_CATEGORY_MENU_ITEM, itemId,
122 "cab_mode", 0);
Andy Huang042a5302013-08-13 12:39:08 -0700123
Jin Caof5586372014-04-08 14:28:36 -0700124 UndoCallback undoCallback = null; // not applicable here (yet)
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700125 if (itemId == R.id.delete) {
126 LogUtils.i(LOG_TAG, "Delete selected from CAB menu");
Jin Caof5586372014-04-08 14:28:36 -0700127 performDestructiveAction(R.id.delete, undoCallback);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700128 } else if (itemId == R.id.discard_drafts) {
Jin Cao512821c2014-05-30 15:54:04 -0700129 LogUtils.i(LOG_TAG, "Discard drafts selected from CAB menu");
Jin Caof5586372014-04-08 14:28:36 -0700130 performDestructiveAction(R.id.discard_drafts, undoCallback);
Jin Cao512821c2014-05-30 15:54:04 -0700131 } else if (itemId == R.id.discard_outbox) {
132 LogUtils.i(LOG_TAG, "Discard outbox selected from CAB menu");
133 performDestructiveAction(R.id.discard_outbox, undoCallback);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700134 } else if (itemId == R.id.archive) {
135 LogUtils.i(LOG_TAG, "Archive selected from CAB menu");
Jin Caof5586372014-04-08 14:28:36 -0700136 performDestructiveAction(R.id.archive, undoCallback);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700137 } else if (itemId == R.id.remove_folder) {
Jin Caoec0fa482014-08-28 16:38:08 -0700138 destroy(R.id.remove_folder, mCheckedSet.values(),
139 mUpdater.getDeferredRemoveFolder(mCheckedSet.values(), mFolder, true,
Jin Caof5586372014-04-08 14:28:36 -0700140 true, true, undoCallback));
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700141 } else if (itemId == R.id.mute) {
Jin Caoec0fa482014-08-28 16:38:08 -0700142 destroy(R.id.mute, mCheckedSet.values(), mUpdater.getBatchAction(R.id.mute,
Jin Caof5586372014-04-08 14:28:36 -0700143 undoCallback));
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700144 } else if (itemId == R.id.report_spam) {
Jin Caoec0fa482014-08-28 16:38:08 -0700145 destroy(R.id.report_spam, mCheckedSet.values(),
Jin Caof5586372014-04-08 14:28:36 -0700146 mUpdater.getBatchAction(R.id.report_spam, undoCallback));
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700147 } else if (itemId == R.id.mark_not_spam) {
148 // Currently, since spam messages are only shown in list with other spam messages,
149 // marking a message not as spam is a destructive action
150 destroy (R.id.mark_not_spam,
Jin Caoec0fa482014-08-28 16:38:08 -0700151 mCheckedSet.values(), mUpdater.getBatchAction(R.id.mark_not_spam,
Jin Caof5586372014-04-08 14:28:36 -0700152 undoCallback)) ;
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700153 } else if (itemId == R.id.report_phishing) {
154 destroy(R.id.report_phishing,
Jin Caoec0fa482014-08-28 16:38:08 -0700155 mCheckedSet.values(), mUpdater.getBatchAction(R.id.report_phishing,
Jin Caof5586372014-04-08 14:28:36 -0700156 undoCallback));
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700157 } else if (itemId == R.id.read) {
158 markConversationsRead(true);
159 } else if (itemId == R.id.unread) {
160 markConversationsRead(false);
161 } else if (itemId == R.id.star) {
162 starConversations(true);
163 } else if (itemId == R.id.remove_star) {
164 if (mFolder.isType(UIProvider.FolderType.STARRED)) {
165 LogUtils.d(LOG_TAG, "We are in a starred folder, removing the star");
Jin Caof5586372014-04-08 14:28:36 -0700166 performDestructiveAction(R.id.remove_star, undoCallback);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700167 } else {
168 LogUtils.d(LOG_TAG, "Not in a starred folder.");
169 starConversations(false);
170 }
Andrew Sapperstein6c570db2013-08-06 17:21:36 -0700171 } else if (itemId == R.id.move_to || itemId == R.id.change_folders) {
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700172 boolean cantMove = false;
173 Account acct = mAccount;
174 // Special handling for virtual folders
175 if (mFolder.supportsCapability(FolderCapabilities.IS_VIRTUAL)) {
176 Uri accountUri = null;
Jin Caoec0fa482014-08-28 16:38:08 -0700177 for (Conversation conv: mCheckedSet.values()) {
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700178 if (accountUri == null) {
179 accountUri = conv.accountUri;
180 } else if (!accountUri.equals(conv.accountUri)) {
181 // Tell the user why we can't do this
182 Toast.makeText(mContext, R.string.cant_move_or_change_labels,
183 Toast.LENGTH_LONG).show();
184 cantMove = true;
185 return handled;
Marc Blanka6b671d2012-05-25 12:52:02 -0700186 }
187 }
188 if (!cantMove) {
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700189 // Get the actual account here, so that we display its folders in the dialog
190 acct = MailAppProvider.getAccountFromAccountUri(accountUri);
Marc Blanka6b671d2012-05-25 12:52:02 -0700191 }
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700192 }
193 if (!cantMove) {
194 final FolderSelectionDialog dialog = FolderSelectionDialog.getInstance(
Jin Caoec0fa482014-08-28 16:38:08 -0700195 acct, mCheckedSet.values(), true, mFolder,
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700196 item.getItemId() == R.id.move_to);
197 if (dialog != null) {
Tony Mantler2a4be242013-12-11 15:30:53 -0800198 dialog.show(mActivity.getFragmentManager(), null);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700199 }
200 }
201 } else if (itemId == R.id.move_to_inbox) {
202 new AsyncTask<Void, Void, Folder>() {
203 @Override
204 protected Folder doInBackground(final Void... params) {
205 // Get the "move to" inbox
206 return Utils.getFolder(mContext, mAccount.settings.moveToInbox,
207 true /* allowHidden */);
208 }
Scott Kennedydd2ec682013-06-03 19:16:13 -0700209
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700210 @Override
211 protected void onPostExecute(final Folder moveToInbox) {
212 final List<FolderOperation> ops = Lists.newArrayListWithCapacity(1);
213 // Add inbox
214 ops.add(new FolderOperation(moveToInbox, true));
Jin Caoec0fa482014-08-28 16:38:08 -0700215 mUpdater.assignFolder(ops, mCheckedSet.values(), true,
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700216 true /* showUndo */, false /* isMoveTo */);
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700217 }
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700218 }.execute((Void[]) null);
219 } else if (itemId == R.id.mark_important) {
220 markConversationsImportant(true);
221 } else if (itemId == R.id.mark_not_important) {
222 if (mFolder.supportsCapability(UIProvider.FolderCapabilities.ONLY_IMPORTANT)) {
Jin Caof5586372014-04-08 14:28:36 -0700223 performDestructiveAction(R.id.mark_not_important, undoCallback);
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700224 } else {
225 markConversationsImportant(false);
226 }
227 } else {
228 handled = false;
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800229 }
230 return handled;
231 }
232
Vikram Aggarwalad6d6282012-03-28 09:16:14 -0700233 /**
234 * Clear the selection and perform related UI changes to keep the state consistent.
235 */
Jin Caoec0fa482014-08-28 16:38:08 -0700236 private void clearChecked() {
237 mCheckedSet.clear();
Vikram Aggarwalad6d6282012-03-28 09:16:14 -0700238 }
239
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700240 /**
241 * Update the underlying list adapter and redraw the menus if necessary.
242 */
243 private void updateSelection() {
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700244 mUpdater.refreshConversationList();
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700245 if (mActionMode != null) {
246 // Calling mActivity.invalidateOptionsMenu doesn't have the correct behavior, since
247 // the action mode is not refreshed when activity's options menu is invalidated.
248 // Since we need to refresh our own menu, it is easy to call onPrepareActionMode
249 // directly.
250 onPrepareActionMode(mActionMode, mActionMode.getMenu());
251 }
252 }
253
Jin Caof5586372014-04-08 14:28:36 -0700254 private void performDestructiveAction(final int action, UndoCallback undoCallback) {
Jin Caoec0fa482014-08-28 16:38:08 -0700255 final Collection<Conversation> conversations = mCheckedSet.values();
Vikram Aggarwal6cadbfc2012-12-27 09:17:05 -0800256 final Settings settings = mAccount.settings;
Andy Huang121c8b82013-08-05 11:52:55 -0700257 final boolean showDialog;
258 // no confirmation dialog by default unless user preference or common sense dictates one
259 if (action == R.id.discard_drafts) {
260 // drafts are lost forever, so always confirm
261 showDialog = true;
262 } else if (settings != null && (action == R.id.archive || action == R.id.delete)) {
263 showDialog = (action == R.id.delete) ? settings.confirmDelete : settings.confirmArchive;
264 } else {
265 showDialog = false;
266 }
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800267 if (showDialog) {
Jin Caof5586372014-04-08 14:28:36 -0700268 mUpdater.makeDialogListener(action, true /* fromSelectedSet */, null /* undoCallback */);
Paul Westbrookef362542012-08-27 14:53:32 -0700269 final int resId;
Scott Kennedy2b9d80e2013-07-30 23:03:45 -0700270 if (action == R.id.delete) {
271 resId = R.plurals.confirm_delete_conversation;
272 } else if (action == R.id.discard_drafts) {
273 resId = R.plurals.confirm_discard_drafts_conversation;
274 } else {
275 resId = R.plurals.confirm_archive_conversation;
Paul Westbrookef362542012-08-27 14:53:32 -0700276 }
Vikram Aggarwal6cadbfc2012-12-27 09:17:05 -0800277 final CharSequence message = Utils.formatPlural(mContext, resId, conversations.size());
278 final ConfirmDialogFragment c = ConfirmDialogFragment.newInstance(message);
Vikram Aggarwalb8c31712013-01-03 17:03:19 -0800279 c.displayDialog(mActivity.getFragmentManager());
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800280 } else {
Vikram Aggarwal6cadbfc2012-12-27 09:17:05 -0800281 // No need to show the dialog, just make a destructive action and destroy the
282 // selected set immediately.
Vikram Aggarwalb8c31712013-01-03 17:03:19 -0800283 // TODO(viki): Stop using the deferred action here. Use the registered action.
Jin Caof5586372014-04-08 14:28:36 -0700284 destroy(action, conversations, mUpdater.getDeferredBatchAction(action, undoCallback));
Mindy Pereira07118a02012-04-02 16:35:01 -0700285 }
286 }
287
Vikram Aggarwal84fe9942013-04-17 11:20:58 -0700288 /**
289 * Destroy these conversations through the conversation updater
290 * @param actionId the ID of the action: R.id.archive, R.id.delete, ...
291 * @param target conversations to destroy
292 * @param action the action that performs the destruction
293 */
294 private void destroy(int actionId, final Collection<Conversation> target,
295 final DestructiveAction action) {
Alice Yang193e05a2013-05-05 14:12:08 -0700296 LogUtils.i(LOG_TAG, "About to remove %d converations", target.size());
Scott Kennedycaaeed32013-06-12 13:39:16 -0700297 mUpdater.delete(actionId, target, action, true);
Mindy Pereiraefe3d252012-03-01 14:20:44 -0800298 }
299
Vikram Aggarwal0e091c52012-05-30 15:07:15 -0700300 /**
301 * Marks the read state of currently selected conversations (<b>and</b> the backing storage)
302 * to the value provided here.
303 * @param read is true if the conversations are to be marked as read, false if they are to be
304 * marked unread.
305 */
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800306 private void markConversationsRead(boolean read) {
Jin Caoec0fa482014-08-28 16:38:08 -0700307 final Collection<Conversation> targets = mCheckedSet.values();
Vikram Aggarwal66bc2aa2012-08-02 10:47:03 -0700308 // The conversations are marked read but not viewed.
309 mUpdater.markConversationsRead(targets, read, false);
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700310 updateSelection();
Mindy Pereira7f0a9622012-02-29 15:00:34 -0800311 }
312
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700313 /**
Vikram Aggarwal0e091c52012-05-30 15:07:15 -0700314 * Marks the important state of currently selected conversations (<b>and</b> the backing
315 * storage) to the value provided here.
316 * @param important is true if the conversations are to be marked as important, false if they
317 * are to be marked not important.
318 */
319 private void markConversationsImportant(boolean important) {
Jin Caoec0fa482014-08-28 16:38:08 -0700320 final Collection<Conversation> target = mCheckedSet.values();
Vikram Aggarwal0e091c52012-05-30 15:07:15 -0700321 final int priority = important ? UIProvider.ConversationPriority.HIGH
322 : UIProvider.ConversationPriority.LOW;
323 mUpdater.updateConversation(target, ConversationColumns.PRIORITY, priority);
324 // Update the conversations in the selection too.
325 for (final Conversation c : target) {
326 c.priority = priority;
327 }
328 updateSelection();
329 }
330
331 /**
332 * Marks the selected conversations with the star setting provided here.
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700333 * @param star true if you want all the conversations to have stars, false if you want to remove
334 * stars from all conversations
335 */
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800336 private void starConversations(boolean star) {
Jin Caoec0fa482014-08-28 16:38:08 -0700337 final Collection<Conversation> target = mCheckedSet.values();
Vikram Aggarwal0e091c52012-05-30 15:07:15 -0700338 mUpdater.updateConversation(target, ConversationColumns.STARRED, star);
339 // Update the conversations in the selection too.
340 for (final Conversation c : target) {
341 c.starred = star;
342 }
Vikram Aggarwal04dc8192012-04-09 13:07:19 -0700343 updateSelection();
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800344 }
345
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800346 @Override
347 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Jin Caoec0fa482014-08-28 16:38:08 -0700348 mCheckedSet.addObserver(this);
Vikram Aggarwal7f602f72012-04-30 16:04:06 -0700349 final MenuInflater inflater = mActivity.getMenuInflater();
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800350 inflater.inflate(R.menu.conversation_list_selection_actions_menu, menu);
351 mActionMode = mode;
Andrew Sappersteindb861e92014-09-07 17:54:30 -0700352 updateCount();
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800353 return true;
354 }
355
356 @Override
357 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
Vikram Aggarwale5e917c2012-09-20 16:27:41 -0700358 // Update the actionbar to select operations available on the current conversation.
Jin Caoec0fa482014-08-28 16:38:08 -0700359 final Collection<Conversation> conversations = mCheckedSet.values();
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800360 boolean showStar = false;
361 boolean showMarkUnread = false;
Mindy Pereira7f0a9622012-02-29 15:00:34 -0800362 boolean showMarkImportant = false;
Paul Westbrook77eee622012-07-10 13:41:57 -0700363 boolean showMarkNotSpam = false;
Paul Westbrook76b20622012-07-12 11:45:43 -0700364 boolean showMarkAsPhishing = false;
Mindy Pereira7f0a9622012-02-29 15:00:34 -0800365
Rohan Shah69419bb2014-08-15 15:53:29 -0700366 // TODO(shahrk): Clean up these dirty calls using Utils.setMenuItemVisibility(...) or
367 // in another way
368
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800369 for (Conversation conversation : conversations) {
370 if (!conversation.starred) {
371 showStar = true;
372 }
373 if (conversation.read) {
374 showMarkUnread = true;
375 }
Mindy Pereira7f0a9622012-02-29 15:00:34 -0800376 if (!conversation.isImportant()) {
377 showMarkImportant = true;
378 }
Paul Westbrook77eee622012-07-10 13:41:57 -0700379 if (conversation.spam) {
380 showMarkNotSpam = true;
381 }
Paul Westbrook76b20622012-07-12 11:45:43 -0700382 if (!conversation.phishing) {
383 showMarkAsPhishing = true;
384 }
385 if (showStar && showMarkUnread && showMarkImportant && showMarkNotSpam &&
386 showMarkAsPhishing) {
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800387 break;
388 }
389 }
Tony Mantlerec86c422014-09-24 14:33:20 -0700390 final boolean canStar = mFolder != null && !mFolder.isTrash();
Paul Westbrook334e64a2012-02-23 13:26:35 -0800391 final MenuItem star = menu.findItem(R.id.star);
Tony Mantlerec86c422014-09-24 14:33:20 -0700392 star.setVisible(showStar && canStar);
Paul Westbrook334e64a2012-02-23 13:26:35 -0800393 final MenuItem unstar = menu.findItem(R.id.remove_star);
Tony Mantlerec86c422014-09-24 14:33:20 -0700394 unstar.setVisible(!showStar && canStar);
Paul Westbrook334e64a2012-02-23 13:26:35 -0800395 final MenuItem read = menu.findItem(R.id.read);
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800396 read.setVisible(!showMarkUnread);
Paul Westbrook334e64a2012-02-23 13:26:35 -0800397 final MenuItem unread = menu.findItem(R.id.unread);
Mindy Pereirafebeb5f2012-02-14 15:08:13 -0800398 unread.setVisible(showMarkUnread);
Mindy Pereira9ba42512012-08-14 11:52:48 -0700399 // We only ever show one of:
400 // 1) remove folder
401 // 2) archive
Mindy Pereira01f30502012-08-14 10:30:51 -0700402 final MenuItem removeFolder = menu.findItem(R.id.remove_folder);
Rohan Shahaab9bc72013-02-07 14:18:05 -0800403 final MenuItem moveTo = menu.findItem(R.id.move_to);
Scott Kennedydd2ec682013-06-03 19:16:13 -0700404 final MenuItem moveToInbox = menu.findItem(R.id.move_to_inbox);
Scott Kennedy8c1058e2013-03-20 13:40:20 -0700405 final boolean showRemoveFolder = mFolder != null && mFolder.isType(FolderType.DEFAULT)
Mindy Pereira9ba42512012-08-14 11:52:48 -0700406 && mFolder.supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
Tony Mantler53b34252013-09-25 16:42:25 -0700407 && !mFolder.isProviderFolder()
408 && mAccount.supportsCapability(AccountCapabilities.ARCHIVE);
Rohan Shahaab9bc72013-02-07 14:18:05 -0800409 final boolean showMoveTo = mFolder != null
410 && mFolder.supportsCapability(FolderCapabilities.ALLOWS_REMOVE_CONVERSATION);
Scott Kennedydd2ec682013-06-03 19:16:13 -0700411 final boolean showMoveToInbox = mFolder != null
412 && mFolder.supportsCapability(FolderCapabilities.ALLOWS_MOVE_TO_INBOX);
Mindy Pereira9ba42512012-08-14 11:52:48 -0700413 removeFolder.setVisible(showRemoveFolder);
Scott Kennedy28f3d5c2013-04-08 16:53:50 -0700414 moveTo.setVisible(showMoveTo);
Scott Kennedydd2ec682013-06-03 19:16:13 -0700415 moveToInbox.setVisible(showMoveToInbox);
416
Rohan Shah69419bb2014-08-15 15:53:29 -0700417 final MenuItem changeFolders = menu.findItem(R.id.change_folders);
418 changeFolders.setVisible(mAccount.supportsCapability(
419 UIProvider.AccountCapabilities.MULTIPLE_FOLDERS_PER_CONV));
420
Mindy Pereira9ba42512012-08-14 11:52:48 -0700421 if (mFolder != null && showRemoveFolder) {
Mindy Pereira01f30502012-08-14 10:30:51 -0700422 removeFolder.setTitle(mActivity.getActivityContext().getString(R.string.remove_folder,
423 mFolder.name));
424 }
Mindy Pereira9ba42512012-08-14 11:52:48 -0700425 final MenuItem archive = menu.findItem(R.id.archive);
Andrew Sapperstein3047a9f2014-07-22 18:17:53 -0700426 if (archive != null) {
427 archive.setVisible(
428 mAccount.supportsCapability(UIProvider.AccountCapabilities.ARCHIVE) &&
429 mFolder.supportsCapability(FolderCapabilities.ARCHIVE));
Mindy Pereira50cfeda2012-08-14 13:54:56 -0700430 }
Paul Westbrook334e64a2012-02-23 13:26:35 -0800431 final MenuItem spam = menu.findItem(R.id.report_spam);
Mindy Pereira9ba42512012-08-14 11:52:48 -0700432 spam.setVisible(!showMarkNotSpam
433 && mAccount.supportsCapability(UIProvider.AccountCapabilities.REPORT_SPAM)
434 && mFolder.supportsCapability(FolderCapabilities.REPORT_SPAM));
Paul Westbrook77eee622012-07-10 13:41:57 -0700435 final MenuItem notSpam = menu.findItem(R.id.mark_not_spam);
436 notSpam.setVisible(showMarkNotSpam &&
437 mAccount.supportsCapability(UIProvider.AccountCapabilities.REPORT_SPAM) &&
438 mFolder.supportsCapability(FolderCapabilities.MARK_NOT_SPAM));
Paul Westbrook76b20622012-07-12 11:45:43 -0700439 final MenuItem phishing = menu.findItem(R.id.report_phishing);
440 phishing.setVisible(showMarkAsPhishing &&
441 mAccount.supportsCapability(UIProvider.AccountCapabilities.REPORT_PHISHING) &&
442 mFolder.supportsCapability(FolderCapabilities.REPORT_PHISHING));
443
Paul Westbrook334e64a2012-02-23 13:26:35 -0800444 final MenuItem mute = menu.findItem(R.id.mute);
Mindy Pereira50cfeda2012-08-14 13:54:56 -0700445 if (mute != null) {
446 mute.setVisible(mAccount.supportsCapability(UIProvider.AccountCapabilities.MUTE)
Scott Kennedy962a6922013-03-21 17:43:49 -0700447 && (mFolder != null && mFolder.isInbox()));
Mindy Pereira50cfeda2012-08-14 13:54:56 -0700448 }
Mindy Pereira7f0a9622012-02-29 15:00:34 -0800449 final MenuItem markImportant = menu.findItem(R.id.mark_important);
450 markImportant.setVisible(showMarkImportant
451 && mAccount.supportsCapability(UIProvider.AccountCapabilities.MARK_IMPORTANT));
452 final MenuItem markNotImportant = menu.findItem(R.id.mark_not_important);
453 markNotImportant.setVisible(!showMarkImportant
454 && mAccount.supportsCapability(UIProvider.AccountCapabilities.MARK_IMPORTANT));
Jin Cao512821c2014-05-30 15:54:04 -0700455
456 boolean shouldShowDiscardOutbox = mFolder != null && mFolder.isType(FolderType.OUTBOX);
457 mDiscardOutboxMenuItem = menu.findItem(R.id.discard_outbox);
458 if (mDiscardOutboxMenuItem != null) {
459 mDiscardOutboxMenuItem.setVisible(shouldShowDiscardOutbox);
460 mDiscardOutboxMenuItem.setEnabled(shouldEnableDiscardOutbox(conversations));
461 }
462 final boolean showDelete = mFolder != null && !mFolder.isType(FolderType.OUTBOX)
Paul Westbrookef362542012-08-27 14:53:32 -0700463 && mFolder.supportsCapability(UIProvider.FolderCapabilities.DELETE);
Mindy Pereirab8073372012-08-09 14:00:10 -0700464 final MenuItem trash = menu.findItem(R.id.delete);
Paul Westbrookef362542012-08-27 14:53:32 -0700465 trash.setVisible(showDelete);
466 // We only want to show the discard drafts menu item if we are not showing the delete menu
467 // item, and the current folder is a draft folder and the account supports discarding
468 // drafts for a conversation
469 final boolean showDiscardDrafts = !showDelete && mFolder != null && mFolder.isDraft() &&
470 mAccount.supportsCapability(AccountCapabilities.DISCARD_CONVERSATION_DRAFTS);
471 final MenuItem discardDrafts = menu.findItem(R.id.discard_drafts);
472 if (discardDrafts != null) {
473 discardDrafts.setVisible(showDiscardDrafts);
474 }
475
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800476 return true;
477 }
478
Jin Cao512821c2014-05-30 15:54:04 -0700479 private boolean shouldEnableDiscardOutbox(Collection<Conversation> conversations) {
480 boolean shouldEnableDiscardOutbox = true;
481 // Java should be smart enough to realize that once showDiscardOutbox becomes false it can
482 // just skip everything remaining in the for-loop..
483 for (Conversation conv : conversations) {
484 shouldEnableDiscardOutbox &=
485 conv.sendingState != UIProvider.ConversationSendingState.SENDING &&
486 conv.sendingState != UIProvider.ConversationSendingState.RETRYING;
487 }
488 return shouldEnableDiscardOutbox;
489 }
490
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800491 @Override
492 public void onDestroyActionMode(ActionMode mode) {
493 mActionMode = null;
494 // The action mode may have been destroyed due to this menu being deactivated, in which
495 // case resources need not be cleaned up. However, if it was destroyed while this menu is
496 // active, that implies the user hit "Done" in the top right, and resources need cleaning.
497 if (mActivated) {
498 destroy();
mindyp54f120f2012-08-28 13:10:33 -0700499 // Only commit destructive actions if the user actually pressed
500 // done; otherwise, this was handled when we toggled conversation
501 // selection state.
Scott Kennedyff8553f2013-04-05 20:57:44 -0700502 mActivity.getListHandler().commitDestructiveActions(true);
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800503 }
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800504 }
505
506 @Override
Jin Caoec0fa482014-08-28 16:38:08 -0700507 public void onSetPopulated(ConversationCheckedSet set) {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800508 // Noop. This object can only exist while the set is non-empty.
509 }
510
511 @Override
Vikram Aggarwal1ddcf0f2012-01-13 11:45:02 -0800512 public void onSetEmpty() {
Vikram Aggarwalad6d6282012-03-28 09:16:14 -0700513 LogUtils.d(LOG_TAG, "onSetEmpty called.");
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800514 destroy();
515 }
516
517 @Override
Jin Caoec0fa482014-08-28 16:38:08 -0700518 public void onSetChanged(ConversationCheckedSet set) {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800519 // If the set is empty, the menu buttons are invalid and most like the menu will be cleaned
520 // up. Avoid making any changes to stop flickering ("Add Star" -> "Remove Star") just
521 // before hiding the menu.
522 if (set.isEmpty()) {
523 return;
524 }
Andrew Sappersteindb861e92014-09-07 17:54:30 -0700525 updateCount();
Jin Cao512821c2014-05-30 15:54:04 -0700526
527 if (mFolder.isType(FolderType.OUTBOX) && mDiscardOutboxMenuItem != null) {
528 mDiscardOutboxMenuItem.setEnabled(shouldEnableDiscardOutbox(set.values()));
529 }
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800530 }
531
532 /**
Andrew Sappersteindb861e92014-09-07 17:54:30 -0700533 * Updates the visible count of how many conversations are selected.
534 */
535 private void updateCount() {
536 if (mActionMode != null) {
537 mActionMode.setTitle(Integer.toString(mCheckedSet.size()));
538 }
539 }
540
541 /**
Vikram Aggarwale128fc22012-04-04 12:33:34 -0700542 * Activates and shows this menu (essentially starting an {@link ActionMode}) if the selected
543 * set is non-empty.
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800544 */
545 public void activate() {
Jin Caoec0fa482014-08-28 16:38:08 -0700546 if (mCheckedSet.isEmpty()) {
Vikram Aggarwale128fc22012-04-04 12:33:34 -0700547 return;
548 }
Andrew Sapperstein2f542872013-06-11 10:48:30 -0700549 mListController.onCabModeEntered();
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800550 mActivated = true;
551 if (mActionMode == null) {
Andrew Sapperstein52882ff2014-07-27 12:30:18 -0700552 mActivity.startSupportActionMode(this);
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800553 }
554 }
555
556 /**
557 * De-activates and hides the menu (essentially disabling the {@link ActionMode}), but maintains
558 * the selection conversation set, and internally updates state as necessary.
559 */
560 public void deactivate() {
Scott Kennedycc139832013-08-19 18:03:54 -0700561 mListController.onCabModeExited();
Andrew Sapperstein4c928742014-08-29 15:34:23 -0700562 mActivated = false;
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800563 if (mActionMode != null) {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800564 mActionMode.finish();
565 }
566 }
567
568 @VisibleForTesting
Andrew Sapperstein4c928742014-08-29 15:34:23 -0700569 /**
570 * Returns true if CAB mode is active.
571 */
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800572 public boolean isActivated() {
573 return mActivated;
574 }
575
576 /**
577 * Destroys and cleans up the resources associated with this menu.
578 */
Vikram Aggarwalad6d6282012-03-28 09:16:14 -0700579 private void destroy() {
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800580 deactivate();
Jin Caoec0fa482014-08-28 16:38:08 -0700581 mCheckedSet.removeObserver(this);
582 clearChecked();
Vikram Aggarwal4f4782b2012-05-30 08:39:09 -0700583 mUpdater.refreshConversationList();
Mindy Pereira14f64ec2012-08-14 17:14:02 -0700584 if (mAccountObserver != null) {
585 mAccountObserver.unregisterAndDestroy();
586 mAccountObserver = null;
587 }
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800588 }
Mindy Pereira11b0bbd2012-01-24 10:06:17 -0800589}