Commit aee6fd2b authored by Rayan Kanso's avatar Rayan Kanso Committed by Commit Bot

[Contacts] Use ContactsContract API in Android to get Address info.

Grab address info in the contacts fetcher task, and pipe down to the
address provider to create a mojo address object.

Bug: 1016870
Change-Id: I4d0580f0c27cbaef813cb2374b010ad182cb64d4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1879240Reviewed-by: default avatarFinnur Thorarinsson <finnur@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarMohamed Heikal <mheikal@chromium.org>
Commit-Queue: Rayan Kanso <rayankans@chromium.org>
Cr-Commit-Position: refs/heads/master@{#711285}
parent ac1bf595
......@@ -9,6 +9,7 @@ import android.content.res.Resources;
import androidx.annotation.Nullable;
import org.chromium.chrome.R;
import org.chromium.payments.mojom.PaymentAddress;
import java.util.ArrayList;
import java.util.Arrays;
......@@ -42,19 +43,24 @@ public class ContactDetails implements Comparable<ContactDetails> {
// The list of phone numbers registered for this contact.
private final List<String> mPhoneNumbers;
// The list of addresses registered for this contact.
private final List<PaymentAddress> mAddresses;
/**
* The ContactDetails constructor.
* @param id The unique identifier of this contact.
* @param displayName The display name of this contact.
* @param emails The emails registered for this contact.
* @param phoneNumbers The phone numbers registered for this contact.
* @param addresses The addresses registered for this contact.
*/
public ContactDetails(
String id, String displayName, List<String> emails, List<String> phoneNumbers) {
public ContactDetails(String id, String displayName, List<String> emails,
List<String> phoneNumbers, List<PaymentAddress> addresses) {
mDisplayName = displayName;
mEmails = emails != null ? new ArrayList<String>(emails) : new ArrayList<String>();
mPhoneNumbers = phoneNumbers != null ? new ArrayList<String>(phoneNumbers)
: new ArrayList<String>();
mEmails = emails != null ? emails : new ArrayList<String>();
mPhoneNumbers = phoneNumbers != null ? phoneNumbers : new ArrayList<String>();
mAddresses = addresses != null ? addresses : new ArrayList<PaymentAddress>();
mId = id;
}
......@@ -70,6 +76,10 @@ public class ContactDetails implements Comparable<ContactDetails> {
return mPhoneNumbers;
}
public List<PaymentAddress> getAddresses() {
return mAddresses;
}
public String getDisplayName() {
return mDisplayName;
}
......
......@@ -11,6 +11,7 @@ import android.provider.ContactsContract;
import org.chromium.base.ThreadUtils;
import org.chromium.base.task.AsyncTask;
import org.chromium.payments.mojom.PaymentAddress;
import java.util.ArrayList;
import java.util.HashMap;
......@@ -52,6 +53,9 @@ class ContactsFetcherWorkerTask extends AsyncTask<ArrayList<ContactDetails>> {
// Whether to include telephones in the data fetched.
private final boolean mIncludeTel;
// Whether to include addresses in the data fetched.
private final boolean mIncludeAddresses;
/**
* A ContactsFetcherWorkerTask constructor.
* @param contentResolver The ContentResolver to use to fetch the contacts data.
......@@ -59,15 +63,17 @@ class ContactsFetcherWorkerTask extends AsyncTask<ArrayList<ContactDetails>> {
* @param includeNames Whether names were requested by the website.
* @param includeEmails Whether to include emails in the data fetched.
* @param includeTel Whether to include telephones in the data fetched.
* @param includeAddresses Whether to include telephones in the data fetched.
*/
public ContactsFetcherWorkerTask(ContentResolver contentResolver,
ContactsRetrievedCallback callback, boolean includeNames, boolean includeEmails,
boolean includeTel) {
boolean includeTel, boolean includeAddresses) {
mContentResolver = contentResolver;
mCallback = callback;
mIncludeNames = includeNames;
mIncludeEmails = includeEmails;
mIncludeTel = includeTel;
mIncludeAddresses = includeAddresses;
}
/**
......@@ -103,6 +109,7 @@ class ContactsFetcherWorkerTask extends AsyncTask<ArrayList<ContactDetails>> {
while (cursor.moveToNext()) {
String id = cursor.getString(cursor.getColumnIndex(idColumn));
value = cursor.getString(cursor.getColumnIndex(dataColumn));
if (value == null) value = "";
if (key.isEmpty()) {
key = id;
list.add(value);
......@@ -123,6 +130,76 @@ class ContactsFetcherWorkerTask extends AsyncTask<ArrayList<ContactDetails>> {
return map;
}
/** Creates a PaymentAddress mojo struct. */
private PaymentAddress createAddress(
String city, String country, String formattedAddress, String postcode, String region) {
PaymentAddress address = new PaymentAddress();
address.city = city != null ? city : "";
address.country = country != null ? country : "";
address.addressLine =
formattedAddress != null ? new String[] {formattedAddress} : new String[] {};
address.postalCode = postcode != null ? postcode : "";
address.region = region != null ? region : "";
// The other fields are required.
address.dependentLocality = "";
address.sortingCode = "";
address.organization = "";
address.recipient = "";
address.phone = "";
return address;
}
/** Fetches all available address info for contacts. */
private Map<String, ArrayList<PaymentAddress>> getAddressDetails() {
Map<String, ArrayList<PaymentAddress>> map = new HashMap<>();
String addressSortOrder = ContactsContract.CommonDataKinds.StructuredPostal.CONTACT_ID
+ " ASC, " + ContactsContract.CommonDataKinds.StructuredPostal.DATA + " ASC";
Cursor cursor = mContentResolver.query(
ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI, null, null, null,
addressSortOrder);
ArrayList<PaymentAddress> list = new ArrayList<>();
String key = "";
while (cursor.moveToNext()) {
String id = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.StructuredPostal.CONTACT_ID));
String city = cursor.getString(
cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.CITY));
String country = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY));
String formattedAddress = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS));
String postcode = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE));
String region = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.StructuredPostal.REGION));
PaymentAddress address =
createAddress(city, country, formattedAddress, postcode, region);
if (key.isEmpty()) {
key = id;
list.add(address);
} else {
if (key.equals(id)) {
list.add(address);
} else {
map.put(key, list);
list = new ArrayList<>();
list.add(address);
key = id;
}
}
}
map.put(key, list);
cursor.close();
return map;
}
/**
* Fetches all known contacts.
* @return The contact list as an array.
......@@ -139,11 +216,14 @@ class ContactsFetcherWorkerTask extends AsyncTask<ArrayList<ContactDetails>> {
Map<String, ArrayList<String>> phoneMap = mIncludeTel
? getDetails(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
ContactsContract.CommonDataKinds.Email.DATA,
ContactsContract.CommonDataKinds.Email.CONTACT_ID + " ASC, "
ContactsContract.CommonDataKinds.Phone.DATA,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " ASC, "
+ ContactsContract.CommonDataKinds.Phone.NUMBER + " ASC")
: null;
Map<String, ArrayList<PaymentAddress>> addressMap =
mIncludeAddresses ? getAddressDetails() : null;
// A cursor containing the raw contacts data.
Cursor cursor = mContentResolver.query(ContactsContract.Contacts.CONTENT_URI, PROJECTION,
null, null, ContactsContract.Contacts.SORT_KEY_PRIMARY + " ASC");
......@@ -156,8 +236,11 @@ class ContactsFetcherWorkerTask extends AsyncTask<ArrayList<ContactDetails>> {
cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
List<String> email = mIncludeEmails ? emailMap.get(id) : null;
List<String> tel = mIncludeTel ? phoneMap.get(id) : null;
if (mIncludeNames || email != null || tel != null)
contacts.add(new ContactDetails(id, name, email, tel));
List<PaymentAddress> address = mIncludeAddresses ? addressMap.get(id) : null;
if (mIncludeNames || email != null || tel != null || address != null) {
contacts.add(new ContactDetails(id, name, email, tel, address));
}
} while (cursor.moveToNext());
cursor.close();
......
......@@ -28,17 +28,18 @@ public class ContactsPickerDialog
* @param includeNames Whether the contacts data returned includes names.
* @param includeEmails Whether the contacts data returned includes emails.
* @param includeTel Whether the contacts data returned includes telephone numbers.
* @param includeAddresses Whether the contacts data returned includes addresses.
* @param formattedOrigin The origin the data will be shared with, formatted for display with
* the scheme omitted.
*/
public ContactsPickerDialog(Context context, ContactsPickerListener listener,
boolean allowMultiple, boolean includeNames, boolean includeEmails, boolean includeTel,
String formattedOrigin) {
boolean includeAddresses, String formattedOrigin) {
super(context, R.style.Theme_Chromium_Fullscreen);
// Initialize the main content view.
mCategoryView = new PickerCategoryView(context, allowMultiple, includeNames, includeEmails,
includeTel, formattedOrigin, this);
includeTel, includeAddresses, formattedOrigin, this);
mCategoryView.initialize(this, listener);
setView(mCategoryView);
}
......
......@@ -124,7 +124,7 @@ public class PickerAdapter extends Adapter<RecyclerView.ViewHolder>
if (getAllContacts() == null && sTestContacts == null) {
mWorkerTask = new ContactsFetcherWorkerTask(mContentResolver, this,
mCategoryView.includeNames, mCategoryView.includeEmails,
mCategoryView.includeTel);
mCategoryView.includeTel, mCategoryView.includeAddresses);
mWorkerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
mContactDetails = sTestContacts;
......
......@@ -113,6 +113,9 @@ public class PickerCategoryView extends OptimizedFrameLayout
// Whether the contacts data returned includes telephone numbers.
public final boolean includeTel;
// Whether the contacts data returned includes addresses.
public final boolean includeAddresses;
/**
* @param multiSelectionAllowed Whether the contacts picker should allow multiple items to be
* selected.
......@@ -120,7 +123,8 @@ public class PickerCategoryView extends OptimizedFrameLayout
@SuppressWarnings("unchecked") // mSelectableListLayout
public PickerCategoryView(Context context, boolean multiSelectionAllowed,
boolean shouldIncludeNames, boolean shouldIncludeEmails, boolean shouldIncludeTel,
String formattedOrigin, ContactsPickerToolbar.ContactsToolbarDelegate delegate) {
boolean shouldIncludeAddresses, String formattedOrigin,
ContactsPickerToolbar.ContactsToolbarDelegate delegate) {
super(context, null);
mActivity = (ChromeActivity) context;
......@@ -128,6 +132,7 @@ public class PickerCategoryView extends OptimizedFrameLayout
includeNames = shouldIncludeNames;
includeEmails = shouldIncludeEmails;
includeTel = shouldIncludeTel;
includeAddresses = shouldIncludeAddresses;
mSelectionDelegate = new SelectionDelegate<ContactDetails>();
if (!multiSelectionAllowed) mSelectionDelegate.setSingleSelectionMode();
......@@ -326,8 +331,8 @@ public class PickerCategoryView extends OptimizedFrameLayout
* @param selected The property values that are currently selected.
* @return The list of property values to share.
*/
private List<String> getContactPropertyValues(
boolean isIncluded, boolean isEnabled, List<String> selected) {
private <T> List<T> getContactPropertyValues(
boolean isIncluded, boolean isEnabled, List<T> selected) {
if (!isIncluded) {
// The property wasn't requested in the API so return null.
return null;
......@@ -335,7 +340,7 @@ public class PickerCategoryView extends OptimizedFrameLayout
if (!isEnabled) {
// The user doesn't want to share this property, so return an empty array.
return new ArrayList<String>();
return new ArrayList<T>();
}
// Share whatever was selected.
......@@ -359,7 +364,10 @@ public class PickerCategoryView extends OptimizedFrameLayout
getContactPropertyValues(includeEmails, PickerAdapter.includesEmails(),
contactDetails.getEmails()),
getContactPropertyValues(includeTel, PickerAdapter.includesTelephones(),
contactDetails.getPhoneNumbers())));
contactDetails.getPhoneNumbers()),
// TODO(crbug.com/1016870): Check the address chip when added.
getContactPropertyValues(
includeAddresses, true, contactDetails.getAddresses())));
}
executeAction(ContactsPickerListener.ContactsPickerAction.CONTACTS_SELECTED, contacts,
ACTION_CONTACTS_SELECTED);
......@@ -383,6 +391,9 @@ public class PickerCategoryView extends OptimizedFrameLayout
propertiesRequested |= ContactsPickerPropertiesRequested.PROPERTIES_EMAILS;
}
if (includeTel) propertiesRequested |= ContactsPickerPropertiesRequested.PROPERTIES_TELS;
if (includeAddresses) {
propertiesRequested |= ContactsPickerPropertiesRequested.PROPERTIES_ADDRESSES;
}
mListener.onContactsPickerUserAction(
action, contacts, percentageShared, propertiesRequested);
......
......@@ -260,9 +260,9 @@ public class ProcessInitializationHandler {
@Override
public void showContactsPicker(Context context, ContactsPickerListener listener,
boolean allowMultiple, boolean includeNames, boolean includeEmails,
boolean includeTel, String formattedOrigin) {
boolean includeTel, boolean includeAddresses, String formattedOrigin) {
mDialog = new ContactsPickerDialog(context, listener, allowMultiple, includeNames,
includeEmails, includeTel, formattedOrigin);
includeEmails, includeTel, includeAddresses, formattedOrigin);
mDialog.getWindow().getAttributes().windowAnimations =
R.style.PickerDialogAnimation;
mDialog.show();
......
......@@ -63,41 +63,56 @@ void ContactsProviderAndroid::Select(bool multiple,
JNIEnv* env = base::android::AttachCurrentThread();
Java_ContactsDialogHost_showDialog(
env, dialog_, multiple, include_names, include_emails, include_tel,
include_addresses,
base::android::ConvertUTF16ToJavaString(env, formatted_origin_));
}
void ContactsProviderAndroid::AddContact(
JNIEnv* env,
jboolean include_names,
jboolean include_emails,
jboolean include_tel,
const base::android::JavaParamRef<jobjectArray>& names_java,
const base::android::JavaParamRef<jobjectArray>& emails_java,
const base::android::JavaParamRef<jobjectArray>& tel_java) {
const base::android::JavaParamRef<jobjectArray>& tel_java,
const base::android::JavaParamRef<jobjectArray>& addresses_java) {
DCHECK(callback_);
base::Optional<std::vector<std::string>> names;
if (include_names) {
if (names_java) {
std::vector<std::string> names_vector;
AppendJavaStringArrayToStringVector(env, names_java, &names_vector);
names = names_vector;
names = std::move(names_vector);
}
base::Optional<std::vector<std::string>> emails;
if (include_emails) {
if (emails_java) {
std::vector<std::string> emails_vector;
AppendJavaStringArrayToStringVector(env, emails_java, &emails_vector);
emails = emails_vector;
emails = std::move(emails_vector);
}
base::Optional<std::vector<std::string>> tel;
if (include_tel) {
if (tel_java) {
std::vector<std::string> tel_vector;
AppendJavaStringArrayToStringVector(env, tel_java, &tel_vector);
tel = tel_vector;
tel = std::move(tel_vector);
}
base::Optional<std::vector<payments::mojom::PaymentAddressPtr>> addresses;
if (addresses_java) {
std::vector<payments::mojom::PaymentAddressPtr> addresses_vector;
for (const base::android::JavaRef<jbyteArray>& j_address :
addresses_java.ReadElements<jbyteArray>()) {
payments::mojom::PaymentAddressPtr address;
if (!payments::mojom::PaymentAddress::Deserialize(
static_cast<jbyte*>(env->GetDirectBufferAddress(j_address.obj())),
env->GetDirectBufferCapacity(j_address.obj()), &address)) {
continue;
}
addresses_vector.push_back(std::move(address));
}
addresses = std::move(addresses_vector);
}
blink::mojom::ContactInfoPtr contact =
blink::mojom::ContactInfo::New(std::move(names), std::move(emails),
......
......@@ -32,13 +32,12 @@ class ContactsProviderAndroid : public ContactsProvider {
// Adds one contact to the list of contacts selected. Note, EndContactsList
// (or EndWithPermissionDenied) must be called to signal the end of the
// construction of the contacts list.
void AddContact(JNIEnv* env,
jboolean includeNames,
jboolean includeEmails,
jboolean includeTel,
void AddContact(
JNIEnv* env,
const base::android::JavaParamRef<jobjectArray>& names_java,
const base::android::JavaParamRef<jobjectArray>& emails_java,
const base::android::JavaParamRef<jobjectArray>& tel_java);
const base::android::JavaParamRef<jobjectArray>& tel_java,
const base::android::JavaParamRef<jobjectArray>& addresses_java);
// Signals the end of adding contacts to the list. The contact list is
// returned to the web page, the other params are logged via UKM.
......
......@@ -15,6 +15,7 @@ import org.chromium.ui.ContactsPickerListener;
import org.chromium.ui.UiUtils;
import org.chromium.ui.base.WindowAndroid;
import java.nio.ByteBuffer;
import java.util.List;
/**
......@@ -44,7 +45,7 @@ public class ContactsDialogHost implements ContactsPickerListener {
@CalledByNative
private void showDialog(boolean multiple, boolean includeNames, boolean includeEmails,
boolean includeTel, String formattedOrigin) {
boolean includeTel, boolean includeAddresses, String formattedOrigin) {
if (mWindowAndroid.getActivity().get() == null) {
ContactsDialogHostJni.get().endWithPermissionDenied(mNativeContactsProviderAndroid);
return;
......@@ -52,7 +53,8 @@ public class ContactsDialogHost implements ContactsPickerListener {
if (mWindowAndroid.hasPermission(Manifest.permission.READ_CONTACTS)) {
if (!UiUtils.showContactsPicker(mWindowAndroid.getActivity().get(), this, multiple,
includeNames, includeEmails, includeTel, formattedOrigin)) {
includeNames, includeEmails, includeTel, includeAddresses,
formattedOrigin)) {
ContactsDialogHostJni.get().endWithPermissionDenied(mNativeContactsProviderAndroid);
}
return;
......@@ -70,7 +72,7 @@ public class ContactsDialogHost implements ContactsPickerListener {
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (!UiUtils.showContactsPicker(mWindowAndroid.getActivity().get(), this,
multiple, includeNames, includeEmails, includeTel,
formattedOrigin)) {
includeAddresses, formattedOrigin)) {
ContactsDialogHostJni.get().endWithPermissionDenied(
mNativeContactsProviderAndroid);
}
......@@ -95,7 +97,6 @@ public class ContactsDialogHost implements ContactsPickerListener {
case ContactsPickerAction.CONTACTS_SELECTED:
for (Contact contact : contacts) {
ContactsDialogHostJni.get().addContact(mNativeContactsProviderAndroid,
contact.names != null, contact.emails != null, contact.tel != null,
contact.names != null
? contact.names.toArray(new String[contact.names.size()])
: null,
......@@ -104,6 +105,10 @@ public class ContactsDialogHost implements ContactsPickerListener {
: null,
contact.tel != null
? contact.tel.toArray(new String[contact.tel.size()])
: null,
contact.serializedAddresses != null
? contact.serializedAddresses.toArray(
new ByteBuffer[contact.serializedAddresses.size()])
: null);
}
ContactsDialogHostJni.get().endContactsList(
......@@ -118,9 +123,8 @@ public class ContactsDialogHost implements ContactsPickerListener {
@NativeMethods
interface Natives {
void addContact(long nativeContactsProviderAndroid, boolean includeNames,
boolean includeEmails, boolean includeTel, String[] names, String[] emails,
String[] tel);
void addContact(long nativeContactsProviderAndroid, String[] names, String[] emails,
String[] tel, ByteBuffer[] addresses);
void endContactsList(
long nativeContactsProviderAndroid, int percentageShared, int propertiesRequested);
void endWithPermissionDenied(long nativeContactsProviderAndroid);
......
......@@ -17,7 +17,8 @@ enum ContactsPickerPropertiesRequested {
PROPERTIES_TELS = 1 << 0,
PROPERTIES_EMAILS = 1 << 1,
PROPERTIES_NAMES = 1 << 2,
PROPERTIES_BOUNDARY = 1 << 3,
PROPERTIES_ADDRESSES = 1 << 3,
PROPERTIES_BOUNDARY = 1 << 4,
};
} // namespace content
......
......@@ -218,6 +218,7 @@ android_library("ui_utils_java") {
]
deps = [
"//base:base_java",
"//components/payments/mojom:mojom_java",
"//third_party/android_deps:android_support_v7_appcompat_java",
]
}
......
......@@ -6,8 +6,12 @@ package org.chromium.ui;
import androidx.annotation.IntDef;
import org.chromium.payments.mojom.PaymentAddress;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
......@@ -15,18 +19,28 @@ import java.util.List;
*/
public interface ContactsPickerListener {
/**
* A container class for exhcanging contact details.
* A container class for exchanging contact details.
*/
public class Contact {
public static class Contact {
public final List<String> names;
public final List<String> emails;
public final List<String> tel;
public final List<ByteBuffer> serializedAddresses;
public Contact(
List<String> contactNames, List<String> contactEmails, List<String> contactTel) {
public Contact(List<String> contactNames, List<String> contactEmails,
List<String> contactTel, List<PaymentAddress> contactAddresses) {
names = contactNames;
emails = contactEmails;
tel = contactTel;
if (contactAddresses != null) {
serializedAddresses = new ArrayList<ByteBuffer>();
for (PaymentAddress address : contactAddresses) {
serializedAddresses.add(address.serialize());
}
} else {
serializedAddresses = null;
}
}
}
......
......@@ -98,12 +98,13 @@ public class UiUtils {
* @param includeNames Whether to include names of the contacts shared.
* @param includeEmails Whether to include emails of the contacts shared.
* @param includeTel Whether to include telephone numbers of the contacts shared.
* @param includeAddresses Whether to include addresses of the contacts shared.
* @param formattedOrigin The origin the data will be shared with, formatted for display
* with the scheme omitted.
*/
void showContactsPicker(Context context, ContactsPickerListener listener,
boolean allowMultiple, boolean includeNames, boolean includeEmails,
boolean includeTel, String formattedOrigin);
boolean includeTel, boolean includeAddresses, String formattedOrigin);
/**
* Called when the contacts picker dialog has been dismissed.
......@@ -156,14 +157,15 @@ public class UiUtils {
* @param includeNames Whether to include names in the contact data returned.
* @param includeEmails Whether to include emails in the contact data returned.
* @param includeTel Whether to include telephone numbers in the contact data returned.
* @param includeAddresses Whether to include addresses of the contacts shared.
* @param formattedOrigin The origin the data will be shared with.
*/
public static boolean showContactsPicker(Context context, ContactsPickerListener listener,
boolean allowMultiple, boolean includeNames, boolean includeEmails, boolean includeTel,
String formattedOrigin) {
boolean includeAddresses, String formattedOrigin) {
if (sContactsPickerDelegate == null) return false;
sContactsPickerDelegate.showContactsPicker(context, listener, allowMultiple, includeNames,
includeEmails, includeTel, formattedOrigin);
includeEmails, includeTel, includeAddresses, formattedOrigin);
return true;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment