From 5c2a502bffc05d54c8de3456fee5aec5b99ff3e9 Mon Sep 17 00:00:00 2001
From: Dirk Dougherty 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
+{@link android.provider.ContactsContract 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. 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. The {@link android.provider.ContactsContract.CommonDataKinds 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 {@link android.provider.ContactsContract.Data android.provider.ContactsContract.Data}.Data structure of Contacts
+
+
+
+
+
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 is
+Email.CONTENT_ITEM_TYPE, then the column stores the email address.
+
+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
+{@link android.provider.ContactsContract.RawContacts ContactsContract.RawContacts}.
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.
+ +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);+ + +
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.
+ +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:
+ +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.
+ +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 +{@link android.provider.ContactsContract.AggregationExceptions} table. Exceptions +defined in the table override all automatic aggregation rules.
+ + +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 +{@link android.provider.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.
+ + +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:
+ +Let's consider these options one by one.
+ +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 {@link android.provider.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.
+ + +If your application will no longer target platforms older than +Android 2.0, you can upgrade to the new API in this way:
+ +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.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:
+ +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).This plan has its disadvantages:
+ +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 +{@link android.provider.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.
diff --git a/docs/html/resources/articles/index.jd b/docs/html/resources/articles/index.jd index d2f0996..d2b7645 100644 --- a/docs/html/resources/articles/index.jd +++ b/docs/html/resources/articles/index.jd @@ -127,6 +127,11 @@ page.title=Technical Articles