Merge "Show error when user pulls to refresh without network connection" into ub-contactsdialer-h-dev
diff --git a/res/layout/multi_select_send_button.xml b/res/layout/multi_select_send_button.xml
new file mode 100644
index 0000000..50a4d45
--- /dev/null
+++ b/res/layout/multi_select_send_button.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    style="?android:attr/buttonBarButtonStyle"
+    android:id="@+id/multi_select_send_button"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:text="@string/send_to_selection"
+    android:textColor="@color/action_bar_button_text_color"
+    android:textSize="14sp">
+</Button>
diff --git a/res/menu/group_member_picker.xml b/res/menu/group_member_picker.xml
index 9e623f6..2dd1990 100644
--- a/res/menu/group_member_picker.xml
+++ b/res/menu/group_member_picker.xml
@@ -19,7 +19,7 @@
 
     <item
         android:id="@+id/menu_select"
-        android:title="@string/menu_selectForGroup" />
+        android:title="@string/menu_selectForGroup"
         contacts:showAsAction="never" />
 
 </menu>
diff --git a/res/menu/items_multi_select.xml b/res/menu/items_multi_select.xml
new file mode 100644
index 0000000..5bb39d4
--- /dev/null
+++ b/res/menu/items_multi_select.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item
+        app:actionLayout="@layout/multi_select_send_button"
+        app:showAsAction="always"
+        android:id="@+id/menu_send"
+        android:title="@string/send_to_selection" />
+</menu>
\ No newline at end of file
diff --git a/res/menu/view_group.xml b/res/menu/view_group.xml
index 24eb0b5..1bbdd86 100644
--- a/res/menu/view_group.xml
+++ b/res/menu/view_group.xml
@@ -29,6 +29,14 @@
         android:title="@string/menu_editGroup" />
 
     <item
+        android:id="@+id/menu_multi_send_email"
+        android:title="@string/menu_sendEmailOption" />
+
+    <item
+        android:id="@+id/menu_multi_send_message"
+        android:title="@string/menu_sendMessageOption" />
+
+    <item
         android:id="@+id/menu_rename_group"
         android:title="@string/menu_renameGroup"/>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8ac6f3f..18e8e92 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -350,6 +350,24 @@
     <!-- Message displayed when creating a group with the same name as an existing group -->
     <string name="groupExistsErrorMessage">That label already exists</string>
 
+    <!-- Toast displayed when some group contacts do not have any emails (for group send) [CHAR LIMIT=50] -->
+    <string name="groupSomeContactsNoEmailsToast">Some contacts do not have emails.</string>
+
+    <!-- Toast displayed when some group contacts do not have any phone numbers (for group send) [CHAR LIMIT=50] -->
+    <string name="groupSomeContactsNoPhonesToast">Some contacts do not have phone numbers.</string>
+
+    <!-- Option name to send email to all members of a group/selection [CHAR LIMIT=30] -->
+    <string name="menu_sendEmailOption">Send email</string>
+
+    <!-- Option name to send message to all members of a group/selection [CHAR LIMIT=30] -->
+    <string name="menu_sendMessageOption">Send message</string>
+
+    <!-- Activity title when the user is selecting items [CHAR LIMIT=128] -->
+    <string name="pickerSelectContactsActivityTitle">Select Contacts</string>
+
+    <!-- The menu item to send the currently selected contacts to selected items [CHAR LIMIT=10] -->
+    <string name="send_to_selection">Send</string>
+
     <!-- Displayed at the top of the contacts showing the total number of contacts visible when "Only contacts with phones" is selected -->
     <plurals name="listTotalPhoneContacts">
         <item quantity="one">1 contact with phone number</item>
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index fe95465..cbf6a69 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -52,6 +52,8 @@
 import com.android.contacts.list.GroupMemberPickerFragment;
 import com.android.contacts.list.JoinContactListFragment;
 import com.android.contacts.list.LegacyPhoneNumberPickerFragment;
+import com.android.contacts.list.MultiSelectEmailAddressesListFragment;
+import com.android.contacts.list.MultiSelectPhoneNumbersListFragment;
 import com.android.contacts.list.MultiSelectContactsListFragment;
 import com.android.contacts.list.MultiSelectContactsListFragment.OnCheckBoxListActionListener;
 import com.android.contacts.list.OnContactPickerActionListener;
@@ -154,12 +156,10 @@
 
         // Postal address pickers (and legacy pickers) don't support search, so just show
         // "HomeAsUp" button and title.
-        if (mRequest.getActionCode() == ContactsRequest.ACTION_PICK_POSTAL ||
-                mRequest.isLegacyCompatibilityMode()) {
-            mIsSearchSupported = false;
-        } else {
-            mIsSearchSupported = true;
-        }
+        mIsSearchSupported = mRequest.getActionCode() != ContactsRequest.ACTION_PICK_POSTAL
+                && mRequest.getActionCode() != ContactsRequest.ACTION_PICK_EMAILS
+                && mRequest.getActionCode() != ContactsRequest.ACTION_PICK_PHONES
+                && !mRequest.isLegacyCompatibilityMode();
         configureSearchMode();
     }
 
@@ -226,6 +226,14 @@
                 titleResId = R.string.contactPickerActivityTitle;
                 break;
             }
+            case ContactsRequest.ACTION_PICK_PHONES: {
+                titleResId = R.string.pickerSelectContactsActivityTitle;
+                break;
+            }
+            case ContactsRequest.ACTION_PICK_EMAILS: {
+                titleResId = R.string.pickerSelectContactsActivityTitle;
+                break;
+            }
             case ContactsRequest.ACTION_CREATE_SHORTCUT_CALL: {
                 titleResId = R.string.callShortcutActivityTitle;
                 break;
@@ -305,6 +313,17 @@
                 break;
             }
 
