// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.browser_ui.contacts_picker;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
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;
import java.util.List;
import java.util.Map;
/** A worker task to retrieve images for contacts. */
class ContactsFetcherWorkerTask extends AsyncTask<ArrayList<ContactDetails>> {
private static final String[] PROJECTION = {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.LOOKUP_KEY,
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
};
/** An interface to use to communicate back the results to the client. */
public interface ContactsRetrievedCallback {
/**
* A callback to define to receive the contact details.
*
* @param contacts The contacts retrieved.
*/
void contactsRetrieved(ArrayList<ContactDetails> contacts);
}
// The current context to use.
private Context mContext;
// The content resolver to use for looking up contacts.
private ContentResolver mContentResolver;
// The callback to use to communicate the results.
private ContactsRetrievedCallback mCallback;
// Whether names were requested by the website.
private final boolean mIncludeNames;
// Whether to include emails in the data fetched.
private final boolean mIncludeEmails;
// 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 context The Context to use.
* @param callback The callback to use to communicate back the results.
* @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(
Context context,
ContactsRetrievedCallback callback,
boolean includeNames,
boolean includeEmails,
boolean includeTel,
boolean includeAddresses) {
mContext = context;
mContentResolver = context.getContentResolver();
mCallback = callback;
mIncludeNames = includeNames;
mIncludeEmails = includeEmails;
mIncludeTel = includeTel;
mIncludeAddresses = includeAddresses;
}
/**
* Fetches the details for all contacts (in a background thread).
*
* @return The icon representing a contact.
*/
@Override
protected ArrayList<ContactDetails> doInBackground() {
assert !ThreadUtils.runningOnUiThread();
if (isCancelled()) return null;
return getAllContacts();
}
/**
* Fetches specific details for contacts.
*
* @param source The source URI to use for the lookup.
* @param idColumn The name of the id column.
* @param idColumn The name of the data column.
* @param sortOrder The sort order. Data must be sorted by CONTACT_ID but can be additionally
* sorted also.
* @return A map of ids to contact details (as ArrayList).
*/
private Map<String, ArrayList<String>> getDetails(
Uri source, String idColumn, String dataColumn, String sortOrder) {
Map<String, ArrayList<String>> map = new HashMap<String, ArrayList<String>>();
Cursor cursor = mContentResolver.query(source, null, null, null, sortOrder);
ArrayList<String> list = new ArrayList<String>();
String key = "";
String value;
while (cursor.moveToNext()) {
String id = cursor.getString(cursor.getColumnIndexOrThrow(idColumn));
value = cursor.getString(cursor.getColumnIndexOrThrow(dataColumn));
if (value == null) value = "";
if (key.isEmpty()) {
key = id;
list.add(value);
} else {
if (key.equals(id)) {
list.add(value);
} else {
map.put(key, list);
list = new ArrayList<String>();
list.add(value);
key = id;
}
}
}
map.put(key, list);
cursor.close();
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.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.StructuredPostal.CONTACT_ID));
String city =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.StructuredPostal.CITY));
String country =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY));
String formattedAddress =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.StructuredPostal
.FORMATTED_ADDRESS));
String postcode =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE));
String region =
cursor.getString(
cursor.getColumnIndexOrThrow(
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.
*/
public ArrayList<ContactDetails> getAllContacts() {
Map<String, ArrayList<String>> emailMap =
mIncludeEmails
? getDetails(
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
ContactsContract.CommonDataKinds.Email.CONTACT_ID,
ContactsContract.CommonDataKinds.Email.DATA,
ContactsContract.CommonDataKinds.Email.CONTACT_ID
+ " ASC, "
+ ContactsContract.CommonDataKinds.Email.DATA
+ " ASC")
: null;
Map<String, ArrayList<String>> phoneMap =
mIncludeTel
? getDetails(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
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");
if (!cursor.moveToFirst()) {
cursor.close();
return new ArrayList<ContactDetails>();
}
ArrayList<ContactDetails> contacts = new ArrayList<ContactDetails>(cursor.getCount());
do {
String id =
cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID));
String name =
cursor.getString(
cursor.getColumnIndexOrThrow(
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY));
List<String> email = mIncludeEmails ? emailMap.get(id) : null;
List<String> tel = mIncludeTel ? phoneMap.get(id) : null;
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();
return contacts;
}
/**
* Communicates the results back to the client. Called on the UI thread.
*
* @param contacts The contacts retrieved.
*/
@Override
protected void onPostExecute(ArrayList<ContactDetails> contacts) {
assert ThreadUtils.runningOnUiThread();
if (isCancelled()) return;
mCallback.contactsRetrieved(contacts);
}
}