diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java index 8e902223c..48206b5c6 100644 --- a/src/com/fsck/k9/activity/MessageList.java +++ b/src/com/fsck/k9/activity/MessageList.java @@ -22,33 +22,33 @@ import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.util.Log; import android.util.TypedValue; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.view.animation.Animation.AnimationListener; import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.Window; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.GestureDetector.SimpleOnGestureListener; -import android.view.View.OnClickListener; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; +import android.view.animation.AnimationUtils; import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.BaseAdapter; import android.widget.CheckBox; import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ImageButton; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; -import android.widget.AdapterView.AdapterContextMenuInfo; -import android.widget.CompoundButton.OnCheckedChangeListener; import com.fsck.k9.Account; import com.fsck.k9.AccountStats; @@ -61,16 +61,16 @@ import com.fsck.k9.activity.setup.AccountSettings; import com.fsck.k9.activity.setup.FolderSettings; import com.fsck.k9.activity.setup.Prefs; import com.fsck.k9.controller.MessagingController; -import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.controller.MessagingController.SORT_TYPE; +import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.helper.MessageHelper; import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.Folder; import com.fsck.k9.mail.Message; import com.fsck.k9.mail.store.LocalStore; -import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.mail.store.LocalStore.LocalFolder; +import com.fsck.k9.mail.store.StorageManager; /** @@ -207,8 +207,6 @@ public class MessageList private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1; private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2; - private static final int ACTIVITY_CHOOSE_FOLDER_MOVE_BATCH = 3; - private static final int ACTIVITY_CHOOSE_FOLDER_COPY_BATCH = 4; private static final String EXTRA_ACCOUNT = "account"; private static final String EXTRA_FOLDER = "folder"; @@ -298,9 +296,21 @@ public class MessageList private FontSizes mFontSizes = K9.getFontSizes(); private Bundle mState = null; - private MessageInfoHolder mSelectedMessage = null; - private Context context = null; + /** + * Remember the selection to be consistent between menu display and menu item + * selection + */ + private MessageInfoHolder mSelectedMessage; + + /** + * Relevant messages for the current context when we have to remember the + * chosen messages between user interactions (eg. Selecting a folder for + * move operation) + */ + private List mActiveMessages; + + private Context context; /* package visibility for faster inner class access */ MessageHelper mMessageHelper = MessageHelper.getInstance(this); @@ -327,7 +337,13 @@ public class MessageList } class MessageListHandler { - public void removeMessage(final List messages) { + /** + * @param messages Never {@code null}. + */ + public void removeMessages(final List messages) { + if (messages.isEmpty()) { + return; + } runOnUiThread(new Runnable() { public void run() { for (MessageInfoHolder message : messages) { @@ -348,7 +364,13 @@ public class MessageList }); } + /** + * @param messages Never {@code null}. + */ public void addMessages(final List messages) { + if (messages.isEmpty()) { + return; + } final boolean wasEmpty = mAdapter.messages.isEmpty(); runOnUiThread(new Runnable() { public void run() { @@ -577,7 +599,7 @@ public class MessageList if (mSelectedCount > 0) { // In multiselect mode make sure that clicking on the item results // in toggling the 'selected' checkbox. - setSelected(message, !message.selected); + setSelected(Collections.singletonList(message), !message.selected); } else { onOpenMessage(message); } @@ -913,18 +935,20 @@ public class MessageList if (position >= 0) { MessageInfoHolder message = (MessageInfoHolder) mAdapter.getItem(position); + final List selection = getSelectionFromMessage(message); + if (message != null) { switch (keyCode) { case KeyEvent.KEYCODE_DEL: { - onDelete(message, position); + onDelete(selection); return true; } case KeyEvent.KEYCODE_S: { - setSelected(message, !message.selected); + setSelected(selection, !message.selected); return true; } case KeyEvent.KEYCODE_D: { - onDelete(message, position); + onDelete(selection); return true; } case KeyEvent.KEYCODE_F: { @@ -940,23 +964,23 @@ public class MessageList return true; } case KeyEvent.KEYCODE_G: { - onToggleFlag(message); + setFlag(selection, Flag.FLAGGED, !message.flagged); return true; } case KeyEvent.KEYCODE_M: { - onMove(message); + onMove(selection); return true; } case KeyEvent.KEYCODE_V: { - onArchive(message); + onArchive(selection); return true; } case KeyEvent.KEYCODE_Y: { - onCopy(message); + onCopy(selection); return true; } case KeyEvent.KEYCODE_Z: { - onToggleRead(message); + setFlag(selection, Flag.SEEN, !message.read); return true; } } @@ -1097,150 +1121,57 @@ public class MessageList reSort(); } - private void onDelete(MessageInfoHolder holder, int position) { - mAdapter.removeMessage(holder); - mController.deleteMessages(new Message[] { holder.message }, null); - } - - private void onMove(MessageInfoHolder holder) { - if (!mController.isMoveCapable(holder.message.getFolder().getAccount())) { - return; + /** + * @param holders + * Never {@code null}. + */ + private void onDelete(final List holders) { + // FIXME: removeMessage does it own check on the 'selected' field, we're duplicating the logic here... + final List messagesToRemove = new ArrayList(); + for (MessageInfoHolder holder : holders) { + if (holder.selected) { + messagesToRemove.add(holder.message); + } } - - if (!mController.isMoveCapable(holder.message)) { - Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG); - toast.show(); - return; - } - - final Account account = holder.message.getFolder().getAccount(); - - Intent intent = new Intent(this, ChooseFolder.class); - intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, account.getUuid()); - intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, holder.folder.name); - intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, account.getLastSelectedFolderName()); - intent.putExtra(ChooseFolder.EXTRA_MESSAGE, holder.message.makeMessageReference()); - startActivityForResult(intent, ACTIVITY_CHOOSE_FOLDER_MOVE); - } - - private void onArchive(MessageInfoHolder holder) { - - if (!mController.isMoveCapable(holder.message)) { - Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG); - toast.show(); - return; - } - - onMoveChosen(holder, holder.message.getFolder().getAccount().getArchiveFolderName()); - } - - private void onSpam(MessageInfoHolder holder) { - if (K9.confirmSpam()) { - // The action handler needs this to move the message later - mSelectedMessage = holder; - showDialog(R.id.dialog_confirm_spam); - } else { - moveToSpamFolder(holder); - } - } - - private void moveToSpamFolder(MessageInfoHolder holder) { - if (!mController.isMoveCapable(holder.message)) { - Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG); - toast.show(); - return; - } - onMoveChosen(holder, holder.message.getFolder().getAccount().getSpamFolderName()); - } - - private void onCopy(MessageInfoHolder holder) { - if (!mController.isCopyCapable(holder.message.getFolder().getAccount())) { - return; - } - - if (!mController.isCopyCapable(holder.message)) { - Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG); - toast.show(); - return; - } - - final Account account = holder.message.getFolder().getAccount(); - - Intent intent = new Intent(this, ChooseFolder.class); - intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, account.getUuid()); - intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, holder.folder.name); - intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, account.getLastSelectedFolderName()); - intent.putExtra(ChooseFolder.EXTRA_MESSAGE, holder.message.makeMessageReference()); - startActivityForResult(intent, ACTIVITY_CHOOSE_FOLDER_COPY); + mHandler.removeMessages(holders); + mController.deleteMessages(messagesToRemove.toArray(EMPTY_MESSAGE_ARRAY), null); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode != RESULT_OK) + if (resultCode != RESULT_OK) { return; + } switch (requestCode) { case ACTIVITY_CHOOSE_FOLDER_MOVE: case ACTIVITY_CHOOSE_FOLDER_COPY: { - if (data == null) + if (data == null) { return; + } final String destFolderName = data.getStringExtra(ChooseFolder.EXTRA_NEW_FOLDER); - final MessageReference ref = data.getParcelableExtra(ChooseFolder.EXTRA_MESSAGE); - final MessageInfoHolder m = mAdapter.getMessage(ref); - if ((destFolderName != null) && (m != null)) { - final Account account = m.message.getFolder().getAccount(); + if (destFolderName != null) { + final List holders = mActiveMessages; + mActiveMessages = null; // don't need it any more + + final Account account = holders.get(0).message.getFolder().getAccount(); account.setLastSelectedFolderName(destFolderName); switch (requestCode) { case ACTIVITY_CHOOSE_FOLDER_MOVE: - onMoveChosen(m, destFolderName); + move(holders, destFolderName); break; case ACTIVITY_CHOOSE_FOLDER_COPY: - onCopyChosen(m, destFolderName); + copy(holders, destFolderName); break; } } break; } - case ACTIVITY_CHOOSE_FOLDER_MOVE_BATCH: - case ACTIVITY_CHOOSE_FOLDER_COPY_BATCH: { - final String destFolderName = data.getStringExtra(ChooseFolder.EXTRA_NEW_FOLDER); - final String accountUuid = data.getStringExtra(ChooseFolder.EXTRA_ACCOUNT); - final Account account = Preferences.getPreferences(this).getAccount(accountUuid); - - account.setLastSelectedFolderName(destFolderName); - - switch (requestCode) { - case ACTIVITY_CHOOSE_FOLDER_MOVE_BATCH: - onMoveChosenBatch(destFolderName); - break; - - case ACTIVITY_CHOOSE_FOLDER_COPY_BATCH: - onCopyChosenBatch(destFolderName); - break; - } - } - } - } - - private void onMoveChosen(MessageInfoHolder holder, String folderName) { - if (mController.isMoveCapable(holder.message.getFolder().getAccount()) && folderName != null) { - if (K9.FOLDER_NONE.equalsIgnoreCase(folderName)) { - return; - } - mAdapter.removeMessage(holder); - mController.moveMessage(holder.message.getFolder().getAccount(), holder.message.getFolder().getName(), holder.message, folderName, null); - } - } - - private void onCopyChosen(MessageInfoHolder holder, String folderName) { - if (mController.isCopyCapable(holder.message.getFolder().getAccount()) && folderName != null) { - mController.copyMessage(holder.message.getFolder().getAccount(), - holder.message.getFolder().getName(), holder.message, folderName, null); } } @@ -1308,9 +1239,9 @@ public class MessageList new Runnable() { @Override public void run() { - moveToSpamFolder(mSelectedMessage); + onSpamConfirmed(mActiveMessages); // No further need for this reference - mSelectedMessage = null; + mActiveMessages = null; } }); } @@ -1353,6 +1284,7 @@ public class MessageList @Override public boolean onOptionsItemSelected(MenuItem item) { + final List selection = getSelectionFromCheckboxes(); int itemId = item.getItemId(); switch (itemId) { case R.id.compose: { @@ -1399,23 +1331,23 @@ public class MessageList return true; } case R.id.batch_delete_op: { - deleteSelected(); + onDelete(selection); return true; } case R.id.batch_mark_read_op: { - flagSelected(Flag.SEEN, true); + setFlag(selection, Flag.SEEN, true); return true; } case R.id.batch_mark_unread_op: { - flagSelected(Flag.SEEN, false); + setFlag(selection, Flag.SEEN, false); return true; } case R.id.batch_flag_op: { - flagSelected(Flag.FLAGGED, true); + setFlag(selection, Flag.FLAGGED, true); return true; } case R.id.batch_unflag_op: { - flagSelected(Flag.FLAGGED, false); + setFlag(selection, Flag.FLAGGED, false); return true; } case R.id.app_settings: { @@ -1462,19 +1394,19 @@ public class MessageList return true; } case R.id.batch_copy_op: { - onCopyBatch(); + onCopy(selection); return true; } case R.id.batch_archive_op: { - onArchiveBatch(); + onArchive(selection); return true; } case R.id.batch_spam_op: { - onSpamBatch(); + onSpam(selection); return true; } case R.id.batch_move_op: { - onMoveBatch(); + onMove(selection); return true; } case R.id.expunge: { @@ -1564,28 +1496,26 @@ public class MessageList @Override public boolean onContextItemSelected(MenuItem item) { AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); - MessageInfoHolder holder = mSelectedMessage; + final MessageInfoHolder holder = mSelectedMessage == null ? (MessageInfoHolder) mAdapter.getItem(info.position) : mSelectedMessage; // don't need this anymore mSelectedMessage = null; - if (holder == null) { - holder = (MessageInfoHolder) mAdapter.getItem(info.position); - } + final List selection = getSelectionFromMessage(holder); switch (item.getItemId()) { case R.id.open: { onOpenMessage(holder); break; } case R.id.select: { - setSelected(holder, true); + setSelected(selection, true); break; } case R.id.deselect: { - setSelected(holder, false); + setSelected(selection, false); break; } case R.id.delete: { - onDelete(holder, info.position); + onDelete(selection); break; } case R.id.reply: { @@ -1614,19 +1544,19 @@ public class MessageList break; } case R.id.archive: { - onArchive(holder); + onArchive(selection); break; } case R.id.spam: { - onSpam(holder); + onSpam(selection); break; } case R.id.move: { - onMove(holder); + onMove(selection); break; } case R.id.copy: { - onCopy(holder); + onCopy(selection); break; } case R.id.send_alternate: { @@ -1800,7 +1730,7 @@ public class MessageList if (holder == null) { Log.w(K9.LOG_TAG, "Got callback to remove non-existent message with UID " + message.getUid()); } else { - removeMessage(holder); + removeMessages(Collections.singletonList(holder)); } } @@ -1840,7 +1770,7 @@ public class MessageList public void listLocalMessagesRemoveMessage(Account account, String folder, Message message) { MessageInfoHolder holder = getMessage(message); if (holder != null) { - removeMessage(holder); + removeMessages(Collections.singletonList(holder)); } } @@ -1914,22 +1844,18 @@ public class MessageList mSelectedCount--; toggleBatchButtons(); } - mAdapter.removeMessage(holder); + mAdapter.removeMessages(Collections.singletonList(holder)); } } } } + /** + * @param holders + * Never {@code null}. + */ public void removeMessages(List holders) { - if (holders != null) { - mHandler.removeMessage(holders); - } - } - - public void removeMessage(MessageInfoHolder holder) { - List messages = new ArrayList(); - messages.add(holder); - removeMessages(messages); + mHandler.removeMessages(holders); } private void addOrUpdateMessage(Account account, String folderName, Message message, boolean verifyAgainstSearch) { @@ -2558,171 +2484,291 @@ public class MessageList toggleBatchButtons(); } - private void setSelected(MessageInfoHolder holder, boolean newState) { - if (holder.selected != newState) { - holder.selected = newState; - mSelectedCount += (newState ? 1 : -1); + private void setSelected(final List holders, final boolean newState) { + for (final MessageInfoHolder holder : holders) { + if (holder.selected != newState) { + holder.selected = newState; + mSelectedCount += (newState ? 1 : -1); + } } mAdapter.notifyDataSetChanged(); toggleBatchButtons(); } - private void flagSelected(Flag flag, boolean newState) { - List messageList = new ArrayList(); - synchronized (mAdapter.messages) { - for (MessageInfoHolder holder : mAdapter.messages) { - if (holder.selected) { - messageList.add(holder.message); - if (flag == Flag.SEEN) { - holder.read = newState; - } else if (flag == Flag.FLAGGED) { - holder.flagged = newState; - } - } + /** + * @param holders + * Messages to update. Never {@code null}. + * @param flag + * Flag to be updated on the specified messages. Never + * {@code null}. + * @param newState + * State to set for the given flag. + */ + private void setFlag(final List holders, final Flag flag, final boolean newState) { + if (holders.isEmpty()) { + return; + } + final Message[] messageList = new Message[holders.size()]; + int i = 0; + for (final Iterator iterator = holders.iterator(); iterator.hasNext(); i++) { + final MessageInfoHolder holder = iterator.next(); + messageList[i] = holder.message; + if (flag == Flag.SEEN) { + holder.read = newState; + } else if (flag == Flag.FLAGGED) { + holder.flagged = newState; } } - mController.setFlag(messageList.toArray(EMPTY_MESSAGE_ARRAY), flag, newState); + mController.setFlag(messageList, flag, newState); mHandler.sortMessages(); } - private void deleteSelected() { - List messageList = new ArrayList(); - List removeHolderList = new ArrayList(); - synchronized (mAdapter.messages) { - for (MessageInfoHolder holder : mAdapter.messages) { - if (holder.selected) { - removeHolderList.add(holder); - messageList.add(holder.message); - } - } - } - mAdapter.removeMessages(removeHolderList); - - mController.deleteMessages(messageList.toArray(EMPTY_MESSAGE_ARRAY), null); - mSelectedCount = 0; - toggleBatchButtons(); - } - - private void onMoveBatch() { - if (!mController.isMoveCapable(mAccount)) { + /** + * Display the message move activity. + * + * @param holders + * Never {@code null}. + */ + private void onMove(final List holders) { + if (!checkCopyOrMovePossible(holders, FolderOperation.MOVE)) { return; } - synchronized (mAdapter.messages) { - for (MessageInfoHolder holder : mAdapter.messages) { - if (holder.selected) { - Message message = holder.message; - if (!mController.isMoveCapable(message)) { - Toast toast = Toast.makeText(this, - R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG); - toast.show(); - return; - } - } - } + final Folder folder = holders.size() == 1 ? holders.get(0).message.getFolder() : mCurrentFolder.folder; + displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_MOVE, folder, holders); + } + + /** + * Display the message copy activity. + * + * @param holders + * Never {@code null}. + */ + private void onCopy(final List holders) { + if (!checkCopyOrMovePossible(holders, FolderOperation.COPY)) { + return; } - final Folder folder = mCurrentFolder.folder; + final Folder folder = holders.size() == 1 ? holders.get(0).message.getFolder() : mCurrentFolder.folder; + displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_COPY, folder, holders); + } + + /** + * Helper method to manage the invocation of + * {@link #startActivityForResult(Intent, int)} for a folder operation + * ({@link ChooseFolder} activity), while saving a list of associated + * messages. + * + * @param requestCode + * If >= 0, this code will be returned in onActivityResult() when + * the activity exits. + * @param folder + * Never {@code null}. + * @param holders + * Messages to be affected by the folder operation. Never + * {@code null}. + * @see #startActivityForResult(Intent, int) + */ + private void displayFolderChoice(final int requestCode, final Folder folder, final List holders) { final Intent intent = new Intent(this, ChooseFolder.class); - intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, mAccount.getUuid()); + intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, folder.getAccount().getUuid()); intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, folder.getName()); intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, folder.getAccount().getLastSelectedFolderName()); - startActivityForResult(intent, ACTIVITY_CHOOSE_FOLDER_MOVE_BATCH); + // remember the selected messages for #onActivityResult + mActiveMessages = holders; + startActivityForResult(intent, requestCode); } - private void onMoveChosenBatch(String folderName) { - if (!mController.isMoveCapable(mAccount)) { - return; - } - List messageList = new ArrayList(); - - List removeHolderList = new ArrayList(); - synchronized (mAdapter.messages) { - for (MessageInfoHolder holder : mAdapter.messages) { - if (holder.selected) { - Message message = holder.message; - if (!mController.isMoveCapable(message)) { - Toast toast = Toast.makeText(this, - R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG); - toast.show(); - return; - } - messageList.add(holder.message); - removeHolderList.add(holder); - } - } - } - mAdapter.removeMessages(removeHolderList); - - mController.moveMessages(mAccount, mCurrentFolder.name, messageList.toArray(EMPTY_MESSAGE_ARRAY), folderName, null); - mSelectedCount = 0; - toggleBatchButtons(); - } - - private void onArchiveBatch() { - String folderName = mAccount.getArchiveFolderName(); + /** + * @param holders + * Never {@code null}. + */ + private void onArchive(final List holders) { + final String folderName = holders.get(0).message.getFolder().getAccount().getArchiveFolderName(); if (K9.FOLDER_NONE.equalsIgnoreCase(folderName)) { return; } - onMoveChosenBatch(folderName); + // TODO one should separate messages by account and call move afterwards + // (because each account might have a specific Archive folder name) + move(holders, folderName); } - private void onSpamBatch() { - String folderName = mAccount.getSpamFolderName(); + /** + * @param holders + * Never {@code null}. + */ + private void onSpam(final List holders) { + if (K9.confirmSpam()) { + // remember the message selection for #onCreateDialog(int) + mActiveMessages = holders; + showDialog(R.id.dialog_confirm_spam); + } else { + onSpamConfirmed(holders); + } + } + + /** + * @param holders + * Never {@code null}. + */ + private void onSpamConfirmed(final List holders) { + final String folderName = holders.get(0).message.getFolder().getAccount().getSpamFolderName(); if (K9.FOLDER_NONE.equalsIgnoreCase(folderName)) { return; } - onMoveChosenBatch(folderName); + // TODO one should separate messages by account and call move afterwards + // (because each account might have a specific Spam folder name) + move(holders, folderName); } - private void onCopyBatch() { - if (!mController.isCopyCapable(mAccount)) { - return; - } + private static enum FolderOperation { + COPY, MOVE + } + /** + * Display an Toast message if any message isn't synchronized + * + * @param holders + * Never null. + * @param move + * true to check move availability, + * false to check the copy availability + * + * @return true if operation is possible + */ + private boolean checkCopyOrMovePossible(final List holders, final FolderOperation operation) { + if (holders.isEmpty()) { + return false; + } + boolean first = true; + for (final MessageInfoHolder holder : holders) { + final Message message = holder.message; + if (first) { + first = false; + // account check + final Account account = message.getFolder().getAccount(); + if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(account)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(account))) { + return false; + } + } + // message check + if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(message)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(message))) { + final Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, + Toast.LENGTH_LONG); + toast.show(); + return false; + } + } + return true; + } + + /** + * Helper method to get a List of message ready to be processed. This implementation will return a list containing the sole argument. + * + * @param holder Never {@code null}. + * @return Never {@code null}. + */ + private List getSelectionFromMessage(final MessageInfoHolder holder) { + final List selection = Collections.singletonList(holder); + return selection; + } + + /** + * Helper method to get a List of message ready to be processed. This implementation will iterate over messages and choose the checked ones. + * + * @return Never {@code null}. + */ + private List getSelectionFromCheckboxes() { + final List selection = new ArrayList(); synchronized (mAdapter.messages) { - for (MessageInfoHolder holder : mAdapter.messages) { + for (final MessageInfoHolder holder : mAdapter.messages) { if (holder.selected) { - Message message = holder.message; - if (!mController.isCopyCapable(message)) { - Toast toast = Toast.makeText(this, - R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG); - toast.show(); - return; - } + selection.add(holder); } } } - - final Folder folder = mCurrentFolder.folder; - final Intent intent = new Intent(this, ChooseFolder.class); - intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, mAccount.getUuid()); - intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, folder.getName()); - intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, folder.getAccount().getLastSelectedFolderName()); - startActivityForResult(intent, ACTIVITY_CHOOSE_FOLDER_COPY_BATCH); + return selection; } - private void onCopyChosenBatch(String folderName) { - if (!mController.isCopyCapable(mAccount)) { + /** + * Copy the specified messages to the specified folder. + * + * @param holders Never {@code null}. + * @param destination Never {@code null}. + */ + private void copy(final List holders, final String destination) { + copyOrMove(holders, destination, FolderOperation.COPY); + } + + /** + * Move the specified messages to the specified folder. + * + * @param holders Never {@code null}. + * @param destination Never {@code null}. + */ + private void move(final List holders, final String destination) { + copyOrMove(holders, destination, FolderOperation.MOVE); + } + + /** + * The underlying implementation for {@link #copy(List, String)} and + * {@link #move(List, String)}. This method was added mainly because those 2 + * methods share common behavior. + * + * @param holders + * Never {@code null}. + * @param destination + * Never {@code null}. + * @param operation + * Never {@code null}. + */ + private void copyOrMove(final List holders, final String destination, final FolderOperation operation) { + if (K9.FOLDER_NONE.equalsIgnoreCase(destination)) { return; } - List messageList = new ArrayList(); - synchronized (mAdapter.messages) { - for (MessageInfoHolder holder : mAdapter.messages) { - if (holder.selected) { - Message message = holder.message; - if (!mController.isCopyCapable(message)) { - Toast toast = Toast.makeText(this, - R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG); - toast.show(); - return; - } - messageList.add(holder.message); + boolean first = true; + Account account = null; + String folderName = null; + + final List messages = new ArrayList(holders.size()); + + for (final MessageInfoHolder holder : holders) { + final Message message = holder.message; + if (first) { + first = false; + folderName = message.getFolder().getName(); + account = message.getFolder().getAccount(); + if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(account)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(account))) { + // account is not copy/move capable + return; } + } else if (!account.equals(message.getFolder().getAccount()) + || !folderName.equals(message.getFolder().getName())) { + // make sure all messages come from the same account/folder? + return; } + if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(message)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(message))) { + final Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, + Toast.LENGTH_LONG); + toast.show(); + + // XXX return meaningful error value? + + // message isn't synchronized + return; + } + messages.add(message); + } + + if (operation == FolderOperation.MOVE) { + mController.moveMessages(account, folderName, messages.toArray(new Message[messages.size()]), destination, + null); + mHandler.removeMessages(holders); + } else { + mController.copyMessages(account, folderName, messages.toArray(new Message[messages.size()]), destination, + null); } - mController.copyMessages(mAccount, mCurrentFolder.name, messageList.toArray(EMPTY_MESSAGE_ARRAY), folderName, null); } protected void onAccountUnavailable() {