From 5c2a502bffc05d54c8de3456fee5aec5b99ff3e9 Mon Sep 17 00:00:00 2001 From: Dirk Dougherty Date: Thu, 17 Dec 2009 16:59:46 -0800 Subject: cherry-pick 72aadeb013548cfdc317b5f6fec9dab9551911b8 to froyo. Change-Id: Ic205588bb0ecd5660b8c8949da88fbae6981f416 --- docs/html/resources/articles/contacts.jd | 422 +++++++++++++++++++++++++++++++ docs/html/resources/articles/index.jd | 5 + docs/html/resources/resources_toc.cs | 3 + 3 files changed, 430 insertions(+) create mode 100644 docs/html/resources/articles/contacts.jd (limited to 'docs/html/resources') diff --git a/docs/html/resources/articles/contacts.jd b/docs/html/resources/articles/contacts.jd new file mode 100644 index 0000000..bdf84e6 --- /dev/null +++ b/docs/html/resources/articles/contacts.jd @@ -0,0 +1,422 @@ +page.title=Using the Contacts API +@jd:body + +

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.

+ +

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.

+ + + + + +

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:

+ + + +

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

+ + +

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 +{@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.

+ + +

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:

+ + + +

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 {@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.

+ + +

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:

+ + + +

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:

+ + + +

This plan has its disadvantages:

+ + + +

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 +{@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
+
Using the Contacts API
+
This article discusses the improved Contacts API introduced in Android 2.0 and how to use it to manage and integrate contacts from multiple accounts and data sources. The article also discusses techniques for using the new API on devices that support it, while maintaining backward compatibility with the old API on other devices.
+
+ +
Using WebViews
WebViews allow an application to dynamically display HTML and execute JavaScript, without relinquishing control to a separate browser application. This article introduces the WebView classes and provides a sample application that demonstrates its use.
diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs index 659e08e..178b41a 100644 --- a/docs/html/resources/resources_toc.cs +++ b/docs/html/resources/resources_toc.cs @@ -113,6 +113,9 @@
  • Using Text-to-Speech
  • +
  • + Using the Contacts API + new!
  • Using WebViews
  • -- cgit v1.1