Retrieval of contacts with “Contact Contract”

Query Contacts AppThis article demonstrates the use of the Contact Contracts to retrieve the contact information on an Android smartphone. It is a revision of the article “Working with the Contact Contract to query contacts in Android”, which was published by Kevin Kratzer at 03/14/2011 here in this blog.
 
 
 

Retrieving Contacts

Usually the Android Developer site is a good starting point for a quick introduction to various topics in the field of Android development. However, if you read the article about retrieval of contacts, you find an outdated example: it uses Android API classes and fields that are marked as deprecated since a long time. As there are developers who rely on this example and therefore work with the old functionality, we’ve looked at the newer way which is now recommended in the Android API. This way is based on the functionalities of the ContactsContract class and subclasses.

To demonstrate the features of the new API, the example App is querying all available contacts on the phone and additionally commonly used information from the contacts content provider such as the name, phone numbers, email addresses and of course the photo.

You can browse or download the source code of the example App at our SVN repository at Google Code (http://code.google.com/p/android-contacts-contract-example/) or use svn to check the project out.

ContactsContract Functionality

The new approach using the ContactsContract was introduced with API level 5 (Android 2.0) and was further improved with API level 11 (Android 3.0).

According to the survey of 1 December 2011 on the distribution of devices that have used the Android Market, still 95.5% are between API level 5 and 11. Therefore, we did not use any of the improved features to create the example app. This allows the code to run directly on the majority of the existing Android devices. Furthermore, the code can also run in an emulator and on the Galaxy Nexus with Android 4.0. However, we will point out the changes that were introduced in API level 11.

Content Provider

Before we start, let’s take a short look at the basic concept how to access data with a content provider.

Content providers are providing data using a database like approach. The database of the content provider is always addressed by an unique URI e.g. “content://com.appsolut.example/exampleData”. To access a specific content provider, the first step is to create a query resulting in a Cursor which represents the returned data as an object with random access.

The configuration of queries is straightforward and can be described in five steps:

  1. Identify the unique resource identifier (URI) of the desired content provider
  2. Generate a String array which is holding the names of the columns which you require from the database (e.g. RawContacts.CONTACT_ID). This is called projection.
  3. If you want to filter the results using the query define a selection clause (e.g. to filter by contact ids: RawContacts.CONTACT_ID + “=?”). The ? operator acts as a parameter which is defined in the next step. This is called selection.
  4. Create another String array for all parameters which you’ve used in your selection clause. For the above example it could be something like new String[]{contactId}. If no parameters where used just ignore this step. This array is called selectionArgs.
  5. If you want to sort your results by table columns, define a sort order like RawContacts.CONTACT_ID + ” ASC”, which will sort the results in ascending order using their contact ids. This string is called sortOrder.

Using the parameters from these five steps, the query method can be called within an activity, a service or an application (see listing 1).

Listing 1: Query Method

public final Cursor managedQuery (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

Using the cursor, rows can be selected and specific values of columns in this row can be returned (with gettern like getString or getInt).

Content Provider Description

For a more detailed description about content providers visit the documentation at http://developer.android.com/guide/topics/providers/content-providers.html – but don’t use the proposed way to access the contacts content provider as it is outdated and deprecated.

Query Method for API Level-11+

Note: starting with API level 11 the managedQuery method will also be marked as deprecated.

The new concept is quite similar. It does not function anymore with the managedQuery method, but uses the LoaderManager.LoaderCallbacks<Cursor> interface and the CursorLoader class. The callback methode defined by the LoaderCallbacks interface

public Loader<Cursor> onCreateLoader(int id, Bundle args)

can then be used to instantiate CursorLoader with the help of the constructor

public CursorLoader (Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

analogously to the managedQuery Methode.

The callback methode

public void onLoadFinished(Loader<Cursor> loader, Cursor data)

provides subsequently the data asynchronously as a cursor.

The tutorial on the Android Developers http://developer.android.com/guide/topics/fundamentals/loaders.html provides further information on this new concept.

Querying Contacts

After the fundamental aspects we are dealing now with the queries of the contacts and with information about the contacts. For that purpose, the “android.provider.ContactsContract“ class and subpackages were introduced at API level 5.

Before you can use the Contact Contract, you must assign the required permissions to the app in the project manifest file. This can be done either directly in the XML file (see listing 2) or with the Eclipse manifest editor.

Listing 2: Permissions in the XML-manifest File

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.appsolut.example.queryContacts" android:versionCode="1" android:versionName="1.0">

<uses-permission android:name="android.permission.READ_CONTACTS" />

</manifest>

Without these permissions, the app would crash with the exception „java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.ContactsProvider2“, as soon as you try to access the content provider.

Querying a List of all Available Contacts

Now we can start accessing the “ContactsContract.RawContacts” (http://developer.android.com/reference/android/provider/ContactsContract.RawContacts.html) content provider to query all available contacts stored in the smartphone.

This table represents every person as a single entry (one row). In this table, a unique id is assigned to each person which is stored in the RawContacts.CONTACT_ID field (column).

Unlike the default _ID column, this id is used in the other tables as well, so we can later use it to query additional information about this person.

To get access to the RawContacts content provider we first define a projection (see listing 3).

Listing 3: Projection CONTACT_ID and DELETED

final String[] projection = new String[] {

RawContacts.CONTACT_ID, // the contact id column

RawContacts.DELETED // column if this contact is deleted

};

We are interested especially in the CONTACT_ID column and the DELETED column. The DELETED column is important because there can be entries in the RawContacts table which have been deleted and therefore should not be displayed anymore.

With the help of the projection we can now create the Cursor using a query (see listing 4).

Listing 4: Query for all Available Contacts

final Cursor rawContacts = managedQuery(

RawContacts.CONTENT_URI, //the URI for raw contact provider

projection

null, //selection = null, retrieve all entries

null, //selection is without parameters

null); //do not order

Using this cursor we can iterate through all available contact ids. To do so, we need to identify the index of the CONTACT_ID and the DELETED column. With the getColumnIndex method of the cursor object we can get this index (see listing 5).

Listing 5: Identifying the CONTACT_ID and DELETED Columns

final int contactIdColumnIndex =

rawContacts.getColumnIndex(RawContacts.CONTACT_ID);

final int deletedColumnIndex =

rawContacts.getColumnIndex(RawContacts.DELETED);

To ensure that the cursor is pointing to the beginning and that there are valid entries, we use the moveToFirst method, which will move the cursor to the first entry and return true if there are entries available. Now we can iterate over all entries using a while loop which checks the isAfterLast method of the cursor. This method returns true if the cursor is pointing to an non-existing entry (see listing 6).

Listing 6: Iteration over all Available Contacts

if(rawContacts.moveToFirst()) {

while(!rawContacts.isAfterLast()) {

final int contactId = rawContacts.getInt(contactIdColumnIndex);

final boolean deleted = (rawContacts.getInt(deletedColumnIndex) == 1);

if(!deleted) {

doSomethingWithAContactId(contactId));

}

rawContacts.moveToNext();// move to the next entry

}

}

To release the resources, it is important to close the cursor after the data is no longer needed. This is done with the

rawContacts.close();

method.

Querying Information for a Contact

Using a given contact id (for example from the previous part) we can access basic information about this person in the “ContactsContract.Contacts” table.

In our example we will query the name of the contact as well as the photo id which is a reference to the photo entry in the data table and can later be used to query the photo as a bitmap.

To realize this in your app, you must again create a query. So first we define our projection for the DISPLAY_NAME and PHOTO_ID columns (see listing 7).

Listing 7: Projection DISPLAY_NAME and PHOTO_ID

final String[] projection = new String[] {

Contacts.DISPLAY_NAME,// the name of the contact

Contacts.PHOTO_ID // the id of the column in the data table for the image

};

By using the selection and selectionArgs parameters of the query method, we can additionally filter the results of the query according to the given contact id (see listing 8).

Listing 8: Query for Name and Photo ID

final Cursor contact = managedQuery(Contacts.CONTENT_URI,projection,Contacts._ID + "=?", // filter entries on the basis of the contact id

new String[]{String.valueOf(contactId)},

// the parameter to which the contact id column is compared to

null);

Now we can retrieve the required information (see listing 9).

Listing 9: Evaluation of the Contact Name and its Photo ID

if(contact.moveToFirst()) {

final String name = contact.getString(

contact.getColumnIndex(Contacts.DISPLAY_NAME));

final String photoId = contact.getString(

contact.getColumnIndex(Contacts.PHOTO_ID));

doSomethingWithAContactName(name);

doSomethingWithAContactPhotoId(photoId);

}

contact.close();

Querying the Photo of a Contact

The contact photos are stored as binary large objects (blob) in the “ContactsContract.Data” table. In this table, all kinds of data about a contact are stored, so we need a given photo id to retrieve the correct entry (see Querying information for a contact). In our example, we use the field photoId to represent this id. The column in which the blob is stored is defined in the “CommonDataKinds.Photo” class. Using this Photo.PHOTO column we can define our query (see listing 10).

Listing 10: Query for the Contact Photos

final Cursor photo = managedQuery(

Data.CONTENT_URI,new String[] {Photo.PHOTO},// column for the blob data.

_ID + "=?",// select row by id

new String[]{photoId},// filter by a given photoId (as retrieved in listing 9)

null);

If the contact has a photo linked to its entry, the cursor will return the photo blob. Using the “BitmapFactory” class we can create a bitmap using this blob (see listing 11).

Listing 11: Create Bitmap from BLOB

if(photo.moveToFirst()) {

byte[] photoBlob = photo.getBlob(

photo.getColumnIndex(Photo.PHOTO));

final Bitmap photoBitmap = BitmapFactory.decodeByteArray(

photoBlob, 0, photoBlob.length);

doSomethingWithAContactPhoto(photoBitmap);

}

photo.close();

Querying all Phone Numbers of a Contact

The phone numbers are stored in the “ContactsContract.Data” table. Every number is represented by one entry in this table. To access the columns used for phone numbers we can use the definitions in the “ContactsContract.CommonDataKinds.Phone” class.

There are three different columns available: number, type and label. The type column is used to define the type of the number, e.g. work, home or other. If the type other is defined, the label column can be used to get the defined name of this type. In our example, we will just look at the types which are defined, so our projection does not contain the label column (see listing 12).

Listing 12: Projection Number and Type

final String[] projection = new String[] {

Phone.NUMBER,

Phone.TYPE,

};

As an URI for the phone number entries we can use the “Phone.CONTENT_URI” URI which filters the data table according to the media type of phone numbers. Because we only want phone numbers of a specific contact we filter the results on the basis of the given contactId field (see listing 13).

Listing 13: Cursor to Query the Phone Numbers

final Cursor phone = managedQuery(

Phone.CONTENT_URI,

projection,

Data.CONTACT_ID + "=?",

new String[]{String.valueOf(contactId)},

null);

Because there can be multiple entries we use a while loop to iterate over this cursor. To get a human readable version of the phone number type we use the getTypeLabelResource method to get the resource id of the label for a specific type (see listing 14).

Listing 14: Iteration over all Phone Numbers of a Contact

if(phone.moveToFirst()) {

final int contactNumberColumnIndex = phone.getColumnIndex(Phone.NUMBER);

final int contactTypeColumnIndex = phone.getColumnIndex(Phone.TYPE);

while(!phone.isAfterLast()) {

final String number = phone.getString(contactNumberColumnIndex);

final int type = phone.getInt(contactTypeColumnIndex);

final int typeLabelResource = Phone.getTypeLabelResource(type);

doSomethingWithAContactPhoneNumber(number, typeLabelResource);

phone.moveToNext();

}

}

phone.close();

Querying Email Addresses of a Contact

The email addresses of a contact are also stored in the “ContactsContract.Data” table. To retrieve the desired information we use the defined column names and URI in the “CommonDataKinds.Email” class (see listing 15).

Listing 15: Projection for Email Addresses

final String[] projection = new String[] {

Email.DATA,// use Email.ADDRESS for API-Level 11+

Email.TYPE

};

The type of the email address is implemented like the type of phone numbers and using the getTypeLabelResource method of the Email class we can retrieve a human readable label resource id for the description of the type.  So now we can create our cursor to retrieve all available email addresses. (siehe Listing 16).

Listing 16: Query for E-Mail Addresses

final Cursor email =

managedQuery(Email.CONTENT_URI,

projection, Data.CONTACT_ID + "=?",

new String[]{String.valueOf(contactId)},

null);

Just like we have processed the results of the phone query we can iterate over the email cursor to extract the addresses (see listing 17).

Listing 17: Extraction of the Email Addresses

if(email.moveToFirst()) {

final int contactEmailColumnIndex =

email.getColumnIndex(Email.DATA);

final int contactTypeColumnIndex =

email.getColumnIndex(Email.TYPE); 

while(!email.isAfterLast()) {

final String address =

email.getString(contactEmailColumnIndex);

final int type = email.getInt(contactTypeColumnIndex);

final int typeLabelResource =

Email.getTypeLabelResource(type);

doSomethingWithAContactEmailAddress(address,typeLabelResource);

email.moveToNext();

}

}

email.close();

Advanced Queries

It is also possible to use SQLLite operators in the selection clause. Under sqlite.org you can find a list of possible operators.

An example of the use of an operator is the like-operator, which supports the wildcard “_” for one character and “%” for multiple characters. With this operator for example, a search for a contact can be implemented (see listing 18).

Listing 18: Example for an Advanced Query - Search for Contacts

searchContacts(String searchName) {

final String[] projection = new String[] {

Contacts.DISPLAY_NAME,

};

final String selection = Contacts.DISPLAY_NAME + " like ?";

final String[] selectionArgs = new String[] {

"%" + searchName + "%"

};

final Cursor contact = managedQuery(Contacts.CONTENT_URI, projection, selection, selectionArgs, null); 

if(contact.moveToFirst()) {

while(!contact.isAfterLast()) {

final String name = contact.getString(contact.getColumnIndex

Contacts.DISPLAY_NAME));

// do something here

contact.moveToNext();

}

}

contact.close();

}

Summary

The presented methods are implemented in the example App which will query the details and displays the information in a GUI. Using this App you can easily test the functions. Other information such as the address, instant messenger, notes, etc. can easily be queried analogously to the way shown above. For further data fields just look at the classes in the “android.provider.ContactsContract.CommonDataKinds” package.

Please note that the example App is not written with the best performance in mind, but to show a clear and easy understandable way to access information about contacts.

Tags: , ,

Comments are closed.