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";