Getting to Grips With the Android 4.0.3 Social APIs
This post also appears on engineering.qype.com.
We recently started getting our hands onto some of the shiny new Ice Cream Sandwich APIs, particularly Beam and the social stream API that was introduced with Android 4.0.3. Integration turned out to be a little harder than we expected, mostly due to two things: code verbosity (you’ll write a lot of boilerplate) and undocumented pitfalls. With this post I’d like to shed some light on a few things that bit us when developing with the new APIs.
Integrating with the social stream
First of all, you should know that to get going with the social stream, you will have to go through two major steps:
- Publish your app as an account provider, and log in your users via the AccountManager API, so that contacts from your service can be synced with the Android address book (now called the People app in ICS)
- Once your contacts are synced with the People app, import their status updates into the “Recent Updates” feed
I won’t talk about 1. There are two excellent blog posts on how to deal with connecting accounts and syncing contacts respectively, plus the SampleSyncAdapter app that is shipped with the SDK ApiDemos. Once you’re there, adding support for publishing a user’s social feed is generally quite simple. From here on I assume that you have read all the documentation that is available, since I want to focus on the difficult parts. Let’s have a look at the final product first.
Let’s quickly recap when the “Recent Updates” pane appears. It becomes available whenever there are status updates from the contact you’re looking at that are no older than a few days (I believe it’s five days, but I haven’t exactly checked that). There is also a threshold for how many items will ever show up at the same time, and as pointed out in the docs, this threshold is platform or even device specific.
There are two ways to import these status updates from your service: eagerly, as part of the contacts sync (i.e. in your contacts SyncAdapter), or lazily, via an Intent that is fired whenever a user looks at another user’s profile. Again, the general mechanics behind this are outlined on the Android dev blog.
Generally, you don’t want to fetch status updates (plus images) for a hundred or more contacts as part of the sync, since it’s very unlikely that a user would look at all of them to see their status updates. I say “plus images”, because you will have to download them synchronously and either insert them in binary form into the StreamItemPhotos table, or write them to disk using an AssetFileDescriptor. Since ICS devices often have high resolution displays, you want to download high res images, so that’s a lot of data you’re pushing over the wire, keep that in mind.
Hence, you most likely want to go down the callback route, perhaps with optionally pre-populating important contacts with their status updates during contacts sync (what important means depends on your service, but you could for instance check if the user has starred a contact, and prefetch status updates accordingly).
Regardless for which sync strategy you settle (lazy or pre-fetched or hybrid), here are a few things that bit me while syncing social stream items.
One thing that I kept stumbling into was security exceptions being raised even though I asked for all necessary permissions in the Android Manifest. It took me a while to figure out why that was happening, since these checks were not mentioned in the docs. Two specific situations were I encountered these were:
- Opening a single stream item in an Activity that is part of your application. You almost always want to do this, as it allows you to have a “detail view” for every type of stream item you publish.
- Reading back photo stream items when opening the social stream. Recall that there are two different kinds of stream items: ordinary stream items, and photo stream items. Both are kept in separate tables.
I’ll address both points briefly now.
Resolving security exceptions for viewStreamItemActivity
The docs mention that you can register an Activity that will handle taps on a stream item to get a detailed view. This Activity is registered in contacts.xml via the viewStreamItemActivity attribute. For instance, if on our service (Qype) a user writes a review, and we then publish this action to the social stream, then clicking the stream item in the People app will open the review Activity in our app. However, it is undocumented how the Intent is formed that the Android People app will emit in order to launch this Activity, and as a matter of fact, if you declare this Activity without specifying a proper Intent filter, it will crash with a security exception. Without further ado, here’s the Intent filter you will have to specifiy in order for that Activity to work:
You will notice that the data Uri passed as part of the Intent points to the stream item that is being viewed, so you can parse its ID conveniently using ContentUris.parseId(getIntent().getData()). Moreover, if you have a separate activity to handle photo taps/clicks in stream items (declared via viewStreamItemPhotoActivity), be aware that the data Uri will not point to a StreamItem, but to a StreamItemPhoto, so you will have to change the pathPattern to /stream_items/*/photos/*.
Resolving security exceptions when accessing StreamItemPhoto data
The second situation where I was encountering security exceptions was after inserting photos into the StreamItemPhoto table and reading them back when the social stream would be populated. Android complained that the account used to insert the photo records was not matching the one it then used to read them back while building the stream. To resolve this issue, one has to supply the same ACCOUNT_NAME and ACCOUNT_TYPE used to insert the respective stream item to which the photo belongs. So make sure that when inserting a photo item, you include these columns, like so:
Unfortunately, this too is not mentioned in the docs.
Watch out for those timestamps
Another thing that had me scratching my head was the stream item timestamps. First of all, I was expecting that Android would use them to not insert (or at least: not show) feed items that already exist, or that are older than the newest existing item. That is not the case; you will have to do these checks yourself if you want to avoid double insertions. This is a bit annoying, since without these checks, opening a user’s profile in the people app, then closing and re-opening it will lead to multiple insertions unless you take further action. It would have been nice to get the solution here out of the box.
The second and more bewildering issue is that timestamps, although used to identify the time since UNIX epoch (as one would expect), are defined in milliseconds, not seconds. Perhaps this was done so as to make it easier to work with Java’s Date.getTime, since I cannot see why one would need millisecond precision in an activity feed. The problem with using milliseconds is that it can overflow 32 bit integers, which especially on Ruby stacks will give you all sorts of trouble (like swapping over from Fixnum to BigDecimal). So keep that in mind when sending these timestamps to your server as part of the sync.
Watch out for back compat issues
If you’re sharing code between your contacts sync and the social stream stuff (which is most likely the case), and if you want the contacts sync to work on pre-Honeycomb installations as well, be careful when accessing the API, since a few changes have been made since even to existing interfaces. One example where I had to put in extra checks for API compatibility was the GROUP_IS_READ_ONLY column when creating a new group for Qype contacts (I suggest you always do that, since the first tab in the people app is the groups tab, and even if you only have one group this helps your data to appear more prominently), since this has been added with Honeycomb.
Another example is the DisplayPhoto table, which you can use to determine the maximum image size appropriate for the current device. Here you must resort to reflection even, since that class was added with ICS, so you must not reference it explicitly.
That’s it so far. Leave me comments if you have corrections or things to add.