0% found this document useful (0 votes)
217 views8 pages

Using The Contacts API

- The new Contacts API in Android 2.0 provides an improved way to manage contacts from multiple accounts and sources by aggregating overlapping contact data into single contacts. - Data is stored in three primary tables: contacts, raw contacts, and data. Raw contacts represent contacts from a single source and can be automatically aggregated into single contact entries. - The ContactsContract.Contacts.CONTENT_LOOKUP_URI can be used with a contact's unique lookup key to reference contacts regardless of aggregation.

Uploaded by

Doan Tat Hien
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
217 views8 pages

Using The Contacts API

- The new Contacts API in Android 2.0 provides an improved way to manage contacts from multiple accounts and sources by aggregating overlapping contact data into single contacts. - Data is stored in three primary tables: contacts, raw contacts, and data. Raw contacts represent contacts from a single source and can be automatically aggregated into single contact entries. - The ContactsContract.Contacts.CONTENT_LOOKUP_URI can be used with a contact's unique lookup key to reference contacts regardless of aggregation.

Uploaded by

Doan Tat Hien
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 8

Using the Contacts API

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
contacts.

The new Contacts API is defined in the android.provider.ContactsContract and related classes. The


older API is still supported, although deprecated. If you have an existing application that uses the older
API, see Considerations for legacy apps, below, for ideas on how to support the Contacts API in
your app.

If you'd like to look at an applied example of how to use the new Contacts API, including how to support
both the new and older API in a single app, please see the Business Card sample application.

Data structure of Contacts


In the new Contacts API, data is laid out in three primary tables: contacts, raw contacts, and data, a
structure that is slightly different from that used in the older API. The new structure allows the system to
more easily store and manage information for a specific contact from multiple contacts sources.

 Data is a generic table that stores all of the data points associated with a raw contact. Each row
stores data of a specific kind — for example name, photo, email addresses, phone numbers, and group
memberships. Each row is tagged with a MIME type to identify what type of data it can contain, across the
entire column. Columns are generic and the type of data they contain is determined by the kind of data
stored in each row. For example, if a row's data kind is Phone.CONTENT_ITEM_TYPE, then the first
column stores the phone number, but if the data kind isEmail.CONTENT_ITEM_TYPE, then the column
stores the email address.

The ContactsContract.CommonDataKinds class provides subclasses corresponding to common MIME


types for contacts data. If needed, your application or other contacts sources can define additional MIME
types for data rows. For more information about the Data table and examples of how to use it,
see android.provider.ContactsContract.Data.
 A row in the RawContacts table represents the set of Data and other information describing a
person and associated with a single contacts source. For example, a row might define the data
associated with a person's Google or Exchange account or Facebook friend. For more information,
see ContactsContract.RawContacts.
 A row in the Contacts table represents an aggregate of one or more RawContacts describing
the same person (or entity).

As mentioned above, the Contacts content provider automatically aggregates Raw Contacts into a single
Contact entry, where possible, since common data fields (such as name or email address) are likely to be
stored in each raw contact. Since the aggregation logic maintains the entries in the Contact rows, the
entries can be read but should not be modified. See the section Aggregation of contacts, below, for
more details, including and information on how to control aggregation.
When displaying contacts to users, applications should typically operate on the Contacts level, since it
provides a unified, aggregated view of contacts from various underlying sources.

Example: Inserting a Phone Number


To insert a phone number using the new APIs you'll need the ID of the Raw Contact to attach the phone
number to, then you'll need to create a Data row:

import android.provider.ContactsContract.CommonDataKinds.Phone;
...
ContentValues values = new ContentValues();
values.put(Phone.RAW_CONTACT_ID, rawContactId);
values.put(Phone.NUMBER, phoneNumber);
values.put(Phone.TYPE, Phone.TYPE_MOBILE);
Uri uri = getContentResolver().insert(Phone.CONTENT_URI, values);

Aggregation of contacts
When users sync contacts from multiple sources, several contacts might refer to the same person or
entity, but with slightly different (or overlapping) data. For example, "Bob Parr" might be a user's co-
worker and also his personal friend, so the user might have his contact information stored in both a
corporate email account and a personal account. To provide a simplified view for the user, the system
locates such overlapping contacts and combines them into a single, aggregate contact.

The system automatically aggregates contacts by default. However, if needed, your application can
control how the system handles aggregation or it can disable aggregation altogether, as described in the
sections below.

Automatic aggregation
When a raw contact is added or modified, the system looks for matching (overlapping) raw contacts with
which to aggregate it. It may not find any matching raw contacts, in which case it will create an aggregate
contact that contains just the original raw contact. If it finds a single match,it creates a new contact that
contains the two raw contacts. And it may even find multiple similar raw contacts, in which case it
chooses the closest match.

Two raw contacts are considered to be a match if at least one of these conditions is met:

 They have matching names.


 Their names consist of the same words but in different order (for example, "Bob Parr" and "Parr,
Bob")
 One of them has a common short name for the other (for example, "Bob Parr" and "Robert Parr")
 One of them has just a first or last name and it matches the other raw contact. This rule is less
reliable, so it only applies if the two raw contacts are also sharing some other data like a phone number,
an email address or a nickname (for example, Helen ["elastigirl"] = Helen Parr ["elastigirl"])
 At least one of the two raw contacts is missing the name altogether and they are sharing a phone
number, an email address or a nickname (for example, Bob Parr [incredible@android.com] =
incredible@android.com).
When comparing names, the system ignores upper/lower case differences (Bob=BOB=bob) and
diacritical marks (Hélène=Helene). When comparing two phone numbers the system ignores special
characters such as "*", "#", "(", ")", and whitespace. Also if the only difference between two numbers is
that one has a country code and the other does not, then the system considers those to be a match
(except for numbers in the Japan country code).

Automatic aggregation is not permanent; any change of a constituent raw contact may create a new
aggregate or break up an existing one.

Explicit aggregation
In some cases, the system's automatic aggregation may not meet the requirements of your application or
sync adapter. There are two sets of APIs you can use to control aggregation explicitly: aggregation
modes allow you to control automatic aggregation behaviors and aggregation exceptions allow you to
override automated aggregation entirely.

Aggregation modes

You can set an aggregation mode for each raw contact individually. To do so, add a mode constant as the
value of the AGGREGATION_MODE column in the RawContact row. The mode constants available
include:

 AGGREGATION_MODE_DEFAULT — normal mode, automatic aggregation is allowed.


 AGGREGATION_MODE_DISABLED — automatic aggregation is not allowed. The raw contact will
not be aggregated.
 AGGREGATION_MODE_SUSPENDED — automatic aggregation is deactivated. If the raw contact is
already a part of an aggregated contact when aggregation mode changes to suspended, it will remain in
the aggregate, even if it changes in such a way that it no longer matches the other raw contacts in the
aggregate.
Aggregation exceptions

To keep two raw contacts unconditionally together or unconditionally apart, you can add a row to
the ContactsContract.AggregationExceptions table. Exceptions defined in the table override all
automatic aggregation rules.

Loookup URI
The new Contacts API introduces the notion of a lookup key for a contact. If your application needs to
maintain references to contacts, you should use lookup keys instead of the traditional row ids. You can
acquire a lookup key from the contact itself, it is a column on the ContactsContract.Contacts table.
Once you have a lookup key, you can construct a URI in this way:

Uri lookupUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey)

and use it like you would use a traditional content URI, for example:

Cursor c = getContentResolver().query(lookupUri, new String[]


{Contacts.DISPLAY_NAME}, ...);
try {
    c.moveToFirst();
    String displayName = c.getString(0);
} finally {
    c.close();
}

The reason for this complication is that regular contact row IDs are inherently volatile. Let's say your app
stored a long ID of a contact. Then the user goes and manually joins the contact with some other contact.
Now there is a single contact where there used to be two, and the stored long contact ID points nowhere.

The lookup key helps resolve the contact in this case. The key is a string that concatenates the server-
side identities of the raw contacts. Your application can use that string to find a contact, regardless
whether the raw contact is aggregated with others or not.

If performance is a concern for your application, you might want to store both the lookup and the long ID
of a contact and construct a lookup URI out of both IDs, as shown here:

Uri lookupUri = getLookupUri(contactId, lookupKey)

When both IDs are present in the URI, the system will try to use the long ID first. That is a very quick
query. If the contact is not found, or if the one that is found has the wrong lookup key, the content provider
will parse the lookup key and track down the constituent raw contacts. If your app bulk-processes
contacts, you should maintain both IDs. If your app works with a single contact per user action, you
probably don't need to bother with storing the long ID.

Android itself uses lookup URIs whenever there is a need to reference a contact, such as with shortcuts
or Quick Contact, and also during editing or even viewing a contact. The latter case is less obvious —
why would a contact ID change while we are simply viewing the contact? It could change because there
might be a sync going in the background, and the contact might get automatically aggregated with
another while being viewed.
In summary: whenever you need to reference a contact, we recommend that you do so by its lookup URI.

Considerations for legacy applications


If you have an existing application that uses the older Contacts API, you should consider upgrading it to
use the new API. You have four options:

 Leave it as-is and rely on the Contacts compatibility mode.


 Upgrade the app and discontinue support of pre-Android 2.0 platforms.
 Build a new version of the app for the new API, while keeping the old version available.
 Make the app use the right set of APIs depending on the platform where it is deployed.
Let's consider these options one by one.

Using compatibility mode


Compatibility mode is the easiest option because you just leave the application as is, and it should run on
Android 2.0 as long as it only uses public APIs. A couple examples of the use of non-public API include
the use of explicit table names in nested queries and the use of columns that were not declared as public
constants in the Contacts class.

