Merge "Cleanup/ refactor apply/remove folder dialogs."
diff --git a/res/layout/folders_view.xml b/res/layout/multi_folders_view.xml
similarity index 100%
rename from res/layout/folders_view.xml
rename to res/layout/multi_folders_view.xml
diff --git a/res/layout/single_folders_view.xml b/res/layout/single_folders_view.xml
new file mode 100644
index 0000000..003f5fa
--- /dev/null
+++ b/res/layout/single_folders_view.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2010 Google Inc.
+ *
+ * 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.
+ */
+-->
+
+<!-- Describes an individual toggleable label entry to be displayed in a list of labels in
+     a label selection UI. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/labels"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:height="?android:attr/listPreferredItemHeight"
+    android:gravity="center_vertical"
+    android:padding="3dip">
+
+    <!-- Note: the checkbox is not focusable because the parent list item itself handles
+         the toggling -->
+    <RadioButton android:id="@+id/checkbox"
+        android:layout_height="wrap_content"
+        android:layout_width="0dip"
+        android:layout_weight="1"
+        android:layout_margin="4dip"
+        android:singleLine="false"
+        android:maxLines="2"
+        android:ellipsize="end"
+        android:drawablePadding="20dip"
+        android:focusable="false"
+        android:focusableInTouchMode="false"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+    <View
+        android:id="@+id/color_block"
+        android:layout_height="36dip"
+        android:layout_width="36dip"
+        android:layout_gravity="right|top"
+        android:layout_margin="4dip" />
+
+</LinearLayout>
diff --git a/src/com/android/mail/ui/ApplyRemoveFolderDialog.java b/src/com/android/mail/ui/ApplyRemoveFolderDialog.java
deleted file mode 100644
index 0eb2423..0000000
--- a/src/com/android/mail/ui/ApplyRemoveFolderDialog.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*******************************************************************************
- *      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 android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.database.Cursor;
-import android.net.Uri;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ListView;
-
-import com.android.mail.R;
-import com.android.mail.providers.Account;
-import com.android.mail.providers.Conversation;
-import com.android.mail.providers.UIProvider;
-import com.android.mail.ui.FoldersSelectionDialog.CommitListener;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Apply labels to a conversation.
- *
- * Invoked by ConversationActivity and ConversationListActivity to display a
- * list of available labels and allow the user to add or remove them from the
- * current conversation. This class doesn't do any cursor manipulation.
- */
-public class ApplyRemoveFolderDialog extends AlertDialog
-        implements OnCancelListener, DialogInterface.OnClickListener {
-
-    // All the labels available on this account
-    public static final String EXTRA_ALL_LABELS = "all-labels";
-
-    // All the labels applied to the current conversation
-    public static final String EXTRA_CURRENT_LABELS = "current-labels";
-
-    // The following two extras are set on the result and they contain
-    // all the labels that were added and removed by the user on this screen.
-    public static final String EXTRA_ADDED_LABELS = "added-labels";
-    public static final String EXTRA_REMOVED_LABELS = "removed-labels";
-
-    private Context mContext;
-    private ListView mListView;
-
-    private FolderSelectorAdapter mAdapter;
-
-    private CommitListener mCommitListener;
-
-    private ConversationsLabelHandler mLabelHandler;
-
-    public ApplyRemoveFolderDialog(Context context, CommitListener commitListener, Account account) {
-        super(context);
-        mContext = context;
-        setTitle("change label");
-        setOnCancelListener(this);
-        setButton(DialogInterface.BUTTON_POSITIVE,
-                mContext.getString(android.R.string.ok), this);
-        setButton(DialogInterface.BUTTON_NEGATIVE,
-            mContext.getString(android.R.string.cancel), this);
-        setInverseBackgroundForced(true);
-
-        LayoutInflater inflater = LayoutInflater.from(mContext);
-        mListView = (ListView) inflater.inflate(R.layout.apply_remove_folder_dialog, null);
-        setView(mListView, 0, 0, 0, 0);
-        mCommitListener = commitListener;
-        onPrepare(account);
-    }
-
-    /**
-     * Invoked before showing the dialog.  This method resets the change list and initializes
-     * the list adapter to reflect the labels found on the current conversations.
-     * @param account the account
-     * @param currentLabels the labels on this/these conversations.
-     */
-    public void onPrepare(Account account) {
-        Cursor folders = getContext().getContentResolver().query(Uri.parse(account.folderListUri),
-                UIProvider.FOLDERS_PROJECTION, null, null, null);
-        mAdapter = new FolderSelectorAdapter(getContext(), folders, new HashSet<String>());
-
-        // Handle toggling of labels on the list item itself.
-        // The clicks on the checkboxes are suppressed so that there is a consistent experience
-        // regardless on whether or not the touch area was the checkbox, or elsewhere in the item.
-        // This also allows the list items to be navigable and toggled using the trackball.
-        mListView.setItemsCanFocus(true);
-        mListView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
-        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                onSelectLabel(position);
-            }
-        });
-
-        mListView.setAdapter(mAdapter);
-        mLabelHandler = new ConversationsLabelHandler(mAdapter);
-    }
-
-    @SuppressWarnings("unchecked")
-    private void onSelectLabel(int position) {
-        // Update the UI
-        mLabelHandler.update(mAdapter.getItem(position));
-    }
-
-    /////
-    // implements DialogInterface.OnClickListener
-    //
-
-    @Override
-    public void onCancel(DialogInterface dialog) {
-        // Nothing to do
-    }
-
-    @Override
-    public void onClick(DialogInterface dialog, int which) {
-        // If the user clicked the OK button, apply the change list
-        if (which == DialogInterface.BUTTON_POSITIVE) {
-            mCommitListener.onCommit(mLabelHandler.getUris());
-        }
-    }
-}
diff --git a/src/com/android/mail/ui/ConversationsLabelHandler.java b/src/com/android/mail/ui/ConversationsLabelHandler.java
deleted file mode 100644
index 618a6de..0000000
--- a/src/com/android/mail/ui/ConversationsLabelHandler.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*******************************************************************************
- *      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 java.util.ArrayList;
-
-/**
- * Utility class that handles all changes to labels associated with a set of
- * conversations.
- *
- * @author mindyp@google.com
- */
-public class ConversationsLabelHandler {
-
-    private final FolderSelectorAdapter mAdapter;
-
-    private final ArrayList<String> mChangeList;
-
-    public ConversationsLabelHandler(FolderSelectorAdapter adapter) {
-        mAdapter = adapter;
-        mChangeList = new ArrayList<String>();
-    }
-
-    /**
-     * Call this to update the state of labels as a result of them being
-     * selected / de-selected.
-     *
-     * @param row The item being updated.
-     */
-    public void update(FolderSelectorAdapter.FolderRow row) {
-        // Update the UI
-        final boolean add = !row.isPresent();
-        final Folder folder = row.getFolder();
-
-        row.setIsPresent(add);
-        mAdapter.notifyDataSetChanged();
-
-        // Update the label
-
-        // Always add the change to our change list since this dialog could
-        // be used to apply labels to several selected conversations and the
-        // user might have to click on the same label (first + and then -) to
-        // remove a label on the set. The previous implementation turned this
-        // operation into a no-op but we can no longer do this now. The downside
-        // is that we might emit label changes to the provider that cancel each
-        // other out but the provider might be already smart enough not to emit
-        // a no-op label change anyway.
-        if (add) {
-            mChangeList.add(folder.uri);
-        } else {
-            int pos = mChangeList.indexOf(folder.uri);
-            if (pos >= 0) {
-                mChangeList.remove(pos);
-            }
-        }
-    }
-
-    /**
-     * Clear the state of the handler.
-     */
-    public void reset() {
-        mChangeList.clear();
-    }
-
-    public String getUris() {
-        StringBuilder folderUris = new StringBuilder();
-        boolean first = true;
-        for (String folderUri : mChangeList) {
-            if (first) {
-                first = false;
-            } else {
-                folderUris.append(',');
-            }
-            folderUris.append(folderUri);
-        }
-        return folderUris.toString();
-    }
-}
diff --git a/src/com/android/mail/ui/FolderSelectorAdapter.java b/src/com/android/mail/ui/FolderSelectorAdapter.java
index 094677e..c0be05d 100644
--- a/src/com/android/mail/ui/FolderSelectorAdapter.java
+++ b/src/com/android/mail/ui/FolderSelectorAdapter.java
@@ -30,6 +30,8 @@
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.RadioButton;
 
 import java.util.Collections;
 import java.util.List;
@@ -78,15 +80,15 @@
 
     private List<FolderRow> mFolderRows = Lists.newArrayList();
     private LayoutInflater mInflater;
+    private int mLayout;
+    private boolean mSingle;
 
-    private final Map<Integer, PaintDrawable> mColorBlockCache = Maps.newHashMap();
-
-    private static int DEFAULT_LABEL_BACKGROUND_COLOR = android.R.color.white;
 
     public FolderSelectorAdapter(Context context, Cursor folders,
-            Set<String> initiallySelected) {
+            Set<String> initiallySelected, boolean single) {
         mInflater = LayoutInflater.from(context);
-
+        mSingle = single;
+        mLayout = single? R.layout.single_folders_view : R.layout.multi_folders_view;
         processLists(folders, initiallySelected);
     }
 
@@ -118,27 +120,26 @@
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
         View view = convertView;
-        CheckBox checkBox;
+        CompoundButton checkBox = null;
         View colorBlock;
 
         if (view == null) {
-            view = mInflater.inflate(R.layout.folders_view, parent, false);
-            checkBox = (CheckBox) view.findViewById(R.id.checkbox);
-            // Suppress the checkbox selection, and handle the toggling of the label
-            // on the parent list item's click handler.
+            view = mInflater.inflate(mLayout, parent, false);
+            checkBox = (CompoundButton) view.findViewById(R.id.checkbox);
+            // Suppress the checkbox selection, and handle the toggling of the
+            // folder on the parent list item's click handler.
             checkBox.setClickable(false);
-            colorBlock = view.findViewById(R.id.color_block);
+            checkBox.setClickable(false);
             view.setTag(R.id.checkbox, checkBox);
+            colorBlock = view.findViewById(R.id.color_block);
             view.setTag(R.id.color_block, colorBlock);
         } else {
-            checkBox = (CheckBox) view.getTag(R.id.checkbox);
+            checkBox = (CompoundButton) view.getTag(R.id.checkbox);
             colorBlock = (View) view.getTag(R.id.color_block);
         }
 
         FolderRow row = getItem(position);
