Update logic for adding/removing folders.
The logic is:
we only want to add/ remove if the folder was changed
So, track only the operations a user makes (aka checks a box/ unchecks a box)
And use this to determine what to add/ remove from a conversation.
Change-Id: I37d9c042e2db5f1a48c5c8a79c52039989f236d1
diff --git a/src/com/android/mail/providers/Folder.java b/src/com/android/mail/providers/Folder.java
index 6c1a518..b2a3995 100644
--- a/src/com/android/mail/providers/Folder.java
+++ b/src/com/android/mail/providers/Folder.java
@@ -43,9 +43,10 @@
import org.json.JSONException;
import org.json.JSONObject;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.List;
+import java.util.HashMap;
import java.util.Map;
/**
@@ -261,8 +262,8 @@
return null;
}
- public static List<Folder> forFoldersString(String foldersString) {
- final List<Folder> folders = Lists.newArrayList();
+ public static ArrayList<Folder> forFoldersString(String foldersString) {
+ final ArrayList<Folder> folders = Lists.newArrayList();
if (foldersString == null) {
return folders;
}
@@ -277,6 +278,25 @@
return folders;
}
+
+ public static HashMap<Uri, Folder> hashMapForFoldersString(String rawFolders) {
+ final HashMap<Uri, Folder> folders = new HashMap<Uri, Folder>();
+ if (TextUtils.isEmpty(rawFolders)) {
+ return folders;
+ }
+ try {
+ JSONArray array = new JSONArray(rawFolders);
+ Folder f;
+ for (int i = 0; i < array.length(); i++) {
+ f = new Folder(array.getJSONObject(i));
+ folders.put(f.uri, f);
+ }
+ } catch (JSONException e) {
+ LogUtils.wtf(LOG_TAG, e, "Unable to create list of folders from serialzied jsonarray");
+ }
+ return folders;
+ }
+
/**
* Return a serialized String for this account.
*/
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index 65255dc..ca4606c 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -85,6 +85,7 @@
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.Set;
import java.util.TimerTask;
@@ -1635,21 +1636,21 @@
// Called from the FolderSelectionDialog after a user is done selecting folders to assign the
// conversations to.
@Override
- public final void assignFolder(Collection<Folder> folders, Collection<Conversation> target,
- boolean batch, boolean showUndo) {
- // Actions are destructive only when the current folder can be assigned to (which is the
- // same as being able to un-assign a conversation from the folder) and when the list of
- // folders contains the current folder.
- final boolean isDestructive =
- mFolder.supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES) &&
- !Folder.containerIncludes(folders, mFolder);
+ public final void assignFolder(Collection<FolderOperation> folderOps,
+ Collection<Conversation> target, boolean batch, boolean showUndo) {
+ // Actions are destructive only when the current folder can be assigned
+ // to (which is the same as being able to un-assign a conversation from the folder) and
+ // when the list of folders contains the current folder.
+ final boolean isDestructive = mFolder
+ .supportsCapability(FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES)
+ && FolderOperation.isDestructive(folderOps, mFolder);
LogUtils.d(LOG_TAG, "onFolderChangesCommit: isDestructive = %b", isDestructive);
if (isDestructive) {
for (final Conversation c : target) {
c.localDeleteOnUpdate = true;
}
}
- final DestructiveAction folderChange = getFolderChange(target, folders, isDestructive,
+ final DestructiveAction folderChange = getFolderChange(target, folderOps, isDestructive,
batch, showUndo);
// Update the UI elements depending no their visibility and availability
// TODO(viki): Consolidate this into a single method requestDelete.
@@ -1859,8 +1860,10 @@
return;
}
final Collection<Conversation> conversations = mSelectedSet.values();
- final Collection<Folder> dropTarget = Folder.listOf(folder);
- // Drag and drop is destructive: we remove conversations from the current folder.
+ final Collection<FolderOperation> dropTarget = FolderOperation.listOf(new FolderOperation(
+ folder, true));
+ // Drag and drop is destructive: we remove conversations from the
+ // current folder.
final DestructiveAction action = getFolderChange(conversations, dropTarget, true, true,
true);
delete(conversations, action);
@@ -2011,7 +2014,7 @@
*/
private class FolderDestruction implements DestructiveAction {
private final Collection<Conversation> mTarget;
- private final ArrayList<Folder> mFolderList = new ArrayList<Folder>();
+ private final ArrayList<FolderOperation> mFolderOps = new ArrayList<FolderOperation>();
private final boolean mIsDestructive;
/** Whether this destructive action has already been performed */
private boolean mCompleted;
@@ -2023,10 +2026,10 @@
* @param target
*/
private FolderDestruction(final Collection<Conversation> target,
- final Collection<Folder> folders, boolean isDestructive, boolean isBatch,
+ final Collection<FolderOperation> folders, boolean isDestructive, boolean isBatch,
boolean showUndo) {
mTarget = ImmutableList.copyOf(target);
- mFolderList.addAll(folders);
+ mFolderOps.addAll(folders);
mIsDestructive = isDestructive;
mIsSelectedSet = isBatch;
mShowUndo = showUndo;
@@ -2041,19 +2044,32 @@
UndoOperation undoOp = new UndoOperation(mTarget.size(), R.id.change_folder);
onUndoAvailable(undoOp);
}
- mConversationListCursor.updateStrings(
- mContext,
- mTarget,
- Conversation.UPDATE_FOLDER_COLUMNS,
- new String[] {
- Folder.getUriString(mFolderList),
- Folder.getSerializedFolderString(mFolder, mFolderList)
- });
+ // For each conversation, for each operation, add/ remove the
+ // appropriate folders.
+ for (Conversation target : mTarget) {
+ HashMap<Uri, Folder> targetFolders = Folder
+ .hashMapForFoldersString(target.rawFolders);
+ for (FolderOperation op : mFolderOps) {
+ if (op.mAdd) {
+ targetFolders.put(op.mFolder.uri, op.mFolder);
+ } else {
+ targetFolders.remove(op.mFolder.uri);
+ }
+ }
+ target.folderList = Folder.getUriString(targetFolders.values());
+ target.rawFolders = Folder.getSerializedFolderString(mFolder,
+ targetFolders.values());
+ mConversationListCursor.updateStrings(mContext, Conversation.listOf(target),
+ Conversation.UPDATE_FOLDER_COLUMNS, new String[] {
+ target.folderList, target.rawFolders
+ });
+ }
refreshConversationList();
if (mIsSelectedSet) {
mSelectedSet.clear();
}
}
+
/**
* Returns true if this action has been performed, false otherwise.
* @return
@@ -2068,7 +2084,8 @@
}
private final DestructiveAction getFolderChange(Collection<Conversation> target,
- Collection<Folder> folders, boolean isDestructive, boolean isBatch, boolean showUndo) {
+ Collection<FolderOperation> folders, boolean isDestructive, boolean isBatch,
+ boolean showUndo) {
final DestructiveAction da = new FolderDestruction(target, folders, isDestructive, isBatch,
showUndo);
registerDestructiveAction(da);
diff --git a/src/com/android/mail/ui/ConversationUpdater.java b/src/com/android/mail/ui/ConversationUpdater.java
index 5f6b93f..5f59770 100644
--- a/src/com/android/mail/ui/ConversationUpdater.java
+++ b/src/com/android/mail/ui/ConversationUpdater.java
@@ -18,7 +18,6 @@
package com.android.mail.ui;
import com.android.mail.providers.Conversation;
-import com.android.mail.providers.Folder;
import com.android.mail.providers.UIProvider;
import java.util.Collection;
@@ -80,7 +79,7 @@
* @param batch whether this is a batch operation
* @param showUndo whether to show the undo bar
*/
- public void assignFolder(Collection<Folder> folders, Collection<Conversation> target,
+ public void assignFolder(Collection<FolderOperation> folders, Collection<Conversation> target,
boolean batch, boolean showUndo);
/**
diff --git a/src/com/android/mail/ui/FolderOperation.java b/src/com/android/mail/ui/FolderOperation.java
new file mode 100644
index 0000000..20614e2
--- /dev/null
+++ b/src/com/android/mail/ui/FolderOperation.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ * Licensed to The Android Open Source Project.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mail.ui;
+
+import com.android.mail.providers.Folder;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+
+public class FolderOperation {
+ /** An immutable, empty conversation list */
+ public static final Collection<FolderOperation> EMPTY = Collections.emptyList();
+ public Folder mFolder;
+ public boolean mAdd;
+
+ public FolderOperation(Folder folder, Boolean operation) {
+ mAdd = operation;
+ mFolder = folder;
+ }
+
+ /**
+ * Get all the unique folders associated with a set of folder operations.
+ * @param ops
+ * @return
+ */
+ public final static ArrayList<Folder> getFolders(Collection<FolderOperation> ops) {
+ HashSet<Folder> folders = new HashSet<Folder>();
+ for (FolderOperation op : ops) {
+ folders.add(op.mFolder);
+ }
+ return new ArrayList<Folder>(folders);
+ }
+
+ /**
+ * Returns a collection of a single FolderOperation. This method always
+ * returns a valid collection even if the input folder is null.
+ *
+ * @param in a FolderOperation, possibly null.
+ * @return a collection of the folder.
+ */
+ public static Collection<FolderOperation> listOf(FolderOperation in) {
+ final Collection<FolderOperation> target = (in == null) ? EMPTY : ImmutableList.of(in);
+ return target;
+ }
+
+ /**
+ * Return if a set of folder operations removes the specified folder, making
+ * it a destructive operation.
+ */
+ public static boolean isDestructive(Collection<FolderOperation> folderOps, Folder folder) {
+ for (FolderOperation op : folderOps) {
+ if (Objects.equal(op.mFolder.uri, folder.uri) && !op.mAdd) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/mail/ui/FoldersSelectionDialog.java b/src/com/android/mail/ui/FoldersSelectionDialog.java
index 66afbed..b6e4b9d 100644
--- a/src/com/android/mail/ui/FoldersSelectionDialog.java
+++ b/src/com/android/mail/ui/FoldersSelectionDialog.java
@@ -21,8 +21,8 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
-import android.content.DialogInterface.OnMultiChoiceClickListener;
import android.database.Cursor;
+import android.net.Uri;
import android.text.TextUtils;
import android.view.View;
import android.widget.AdapterView;
@@ -35,14 +35,12 @@
import com.android.mail.ui.FolderSelectorAdapter.FolderRow;
import com.android.mail.utils.Utils;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Map.Entry;
-public class FoldersSelectionDialog implements OnClickListener, OnMultiChoiceClickListener {
+public class FoldersSelectionDialog implements OnClickListener {
private AlertDialog mDialog;
private ConversationUpdater mUpdater;
private HashMap<Folder, Boolean> mCheckedState;
@@ -50,6 +48,7 @@
private SeparatedFolderListAdapter mAdapter;
private final Collection<Conversation> mTarget;
private boolean mBatch;
+ private HashMap<Uri, FolderOperation> mOperations;
public FoldersSelectionDialog(final Context context, Account account,
final ConversationUpdater updater, Collection<Conversation> target, boolean isBatch) {
@@ -59,6 +58,7 @@
// Mapping of a folder's uri to its checked state
mCheckedState = new HashMap<Folder, Boolean>();
+ mOperations = new HashMap<Uri, FolderOperation>();
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.folder_selection_dialog_title);
builder.setPositiveButton(R.string.ok, this);
@@ -143,46 +143,31 @@
Object item = mAdapter.getItem(i);
if (item instanceof FolderRow) {
((FolderRow)item).setIsPresent(false);
+ Folder folder = ((FolderRow)item).getFolder();
+ mOperations.put(folder.uri, new FolderOperation(folder, false));
}
}
mCheckedState.clear();
}
row.setIsPresent(add);
mAdapter.notifyDataSetChanged();
- mCheckedState.put(row.getFolder(), add);
+ Folder folder = row.getFolder();
+ mCheckedState.put(folder, add);
+ mOperations.put(folder.uri, new FolderOperation(folder, add));
}
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
- final Collection<Folder> folders = new ArrayList<Folder>();
- for (Entry<Folder, Boolean> entry : mCheckedState.entrySet()) {
- if (entry.getValue()) {
- folders.add(entry.getKey());
- }
- }
if (mUpdater != null) {
- mUpdater.assignFolder(folders, mTarget, mBatch, true);
+ mUpdater.assignFolder(mOperations.values(), mTarget, mBatch, true);
}
break;
case DialogInterface.BUTTON_NEGATIVE:
break;
default:
- onClick(dialog, which, true);
break;
}
}
-
- @Override
- public void onClick(DialogInterface dialog, int which, boolean isChecked) {
- final FolderRow row = (FolderRow) mAdapter.getItem(which);
- if (mSingle) {
- // Clear any other checked items.
- mCheckedState.clear();
- isChecked = true;
- }
- mCheckedState.put(row.getFolder(), isChecked);
- mDialog.getListView().setItemChecked(which, false);
- }
}
diff --git a/src/com/android/mail/ui/SwipeableListView.java b/src/com/android/mail/ui/SwipeableListView.java
index 9f6a58d..93237c3 100644
--- a/src/com/android/mail/ui/SwipeableListView.java
+++ b/src/com/android/mail/ui/SwipeableListView.java
@@ -21,6 +21,7 @@
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Configuration;
+import android.net.Uri;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@@ -39,6 +40,7 @@
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
public class SwipeableListView extends ListView implements Callback{
private SwipeHelper mSwipeHelper;
@@ -184,27 +186,33 @@
conversations.add(conversation);
}
}
- undoOp = new UndoOperation(
- conversationViews != null ? (conversations.size() + 1) : 1, mSwipeAction);
+ undoOp = new UndoOperation(conversationViews != null ? (conversations.size() + 1) : 1,
+ mSwipeAction);
handleLeaveBehind(target, undoOp, context);
adapter.delete(conversations, new DestructiveAction() {
@Override
public void performAction() {
- ConversationCursor cc = (ConversationCursor)adapter.getCursor();
+ ConversationCursor cc = (ConversationCursor) adapter.getCursor();
switch (mSwipeAction) {
case R.id.archive:
cc.archive(context, conversations);
break;
case R.id.change_folder:
- Collection<Folder> folders = getFolders(conversations);
- cc.updateStrings(
- context,
- conversations,
- Conversation.UPDATE_FOLDER_COLUMNS,
- new String[] {
- Folder.getUriString(folders),
- Folder.getSerializedFolderString(mFolder, folders)
- });
+ FolderOperation folderOp = new FolderOperation(mFolder, false);
+ // For each conversation, for each operation, remove
+ // the current folder.
+ for (Conversation target : conversations) {
+ HashMap<Uri, Folder> targetFolders = Folder
+ .hashMapForFoldersString(target.rawFolders);
+ targetFolders.remove(folderOp.mFolder.uri);
+ target.folderList = Folder.getUriString(targetFolders.values());
+ target.rawFolders = Folder.getSerializedFolderString(mFolder,
+ targetFolders.values());
+ cc.updateStrings(context, Conversation.listOf(target),
+ Conversation.UPDATE_FOLDER_COLUMNS, new String[] {
+ target.folderList, target.rawFolders
+ });
+ }
break;
case R.id.delete:
cc.delete(context, conversations);
@@ -241,15 +249,15 @@
ConversationCursor cc = (ConversationCursor)adapter.getCursor();
switch (mSwipeAction) {
case R.id.change_folder:
- Collection<Conversation> convs = Conversation.listOf(conv);
- Collection<Folder> folders = getFolders(convs);
- cc.mostlyDestructiveUpdate(
- context,
- convs,
- Conversation.UPDATE_FOLDER_COLUMNS,
- new String[] {
- Folder.getUriString(folders),
- Folder.getSerializedFolderString(mFolder, folders)
+ FolderOperation folderOp = new FolderOperation(mFolder, false);
+ HashMap<Uri, Folder> targetFolders = Folder
+ .hashMapForFoldersString(conv.rawFolders);
+ targetFolders.remove(folderOp.mFolder.uri);
+ conv.folderList = Folder.getUriString(targetFolders.values());
+ conv.rawFolders = Folder.getSerializedFolderString(mFolder, targetFolders.values());
+ cc.mostlyDestructiveUpdate(context, Conversation.listOf(conv),
+ Conversation.UPDATE_FOLDER_COLUMNS, new String[] {
+ conv.folderList, conv.rawFolders
});
break;
case R.id.archive: