Starting from Android 2.0 (API Level 5),
the Android platform provides an improved Contacts API for managing and
integrating contacts from multiple accounts and from other data sources. To
handle overlapping data from multiple sources, the contacts content provider
aggregates similar contacts and presents them to users as a single entity. This
article describes how to use the new API to manage (insert, update, delete,
view) contacts.
The new Contacts API is defined in the
android.provider.ContactsContract
and related classes. The older API is still supported, although
deprecated.
We need to understand the underlying
structure of storage to better manipulate the contacts. We have three distinct
types of tables – Contacts, Raw Contacts and Data.
All data related to a contact is stored in
this generic data table with each row telling what is the data it stores
through its MIME type. So we could have a Phone.CONTENT_ITEM_TYPE as the MIME
type of the data row, it contains Phone data. Similarly, if we have
Email.CONTENT_ITEM_TYPE as the row’s MIME type, then it stores email data. Like
this lot of data rows are associated with a single Raw Contact.
Each Raw Contact refers to a specific
contact’s data coming from one single source – say, you gmail account or your
office Microsoft account.
The Contact is the topmost in the hierarchy
which aggregates similar looking data from various sources into one single
contact – a very handy feature when you have redundant data coming about the
same contact from you various accounts – like a facebook account, orkut
account, yahoo account and goggle account.
So the hierarchy looks like this:
So, when we want to insert a new contact,
we always insert a Raw Contact. When we want to update an existing contact, we
most often deal with the data tables which are accessible through a series of
CommonDataKind classes. Because this would be to update particular types of
data like phone or email.
Coming to the example:
I create an activity with four buttons to
View, Add, Modify and Delete Contacts.
Button view =
(Button)findViewById(R.id.viewButton);
Button add =
(Button)findViewById(R.id.createButton);
Button modify =
(Button)findViewById(R.id.updateButton);
Button delete =
(Button)findViewById(R.id.deleteButton);
view.setOnClickListener(new OnClickListener() {
public void onClick(View v){
displayContacts();
Log.i("NativeContentProvider", "Completed Displaying Contact list");
}
});
On the click of each of the buttons I
invoke their respective methods: like displayContacts(), createContact(), updatecContact() and deleteContact(). We will now see each of these methods.
displayContacts() is pretty straightforward. Access to each of the tables mentioned
above is through a content URI. I use the topmost level ‘Contacts’ URI to
iterate through all the existing contacts and display their names and phone
numbers (Toast them).
We know Contacts is a ContentProvider and hence we need to query through a ContentResolver which returns all the data of the contacts.
private void
displayContacts() {
ContentResolver
cr = getContentResolver();
Cursor cur =
cr.query(ContactsContract.Contacts.CONTENT_URI,
null, null, null, null);
if (cur.getCount() > 0) {
while (cur.moveToNext()) {
String
id = cur.getString(cur.getColumnIndex(ContactsContract.Contacts._ID));
String
name = cur.getString(cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
if (Integer.parseInt(cur.getString(
cur.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
Cursor pCur = cr.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" =
?",
new String[]{id}, null);
while (pCur.moveToNext()) {
String phoneNo =
pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Toast.makeText(NativeContentProvider.this, "Name:
" + name + ",
Phone No: " + phoneNo, Toast.LENGTH_SHORT).show();
}
pCur.close();
}
}
}
}
I will try to briefly explain
the above method. Line 1 gets a handle
to the ContentResolver. Line 2 queries the Contacts URI (ContactsContract.Contacts.CONTENT_URI ) without any mention of the specific columns or “where” clause of an
SQL query. Notice all the 4 parameters are null. This means that we are not
making any conditional query into the contacts table and hence all data is returned
into the cursor.
Next, I check that the
cursor is not empty and iterate through the cursor data. I retrieve the _ID and DISPLAY_NAME from the Contacts and then, I check for the flag if the contact has a
phone number. This information is available in contacts table itself. But the Phone
number details are in the data tables. Hence after checking for the flag, I
query the CommonDataKings.Phone.CONTENT_URI for the phone data of that specific ID. From
this new cursor named pCur, I retrieve the Phone
Number. If there are multiple phone number for one contact, all of them will be
retrieved and toasted one after another.
Now, let us see how to
create or insert a new contact.
In the createContact() method which is called when you click on the “Add
Contact” button, I first query to see if the hardcoded name “Sample Name”
already exists. If so, I toast a message stating the same. If not, then I get
into actually inserting the name along with a phone number. The first part of
the check you can view in the complete source code available for download. Only
the second part of inserting a contact is explained here. For this, we need to
use a ContentResolver
always.
A ContentResolverprovides an applyBatch(…) method which takes an array of ContentProviderOperation classes as a parameter. All the data built
into the ContentProviderOperations are committed or inserted into the ContentProvider that we are working on. IN this case, the ContentProvider we are working on are Contacts and the
authority associated with the same is ContactsContract.AUTHORITY
Here is the code for the same:
ArrayList<ContentProviderOperation> ops = new
ArrayList<ContentProviderOperation>();
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, "accountname@gmail.com")
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, "com.google")
.build());
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)
.build());
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_HOME)
.build());
try {
cr.applyBatch(ContactsContract.AUTHORITY, ops);
….
In the first element in the ops array,
I am setting the details of the account to which I want to add the contact. In
this case the gmail account type is accontname@gmail.com and the account name is “com.google”. The latter has to be unique
and hence it is recommended to use the internet URLs of the source of data.
In the second element, I am adding the name
of the contact and in the third the phone data. You notice that I use .withValueBackReference(..) as we still have not created the Raw contact and hence we do not
have the Id. The first row creates the id and hands over the id to the next
rows of data.
For updating the phone number of an existing
contact, I again use the ContentResolver
with the ContentProviderOperation array. However, now, I
pass the where clause and the parameters of the where clause – specifically indicating
that only the phone number of the “Sample Name” contact has to be updated.
ops.add(ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI)
.withSelection(where, params)
.withValue(ContactsContract.CommonDataKinds.Phone.DATA, phone)
.build());
Notice the .withSelection(where, params). The
where and params look like this:
String where = ContactsContract.Data.DISPLAY_NAME + " = ? AND
" +
ContactsContract.Data.MIMETYPE + " = ? AND
" +
String.valueOf(ContactsContract.CommonDataKinds.Phone.TYPE) + " = ?
";
String[] params = new String[] {name,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
String.valueOf(ContactsContract.CommonDataKinds.Phone.TYPE_HOME)};
Delete too is done in a very similar manner
with no values but only the selection criteria is provided for which contact to
be deleted.
ContentResolver
cr = getContentResolver();
String
where = ContactsContract.Data.DISPLAY_NAME + " = ? ";
String[]
params = new String[]
{name};
ArrayList<ContentProviderOperation>
ops = new
ArrayList<ContentProviderOperation>();
ops.add(ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI)
.withSelection(where, params)
.build());
try {
cr.applyBatch(ContactsContract.AUTHORITY, ops);
} catch (RemoteException e) {
….
I really scouted the internet a lot to find
a comprehensive tutorial on the new Contacts Content Provider API but could not
find anything that easily. Hope this helps all who of you who are looking for
the same.
NOTE: Many have been asking me questions on why the contacts that have just been added through this app are not visible in the phone contacts. All your contacts on phone are linked to your google account on the phone. However, the contacts from the above code have got added to psuedo google account and hence will not be visible directly. You search for the just added contact and you will be able to find it.
Hope this helps.