Dirk Dougherty | 5c2a502 | 2009-12-17 16:59:46 -0800 | [diff] [blame] | 1 | page.title=Using the Contacts API |
Scott Main | 796ce77 | 2011-02-16 10:04:45 -0800 | [diff] [blame] | 2 | parent.title=Articles |
| 3 | parent.link=../browser.html?tag=article |
Dirk Dougherty | 5c2a502 | 2009-12-17 16:59:46 -0800 | [diff] [blame] | 4 | @jd:body |
| 5 | |
| 6 | <p>Starting from Android 2.0 (API Level 5), the Android platform provides an |
| 7 | improved Contacts API for managing and integrating contacts from multiple |
| 8 | accounts and from other data sources. To handle overlapping data from multiple |
| 9 | sources, the contacts content provider aggregates similar contacts and presents |
| 10 | them to users as a single entity. This article describes how to use the new API |
| 11 | to manage contacts.</p> |
| 12 | |
| 13 | <p>The new Contacts API is defined in the |
| 14 | {@link android.provider.ContactsContract android.provider.ContactsContract} |
| 15 | and related classes. The older API is still supported, although deprecated. |
| 16 | If you have an existing application that uses the older API, |
| 17 | see <a href="#legacy">Considerations for legacy apps</a>, below, for ideas |
| 18 | on 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 |
| 21 | API, including how to support both the new and older API in a single app, |
| 22 | please see the <a |
| 23 | href="{@docRoot}resources/samples/BusinessCard/index.html">Business Card |
| 24 | sample 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 |
| 30 | is slightly different from that used in the older API. The new structure |
| 31 | allows the system to more easily store and manage information for a |
| 32 | specific 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 |
| 38 | associated with a raw contact. Each row stores data of a specific kind — |
| 39 | for example name, photo, email addresses, phone numbers, and group memberships. |
| 40 | Each row is tagged with a MIME type to identify what type of data it can |
| 41 | contain, across the entire column. Columns are generic and the type of data they |
| 42 | contain is determined by the kind of data stored in each row. For example, if a |
| 43 | row's data kind is <code>Phone.CONTENT_ITEM_TYPE</code>, then the first column |
| 44 | stores 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} |
| 48 | class provides subclasses corresponding to common MIME types for contacts data. |
| 49 | If needed, your application or other contacts sources can define additional MIME |
| 50 | types for data rows. For more information about the Data table and examples of |
| 51 | how 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 |
| 55 | a single contacts source. For example, a row might define the data associated |
| 56 | with a person's Google or Exchange account or Facebook friend. For more |
| 57 | information, 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 |
| 61 | more <code>RawContacts</code> describing the same person (or entity). |
| 62 | |
| 63 | <p>As mentioned above, the Contacts content provider automatically aggregates |
| 64 | Raw Contacts into a single Contact entry, where possible, since common data |
| 65 | fields (such as name or email address) are likely to be stored in each raw |
| 66 | contact. Since the aggregation logic maintains the entries in the Contact rows, |
| 67 | the entries can be read but should not be modified. See the section <a |
| 68 | href="#aggregation">Aggregation of contacts</a>, below, for more details, |
| 69 | including and information on how to |
| 70 | control aggregation.</li> |
| 71 | |
| 72 | </ul> |
| 73 | |
| 74 | <p>When displaying contacts to users, applications should typically operate on |
| 75 | the Contacts level, since it provides a unified, aggregated view of contacts |
| 76 | from 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 |
| 81 | Contact to attach the phone number to, then you'll need to create a Data |
| 82 | row:</p> |
| 83 | |
| 84 | <pre>import android.provider.ContactsContract.CommonDataKinds.Phone; |
| 85 | ... |
| 86 | ContentValues values = new ContentValues(); |
| 87 | values.put(Phone.RAW_CONTACT_ID, rawContactId); |
| 88 | values.put(Phone.NUMBER, phoneNumber); |
| 89 | values.put(Phone.TYPE, Phone.TYPE_MOBILE); |
| 90 | Uri 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 |
| 96 | to 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 |
| 98 | friend, so the user might have his contact information stored in both a |
| 99 | corporate email account and a personal account. To provide a simplified view for |
| 100 | the user, the system locates such overlapping contacts and combines them into a |
| 101 | single, aggregate contact. </p> |
| 102 | |
| 103 | <p>The system automatically aggregates contacts by default. However, if needed, |
| 104 | your application can control how the system handles aggregation or it can |
| 105 | disable 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 |
| 111 | matching raw contacts, in which case it will create an aggregate contact that |
Brad Fitzpatrick | 69ea4e1 | 2011-01-05 11:13:40 -0800 | [diff] [blame] | 112 | contains just the original raw contact. If it finds a single match, it creates a |
Dirk Dougherty | 5c2a502 | 2009-12-17 16:59:46 -0800 | [diff] [blame] | 113 | new contact that contains the two raw contacts. And it may even find multiple |
| 114 | similar 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 |
| 117 | conditions 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 |
| 126 | raw contact. This rule is less reliable, so it only applies if the two |
| 127 | raw contacts are also sharing some other data like a phone number, an |
| 128 | email address or a nickname (for example, Helen ["elastigirl"] = Helen |
| 129 | Parr ["elastigirl"])</li> |
| 130 | <li>At least one of the two raw contacts is missing the name altogether |
| 131 | and they are sharing a phone number, an email address or a nickname (for |
| 132 | example, 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 |
| 137 | phone numbers the system ignores special characters such as "*", "#", |
| 138 | "(", ")", and whitespace. Also if the only difference between two numbers |
| 139 | is that one has a country code and the other does not, then the system |
| 140 | considers 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 |
| 143 | contact 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 |
| 148 | requirements of your application or sync adapter. There are two sets of APIs you |
| 149 | can use to control aggregation explicitly: <em>aggregation modes</em> allow you |
| 150 | to control automatic aggregation behaviors and <em>aggregation exceptions</em> |
| 151 | allow 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, |
| 156 | add a mode constant as the value of the <code>AGGREGATION_MODE column</code> in |
| 157 | the <code>RawContact</code> row. The mode constants available include: </p> |
| 158 | |
| 159 | <ul> |
| 160 | <li><code>AGGREGATION_MODE_DEFAULT</code> — normal mode, automatic |
| 161 | aggregation is allowed.</li> |
| 162 | <li><code>AGGREGATION_MODE_DISABLED</code> — automatic aggregation is not |
| 163 | allowed. The raw contact will not be aggregated.</li> |
| 164 | <li><code>AGGREGATION_MODE_SUSPENDED</code> — automatic aggregation is |
| 165 | deactivated. If the raw contact is already a part of an aggregated contact when |
| 166 | aggregation mode changes to suspended, it will remain in the aggregate, even if |
| 167 | it changes in such a way that it no longer matches the other raw contacts in the |
| 168 | aggregate.</li> |
| 169 | </ul> |
| 170 | |
| 171 | <p><strong>Aggregation exceptions</strong></p> |
| 172 | |
| 173 | <p>To keep two raw contacts unconditionally together or unconditionally apart, |
| 174 | you can add a row to the |
| 175 | {@link android.provider.ContactsContract.AggregationExceptions} table. Exceptions |
| 176 | defined 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 |
| 182 | your application needs to maintain references to contacts, you should use lookup |
| 183 | keys instead of the traditional row ids. You can acquire a lookup key from the |
| 184 | contact itself, it is a column on the |
| 185 | {@link android.provider.ContactsContract.Contacts} table. Once you have a lookup key, |
| 186 | you 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}, ...); |
| 193 | try { |
| 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 |
| 201 | inherently volatile. Let's say your app stored a long ID of a contact. Then the |
| 202 | user goes and manually joins the contact with some other contact. Now there is a |
| 203 | single contact where there used to be two, and the stored long contact ID points |
| 204 | nowhere. |
| 205 | |
| 206 | <p>The lookup key helps resolve the contact in this case. The key is a string |
| 207 | that concatenates the server-side identities of the raw contacts. Your |
| 208 | application can use that string to find a contact, regardless whether the raw |
| 209 | contact is aggregated with others or not. </p> |
| 210 | |
| 211 | <p>If performance is a concern for your application, you might want to store |
| 212 | both the lookup and the long ID of a contact and construct a lookup URI out of |
| 213 | both 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 |
| 218 | first. That is a very quick query. If the contact is not found, or if the one |
| 219 | that is found has the wrong lookup key, the content provider will parse the |
| 220 | lookup key and track down the constituent raw contacts. If your app |
| 221 | bulk-processes contacts, you should maintain both IDs. If your app works with a |
| 222 | single contact per user action, you probably don't need to bother with storing |
| 223 | the long ID.</p> |
| 224 | |
| 225 | Android itself uses lookup URIs whenever there is a need to reference a contact, |
| 226 | such as with shortcuts or Quick Contact, and also during editing or even viewing |
| 227 | a contact. The latter case is less obvious — why would a contact ID change |
| 228 | while we are simply viewing the contact? It could change because there might be |
| 229 | a sync going in the background, and the contact might get automatically |
| 230 | aggregated with another while being viewed.</p> |
| 231 | |
| 232 | <p>In summary: whenever you need to reference a contact, we recommend that you |
| 233 | do 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, |
| 239 | you 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 |
| 253 | application as is, and it should run on Android 2.0 as long as it only uses |
| 254 | public APIs. A couple examples of the use of non-public API include the use of |
| 255 | explicit table names in nested queries and the use of columns that were not |
| 256 | declared 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 |
| 260 | for long. The main reason is that it will only have access to contacts from one |
| 261 | account, namely the first Google account on the device. If the user opens other |
| 262 | accounts in addition to or instead of a Google account, your application will |
| 263 | not 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 |
| 269 | Android 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 |
| 273 | API. After you are done, you should not see any deprecation warnings during |
| 274 | application build. The new application will be able to take full advantage of |
| 275 | multiple 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><uses-sdk></code> element. To use the new Contacts API, you should |
| 280 | set the value of the attribute to "5" (or higher, as appropriate). For more |
| 281 | information about <code>android:minSdkVersion</code>, see the documentation for |
| 282 | the <a |
| 283 | href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><code><uses-sdk></code></a> |
| 284 | element. For more information about the value of the |
| 285 | <code>minSdkVersion</code>, see <a |
| 286 | href="{@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 |
| 292 | platforms 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 |
| 299 | is 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 Dougherty | 4d7bc655 | 2012-01-27 17:56:49 -0800 | [diff] [blame] | 301 | suggesting to the user that it's time to go to Google Play and find a new version of |
| 302 | the app. You can even provide a link to the new app on Google Play (see <a |
Dirk Dougherty | 5c2a502 | 2009-12-17 16:59:46 -0800 | [diff] [blame] | 303 | href="{@docRoot}guide/publishing/publishing.html#marketintent">Using Intents |
Dirk Dougherty | 4d7bc655 | 2012-01-27 17:56:49 -0800 | [diff] [blame] | 304 | to Launch Google Play</a>). </li> |
Dirk Dougherty | 5c2a502 | 2009-12-17 16:59:46 -0800 | [diff] [blame] | 305 | </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. |
| 309 | The new application will be able to take full advantage of multiple accounts |
| 310 | and 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 Dougherty | 4d7bc655 | 2012-01-27 17:56:49 -0800 | [diff] [blame] | 314 | Google Play does not allow you to have two applications with the same |
Dirk Dougherty | 5c2a502 | 2009-12-17 16:59:46 -0800 | [diff] [blame] | 315 | name/package.</li> |
| 316 | <li>Update (or add) the <code>android:minSdkVersion</code> attribute |
| 317 | to the <code><uses-sdk></code> element. To use the new Contacts API, |
| 318 | you should set the value of the attribute to "5" (or higher, as appropriate).</li> |
| 319 | </ul> |
| 320 | </ul> |
Dirk Dougherty | 4d7bc655 | 2012-01-27 17:56:49 -0800 | [diff] [blame] | 321 | <li>Publish both apps on Google Play, the old app one as an upgrade and the |
Dirk Dougherty | 5c2a502 | 2009-12-17 16:59:46 -0800 | [diff] [blame] | 322 | other as new. Make sure to explain the difference between the apps in their |
| 323 | descriptions.</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. |
| 330 | Application data can only be accessed by code living in the same package. So |
| 331 | databases, shared preferences, and so on, will need to be populated from |
| 332 | scratch.</li> |
| 333 | <li>The upgrade process is too clunky for the user. Some users may choose |
| 334 | to 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 |
| 340 | build 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. |
| 344 | For 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 |
| 366 | from ContactAccessorOldApi. </p> |
| 367 | |
| 368 | <p>Create a new abstract class ContactAccessor, make sure the abstract |
| 369 | class has all method signatures from ContactAccessorOldApi. Make |
| 370 | ContactAccessorOldApi 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 |
| 378 | implement all methods using the new API:</p> |
| 379 | |
| 380 | <pre> public class ContactAccessorNewApi extends ContactAccessor { |
| 381 | @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 |
| 389 | old API and another using the new API. Let's plug them in. Add this code to |
| 390 | the 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 < Build.VERSION_CODES.ECLAIR) { |
| 399 | className = "ContactAccessorOldApi"; |
| 400 | } else { |
| 401 | className = "ContactAccessorNewApi"; |
| 402 | } |
| 403 | try { |
| 404 | Class<? extends ContactAccessor> 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 |
| 416 | ContactsAccessor:</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 |
| 423 | Android 2.0, and we can't wait to see what cool things developers do with |
| 424 | the new APIs.</p> |