[This post is by Daniel Lehmann, Tech Lead on the Android Apps team. — Tim Bray]
With Android Ice Cream Sandwich, we set out to build software that supports emotional connections between humans and the devices they carry. We wanted to build the most personal device that the user has ever owned.
The first ingredient in our recipe is to show users the people that they care about most in a magazine-like way. High-resolution photos replace simple lists of text.
The second ingredient is to more prominently visualize their friends’ activities. We show updates from multiple sources wherever a contact is displayed, without the need to open each social networking app individually.
Android is an open platform, and in Ice Cream Sandwich we provide a rich new API to allow any social networking application to integrate with the system. This post explains how apps like Google+ use these APIs, and how other social networks can do the same.
A few basics

Since Eclair (Android 2.0), the system has been able to join contacts from different sources. Android can notice if you are connected to the same person and different networks, and join those into aggregate contacts.
Essential terms to understand throughout the remainder of this post are:

  • RawContact is a contact as it exists in one source, for example a friend in Skype.
  • Data rows exists for each piece of information that the raw contact contains (name, phone number, email address, etc.).
  • A Contact joins multiple raw contacts into one aggregate. This is what the user perceives as a real contact in the People and Phone apps.
  • A sync adapter synchronizes its raw contacts with its cloud source. It can be bundled with a Market application (examples: Skype, Twitter, Google+).

While users deal with contacts, sync adapters work with their raw contact rows. They own the data inside a raw contact, but by design it is left up to Android to properly join raw contact rows with others.
Contacts sync adapters have a special xml file that describes their content, which is documented in the Android SDK. In the following paragraphs, we’ll assume this file is named contacts.xml.
The Android SDK also contains the application SampleSyncAdapter (and its source code) that implements everything mentioned in here in an easy to understand way.
High resolution photos

In Android versions prior to Honeycomb (3.0), contact photos used to be 96x96. Starting with ICS, they now have a thumbnail (which is the 96x96 version) and a display photo. The display photo’s maximum size can vary from device to device (On Galaxy Nexus and Nexus S, it is currently configured to be 256x256, but expect this to vary with future devices). The size as configured can be queried like this:

private static int getPhotoPickSize(Context context) { // Note that this URI is safe to call on the UI thread. Cursor c = context.getContentResolver().query(DisplayPhoto.CO NTENT_MAX_DIMENSIONS_URI, new String[]{ DisplayPhoto.DISPLAY_MAX_DIM }, null, null, null); try { c.moveToFirst(); return c.getInt(0); } finally { c.close(); } }This value is useful if you need to query the picture from the server (as you can specify the right size for the download). If you already have a high resolution picture, there is no need for any resizing on your side; if it is too big, the contacts provider will downsample it automatically.
Up until now, pictures were written using a ContentValues object, just like all the other data rows of the raw contact. While this approach is still supported, it might fail when used with bigger pictures, as there is a size limit when sending ContentValues across process boundaries. The prefered way now is to use an AssetFileDescriptor and write them using a FileOutputStream instead:

private static void saveBitmapToRawContact(Context context, long rawContactId, byte[] photo) throws IOException { Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI , rawContactId); Uri outputFileUri = Uri.withAppendedPath(rawContactUri, RawContacts.DisplayPhoto.CONTENT_DIRECTORY); AssetFileDescriptor descriptor = context.getContentResolver().openAssetFileDescript or( outputFileUri, "rw"); FileOutputStream stream = descriptor.createOutputStream(); try { stream.write(photo); } finally { stream.close(); descriptor.close(); } }For best results, store uncompressed square photos and let the contacts provider take care of compressing the photo. It will create both a thumbnail and a display photo as necessary.
This API is available on API version 14+. For older versions, we recommend to fallback to the old method using ContentValues and assuming a constant size of 96x96.
Update streams

The API for update streams is the biggest new addition for contacts in Ice Cream Sandwich. Sync adapters can now enrich their contact data by providing a social stream that includes text and photos.
This API is intended to provide an entry point into your social app to increase user engagement. We chose to only surface the most recent few stream items, as we believe that your social app will always be the best way to interact with posts on your network.
StreamItems rows are associated with a raw contact row. They contain the newest updates of that raw contact, along with text, time stamp and comments. They can also have pictures, which are stored in StreamItemPhotos. The number of stream items per raw contact has a limit, which on the current Nexus devices is set to 5, but expect this number to change with future devices. The limit can be queried like this:

private static int getStreamItemLimit(Context context) { // Note that this URI is safe to call on the UI thread. Cursor c = context.getContentResolver().query(StreamItems.CON TENT_LIMIT_URI, new String[]{ StreamItems.MAX_ITEMS }, null, null, null); try { c.moveToFirst(); return c.getInt(0); } finally { c.close(); } }When displayed in the People app, stream items from all participating raw contacts will be intermixed and shown chronologically.
The following function shows how to add a stream item to an existing raw contact:

private static void addContactStreamItem(Context context, long rawContactId, String text, String comments, long timestamp, String accountName, String accountType){ ContentValues values = new ContentValues(); values.put(StreamItems.RAW_CONTACT_ID, rawContactId); values.put(StreamItems.TEXT, "Breakfasted at Tiffanys"); values.put(StreamItems.TIMESTAMP, timestamp); values.put(StreamItems.COMMENTS, comments); values.put(StreamItems.ACCOUNT_NAME, accountName); values.put(StreamItems.ACCOUNT_TYPE, accountType); context.getContentResolver().insert(StreamItems.CO NTENT_URI, values); }You can also specify an action that should be executed when a stream item or one of its photos is tapped. To achieve this, specify the receiving Activities in your contacts.xml using the tags viewStreamItemActivity and viewStreamItemPhotoActivity: