Make the MulitSelect contact ID column configurable

* So that the MultiSelectAdapter can support different kinds of contact
  lists (e.g. aggregate contacts or raw contacts).
* Also move utility method to convert a MultiSelected contact IDs TreeSet
  into an array to the MultiSelectAdapter class since more than just
  the PeopleActivity will need to do it when there are more MulitSelect
  lists.

Bug 18641067

Change-Id: I136137b3f7d3c7a7faa0f381eb66d9648a46cf5c
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index eea55da..38cd366 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -1310,14 +1310,8 @@
     }
 
     private void joinSelectedContacts() {
-        final Long[] contactIdsArray = mAllFragment.getSelectedContactIds().toArray(
-                new Long[mAllFragment.getSelectedContactIds().size()]);
-        final long[] contactIdsArray2 = new long[contactIdsArray.length];
-        for (int i = 0; i < contactIdsArray.length; i++) {
-            contactIdsArray2[i] = contactIdsArray[i];
-        }
-        final Intent intent = ContactSaveService.createJoinSeveralContactsIntent(this,
-                contactIdsArray2);
+        final Intent intent = ContactSaveService.createJoinSeveralContactsIntent(
+                this, mAllFragment.getSelectedContactIdsArray());
         this.startService(intent);
 
         mActionBarAdapter.setSelectionMode(false);
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 672e63e..6e721eb 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -39,8 +39,6 @@
  * picking a contact with one of the PICK intents).
  */
 public class DefaultContactBrowseListFragment extends ContactBrowseListFragment {
-    private static final String TAG = DefaultContactBrowseListFragment.class.getSimpleName();
-
     private View mSearchHeaderView;
     private View mSearchProgress;
     private TextView mSearchProgressText;
diff --git a/src/com/android/contacts/list/MultiSelectContactsListFragment.java b/src/com/android/contacts/list/MultiSelectContactsListFragment.java
index 8dd18c1..aba3a28 100644
--- a/src/com/android/contacts/list/MultiSelectContactsListFragment.java
+++ b/src/com/android/contacts/list/MultiSelectContactsListFragment.java
@@ -19,15 +19,14 @@
 import com.android.contacts.common.list.ContactListAdapter;
 import com.android.contacts.common.list.ContactListItemView;
 import com.android.contacts.common.list.DefaultContactListAdapter;
+import com.android.contacts.common.logging.Logger;
 import com.android.contacts.common.logging.SearchState;
 import com.android.contacts.list.MultiSelectEntryContactListAdapter.SelectedContactsListener;
-import com.android.contacts.common.logging.Logger;
 
 import android.database.Cursor;
-import android.net.Uri;
 import android.os.Bundle;
 import android.provider.ContactsContract;
-import android.text.TextUtils;
+import android.util.Log;
 import android.view.accessibility.AccessibilityEvent;
 
 import java.util.ArrayList;
@@ -41,6 +40,8 @@
 public class MultiSelectContactsListFragment extends DefaultContactBrowseListFragment
         implements SelectedContactsListener {
 
+    private static final String TAG = "MultiContactsList";
+
     public interface OnCheckBoxListActionListener {
         void onStartDisplayingCheckBoxes();
         void onSelectedContactIdsChanged();
@@ -60,7 +61,7 @@
 
     /**
      * Whether a search result was clicked by the user. Tracked so that we can distinguish
-     * between exiting the search mode after a result was clicked from existing w/o clicking
+     * between exiting the search mode after a result was clicked from exiting w/o clicking
      * any search result.
      */
     public boolean wasSearchResultClicked() {
@@ -76,9 +77,7 @@
 
     @Override
     public void onSelectedContactsChanged() {
-        if (mCheckBoxListListener != null) {
-            mCheckBoxListListener.onSelectedContactIdsChanged();
-        }
+        if (mCheckBoxListListener != null) mCheckBoxListListener.onSelectedContactIdsChanged();
     }
 
     @Override
@@ -106,8 +105,11 @@
     }
 
     public TreeSet<Long> getSelectedContactIds() {
-        final MultiSelectEntryContactListAdapter adapter = getAdapter();
-        return adapter.getSelectedContactIds();
+        return getAdapter().getSelectedContactIds();
+    }
+
+    public long[] getSelectedContactIdsArray() {
+        return getAdapter().getSelectedContactIdsArray();
     }
 
     @Override
@@ -129,9 +131,11 @@
     }
 
     public void displayCheckBoxes(boolean displayCheckBoxes) {
-        getAdapter().setDisplayCheckBoxes(displayCheckBoxes);
-        if (!displayCheckBoxes) {
-            clearCheckBoxes();
+        if (getAdapter() != null) {
+            getAdapter().setDisplayCheckBoxes(displayCheckBoxes);
+            if (!displayCheckBoxes) {
+                clearCheckBoxes();
+            }
         }
     }
 
@@ -142,23 +146,20 @@
     @Override
     protected boolean onItemLongClick(int position, long id) {
         final int previouslySelectedCount = getAdapter().getSelectedContactIds().size();
-        final Uri uri = getAdapter().getContactUri(position);
+        final long contactId = getContactId(position);
         final int partition = getAdapter().getPartitionForPosition(position);
-        if (uri != null && partition == ContactsContract.Directory.DEFAULT) {
-            final String contactId = uri.getLastPathSegment();
-            if (!TextUtils.isEmpty(contactId)) {
-                if (mCheckBoxListListener != null) {
-                    mCheckBoxListListener.onStartDisplayingCheckBoxes();
-                }
-                getAdapter().toggleSelectionOfContactId(Long.valueOf(contactId));
-                // Manually send clicked event if there is a checkbox.
-                // See b/24098561.  TalkBack will not read it otherwise.
-                final int index = position + getListView().getHeaderViewsCount() - getListView()
-                        .getFirstVisiblePosition();
-                if (index >= 0 && index < getListView().getChildCount()) {
-                    getListView().getChildAt(index).sendAccessibilityEvent(AccessibilityEvent
-                            .TYPE_VIEW_CLICKED);
-                }
+        if (contactId >= 0 && partition == ContactsContract.Directory.DEFAULT) {
+            if (mCheckBoxListListener != null) {
+                mCheckBoxListListener.onStartDisplayingCheckBoxes();
+            }
+            getAdapter().toggleSelectionOfContactId(contactId);
+            // Manually send clicked event if there is a checkbox.
+            // See b/24098561.  TalkBack will not read it otherwise.
+            final int index = position + getListView().getHeaderViewsCount() - getListView()
+                    .getFirstVisiblePosition();
+            if (index >= 0 && index < getListView().getChildCount()) {
+                getListView().getChildAt(index).sendAccessibilityEvent(AccessibilityEvent
+                        .TYPE_VIEW_CLICKED);
             }
         }
         final int nowSelectedCount = getAdapter().getSelectedContactIds().size();
@@ -172,15 +173,12 @@
 
     @Override
     protected void onItemClick(int position, long id) {
-        final Uri uri = getAdapter().getContactUri(position);
-        if (uri == null) {
+        final long contactId = getContactId(position);
+        if (contactId < 0) {
             return;
         }
         if (getAdapter().isDisplayingCheckBoxes()) {
-            final String contactId = uri.getLastPathSegment();
-            if (!TextUtils.isEmpty(contactId)) {
-                getAdapter().toggleSelectionOfContactId(Long.valueOf(contactId));
-            }
+            getAdapter().toggleSelectionOfContactId(contactId);
         } else {
             if (isSearchMode()) {
                 mSearchResultClicked = true;
@@ -193,6 +191,20 @@
         }
     }
 
+    private long getContactId(int position) {
+        final int contactIdColumnIndex = getAdapter().getContactColumnIdIndex();
+
+        final Cursor cursor = (Cursor) getAdapter().getItem(position);
+        if (cursor != null) {
+            if (cursor.getColumnCount() > contactIdColumnIndex) {
+                return cursor.getLong(contactIdColumnIndex);
+            }
+        }
+
+        Log.w(TAG, "Failed to get contact ID from cursor column " + contactIdColumnIndex);
+        return -1;
+    }
+
     /**
      * Returns the state of the search results currently presented to the user.
      */
diff --git a/src/com/android/contacts/list/MultiSelectEntryContactListAdapter.java b/src/com/android/contacts/list/MultiSelectEntryContactListAdapter.java
index 5a54c51..1015a9d 100644
--- a/src/com/android/contacts/list/MultiSelectEntryContactListAdapter.java
+++ b/src/com/android/contacts/list/MultiSelectEntryContactListAdapter.java
@@ -37,6 +37,7 @@
     private SelectedContactsListener mSelectedContactsListener;
     private TreeSet<Long> mSelectedContactIds = new TreeSet<Long>();
     private boolean mDisplayCheckBoxes;
+    private final int mContactIdColumnIndex;
 
     public interface SelectedContactsListener {
         void onSelectedContactsChanged();
@@ -44,7 +45,26 @@
     }
 
     public MultiSelectEntryContactListAdapter(Context context) {
+        this(context, ContactQuery.CONTACT_ID);
+    }
+
+    /**
+     * @param contactIdColumnIndex the column index of the contact ID in the underlying cursor;
+     *         it is passed in so that this adapter can support different kinds of contact
+     *         lists (e.g. aggregate contacts or raw contacts).
+     */
+    public MultiSelectEntryContactListAdapter(Context context, int contactIdColumnIndex) {
         super(context);
+        mContactIdColumnIndex = contactIdColumnIndex;
+    }
+
+    /**
+     * Returns the column index of the contact ID in the underlying cursor; the contact ID
+     * retrieved using this index is the value that is selected by this adapter (and returned
+     * by {@link #getSelectedContactIds}).
+     */
+    public int getContactColumnIdIndex() {
+        return mContactIdColumnIndex;
     }
 
     public void setSelectedContactsListener(SelectedContactsListener listener) {
@@ -59,6 +79,19 @@
     }
 
     /**
+     * Returns the selected contacts as an array.
+     */
+    public long[] getSelectedContactIdsArray() {
+        final Long[] contactIds = mSelectedContactIds.toArray(
+                new Long[mSelectedContactIds.size()]);
+        final long[] result = new long[contactIds.length];
+        for (int i = 0; i < contactIds.length; i++) {
+            result[i] = contactIds[i];
+        }
+        return result;
+    }
+
+    /**
      * Update set of selected contacts. This changes which checkboxes are set.
      */
     public void setSelectedContactIds(TreeSet<Long> selectedContactIds) {
@@ -125,7 +158,7 @@
             return;
         }
         final CheckBox checkBox = view.getCheckBox();
-        final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
+        final long contactId = cursor.getLong(mContactIdColumnIndex);
         checkBox.setChecked(mSelectedContactIds.contains(contactId));
         checkBox.setTag(contactId);
         checkBox.setOnClickListener(mCheckBoxClickListener);