blob: 374587b284a5c06a1b7450a88850bbc9adda226e [file] [log] [blame]
Dirk Dougherty5c2a5022009-12-17 16:59:46 -08001page.title=Using the Contacts API
Scott Main796ce772011-02-16 10:04:45 -08002parent.title=Articles
3parent.link=../browser.html?tag=article
Dirk Dougherty5c2a5022009-12-17 16:59:46 -08004@jd:body
5
6<p>Starting from Android 2.0 (API Level 5), the Android platform provides an
7improved Contacts API for managing and integrating contacts from multiple
8accounts and from other data sources. To handle overlapping data from multiple
9sources, the contacts content provider aggregates similar contacts and presents
10them to users as a single entity. This article describes how to use the new API
11to manage contacts.</p>
12
13<p>The new Contacts API is defined in the
14{@link android.provider.ContactsContract android.provider.ContactsContract}
15and related classes. The older API is still supported, although deprecated.
16If you have an existing application that uses the older API,
17see <a href="#legacy">Considerations for legacy apps</a>, below, for ideas
18on how to support the Contacts API in your app.</p>
19
20<p>If you'd like to look at an applied example of how to use the new Contacts
21API, including how to support both the new and older API in a single app,
22please see the <a
23href="{@docRoot}resources/samples/BusinessCard/index.html">Business Card
24sample application</a>.</p>
25
26<h3>Data structure of Contacts</h3>
27
28<p>In the new Contacts API, data is laid out in three primary tables:
29<em>contacts</em>, <em>raw contacts</em>, and <em>data</em>, a structure that
30is slightly different from that used in the older API. The new structure
31allows the system to more easily store and manage information for a
32specific contact from multiple contacts sources. </p>
33
34<img style="margin: 0px auto 10px; display: block; text-align: center; width: 471px; height: 255px;" src="images/contacts-2.png" alt="" border="0">
35
36<ul>
37<li><code>Data</code> is a generic table that stores all of the data points
38associated with a raw contact. Each row stores data of a specific kind &mdash;
39for example name, photo, email addresses, phone numbers, and group memberships.
40Each row is tagged with a MIME type to identify what type of data it can
41contain, across the entire column. Columns are generic and the type of data they
42contain is determined by the kind of data stored in each row. For example, if a
43row's data kind is <code>Phone.CONTENT_ITEM_TYPE</code>, then the first column
44stores the phone number, but if the data kind is
45<code>Email.CONTENT_ITEM_TYPE</code>, then the column stores the email address.
46
47<p>The {@link android.provider.ContactsContract.CommonDataKinds ContactsContract.CommonDataKinds}
48class provides subclasses corresponding to common MIME types for contacts data.
49If needed, your application or other contacts sources can define additional MIME
50types for data rows. For more information about the Data table and examples of
51how to use it, see {@link android.provider.ContactsContract.Data android.provider.ContactsContract.Data}.</p></li>
52
53<li>A row in the <code>RawContacts</code> table represents the set of
54<code>Data</code> and other information describing a person and associated with
55a single contacts source. For example, a row might define the data associated
56with a person's Google or Exchange account or Facebook friend. For more
57information, see
58{@link android.provider.ContactsContract.RawContacts ContactsContract.RawContacts}.</p>
59
60<li>A row in the <code>Contacts</code> table represents an aggregate of one or
61more <code>RawContacts</code> describing the same person (or entity).
62
63<p>As mentioned above, the Contacts content provider automatically aggregates
64Raw Contacts into a single Contact entry, where possible, since common data
65fields (such as name or email address) are likely to be stored in each raw
66contact. Since the aggregation logic maintains the entries in the Contact rows,
67the entries can be read but should not be modified. See the section <a
68href="#aggregation">Aggregation of contacts</a>, below, for more details,
69including and information on how to
70control aggregation.</li>
71
72</ul>
73
74<p>When displaying contacts to users, applications should typically operate on
75the Contacts level, since it provides a unified, aggregated view of contacts
76from various underlying sources. </p>
77
78<h4>Example: Inserting a Phone Number</h4>
79
80<p>To insert a phone number using the new APIs you'll need the ID of the Raw
81Contact to attach the phone number to, then you'll need to create a Data
82row:</p>
83
84<pre>import android.provider.ContactsContract.CommonDataKinds.Phone;
85...
86ContentValues values = new ContentValues();
87values.put(Phone.RAW_CONTACT_ID, rawContactId);
88values.put(Phone.NUMBER, phoneNumber);
89values.put(Phone.TYPE, Phone.TYPE_MOBILE);
90Uri uri = getContentResolver().insert(Phone.CONTENT_URI, values);</pre>
91
92
93<h3 id="aggregation">Aggregation of contacts</h3>
94
95<p>When users sync contacts from multiple sources, several contacts might refer
96to the same person or entity, but with slightly different (or overlapping) data.
97 For example, "Bob Parr" might be a user's co-worker and also his personal
98friend, so the user might have his contact information stored in both a
99corporate email account and a personal account. To provide a simplified view for
100the user, the system locates such overlapping contacts and combines them into a
101single, aggregate contact. </p>
102
103<p>The system automatically aggregates contacts by default. However, if needed,
104your application can control how the system handles aggregation or it can
105disable aggregation altogether, as described in the sections below.</p>
106
107<h4>Automatic aggregation</h4>
108
109<p>When a raw contact is added or modified, the system looks for matching
110(overlapping) raw contacts with which to aggregate it. It may not find any
111matching raw contacts, in which case it will create an aggregate contact that
Brad Fitzpatrick69ea4e12011-01-05 11:13:40 -0800112contains just the original raw contact. If it finds a single match, it creates a
Dirk Dougherty5c2a5022009-12-17 16:59:46 -0800113new contact that contains the two raw contacts. And it may even find multiple
114similar raw contacts, in which case it chooses the closest match. </p>
115
116<p>Two raw contacts are considered to be a match if at least one of these
117conditions is met:</p>
118
119<ul>
120<li>They have matching names.</li>
121<li>Their names consist of the same words but in different order
122(for example, "Bob Parr" and "Parr, Bob")</li>
123<li>One of them has a common short name for the other (for example,
124"Bob Parr" and "Robert Parr")</li>
125<li>One of them has just a first or last name and it matches the other
126raw contact. This rule is less reliable, so it only applies if the two
127raw contacts are also sharing some other data like a phone number, an
128email address or a nickname (for example, Helen ["elastigirl"] = Helen
129Parr ["elastigirl"])</li>
130<li>At least one of the two raw contacts is missing the name altogether
131and they are sharing a phone number, an email address or a nickname (for
132example, Bob Parr [incredible@android.com] = incredible@android.com).</li>
133</ul>
134
135<p>When comparing names, the system ignores upper/lower case differences
136(Bob=BOB=bob) and diacritical marks (Hélène=Helene). When comparing two
137phone numbers the system ignores special characters such as "*", "#",
138"(", ")", and whitespace. Also if the only difference between two numbers
139is that one has a country code and the other does not, then the system
140considers those to be a match (except for numbers in the Japan country code).</p>
141
142<p>Automatic aggregation is not permanent; any change of a constituent raw
143contact may create a new aggregate or break up an existing one.</p>
144
145<h4>Explicit aggregation</h4>
146
147<p>In some cases, the system's automatic aggregation may not meet the
148requirements of your application or sync adapter. There are two sets of APIs you
149can use to control aggregation explicitly: <em>aggregation modes</em> allow you
150to control automatic aggregation behaviors and <em>aggregation exceptions</em>
151allow you to override automated aggregation entirely.
152
153<p><strong>Aggregation modes</strong></p>
154
155<p>You can set an aggregation mode for each raw contact individually. To do so,
156add a mode constant as the value of the <code>AGGREGATION_MODE column</code> in
157the <code>RawContact</code> row. The mode constants available include: </p>
158
159<ul>
160<li><code>AGGREGATION_MODE_DEFAULT</code> &mdash; normal mode, automatic
161aggregation is allowed.</li>
162<li><code>AGGREGATION_MODE_DISABLED</code> &mdash; automatic aggregation is not
163allowed. The raw contact will not be aggregated.</li>
164<li><code>AGGREGATION_MODE_SUSPENDED</code> &mdash; automatic aggregation is
165deactivated. If the raw contact is already a part of an aggregated contact when
166aggregation mode changes to suspended, it will remain in the aggregate, even if
167it changes in such a way that it no longer matches the other raw contacts in the
168aggregate.</li>
169</ul>
170
171<p><strong>Aggregation exceptions</strong></p>
172
173<p>To keep two raw contacts unconditionally together or unconditionally apart,
174you can add a row to the
175{@link android.provider.ContactsContract.AggregationExceptions} table. Exceptions
176defined in the table override all automatic aggregation rules. </p>
177
178
179<h3>Loookup URI</h3>
180
181<p>The new Contacts API introduces the notion of a lookup key for a contact. If
182your application needs to maintain references to contacts, you should use lookup
183keys instead of the traditional row ids. You can acquire a lookup key from the
184contact itself, it is a column on the
185{@link android.provider.ContactsContract.Contacts} table. Once you have a lookup key,
186you can construct a URI in this way:</p>
187
188<pre>Uri lookupUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey)</pre>
189
190<p>and use it like you would use a traditional content URI, for example: </p>
191
192<pre>Cursor c = getContentResolver().query(lookupUri, new String[]{Contacts.DISPLAY_NAME}, ...);
193try {
194 c.moveToFirst();
195 String displayName = c.getString(0);
196} finally {
197 c.close();
198}</pre>
199
200<p>The reason for this complication is that regular contact row IDs are
201inherently volatile. Let's say your app stored a long ID of a contact. Then the
202user goes and manually joins the contact with some other contact. Now there is a
203single contact where there used to be two, and the stored long contact ID points
204nowhere.
205
206<p>The lookup key helps resolve the contact in this case. The key is a string
207that concatenates the server-side identities of the raw contacts. Your
208application can use that string to find a contact, regardless whether the raw
209contact is aggregated with others or not. </p>
210
211<p>If performance is a concern for your application, you might want to store
212both the lookup and the long ID of a contact and construct a lookup URI out of
213both IDs, as shown here:</p>
214
215<pre>Uri lookupUri = getLookupUri(contactId, lookupKey)</pre>
216
217<p>When both IDs are present in the URI, the system will try to use the long ID
218first. That is a very quick query. If the contact is not found, or if the one
219that is found has the wrong lookup key, the content provider will parse the
220lookup key and track down the constituent raw contacts. If your app
221bulk-processes contacts, you should maintain both IDs. If your app works with a
222single contact per user action, you probably don't need to bother with storing
223the long ID.</p>
224
225Android itself uses lookup URIs whenever there is a need to reference a contact,
226such as with shortcuts or Quick Contact, and also during editing or even viewing
227a contact. The latter case is less obvious &mdash; why would a contact ID change
228while we are simply viewing the contact? It could change because there might be
229a sync going in the background, and the contact might get automatically
230aggregated with another while being viewed.</p>
231
232<p>In summary: whenever you need to reference a contact, we recommend that you
233do so by its lookup URI.</p>
234
235
236<h3 id="legacy">Considerations for legacy applications</h3>
237
238<p>If you have an existing application that uses the older Contacts API,
239you should consider upgrading it to use the new API. You have four options: </p>
240
241<ul>
242<li>Leave it as-is and rely on the Contacts compatibility mode.</li>
243<li>Upgrade the app and discontinue support of pre-Android 2.0 platforms.</li>
244<li>Build a new version of the app for the new API, while keeping the old version available.</li>
245<li>Make the app use the right set of APIs depending on the platform where it is deployed. </li>
246</ul>
247
248<p>Let's consider these options one by one.</p>
249
250<h4>Using compatibility mode</h4>
251
252<p>Compatibility mode is the easiest option because you just leave the
253application as is, and it should run on Android 2.0 as long as it only uses
254public APIs. A couple examples of the use of non-public API include the use of
255explicit table names in nested queries and the use of columns that were not
256declared as public constants in the {@link android.provider.Contacts} class.
257</p>
258
259<p>Even if the application currently runs, you don't want to leave it like this
260for long. The main reason is that it will only have access to contacts from one
261account, namely the first Google account on the device. If the user opens other
262accounts in addition to or instead of a Google account, your application will
263not be able to access those contacts.</p>
264
265
266<h4>Upgrading to the new API and dropping support for older platforms</h4>
267
268<p>If your application will no longer target platforms older than
269Android 2.0, you can upgrade to the new API in this way:</p>
270
271<ul>
272<li>Replace all usages of {@link android.provider.Contacts} with calls to new
273API. After you are done, you should not see any deprecation warnings during
274application build. The new application will be able to take full advantage of
275multiple accounts and other new features of Android 2.0. </p>
276
277<li>In the application's manifest, update (or add) the
278<code>android:minSdkVersion</code> attribute to the
279<code>&lt;uses-sdk&gt;</code> element. To use the new Contacts API, you should
280set the value of the attribute to "5" (or higher, as appropriate). For more
281information about <code>android:minSdkVersion</code>, see the documentation for
282the <a
283href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><code>&lt;uses-sdk&gt;</code></a>
284element. For more information about the value of the
285<code>minSdkVersion</code>, see <a
286href="{@docRoot}guide/appendix/api-levels.html">API Levels</a>.</li>
287</ul>
288
289<h4>Maintaining two applications</h4>
290
291<p>You may decide to have two different applications: one for pre-Android 2.0
292platforms and one for Android 2.0 and beyond. If so, here's what you'll need to do:</p>
293
294<ul>
295 <li>Clone your existing app. </li>
296 <li>Change the old application: </li>
297 <ul>
298 <li>At launch time, check the version of the SDK. The version of the SDK
299is available as {@link android.os.Build.VERSION#SDK android.os.Build.VERSION.SDK}.</li>
300 <li>If the SDK version is greater or equal to 5 (Android 2.0), show a dialog
Dirk Dougherty4d7bc6552012-01-27 17:56:49 -0800301suggesting to the user that it's time to go to Google Play and find a new version of
302the app. You can even provide a link to the new app on Google Play (see <a
Dirk Dougherty5c2a5022009-12-17 16:59:46 -0800303href="{@docRoot}guide/publishing/publishing.html#marketintent">Using Intents
Dirk Dougherty4d7bc6552012-01-27 17:56:49 -0800304to Launch Google Play</a>). </li>
Dirk Dougherty5c2a5022009-12-17 16:59:46 -0800305 </ul>
306 <li>Change the new application:</li>
307 <ul>
308 <li>Replace all usages of the older Contacts API with calls to new API.
309The new application will be able to take full advantage of multiple accounts
310and other new features of Android 2.0. </li>
311 <li>Modify that application's AndroidManifest.xml file: </li>
312 <ul>
313 <li>Give the application a new name and a new package name. Currently
Dirk Dougherty4d7bc6552012-01-27 17:56:49 -0800314Google Play does not allow you to have two applications with the same
Dirk Dougherty5c2a5022009-12-17 16:59:46 -0800315name/package.</li>
316 <li>Update (or add) the <code>android:minSdkVersion</code> attribute
317to the <code>&lt;uses-sdk&gt;</code> element. To use the new Contacts API,
318you should set the value of the attribute to "5" (or higher, as appropriate).</li>
319 </ul>
320 </ul>
Dirk Dougherty4d7bc6552012-01-27 17:56:49 -0800321 <li>Publish both apps on Google Play, the old app one as an upgrade and the
Dirk Dougherty5c2a5022009-12-17 16:59:46 -0800322other as new. Make sure to explain the difference between the apps in their
323descriptions.</li>
324</ul>
325
326<p>This plan has its disadvantages: </p>
327
328<ul>
329<li>The new application will not be able to read the old application's data.
330Application data can only be accessed by code living in the same package. So
331databases, shared preferences, and so on, will need to be populated from
332scratch.</li>
333<li>The upgrade process is too clunky for the user. Some users may choose
334to either stay with the crippled old version or uninstall altogether.</li>
335</ul>
336
337<h4>Supporting the old and new APIs in the same application</h4>
338
339<p>This is a bit tricky, but the result is worth the effort. You can
340build a single package that will work on any platform:</p>
341
342<p>Go through the existing application and factor out all access to
343{@link android.provider.Contacts} into one class, such as ContactAccessorOldApi.
344For example, if you have code like this:
345
346<pre> protected void pickContact() {
347 startActivityForResult(new Intent(Intent.ACTION_PICK, People.CONTENT_URI), 0);
348 }</pre>
349
350<p>it will change to:</p>
351
352
353<pre> private final ContactAccessorOldApi mContactAccessor = new ContactAccessorOldApi();
354
355 void pickContact() {
356 startActivityForResult(mContactAccessor.getContactPickerIntent(), 0);
357 }</pre>
358
359<p>The corresponding method on ContactAccessorOldApi will look like this:</p>
360
361<pre> public Intent getContactPickerIntent() {
362 return new Intent(Intent.ACTION_PICK, People.CONTENT_URI);
363 }</pre>
364
365<p>Once you are done, you should see deprecation warnings coming only
366from ContactAccessorOldApi. </p>
367
368<p>Create a new abstract class ContactAccessor, make sure the abstract
369class has all method signatures from ContactAccessorOldApi. Make
370ContactAccessorOldApi extend ContactAccessor:</p>
371
372<pre> public abstract class ContactAccessor {
373 public abstract Intent getContactPickerIntent();
374 ...
375 }</pre>
376
377<p>Create a new subclass of ContactAccessor, ContactAccessorNewApi and
378implement all methods using the new API:</p>
379
380<pre> public class ContactAccessorNewApi extends ContactAccessor {
381 &#64;Override
382 public Intent getContactPickerIntent() {
383 return new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
384 }
385 ...
386 }</pre>
387
388<p>At this point, you have two implementations of the same API, one using the
389old API and another using the new API. Let's plug them in. Add this code to
390the ContactAccessor class:</p>
391
392<pre> private static ContactAccessor sInstance;
393
394 public static ContactAccessor getInstance() {
395 if (sInstance == null) {
396 String className;
397 int sdkVersion = Integer.parseInt(Build.VERSION.SDK);
398 if (sdkVersion &lt; Build.VERSION_CODES.ECLAIR) {
399 className = "ContactAccessorOldApi";
400 } else {
401 className = "ContactAccessorNewApi";
402 }
403 try {
404 Class&lt;? extends ContactAccessor&gt; clazz =
405 Class.forName(ContactAccessor.class.getPackage() + "." + className)
406 .asSubclass(ContactAccessor.class);
407 sInstance = clazz.newInstance();
408 } catch (Exception e) {
409 throw new IllegalStateException(e);
410 }
411 }
412 return sInstance;
413 }</pre>
414
415<p>Now replace references to ContactsAccessorOldApi with references to
416ContactsAccessor:</p>
417
418<pre> private final ContactAccessor mContactAccessor = ContactAccessor.getInstance();</pre>
419
420<p>You are done! Now you will want to test on Android 2.0, 1.6 and 1.5.</p>
421
422<p>We hope you like the new features and APIs we've added to Contacts in
423Android 2.0, and we can't wait to see what cool things developers do with
424the new APIs.</p>