+            case ContactsRequest.ACTION_PICK_PHONES: {
+                mListFragment = new MultiSelectPhoneNumbersListFragment();
+                mListFragment.setArguments(getIntent().getExtras());
+                break;
+            }
+
+            case ContactsRequest.ACTION_PICK_EMAILS: {
+                mListFragment = new MultiSelectEmailAddressesListFragment();
+                mListFragment.setArguments(getIntent().getExtras());
+                break;
+            }
             case ContactsRequest.ACTION_CREATE_SHORTCUT_CALL: {
                 PhoneNumberPickerFragment fragment = getPhoneNumberPickerFragment(mRequest);
                 fragment.setShortcutAction(Intent.ACTION_CALL);
@@ -387,6 +406,10 @@
         } else if (mListFragment instanceof EmailAddressPickerFragment) {
             ((EmailAddressPickerFragment) mListFragment).setOnEmailAddressPickerActionListener(
                     new EmailAddressPickerActionListener());
+        } else if (mListFragment instanceof MultiSelectEmailAddressesListFragment) {
+            ((MultiSelectEmailAddressesListFragment) mListFragment).setCheckBoxListListener(this);
+        } else if (mListFragment instanceof MultiSelectPhoneNumbersListFragment) {
+            ((MultiSelectPhoneNumbersListFragment) mListFragment).setCheckBoxListListener(this);
         } else if (mListFragment instanceof JoinContactListFragment) {
             ((JoinContactListFragment) mListFragment).setOnContactPickerActionListener(
                     new JoinContactActionListener());
diff --git a/src/com/android/contacts/common/Experiments.java b/src/com/android/contacts/common/Experiments.java
index 5de4d5d..e872694 100644
--- a/src/com/android/contacts/common/Experiments.java
+++ b/src/com/android/contacts/common/Experiments.java
@@ -73,6 +73,11 @@
      */
     public static final String SEARCH_YENTA_TIMEOUT_MILLIS = "Search__yenta_timeout";
 
+    /**
+     * The options for sending email/messages to groups and selections
+     */
+    public static final String SEND_TO_GROUP = "Groups__send_to_group";
+
     private Experiments() {
     }
 }
diff --git a/src/com/android/contacts/common/list/MultiSelectEntryContactListAdapter.java b/src/com/android/contacts/common/list/MultiSelectEntryContactListAdapter.java
index dbfd70e..b08e367 100644
--- a/src/com/android/contacts/common/list/MultiSelectEntryContactListAdapter.java
+++ b/src/com/android/contacts/common/list/MultiSelectEntryContactListAdapter.java
@@ -23,6 +23,8 @@
 import android.view.View.OnClickListener;
 import android.widget.CheckBox;
 
+import com.android.contacts.group.GroupUtil;
+
 import java.util.TreeSet;
 
 /**
@@ -84,17 +86,15 @@
         return mSelectedContactIds;
     }
 
+    public boolean hasSelectedItems() {
+        return mSelectedContactIds.size() > 0;
+    }
+
     /**
      * 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;
+        return GroupUtil.convertLongSetToLongArray(mSelectedContactIds);
     }
 
     /**
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 5f83be7..e013da4 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -615,10 +615,7 @@
                 Lists.<RawContact>newArrayList() : Lists.newArrayList(mRawContacts));
         // NOTE: mGroupMetaData is not saved
 
-        if (hasValidState()) {
-            // Store entities with modifications
-            outState.putParcelable(KEY_EDIT_STATE, mState);
-        }
+        outState.putParcelable(KEY_EDIT_STATE, mState);
         outState.putInt(KEY_STATUS, mStatus);
         outState.putBoolean(KEY_HAS_NEW_CONTACT, mHasNewContact);
         outState.putBoolean(KEY_NEW_CONTACT_READY, mNewContactDataReady);
diff --git a/src/com/android/contacts/group/GroupMembersFragment.java b/src/com/android/contacts/group/GroupMembersFragment.java
index 30f339c..43acd0a 100644
--- a/src/com/android/contacts/group/GroupMembersFragment.java
+++ b/src/com/android/contacts/group/GroupMembersFragment.java
@@ -17,6 +17,7 @@
 
 import android.app.Activity;
 import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ContentResolver;
 import android.content.CursorLoader;
 import android.content.Intent;
 import android.content.Loader;
@@ -26,7 +27,9 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -46,6 +49,8 @@
 import com.android.contacts.GroupMetaDataLoader;
 import com.android.contacts.R;
 import com.android.contacts.activities.ActionBarAdapter;
+import com.android.contacts.common.ContactsUtils;
+import com.android.contacts.common.Experiments;
 import com.android.contacts.common.list.ContactsSectionIndexer;
 import com.android.contacts.common.list.MultiSelectEntryContactListAdapter.DeleteContactListener;
 import com.android.contacts.common.logging.ListEvent;
@@ -59,10 +64,14 @@
 import com.android.contacts.list.ContactsRequest;
 import com.android.contacts.list.MultiSelectContactsListFragment;
 import com.android.contacts.list.UiIntentActions;
+import com.android.contactsbind.experiments.Flags;
+import com.google.common.primitives.Longs;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /** Displays the members of a group. */
@@ -81,6 +90,7 @@
     private static final int LOADER_GROUP_METADATA = 0;
     private static final int MSG_FAIL_TO_LOAD = 1;
     private static final int RESULT_GROUP_ADD_MEMBER = 100;
+    private static final int RESULT_SEND_TO_SELECTION = 200;
 
     /** Filters out duplicate contacts. */
     private class FilterCursorWrapper extends CursorWrapper {
@@ -257,7 +267,13 @@
         final boolean isSelectionMode = mActionBarAdapter.isSelectionMode();
         final boolean isGroupEditable = mGroupMetaData != null && mGroupMetaData.editable;
         final boolean isGroupReadOnly = mGroupMetaData != null && mGroupMetaData.readOnly;
+        final boolean experimentFlagSet =
+                Flags.getInstance(getContext()).getBoolean(Experiments.SEND_TO_GROUP);
 
+        setVisible(menu, R.id.menu_multi_send_email, !mIsEditMode && !isGroupEmpty()
+                && experimentFlagSet);
+        setVisible(menu, R.id.menu_multi_send_message, !mIsEditMode && !isGroupEmpty()
+                && experimentFlagSet);
         setVisible(menu, R.id.menu_add, isGroupEditable && !isSelectionMode);
         setVisible(menu, R.id.menu_rename_group, !isGroupReadOnly && !isSelectionMode);
         setVisible(menu, R.id.menu_delete_group, !isGroupReadOnly && !isSelectionMode);
@@ -278,6 +294,171 @@
         }
     }
 