-        Folder folder = row.getFolder();
-
-        checkBox.setText(folder.name);
+        checkBox.setText(row.getFolder().name);
         checkBox.setChecked(row.isPresent());
 
         return view;
diff --git a/src/com/android/mail/ui/FoldersSelectionDialog.java b/src/com/android/mail/ui/FoldersSelectionDialog.java
index a6488a8..561fec8 100644
--- a/src/com/android/mail/ui/FoldersSelectionDialog.java
+++ b/src/com/android/mail/ui/FoldersSelectionDialog.java
@@ -22,37 +22,27 @@
 import android.content.DialogInterface.OnClickListener;
 import android.content.DialogInterface.OnMultiChoiceClickListener;
 import android.database.Cursor;
-import android.database.MatrixCursor;
 import android.net.Uri;
-import android.provider.BaseColumns;
+import android.view.View;
+import android.widget.AdapterView;
 
 import com.android.mail.R;
 import com.android.mail.providers.Account;
 import com.android.mail.providers.UIProvider;
+import com.android.mail.ui.FolderSelectorAdapter.FolderRow;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Set;
 import java.util.Map.Entry;
 
 public class FoldersSelectionDialog implements OnClickListener, OnMultiChoiceClickListener {
-    private static final String CHECKED_COLUMN_NAME = "checked";
-    // We only need _id because MatrixCursor insists
-    private static final String[] FOLDER_DIALOG_PROJECTION = new String[] {
-            BaseColumns._ID, UIProvider.FolderColumns.URI, UIProvider.FolderColumns.NAME,
-            CHECKED_COLUMN_NAME
-    };
-    private static final int FOLDERS_CURSOR_ID = 0;
-    private static final int FOLDERS_CURSOR_URI = 1;
-    private static final int FOLDERS_CURSOR_NAME = 2;
-    private static final int FOLDERS_CURSOR_CHECKED = 3;
-
-    private int mCheckedItem;
     private AlertDialog mDialog;
     private CommitListener mCommitListener;
     private HashMap<String, Boolean> mCheckedState;
-    private MatrixCursor mFolderDialogCursor;
     private boolean mSingle = false;
+    private FolderSelectorAdapter mAdapter;
 
     public interface CommitListener {
         public void onCommit(String uris);
@@ -63,50 +53,55 @@
         mCommitListener = commitListener;
         // Mapping of a folder's uri to its checked state
         mCheckedState = new HashMap<String, Boolean>();
-
-        if (!account.supportsCapability(UIProvider.AccountCapabilities.MULTIPLE_FOLDERS_PER_CONV)) {
-            AlertDialog.Builder builder = new AlertDialog.Builder(context);
-            builder.setTitle("Change folders");
-            builder.setPositiveButton(R.string.ok, this);
-            builder.setNegativeButton(R.string.cancel, this);
-
-            // Get all of our folders
-            // TODO: Should only be folders that allow messages to be moved
-            // there!!
-            Cursor foldersCursor = context.getContentResolver().query(
-                    Uri.parse(account.folderListUri), UIProvider.FOLDERS_PROJECTION, null, null,
-                    null);
-            // Get the id, name, and a placeholder for check information
-            Object[] columnValues = new Object[FOLDER_DIALOG_PROJECTION.length];
-            mFolderDialogCursor = new MatrixCursor(FOLDER_DIALOG_PROJECTION);
-            int i = 0;
-            while (foldersCursor.moveToNext()) {
-                int flags = foldersCursor.getInt(UIProvider.FOLDER_CAPABILITIES_COLUMN);
-                if ((flags & UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES) == 0) {
-                    continue;
-                }
-                String uri = foldersCursor.getString(UIProvider.FOLDER_URI_COLUMN);
-                columnValues[FOLDERS_CURSOR_ID] = i++;
-                columnValues[FOLDERS_CURSOR_URI] = uri;
-                columnValues[FOLDERS_CURSOR_NAME] = foldersCursor
-                        .getString(UIProvider.FOLDER_NAME_COLUMN);
-                columnValues[FOLDERS_CURSOR_CHECKED] = 1; // 0 = unchecked
-                mFolderDialogCursor.addRow(columnValues);
-                mCheckedState.put(uri, true);
-            }
-            foldersCursor.close();
-            mSingle = true;
-            builder.setSingleChoiceItems(mFolderDialogCursor, mCheckedItem,
-                    UIProvider.FolderColumns.NAME, this);
-            mDialog = builder.create();
-        } else {
-            mSingle = false;
-            mDialog = new ApplyRemoveFolderDialog(context, commitListener, account);
-        }
+        AlertDialog.Builder builder = new AlertDialog.Builder(context);
+        builder.setTitle("Change folders");
+        builder.setPositiveButton(R.string.ok, this);
+        builder.setNegativeButton(R.string.cancel, this);
+        mSingle = !account
+                .supportsCapability(UIProvider.AccountCapabilities.MULTIPLE_FOLDERS_PER_CONV);
+        // TODO: (mindyp) make async
+        Cursor foldersCursor = context.getContentResolver().query(Uri.parse(account.folderListUri),
+                UIProvider.FOLDERS_PROJECTION, null, null, null);
+        mAdapter = new FolderSelectorAdapter(context, foldersCursor, new HashSet<String>(), mSingle);
+        builder.setAdapter(mAdapter, this);
+        mDialog = builder.create();
     }
 
     public void show() {
         mDialog.show();
+        mDialog.getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+               update(mAdapter.getItem(position));
+            }
+        });
+    }
+
+    /**
+     * Call this to update the state of labels as a result of them being
+     * selected / de-selected.
+     *
+     * @param row The item being updated.
+     */
+    public void update(FolderSelectorAdapter.FolderRow row) {
+        // Update the UI
+        boolean add = !row.isPresent();
+        if (mSingle) {
+            if (!add) {
+                // This would remove the check on a single radio button, so just
+                // return.
+                return;
+            }
+            // Clear any other checked items.
+            mAdapter.getCount();
+            for (int i = 0; i < mAdapter.getCount(); i++) {
+                mAdapter.getItem(i).setIsPresent(false);
+            }
+            mCheckedState.clear();
+        }
+        row.setIsPresent(add);
+        mAdapter.notifyDataSetChanged();
+        mCheckedState.put(row.getFolder().uri, add);
     }
 
     @Override
@@ -144,13 +139,13 @@
 
     @Override
     public void onClick(DialogInterface dialog, int which, boolean isChecked) {
-        mFolderDialogCursor.moveToPosition(which);
+        FolderRow row = mAdapter.getItem(which);
         if (mSingle) {
             // Clear any other checked items.
             mCheckedState.clear();
             isChecked = true;
         }
-        mCheckedState.put(mFolderDialogCursor.getString(FOLDERS_CURSOR_URI), isChecked);
+        mCheckedState.put(row.getFolder().uri, isChecked);
         mDialog.getListView().setItemChecked(which, false);
     }
 }