Even if the application currently runs, you don't want to leave it like this for long. The main reason is that it
will only have access to contacts from one account, namely the first Google account on the device. If the
user opens other accounts in addition to or instead of a Google account, your application will not be able
to access those contacts.

Upgrading to the new API and dropping support for older platforms
If your application will no longer target platforms older than Android 2.0, you can upgrade to the new API
in this way:

 Replace all usages of Contacts with calls to new API. After you are done, you should not see any
deprecation warnings during application build. The new application will be able to take full advantage of
multiple accounts and other new features of Android 2.0.
 In the application's manifest, update (or add) the android:minSdkVersion attribute to
the <uses-sdk> element. To use the new Contacts API, you should set the value of the attribute to "5"
(or higher, as appropriate). For more information about android:minSdkVersion, see the
documentation for the <uses-sdk> element. For more information about the value of
the minSdkVersion, see API Levels.
Maintaining two applications
You may decide to have two different applications: one for pre-Android 2.0 platforms and one for Android
2.0 and beyond. If so, here's what you'll need to do:

 Clone your existing app.


 Change the old application:
o At launch time, check the version of the SDK. The version of the SDK is available
as android.os.Build.VERSION.SDK.
o If the SDK version is greater or equal to 5 (Android 2.0), show a dialog suggesting to the
user that it's time to go to Market and find a new version of the app. You can even provide a link to the
new app on Market (see Using Intents to Launch Market).
 Change the new application:
o Replace all usages of the older Contacts API with calls to new API. The new application
will be able to take full advantage of multiple accounts and other new features of Android 2.0.
o Modify that application's AndroidManifest.xml file:
 Give the application a new name and a new package name. Currently Android
Market does not allow you to have two applications with the same name/package.
 Update (or add) the android:minSdkVersion attribute to the <uses-
sdk> element. To use the new Contacts API, you should set the value of the attribute to "5" (or higher, as
appropriate).
 Publish both apps on Market, the old app one as an upgrade and the other as new. Make sure to
explain the difference between the apps in their descriptions.
This plan has its disadvantages:

 The new application will not be able to read the old application's data. Application data can only
be accessed by code living in the same package. So databases, shared preferences, and so on, will need
to be populated from scratch.
 The upgrade process is too clunky for the user. Some users may choose to either stay with the
crippled old version or uninstall altogether.

Supporting the old and new APIs in the same application


This is a bit tricky, but the result is worth the effort. You can build a single package that will work on any
platform:

Go through the existing application and factor out all access to Contacts into one class, such as
ContactAccessorOldApi. For example, if you have code like this:

    protected void pickContact() {


        startActivityForResult(new Intent(Intent.ACTION_PICK,
People.CONTENT_URI), 0);
    }
it will change to:

    private final ContactAccessorOldApi mContactAccessor = new


ContactAccessorOldApi();

    void pickContact() {
        startActivityForResult(mContactAccessor.getContactPickerIntent(),
0);
    }

The corresponding method on ContactAccessorOldApi will look like this:

    public Intent getContactPickerIntent() {


        return new Intent(Intent.ACTION_PICK, People.CONTENT_URI);
    }

Once you are done, you should see deprecation warnings coming only from ContactAccessorOldApi.

Create a new abstract class ContactAccessor, make sure the abstract class has all method signatures
from ContactAccessorOldApi. Make ContactAccessorOldApi extend ContactAccessor:

    public abstract class ContactAccessor {


        public abstract Intent getContactPickerIntent();
        ...
    }

Create a new subclass of ContactAccessor, ContactAccessorNewApi and implement all methods using
the new API:

    public class ContactAccessorNewApi extends ContactAccessor {    


        @Override
        public Intent getContactPickerIntent() {
            return new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
        }
        ...
    }

At this point, you have two implementations of the same API, one using the old API and another using the
new API. Let's plug them in. Add this code to the ContactAccessor class:

    private static ContactAccessor sInstance;

    public static ContactAccessor getInstance() {


        if (sInstance == null) {
            String className;
            int sdkVersion = Integer.parseInt(Build.VERSION.SDK);
            if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
                className = "ContactAccessorOldApi";
            } else {
                className = "ContactAccessorNewApi";
            }
            try {
                Class<? extends ContactAccessor> clazz =
                        Class.forName(ContactAccessor.class.getPackage() +
"." + className)
                                .asSubclass(ContactAccessor.class);
                sInstance = clazz.newInstance();
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }
        return sInstance;
    }

Now replace references to ContactsAccessorOldApi with references to ContactsAccessor:

    private final ContactAccessor mContactAccessor =


ContactAccessor.getInstance();

You are done! Now you will want to test on Android 2.0, 1.6 and 1.5.

We hope you like the new features and APIs we've added to Contacts in Android 2.0, and we can't wait to
see what cool things developers do with the new APIs.

You might also like