+    /**
+     * Helper class for cp2 query used to look up all contact's emails and phone numbers.
+     */
+    private static abstract class Query {
+        public static final String EMAIL_SELECTION =
+                ContactsContract.Data.MIMETYPE + "='"
+                        + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE + "'";
+
+        public static final String PHONE_SELECTION =
+                ContactsContract.Data.MIMETYPE + "='"
+                        + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "'";
+
+        public static final String[] EMAIL_PROJECTION = {
+                ContactsContract.Data.CONTACT_ID,
+                ContactsContract.CommonDataKinds.Email._ID,
+                ContactsContract.Data.IS_SUPER_PRIMARY,
+                ContactsContract.Data.DATA1
+        };
+
+        public static final String[] PHONE_PROJECTION = {
+                ContactsContract.Data.CONTACT_ID,
+                ContactsContract.CommonDataKinds.Phone._ID,
+                ContactsContract.Data.IS_SUPER_PRIMARY,
+                ContactsContract.Data.DATA1
+        };
+
+        public static final int CONTACT_ID = 0;
+        public static final int ITEM_ID = 1;
+        public static final int PRIMARY = 2;
+        public static final int DATA1 = 3;
+    }
+
+    private List<String> getSendToDataForIds(long[] ids, String scheme) {
+        final List<String> items = new ArrayList<>();
+        final String sIds = GroupUtil.convertArrayToString(ids);
+        final String select = (ContactsUtils.SCHEME_MAILTO.equals(scheme)
+                ? Query.EMAIL_SELECTION
+                + " AND " + ContactsContract.CommonDataKinds.Email._ID + " IN (" + sIds + ")"
+                : Query.PHONE_SELECTION
+                + " AND " + ContactsContract.CommonDataKinds.Phone._ID + " IN (" + sIds + ")");
+        final ContentResolver contentResolver = getContext().getContentResolver();
+        final Cursor cursor = contentResolver.query(ContactsContract.Data.CONTENT_URI,
+                ContactsUtils.SCHEME_MAILTO.equals(scheme)
+                    ? Query.EMAIL_PROJECTION
+                    : Query.PHONE_PROJECTION,
+                select, null, null);
+
+        if (cursor == null) {
+            return items;
+        }
+
+        try {
+            cursor.moveToPosition(-1);
+            while (cursor.moveToNext()) {
+                final String data = cursor.getString(Query.DATA1);
+
+                if (!TextUtils.isEmpty(data)) {
+                    items.add(data);
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+
+        return items;
+    }
+
+    private void sendToGroup(long[] ids, String sendScheme, String title) {
+        if(ids == null || ids.length == 0) return;
+
+        // Get emails or phone numbers
+        // encounteredIds <contact_id, <item_id, is_super_primary>>
+        final Map<String, Map<String, Boolean>> encounteredIds = new HashMap<>();
+        // primaryItems <contact_id, has_super_primary>
+        final Map<String, Boolean> primaryItems = new HashMap<>();
+        // itemList <item_data>
+        final List<String> itemList = new ArrayList<>();
+        final String sIds = GroupUtil.convertArrayToString(ids);
+        final String select = (ContactsUtils.SCHEME_MAILTO.equals(sendScheme)
+                ? Query.EMAIL_SELECTION
+                : Query.PHONE_SELECTION)
+                + " AND " + ContactsContract.Data.CONTACT_ID + " IN (" + sIds + ")";
+        final ContentResolver contentResolver = getContext().getContentResolver();
+        final Cursor cursor = contentResolver.query(ContactsContract.Data.CONTENT_URI,
+                ContactsUtils.SCHEME_MAILTO.equals(sendScheme)
+                        ? Query.EMAIL_PROJECTION
+                        : Query.PHONE_PROJECTION,
+                select, null, null);
+
+        if (cursor == null) {
+            return;
+        }
+
+        try {
+            cursor.moveToPosition(-1);
+            while (cursor.moveToNext()) {
+                final String contactId = cursor.getString(Query.CONTACT_ID);
+                final String itemId = cursor.getString(Query.ITEM_ID);
+                final boolean isPrimary = cursor.getInt(Query.PRIMARY) != 0;
+                final String data = cursor.getString(Query.DATA1);
+
+                if (!encounteredIds.containsKey(contactId)) {
+                    encounteredIds.put(contactId, new HashMap<String, Boolean>());
+                }
+                final Boolean prevHasSuperPrimary = primaryItems.get(contactId);
+                final boolean hasPrimary = prevHasSuperPrimary == null
+                        ? isPrimary
+                        : prevHasSuperPrimary || isPrimary;
+                primaryItems.put(contactId, hasPrimary);
+
+                if (!TextUtils.isEmpty(data)) {
+                    final Map<String, Boolean> itemMap = encounteredIds.get(contactId);
+                    itemMap.put(itemId, isPrimary);
+                    itemList.add(data);
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+
+        // Start picker if a contact has multiple items with no superPrimary
+        for (Map.Entry<String, Map<String, Boolean>> i : encounteredIds.entrySet()) {
+            boolean hasSuperPrimary = primaryItems.get(i.getKey());
+            if (i.getValue().size() > 1 && !hasSuperPrimary) {
+                // Build list of default selected item ids
+                final List<Long> defaultSelection = new ArrayList<>();
+                for (Map.Entry<String, Map<String, Boolean>> j : encounteredIds.entrySet()) {
+                    for (Map.Entry<String, Boolean> k : j.getValue().entrySet()) {
+                        final String itemId = k.getKey();
+                        if (j.getValue().size() == 1 || k.getValue()) {
+                            defaultSelection.add(Long.parseLong(itemId));
+                        }
+                    }
+                }
+                final long[] defaultSelectionArray = Longs.toArray(defaultSelection);
+                startSendToSelectionPickerActivity(ids, defaultSelectionArray, sendScheme, title);
+                return;
+            }
+        }
+
+        if (itemList.size() == 0 || encounteredIds.size() < ids.length) {
+            Toast.makeText(getContext(), ContactsUtils.SCHEME_MAILTO.equals(sendScheme)
+                            ? getString(R.string.groupSomeContactsNoEmailsToast)
+                            : getString(R.string.groupSomeContactsNoPhonesToast),
+                    Toast.LENGTH_LONG).show();
+        }
+
+        if (itemList.size() == 0) {
+            return;
+        }
+
+        final String itemsString = TextUtils.join(",", itemList);
+        startSendToSelectionActivity(itemsString, sendScheme, title);
+    }
+
+    private void startSendToSelectionActivity(String listItems, String sendScheme, String title) {
+        startActivity(GroupUtil.createSendToSelectionIntent(listItems, sendScheme, title));
+    }
+
+    private void startSendToSelectionPickerActivity(long[] ids, long[] defaultSelection,
+            String sendScheme, String title) {
+        startActivityForResult(GroupUtil.createSendToSelectionPickerIntent(getContext(), ids,
+                defaultSelection, sendScheme, title), RESULT_SEND_TO_SELECTION);
+    }
+
     private void startGroupAddMemberActivity() {
         startActivityForResult(GroupUtil.createPickMemberIntent(getContext(), mGroupMetaData,
                 getMemberContactIds()), RESULT_GROUP_ADD_MEMBER);
@@ -294,6 +475,22 @@
                 startGroupAddMemberActivity();
                 return true;
             }
+            case R.id.menu_multi_send_email: {
+                final long[] ids = mActionBarAdapter.isSelectionMode()
+                        ? getAdapter().getSelectedContactIdsArray()
+                        : GroupUtil.convertStringSetToLongArray(mGroupMemberContactIds);
+                sendToGroup(ids, ContactsUtils.SCHEME_MAILTO,
+                        getString(R.string.menu_sendEmailOption));
+                return true;
+            }
+            case R.id.menu_multi_send_message: {
+                final long[] ids = mActionBarAdapter.isSelectionMode()
+                        ? getAdapter().getSelectedContactIdsArray()
+                        : GroupUtil.convertStringSetToLongArray(mGroupMemberContactIds);
+                sendToGroup(ids, ContactsUtils.SCHEME_SMSTO,
+                        getString(R.string.menu_sendMessageOption));
+                return true;
+            }
             case R.id.menu_rename_group: {
                 GroupNameEditDialogFragment.newInstanceForUpdate(
                         new AccountWithDataSet(mGroupMetaData.accountName,
@@ -333,22 +530,37 @@
 
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (requestCode == RESULT_GROUP_ADD_MEMBER && resultCode ==
-                Activity.RESULT_OK && data != null) {
-            long[] contactIds = data.getLongArrayExtra(
-                    UiIntentActions.TARGET_CONTACT_IDS_EXTRA_KEY);
-            if (contactIds == null) {
-                final long contactId = data.getLongExtra(
-                        UiIntentActions.TARGET_CONTACT_ID_EXTRA_KEY, -1);
-                if (contactId > -1) {
-                    contactIds = new long[1];
-                    contactIds[0] = contactId;
+        if (resultCode != Activity.RESULT_OK || data == null) {
+            return;
+        }
+        switch(requestCode) {
+            case RESULT_GROUP_ADD_MEMBER: {
+                long[] contactIds = data.getLongArrayExtra(
+                        UiIntentActions.TARGET_CONTACT_IDS_EXTRA_KEY);
+                if (contactIds == null) {
+                    final long contactId = data.getLongExtra(
+                            UiIntentActions.TARGET_CONTACT_ID_EXTRA_KEY, -1);
+                    if (contactId > -1) {
+                        contactIds = new long[1];
+                        contactIds[0] = contactId;
+                    }
                 }
+                new UpdateGroupMembersAsyncTask(
+                        UpdateGroupMembersAsyncTask.TYPE_ADD,
+                        getContext(), contactIds, mGroupMetaData.groupId, mGroupMetaData.accountName,
+                        mGroupMetaData.accountType, mGroupMetaData.dataSet).execute();
+                break;
             }
-            new UpdateGroupMembersAsyncTask(
-                    UpdateGroupMembersAsyncTask.TYPE_ADD,
-                    getContext(), contactIds, mGroupMetaData.groupId, mGroupMetaData.accountName,
-                    mGroupMetaData.accountType, mGroupMetaData.dataSet).execute();
+            case RESULT_SEND_TO_SELECTION: {
+                final long[] ids = data.getLongArrayExtra(
+                        UiIntentActions.TARGET_CONTACT_IDS_EXTRA_KEY);
+                final String sendScheme = data.getStringExtra(UiIntentActions.SELECTION_SEND_SCHEME);
+                final String sendTitle = data.getStringExtra(UiIntentActions.SELECTION_SEND_TITLE);
+                final List<String> items = getSendToDataForIds(ids, sendScheme);
+                final String list = TextUtils.join(",", items);
+                startSendToSelectionActivity(list, sendScheme, sendTitle);
+                break;
+            }
         }
     }
 
diff --git a/src/com/android/contacts/group/GroupUtil.java b/src/com/android/contacts/group/GroupUtil.java
index 1a99a65..dc85152 100644
--- a/src/com/android/contacts/group/GroupUtil.java
+++ b/src/com/android/contacts/group/GroupUtil.java
@@ -21,12 +21,14 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
+import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Groups;
 import android.text.TextUtils;
 
 import com.android.contacts.GroupListLoader;
 import com.android.contacts.activities.ContactSelectionActivity;
+import com.android.contacts.common.ContactsUtils;
 import com.android.contacts.common.list.ContactsSectionIndexer;
 import com.android.contacts.common.model.account.GoogleAccountType;
 import com.android.contacts.list.UiIntentActions;
@@ -98,6 +100,30 @@
                 isFirstGroupInAccount, memberCount, isReadOnly, systemId);
     }
 
+    /** Returns an Intent to send emails/phones to some activity/app */
+    public static Intent createSendToSelectionIntent(
+            String itemsList, String sendScheme, String title) {
+        final Intent intent = new Intent(Intent.ACTION_SENDTO,
+                Uri.fromParts(sendScheme, itemsList, null));
+        return Intent.createChooser(intent, title);
+    }
+
+    /** Returns an Intent to pick emails/phones to send to selection (or group) */
+    public static Intent createSendToSelectionPickerIntent(Context context, long[] ids,
+            long[] defaultSelection, String sendScheme, String title) {
+        final Intent intent = new Intent(context, ContactSelectionActivity.class);
+        intent.setAction(UiIntentActions.ACTION_SELECT_ITEMS);
+        intent.setType(ContactsUtils.SCHEME_MAILTO.equals(sendScheme)
+                ? ContactsContract.CommonDataKinds.Email.CONTENT_TYPE
+                : ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
+        intent.putExtra(UiIntentActions.SELECTION_ITEM_LIST, ids);
+        intent.putExtra(UiIntentActions.SELECTION_DEFAULT_SELECTION, defaultSelection);
+        intent.putExtra(UiIntentActions.SELECTION_SEND_SCHEME, sendScheme);
+        intent.putExtra(UiIntentActions.SELECTION_SEND_TITLE, title);
+
+        return intent;
+    }
+
     /** Returns an Intent to pick contacts to add to a group. */
     public static Intent createPickMemberIntent(Context context,
             GroupMetaData groupMetaData, ArrayList<String> memberContactIds) {
@@ -111,6 +137,33 @@
         return intent;
     }
 
+    public static String convertArrayToString(long[] list) {
+        if (list == null || list.length == 0) return "";
+        return Arrays.toString(list).replace("[", "").replace("]", "");
+    }
+
+    public static long[] convertLongSetToLongArray(Set<Long> set) {
+        final Long[] contactIds = set.toArray(new Long[set.size()]);
+        final long[] result = new long[contactIds.length];
+        for (int i = 0; i < contactIds.length; i++) {
+            result[i] = contactIds[i];
+        }
+        return result;
+    }
+
+    public static long[] convertStringSetToLongArray(Set<String> set) {
+        final String[] contactIds = set.toArray(new String[set.size()]);
+        final long[] result = new long[contactIds.length];
+        for (int i = 0; i < contactIds.length; i++) {
+            try {
+                result[i] = Long.parseLong(contactIds[i]);
+            } catch (NumberFormatException e) {
+                result[i] = -1;
+            }
+        }
+        return result;
+    }
+
     /**
      * Returns true if it's an empty and read-only group and the system ID of
      * the group is one of "Friends", "Family" and "Coworkers".
diff --git a/src/com/android/contacts/list/ContactsIntentResolver.java b/src/com/android/contacts/list/ContactsIntentResolver.java
index 8e93baf..b110605 100644
--- a/src/com/android/contacts/list/ContactsIntentResolver.java
+++ b/src/com/android/contacts/list/ContactsIntentResolver.java
@@ -74,6 +74,13 @@
         } else if (UiIntentActions.LIST_GROUP_ACTION.equals(action)) {
             request.setActionCode(ContactsRequest.ACTION_GROUP);
             // We no longer support UiIntentActions.GROUP_NAME_EXTRA_KEY
+        } else if (UiIntentActions.ACTION_SELECT_ITEMS.equals(action)) {
+            final String resolvedType = intent.resolveType(mContext);
+            if (Phone.CONTENT_TYPE.equals(resolvedType)) {
+                request.setActionCode(ContactsRequest.ACTION_PICK_PHONES);
+            } else if (Email.CONTENT_TYPE.equals(resolvedType)) {
+                request.setActionCode(ContactsRequest.ACTION_PICK_EMAILS);
+            }
         } else if (Intent.ACTION_PICK.equals(action)) {
             final String resolvedType = intent.resolveType(mContext);
             if (Contacts.CONTENT_TYPE.equals(resolvedType)) {
diff --git a/src/com/android/contacts/list/ContactsRequest.java b/src/com/android/contacts/list/ContactsRequest.java
index de6a4ba..70ce80d 100644
--- a/src/com/android/contacts/list/ContactsRequest.java
+++ b/src/com/android/contacts/list/ContactsRequest.java
@@ -78,6 +78,12 @@
     /** Show all postal addresses and pick them when clicking */
     public static final int ACTION_PICK_EMAIL = 105;
 
+    /** Show a list of emails for selected contacts and select them when clicking */
+    public static final int ACTION_PICK_EMAILS = 106;
+
+    /** Show a list of phones for selected contacts and select them when clicking */
+    public static final int ACTION_PICK_PHONES = 107;
+
     /** Show all contacts and create a shortcut for the picked contact */
     public static final int ACTION_CREATE_SHORTCUT_CONTACT = 110;
 
diff --git a/src/com/android/contacts/list/MultiSelectEmailAddressesListAdapter.java b/src/com/android/contacts/list/MultiSelectEmailAddressesListAdapter.java
new file mode 100644
index 0000000..b225ba4
--- /dev/null
+++ b/src/com/android/contacts/list/MultiSelectEmailAddressesListAdapter.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2016 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.contacts.list;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+
+import android.provider.ContactsContract.CommonDataKinds.Email;
+
+import com.android.contacts.common.list.ContactListItemView;
+import com.android.contacts.common.list.MultiSelectEntryContactListAdapter;
+import com.android.contacts.common.preference.ContactsPreferences;
+import com.android.contacts.group.GroupUtil;
+
+/** Email addresses multi-select cursor adapter. */
+public class MultiSelectEmailAddressesListAdapter extends MultiSelectEntryContactListAdapter {
+
+    protected static class EmailQuery {
+        public static final String[] PROJECTION_PRIMARY = new String[] {
+                Email._ID,                          // 0
+                Email.TYPE,                         // 1
+                Email.LABEL,                        // 2
+                Email.ADDRESS,                      // 3
+                Email.CONTACT_ID,                   // 4
+                Email.LOOKUP_KEY,                   // 5
+                Email.PHOTO_ID,                     // 6
+                Email.DISPLAY_NAME_PRIMARY,         // 7
+                Email.PHOTO_THUMBNAIL_URI,          // 8
+        };
+
+        public static final String[] PROJECTION_ALTERNATIVE = new String[] {
+                Email._ID,                          // 0
+                Email.TYPE,                         // 1
+                Email.LABEL,                        // 2
+                Email.ADDRESS,                      // 3
+                Email.CONTACT_ID,                   // 4
+                Email.LOOKUP_KEY,                   // 5
+                Email.PHOTO_ID,                     // 6
+                Email.DISPLAY_NAME_ALTERNATIVE,     // 7
+                Email.PHOTO_THUMBNAIL_URI,          // 8
+        };
+
+        public static final int EMAIL_ID                = 0;
+        public static final int EMAIL_TYPE              = 1;
+        public static final int EMAIL_LABEL             = 2;
+        public static final int EMAIL_ADDRESS           = 3;
+        public static final int CONTACT_ID              = 4;
+        public static final int LOOKUP_KEY              = 5;
+        public static final int PHOTO_ID                = 6;
+        public static final int DISPLAY_NAME            = 7;
+        public static final int PHOTO_URI               = 8;
+    }
+
+    private final CharSequence mUnknownNameText;
+    private long[] mContactIdsFilter = null;
+
+    public MultiSelectEmailAddressesListAdapter(Context context) {
+        super(context, EmailQuery.EMAIL_ID);
+
+        mUnknownNameText = context.getText(android.R.string.unknownName);
+    }
+
+    public void setArguments(Bundle bundle) {
+        mContactIdsFilter = bundle.getLongArray(UiIntentActions.SELECTION_ITEM_LIST);
+    }
+
+    @Override
+    public void configureLoader(CursorLoader loader, long directoryId) {
+        final Builder builder;
+        if (isSearchMode()) {
+            builder = Email.CONTENT_FILTER_URI.buildUpon();
+            final String query = getQueryString();
+            builder.appendPath(TextUtils.isEmpty(query) ? "" : query);
+        } else {
+            builder = Email.CONTENT_URI.buildUpon();
+            if (isSectionHeaderDisplayEnabled()) {
+                builder.appendQueryParameter(Email.EXTRA_ADDRESS_BOOK_INDEX, "true");
+            }
+        }
+        builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+                String.valueOf(directoryId));
+        loader.setUri(builder.build());
+
+        if (mContactIdsFilter != null) {
+            loader.setSelection(ContactsContract.Data.CONTACT_ID
+                    + " IN (" + GroupUtil.convertArrayToString(mContactIdsFilter) + ")");
+        }
+
+        if (getContactNameDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
+            loader.setProjection(EmailQuery.PROJECTION_PRIMARY);
+        } else {
+            loader.setProjection(EmailQuery.PROJECTION_ALTERNATIVE);
+        }
+
+        if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) {
+            loader.setSortOrder(Email.SORT_KEY_PRIMARY);
+        } else {
+            loader.setSortOrder(Email.SORT_KEY_ALTERNATIVE);
+        }
+    }
+
+    @Override
+    public String getContactDisplayName(int position) {
+        return ((Cursor) getItem(position)).getString(EmailQuery.DISPLAY_NAME);
+    }
+
+    /**
+     * Builds a {@link Data#CONTENT_URI} for the current cursor position.
+     */
+    public Uri getDataUri(int position) {
+        final long id = ((Cursor) getItem(position)).getLong(EmailQuery.EMAIL_ID);
+        return ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, id);
+    }
+
+    @Override
+    protected ContactListItemView newView(
+            Context context, int partition, Cursor cursor, int position, ViewGroup parent) {
+        final ContactListItemView view = super.newView(context, partition, cursor, position, parent);
+        view.setUnknownNameText(mUnknownNameText);
+        view.setQuickContactEnabled(isQuickContactEnabled());
+        return view;
+    }
+
+    @Override
+    protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+        super.bindView(itemView, partition, cursor, position);
+        final ContactListItemView view = (ContactListItemView)itemView;
+
+        cursor.moveToPosition(position);
+        boolean isFirstEntry = true;
+        final long currentContactId = cursor.getLong(EmailQuery.CONTACT_ID);
+        if (cursor.moveToPrevious() && !cursor.isBeforeFirst()) {
+            final long previousContactId = cursor.getLong(EmailQuery.CONTACT_ID);
+            if (currentContactId == previousContactId) {
+                isFirstEntry = false;
+            }
+        }
+        cursor.moveToPosition(position);
+
+        bindViewId(view, cursor, EmailQuery.EMAIL_ID);
+        bindSectionHeaderAndDivider(view, position);
+        if (isFirstEntry) {
+            bindName(view, cursor);
+            bindQuickContact(view, partition, cursor, EmailQuery.PHOTO_ID,
+                    EmailQuery.PHOTO_URI, EmailQuery.CONTACT_ID,
+                    EmailQuery.LOOKUP_KEY, EmailQuery.DISPLAY_NAME);
+        } else {
+            unbindName(view);
+            view.removePhotoView(true, false);
+        }
+        bindEmailAddress(view, cursor);
+    }
+
+    protected void unbindName(final ContactListItemView view) {
+        view.hideDisplayName();
+    }
+
+    protected void bindEmailAddress(ContactListItemView view, Cursor cursor) {
+        CharSequence label = null;
+        if (!cursor.isNull(EmailQuery.EMAIL_TYPE)) {
+            final int type = cursor.getInt(EmailQuery.EMAIL_TYPE);
+            final String customLabel = cursor.getString(EmailQuery.EMAIL_LABEL);
+
+            // TODO cache
+            label = Email.getTypeLabel(getContext().getResources(), type, customLabel);
+        }
+        view.setLabel(label);
+        view.showData(cursor, EmailQuery.EMAIL_ADDRESS);
+    }
+
+    protected void bindSectionHeaderAndDivider(final ContactListItemView view, int position) {
+        final int section = getSectionForPosition(position);
+        if (getPositionForSection(section) == position) {
+            final String title = (String)getSections()[section];
+            view.setSectionHeader(title);
+        } else {
+            view.setSectionHeader(null);
+        }
+    }
+
+    protected void bindName(final ContactListItemView view, Cursor cursor) {
+        view.showDisplayName(cursor, EmailQuery.DISPLAY_NAME, getContactNameDisplayOrder());
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/list/MultiSelectEmailAddressesListFragment.java b/src/com/android/contacts/list/MultiSelectEmailAddressesListFragment.java
new file mode 100644
index 0000000..d3aa5ca
--- /dev/null
+++ b/src/com/android/contacts/list/MultiSelectEmailAddressesListFragment.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2016 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.contacts.list;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.contacts.R;
+import com.android.contacts.common.logging.ListEvent;
+
+import java.util.TreeSet;
+
+/** Displays a list of emails with check boxes. */
+public class MultiSelectEmailAddressesListFragment
+        extends MultiSelectContactsListFragment<MultiSelectEmailAddressesListAdapter>{
+
+    public MultiSelectEmailAddressesListFragment() {
+        setPhotoLoaderEnabled(true);
+        setSectionHeaderDisplayEnabled(true);
+        setSearchMode(false);
+        setHasOptionsMenu(true);
+        setListType(ListEvent.ListType.PICK_EMAIL);
+    }
+
+    @Override
+    public MultiSelectEmailAddressesListAdapter createListAdapter() {
+        final MultiSelectEmailAddressesListAdapter adapter =
+                new MultiSelectEmailAddressesListAdapter(getActivity());
+        adapter.setArguments(getArguments());
+        return adapter;
+    }
+
+    @Override
+    public void onSelectedContactsChangedViaCheckBox() {
+        onSelectedContactsChanged();
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+        inflater.inflate(R.menu.items_multi_select, menu);
+    }
+
+    @Override
+    public void onPrepareOptionsMenu(Menu menu) {
+        final MenuItem item = menu.findItem(R.id.menu_send);
+        item.setVisible(getAdapter().hasSelectedItems());
+        item.getActionView().setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                onOptionsItemSelected(item);
+            }
+        });
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch(item.getItemId()) {
+            case R.id.menu_send: {
+                final String scheme = getActivity().getIntent().getStringExtra(
+                        UiIntentActions.SELECTION_SEND_SCHEME);
+                final String title= getActivity().getIntent().getStringExtra(
+                        UiIntentActions.SELECTION_SEND_TITLE);
+                final Intent intent = new Intent();
+                intent.putExtra(UiIntentActions.TARGET_CONTACT_IDS_EXTRA_KEY,
+                        getAdapter().getSelectedContactIdsArray());
+                intent.putExtra(UiIntentActions.SELECTION_SEND_SCHEME, scheme);
+                intent.putExtra(UiIntentActions.SELECTION_SEND_TITLE, title);
+                getActivity().setResult(Activity.RESULT_OK, intent);
+                getActivity().finish();
+                return true;
+            }
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        final long[] selectedIds = getActivity().getIntent().getLongArrayExtra(
+                UiIntentActions.SELECTION_DEFAULT_SELECTION);
+        if (selectedIds != null && selectedIds.length != 0) {
+            final TreeSet<Long> selectedIdsTree = new TreeSet<>();
+            for (int i = 0; i < selectedIds.length; i++) {
+                selectedIdsTree.add(selectedIds[i]);
+            }
+            getAdapter().setSelectedContactIds(selectedIdsTree);
+            onSelectedContactsChanged();
+        }
+        return super.onCreateView(inflater, container, savedInstanceState);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        displayCheckBoxes(true);
+
+        final long[] itemIds = getActivity().getIntent().getLongArrayExtra(
+                UiIntentActions.SELECTION_ITEM_LIST);
+        final boolean[] selectedFlags = getActivity().getIntent().getBooleanArrayExtra(
+                UiIntentActions.SELECTION_DEFAULT_SELECTION);
+        if (itemIds != null && selectedFlags != null && itemIds.length == selectedFlags.length) {
+            TreeSet<Long> selectedIds = new TreeSet<>();
+            for (int i = 0; i < itemIds.length; i++) {
+                if (selectedFlags[i]) {
+                    selectedIds.add(itemIds[i]);
+                }
+            }
+            getAdapter().setSelectedContactIds(selectedIds);
+            onSelectedContactsChanged();
+        }
+    }
+
+    @Override
+    protected boolean onItemLongClick(int position, long id) {
+        return true;
+    }
+
+    @Override
+    protected View inflateView(LayoutInflater inflater, ViewGroup container) {
+        return inflater.inflate(com.android.contacts.common.R.layout.contact_list_content, null);
+    }
+}
diff --git a/src/com/android/contacts/list/MultiSelectPhoneNumbersListAdapter.java b/src/com/android/contacts/list/MultiSelectPhoneNumbersListAdapter.java
new file mode 100644
index 0000000..b3574ab
--- /dev/null
+++ b/src/com/android/contacts/list/MultiSelectPhoneNumbersListAdapter.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2016 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.contacts.list;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+
+import com.android.contacts.common.list.ContactListItemView;
+import com.android.contacts.common.list.MultiSelectEntryContactListAdapter;
+import com.android.contacts.common.preference.ContactsPreferences;
+import com.android.contacts.group.GroupUtil;
+
+/** Phone Numbers multi-select cursor adapter. */
+public class MultiSelectPhoneNumbersListAdapter extends MultiSelectEntryContactListAdapter {
+
+    public static class PhoneQuery {
+        public static final String[] PROJECTION_PRIMARY = new String[] {
+                Phone._ID,                          // 0
+                Phone.TYPE,                         // 1
+                Phone.LABEL,                        // 2
+                Phone.NUMBER,                       // 3
+                Phone.CONTACT_ID,                   // 4
+                Phone.LOOKUP_KEY,                   // 5
+                Phone.PHOTO_ID,                     // 6
+                Phone.DISPLAY_NAME_PRIMARY,         // 7
+                Phone.PHOTO_THUMBNAIL_URI,          // 8
+        };
+
+        public static final String[] PROJECTION_ALTERNATIVE = new String[] {
+                Phone._ID,                          // 0
+                Phone.TYPE,                         // 1
+                Phone.LABEL,                        // 2
+                Phone.NUMBER,                       // 3
+                Phone.CONTACT_ID,                   // 4
+                Phone.LOOKUP_KEY,                   // 5
+                Phone.PHOTO_ID,                     // 6
+                Phone.DISPLAY_NAME_ALTERNATIVE,     // 7
+                Phone.PHOTO_THUMBNAIL_URI,          // 8
+        };
+
+        public static final int PHONE_ID                = 0;
+        public static final int PHONE_TYPE              = 1;
+        public static final int PHONE_LABEL             = 2;
+        public static final int PHONE_NUMBER            = 3;
+        public static final int CONTACT_ID              = 4;
+        public static final int LOOKUP_KEY              = 5;
+        public static final int PHOTO_ID                = 6;
+        public static final int DISPLAY_NAME            = 7;
+        public static final int PHOTO_URI               = 8;
+    }
+
+    private final CharSequence mUnknownNameText;
+    private long[] mContactIdsFilter = null;
+
+    public MultiSelectPhoneNumbersListAdapter(Context context) {
+        super(context, PhoneQuery.PHONE_ID);
+
+        mUnknownNameText = context.getText(android.R.string.unknownName);
+    }
+
+    public void setArguments(Bundle bundle) {
+        mContactIdsFilter = bundle.getLongArray(UiIntentActions.SELECTION_ITEM_LIST);
+    }
+
+    @Override
+    public void configureLoader(CursorLoader loader, long directoryId) {
+        final Builder builder;
+        if (isSearchMode()) {
+            builder = Phone.CONTENT_FILTER_URI.buildUpon();
+            final String query = getQueryString();
+            builder.appendPath(TextUtils.isEmpty(query) ? "" : query);
+        } else {
+            builder = Phone.CONTENT_URI.buildUpon();
+            if (isSectionHeaderDisplayEnabled()) {
+                builder.appendQueryParameter(Phone.EXTRA_ADDRESS_BOOK_INDEX, "true");
+            }
+        }
+        builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+                String.valueOf(directoryId));
+        loader.setUri(builder.build());
+
+        if (mContactIdsFilter != null) {
+            loader.setSelection(ContactsContract.Data.CONTACT_ID
+                    + " IN (" + GroupUtil.convertArrayToString(mContactIdsFilter) + ")");
+        }
+
+        if (getContactNameDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
+            loader.setProjection(PhoneQuery.PROJECTION_PRIMARY);
+        } else {
+            loader.setProjection(PhoneQuery.PROJECTION_ALTERNATIVE);
+        }
+
+        if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) {
+            loader.setSortOrder(Phone.SORT_KEY_PRIMARY);
+        } else {
+            loader.setSortOrder(Phone.SORT_KEY_ALTERNATIVE);
+        }
+    }
+
+    @Override
+    public String getContactDisplayName(int position) {
+        return ((Cursor) getItem(position)).getString(PhoneQuery.DISPLAY_NAME);
+    }
+
+    /**
+     * Builds a {@link Data#CONTENT_URI} for the current cursor position.
+     */
+    public Uri getDataUri(int position) {
+        final long id = ((Cursor) getItem(position)).getLong(PhoneQuery.PHONE_ID);
+        return ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, id);
+    }
+
+    @Override
+    protected ContactListItemView newView(
+            Context context, int partition, Cursor cursor, int position, ViewGroup parent) {
+        final ContactListItemView view = super.newView(context, partition, cursor, position, parent);
+        view.setUnknownNameText(mUnknownNameText);
+        view.setQuickContactEnabled(isQuickContactEnabled());
+        return view;
+    }
+
+    @Override
+    protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+        super.bindView(itemView, partition, cursor, position);
+        final ContactListItemView view = (ContactListItemView)itemView;
+
+        cursor.moveToPosition(position);
+        boolean isFirstEntry = true;
+        final long currentContactId = cursor.getLong(PhoneQuery.CONTACT_ID);
+        if (cursor.moveToPrevious() && !cursor.isBeforeFirst()) {
+            final long previousContactId = cursor.getLong(PhoneQuery.CONTACT_ID);
+            if (currentContactId == previousContactId) {
+                isFirstEntry = false;
+            }
+        }
+        cursor.moveToPosition(position);
+
+        bindViewId(view, cursor, PhoneQuery.PHONE_ID);
+        bindSectionHeaderAndDivider(view, position);
+        if (isFirstEntry) {
+            bindName(view, cursor);
+            bindQuickContact(view, partition, cursor, PhoneQuery.PHOTO_ID,
+                        PhoneQuery.PHOTO_URI, PhoneQuery.CONTACT_ID,
+                        PhoneQuery.LOOKUP_KEY, PhoneQuery.DISPLAY_NAME);
+        } else {
+            unbindName(view);
+            view.removePhotoView(true, false);
+        }
+        bindPhoneNumber(view, cursor);
+    }
+
+    protected void unbindName(final ContactListItemView view) {
+        view.hideDisplayName();
+    }
+
+    protected void bindPhoneNumber(ContactListItemView view, Cursor cursor) {
+        CharSequence label = null;
+        if (!cursor.isNull(PhoneQuery.PHONE_TYPE)) {
+            final int type = cursor.getInt(PhoneQuery.PHONE_TYPE);
+            final String customLabel = cursor.getString(PhoneQuery.PHONE_LABEL);
+
+            // TODO cache
+            label = Phone.getTypeLabel(getContext().getResources(), type, customLabel);
+        }
+        view.setLabel(label);
+        view.showData(cursor, PhoneQuery.PHONE_NUMBER);
+    }
+
+    protected void bindSectionHeaderAndDivider(final ContactListItemView view, int position) {
+        if (isSectionHeaderDisplayEnabled()) {
+            Placement placement = getItemPlacementInSection(position);
+            view.setSectionHeader(placement.firstInSection ? placement.sectionHeader : null);
+        } else {
+            view.setSectionHeader(null);
+        }
+    }
+
+    protected void bindName(final ContactListItemView view, Cursor cursor) {
+        view.showDisplayName(cursor, PhoneQuery.DISPLAY_NAME, getContactNameDisplayOrder());
+    }
+}
diff --git a/src/com/android/contacts/list/MultiSelectPhoneNumbersListFragment.java b/src/com/android/contacts/list/MultiSelectPhoneNumbersListFragment.java
new file mode 100644
index 0000000..aea89e4
--- /dev/null
+++ b/src/com/android/contacts/list/MultiSelectPhoneNumbersListFragment.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 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.contacts.list;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.contacts.R;
+import com.android.contacts.common.logging.ListEvent;
+
+import java.util.TreeSet;
+
+/** Displays a list of phone numbers with check boxes. */
+public class MultiSelectPhoneNumbersListFragment
+        extends MultiSelectContactsListFragment<MultiSelectPhoneNumbersListAdapter> {
+
+    public MultiSelectPhoneNumbersListFragment() {
+        setPhotoLoaderEnabled(true);
+        setSectionHeaderDisplayEnabled(true);
+        setSearchMode(false);
+        setHasOptionsMenu(true);
+        setListType(ListEvent.ListType.PICK_PHONE);
+    }
+
+    @Override
+    public MultiSelectPhoneNumbersListAdapter createListAdapter() {
+        final MultiSelectPhoneNumbersListAdapter adapter =
+                new MultiSelectPhoneNumbersListAdapter(getActivity());
+        adapter.setArguments(getArguments());
+        return adapter;
+    }
+
+    @Override
+    public void onSelectedContactsChangedViaCheckBox() {
+        onSelectedContactsChanged();
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+        inflater.inflate(R.menu.items_multi_select, menu);
+    }
+
+    @Override
+    public void onPrepareOptionsMenu(Menu menu) {
+        final MenuItem item = menu.findItem(R.id.menu_send);
+        item.setVisible(getAdapter().hasSelectedItems());
+        item.getActionView().setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                onOptionsItemSelected(item);
+            }
+        });
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch(item.getItemId()) {
+            case R.id.menu_send: {
+                final String scheme = getActivity().getIntent().getStringExtra(
+                        UiIntentActions.SELECTION_SEND_SCHEME);
+                final String title= getActivity().getIntent().getStringExtra(
+                        UiIntentActions.SELECTION_SEND_TITLE);
+                final Intent intent = new Intent();
+                intent.putExtra(UiIntentActions.TARGET_CONTACT_IDS_EXTRA_KEY,
+                        getAdapter().getSelectedContactIdsArray());
+                intent.putExtra(UiIntentActions.SELECTION_SEND_SCHEME, scheme);
+                intent.putExtra(UiIntentActions.SELECTION_SEND_TITLE, title);
+                getActivity().setResult(Activity.RESULT_OK, intent);
+                getActivity().finish();
+                return true;
+            }
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        final long[] selectedIds = getActivity().getIntent().getLongArrayExtra(
+                UiIntentActions.SELECTION_DEFAULT_SELECTION);
+        if (selectedIds != null && selectedIds.length != 0) {
+            final TreeSet<Long> selectedIdsTree = new TreeSet<>();
+            for (int i = 0; i < selectedIds.length; i++) {
+                selectedIdsTree.add(selectedIds[i]);
+            }
+            getAdapter().setSelectedContactIds(selectedIdsTree);
+            onSelectedContactsChanged();
+        }
+        return super.onCreateView(inflater, container, savedInstanceState);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        displayCheckBoxes(true);
+    }
+
+    @Override
+    protected boolean onItemLongClick(int position, long id) {
+        return true;
+    }
+
+    @Override
+    protected View inflateView(LayoutInflater inflater, ViewGroup container) {
+        return inflater.inflate(com.android.contacts.common.R.layout.contact_list_content, null);
+    }
+}
diff --git a/src/com/android/contacts/list/UiIntentActions.java b/src/com/android/contacts/list/UiIntentActions.java
index 6ea984f..b2157fb 100644
--- a/src/com/android/contacts/list/UiIntentActions.java
+++ b/src/com/android/contacts/list/UiIntentActions.java
@@ -35,12 +35,42 @@
             "com.android.contacts.action.LIST_CONTACTS";
 
     /**
+     * The action for selecting multiple items (email, phone) from a list.
+     */
+    public static final String ACTION_SELECT_ITEMS =
+            "com.android.contacts.action.ACTION_SELECT_ITEMS";
+
+    /**
      * The action for the contacts list tab.
      */
     public static final String LIST_GROUP_ACTION =
             "com.android.contacts.action.LIST_GROUP";
 
     /**
+     * The send scheme for multi email/phone picker fragment
+     */
+    public static final String SELECTION_SEND_SCHEME =
+            "com.android.contacts.extra.SELECTION_SEND_SCHEME";
+
+    /**
+     * The send title for multi email/phone picker fragment
+     */
+    public static final String SELECTION_SEND_TITLE =
+            "com.android.contacts.extra.SELECTION_SEND_TITLE";
+
+    /**
+     * The item ids for multi select picker fragment/adapter
+     */
+    public static final String SELECTION_ITEM_LIST =
+            "com.android.contacts.extra.SELECTION_ITEM_LIST";
+
+    /**
+     * The default selection flags for the multi select picker fragment/adapter
+     */
+    public static final String SELECTION_DEFAULT_SELECTION =
+            "com.android.contacts.extra.SELECTION_DEFAULT_SELECTION";
+
+    /**
      * When in LIST_GROUP_ACTION mode, this is the group to display.
      */
     public static final String GROUP_NAME_EXTRA_KEY = "com.android.contacts.extra.GROUP";