I am writing an Android app that has a data type that represents a person (specifically, the parent or guardian of a child). I'd like to be able to "import" the relevant data fields from the Contacts database in the Android device. (This should be optional; that is, it will not be a requirement that the parent/guardian is already in the Contacts database, nor will the Contacts database be updated if they add new parents/guardians.)
So far, I have written code to start a new Intent to choose the specific Contact (using Intent.ACTION_PICK). I then get a URI that represents a specific Contact in the database.
Unfortunately, I don't know what the next step is. It seems like this should be the simplest thing in the world to do, but apparently not. I've read through the documentation on the Android developer website, and I've looked through more than one Android book. No joy.
The specific information I'd like to get, is:
The contact's name (first and last separately if possible)
The contact's (primary) email address
The contact's cell phone number
I imagine that this should be possible by querying using the ContentResolver, but I have no idea how to do this with the URI returned from the Intent. Most of the documentation assumes you have the Contact ID, not the Contact's URI. Also, I have no idea what kind of fields I can put into the projection for the query, assuming that this is even the right way to do what I want.
Here is my starting code:
// In a button's onClick event handler:
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(intent, PICK_CONTACT);
// In onActivityResult:
if (resultCode == RESULT_OK) {
if (requestCode == PICK_CONTACT) {
contactURI = data.getData();
// NOW WHAT?
}
}
Okay, after lots of digging, I found what I believe to be the answers. The solutions I found differ according to which Android API level you're using. However, they're not pretty at all, so if there are better solutions, I'd love to know.
In any case, the first step is to get the ID of the Contact, by doing a query on the URI returned from Intent.ACTION_PICK. While we're here, we should also get the display name, and the string representing whether the contact has a phone number or not. (We won't need them for the modern solution, but we will need them for the legacy solution.)
String id, name, phone, hasPhone;
int idx;
Cursor cursor = getContentResolver().query(contactUri, null, null, null, null);
if (cursor.moveToFirst()) {
idx = cursor.getColumnIndex(ContactsContract.Contacts._ID);
id = cursor.getString(idx);
idx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
name = cursor.getString(idx);
idx = cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER);
hasPhone = cursor.getString(idx);
}
For the record, the columns returned from this URI are most of the fields represented by constants in the ContactsContract.Profile class (including constants inherited from other interfaces). Not included are PHOTO_FILE_ID, PHOTO_THUMBNAIL_URI, or PHOTO_URI (but PHOTO_ID is included).
Now that we have the ID, we need to get the relevant data. The first (and simplest) solution is to query an Entity. Entity queries retrieve all of the contacts data for a contact or raw contact at once. Each row represents a single Raw Contact, accessed using the constants in ContactsContract.Contacts.Entity. Usually you'll only be concerned with RAW_CONTACT_ID, DATA1, and MIMETYPE. However, if you want the first and last names separately, the Name MIME type holds the first name in DATA2 and the last name in DATA3.
You load up the variables by matching the MIMETYPE column with ContactsContract.CommonDataKinds constants; for example, the email MIME type is in ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE.
// Build the Entity URI.
Uri.Builder b = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, id).buildUpon();
b.appendPath(ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
URI contactUri = b.build();
// Create the projection (SQL fields) and sort order.
String[] projection = {
ContactsContract.Contacts.Entity.RAW_CONTACT_ID,
ContactsContract.Contacts.Entity.DATA1,
ContactsContract.Contacts.Entity.MIMETYPE };
String sortOrder = ContactsContract.Contacts.Entity.RAW_CONTACT_ID + " ASC";
cursor = getContentResolver().query(contactUri, projection, null, null, sortOrder);
String mime;
int mimeIdx = cursor.getColumnIndex(ContactsContract.Contacts.Entity.MIMETYPE);
int dataIdx = cursor.getColumnIndex(ContactsContract.Contacts.Entity.DATA1);
if (cursor.moveToFirst()) {
do {
mime = cursor.getString(mimeIdx);
if (mime.equalsIgnoreCase(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
email = cursor.getString(dataIdx);
}
if (mime.equalsIgnoreCase(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
phone = cursor.getString(dataIdx);
}
// ...etc.
} while (cursor.moveToNext());
}
Unfortunately, Entities were not introduced unti API 11 (Android 3.0, Honeycomb), which means this code is incompatible with roughly 65% of the Android devices in the marketplace (as of this writing). If you try it, you will get an IllegalArgumentException from the URI.
The second solution is to build a query string, and make one query for each data type you want to use:
// Get phone number - if they have one
if ("1".equalsIgnoreCase(hasPhone)) {
cursor = getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = "+ id,
null, null);
if (cursor.moveToFirst()) {
colIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
phone = cursor.getString(colIdx);
}
cursor.close();
}
// Get email address
cursor = getContentResolver().query(
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = " + id,
null, null);
if (cursor.moveToFirst()) {
colIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS);
email = cursor.getString(colIdx);
}
cursor.close();
// ...etc.
Obviously, this way will result in a lot of separate database queries, so it's not recommended for efficiency reasons.
The solution I've come up with is to try the version that uses Entity queries, catch the IllegalArgumentException, and put the legacy code inside the catch block:
try {
// Build the Entity URI.
Uri.Builder b = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, id).buildUpon();
b.appendPath(ContactsContract.Contacts.Entity.CONTENT_DIRECTORY);
// ...etc...
} catch (IllegalArgumentException e) {
// Get phone number - if they have one
if ("1".equalsIgnoreCase(hasPhone)) {
// ...etc...
} finally {
// If you want to display the info in GUI objects, put the code here
}
I hope this helps someone. And, again, if there are better ways to do this, I'm all ears.
As it turns out, there is a better way to do this.
As I mentioned, the ContactsContract.Contacts.Entity class was not available until API 11. However, the ContactsContract.Data class was available way back in API 5, and you can use that class in largely the same way that you use the Entity class.
I've updated my code. It is very similar to the code for the Entity class, and works largely the same way. However, I've tested it with my phone running Gingerbread, and it works just fine.
One change I had to make is this: There doesn't seem to be a way to get the ContactsContract.Data.RAW_CONTACT_ID
from the initial query, and that ID is not the same as the ID you get from e.g. ContactsContract.Contacts._ID
. Instead, I queried on the ContactsContract.Contacts.DISPLAY_NAME
constant, which is consistent across pretty much every ContactsContract class.
Here's the working code:
Cursor cursor; // Cursor object
String mime; // MIME type
int dataIdx; // Index of DATA1 column
int mimeIdx; // Index of MIMETYPE column
int nameIdx; // Index of DISPLAY_NAME column
// Get the name
cursor = getContentResolver().query(params[0],
new String[] { ContactsContract.Contacts.DISPLAY_NAME },
null, null, null);
if (cursor.moveToFirst()) {
nameIdx = cursor.getColumnIndex(
ContactsContract.Contacts.DISPLAY_NAME);
name = cursor.getString(nameIdx);
// Set up the projection
String[] projection = {
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Contacts.Data.DATA1,
ContactsContract.Contacts.Data.MIMETYPE };
// Query ContactsContract.Data
cursor = getContentResolver().query(
ContactsContract.Data.CONTENT_URI, projection,
ContactsContract.Data.DISPLAY_NAME + " = ?",
new String[] { name },
null);
if (cursor.moveToFirst()) {
// Get the indexes of the MIME type and data
mimeIdx = cursor.getColumnIndex(
ContactsContract.Contacts.Data.MIMETYPE);
dataIdx = cursor.getColumnIndex(
ContactsContract.Contacts.Data.DATA1);
// Match the data to the MIME type, store in variables
do {
mime = cursor.getString(mimeIdx);
if (ContactsContract.CommonDataKinds.Email
.CONTENT_ITEM_TYPE.equalsIgnoreCase(mime)) {
email = cursor.getString(dataIdx);
}
if (ContactsContract.CommonDataKinds.Phone
.CONTENT_ITEM_TYPE.equalsIgnoreCase(mime)) {
phone = cursor.getString(dataIdx);
phone = PhoneNumberUtils.formatNumber(phone);
}
} while (cursor.moveToNext());
}
}
//Add a permission to read contacts data to your application manifest.
<uses-permission android:name="android.permission.READ_CONTACTS"/>
//Use Intent.ACTION_PICK in your Activity
Intent contactPickerIntent = new Intent(Intent.ACTION_PICK,
ContactsContract.CommonDataKinds.Phone.CONTENT_URI);
startActivityForResult(contactPickerIntent, RESULT_PICK_CONTACT);
//Then Override the onActivityResult() and retrieve the ID,Phone number and Name in the data.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// check whether the result is ok
if (resultCode == RESULT_OK) {
// Check for the request code, we might be usign multiple startActivityForReslut
switch (requestCode) {
case RESULT_PICK_CONTACT:
Cursor cursor = null;
try {
String phoneNo = null ;
String name = null;
Uri uri = data.getData();
cursor = getContentResolver().query(uri, null, null, null, null);
cursor.moveToFirst();
int phoneIndex =cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
phoneNo = cursor.getString(phoneIndex);
textView2.setText(phoneNo);
} catch (Exception e) {
e.printStackTrace();
}
break;
}
} else {
Log.e("MainActivity", "Failed to pick contact");
}
}
Note: Before Android 2.3 (API level 9), performing a query on the Contacts Provider (like the one shown above) requires that your app declare the READ_CONTACTS permission (see Security and Permissions). However, beginning with Android 2.3, the Contacts/People app grants your app a temporary permission to read from the Contacts Provider when it returns you a result. The temporary permission applies only to the specific contact requested, so you cannot query a contact other than the one specified by the intent's Uri, unless you do declare the READ_CONTACTS permission.