Skip to content

Handling Contact Photos (All API Levels)

December 30, 2009

Hey everyone,

So I recently released my Facebook Contact Sync application, and even though I tested it thoroughly on my G1 I received a lot of complaints from people with M-droids, etc. Well, apparently, with the release of Android 2.0 and higher, even though the android.provider.Contacts.People class is only deprecated, often times the same methods which would have worked in Android 1.6 and lower will no longer work in 2.0 and higher (kind of silly to mark something as deprecated if it won’t work most of the time no?). And so, after browsing numerous blogs and email lists I was able to get a better grasp of how to deal with the new ContactsContract structure (API level 5 and above) and this post will talk specifically about how to load and set the user’s Contact’s photos, both in API levels 5 and 6 but also in the lower API levels.

Loading Contact Photos:

For API LEVEL 5 / 6 the code looks like:

public static Bitmap loadContactPhoto(ContentResolver cr, long id) {
    Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
    InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(cr, uri);
    if (input == null) {
         return getBitmapFromURL("https://thinkandroid.wordpress.com");
     }
    return BitmapFactory.decodeStream(input);
}

Nothing too complicated. Simply create the contact’s URI based off his/her id, and then use the new openContactPhotoInputStream method to retrieve the InputSream of the BLOB representation of the contact’s photo. Now, if no photo exists, then the method will return null for the InputStream, so in my case I return some default avatar image but you can handle it however you want. If the contact does have a photo however, then use the BitmapFactory’s decode method to obtain the Bitmap object.

For API LEVEL 4 and lower, the same method looks like:

public static Bitmap getBitmapFromId(Context context, long id) {
    Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, id);
    Bitmap bitmap = People.loadContactPhoto(context, uri, R.drawable.generic_profile_man, null);
    return bitmap;
}

Seems a lot more convenient no? Well it is – but it’s not necessarily Google’s fault. The simple truth is the Android 2.0 phones can do a lot more by giving user’s the option of creating multiple accounts on the phone and saving different information for each contact based on what account you’re logged into. Because of this, the database structure naturally got a little more complicated.

Now, for setting photos, things get slightly more complicated:

For API LEVEL 5 / 6 the code looks like:

    public static void setContactPhoto(ContentResolver c, byte[] bytes, long personId) {
        ContentValues values = new ContentValues();
        int photoRow = -1;
        String where = ContactsContract.Data.RAW_CONTACT_ID + " = " + personId + " AND " + ContactsContract.Data.MIMETYPE + "=='" + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "'";
        Cursor cursor = c.query(ContactsContract.Data.CONTENT_URI, null, where, null, null);
        int idIdx = cursor.getColumnIndexOrThrow(ContactsContract.Data._ID);
        if (cursor.moveToFirst()) {
            photoRow = cursor.getInt(idIdx);
        }
        cursor.close();

        values.put(ContactsContract.Data.RAW_CONTACT_ID, personId);
        values.put(ContactsContract.Data.IS_SUPER_PRIMARY, 1);
        values.put(ContactsContract.CommonDataKinds.Photo.PHOTO, bytes);
        values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE);

        if (photoRow >= 0) {
            c.update(ContactsContract.Data.CONTENT_URI, values, ContactsContract.Data._ID + " = " + photoRow, null);
        } else {
            c.insert(ContactsContract.Data.CONTENT_URI, values);
        }
    }

Basically, what you can’t just do is:


ContentValues values = new ContentValues();
values.put(ContactsContract.Data.RAW_CONTACT_ID, personId);
values.put(ContactsContract.Data.IS_SUPER_PRIMARY, 1);
values.put(ContactsContract.CommonDataKinds.Photo.PHOTO, bytes.toByteArray());
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE );
context.getContentResolver().update(ContactsContract.Data.CONTENT_URI, values, ContactsContract.Data.RAW_CONTACT_ID + " = " + personId, null);

And that’s because there are many rows where the RAW_CONTACT_ID = personId (there are data rows for the photo, phone numbers, emails, etc) and so trying this should give you some kind of SQLite exception along the lines of “CANNOT CONVERT BLOB TO STRING” and this makes sense as in this case you’re probably trying to update the phone number row with these bytes and so it’s throwing the appropriate error. Thus, in the first example, you need to begin by identifying which of these rows where RAW_CONTACT_ID = personId contains the BLOB value of the photo, and in the second query you want to set it in that row specifically. Now notice that sometimes photoRow may come back as -1. This basically tells us that no one has tried to set the photo or any other information before, and so we simply need to do a getContentResolver.insert(). Now, exactly why the behavior is different in these two cases, I don’t know. But the code above works, so once Google releases some better documentation maybe we’ll find out!

Finally, to do the same thing on API LEVEL 4 or lower, simply do:

public static void setContactPhoto(byte[] bytes, long id) {
    Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, id);
    Contacts.People.setPhotoData(c, uri, bytes);
}

Hopefully I’ll have time to post more on my findings of working with Contact’s in Android 2.0. But for now, hope this helped!

Credit goes to: http://groups.google.com/group/android-developers/browse_thread/thread/133816827efc8eb9

Good stuff there – just thought I’d condense it and explain it a little.

– jwei

PS: For more on working with Bitmaps, see
Converting Image URL to Bitmap

11 Comments leave one →
  1. lore permalink
    January 22, 2010 3:14 am

    Hi,

    Did you manage to read also the facebook contacts information?

    Thanks

  2. lore permalink
    January 22, 2010 3:16 am

    on 2.0 I mean

  3. Wysie permalink
    February 8, 2010 9:07 am

    Hi,

    I followed your instructions for 2.0 onwards, but openContactPhotoInputStream keeps returning null. Any pointers? Am I right to say id refers to Contacts._ID? If so I can’t understand where I went wrong…

    Thanks!

    Regards,
    Wysie

  4. March 2, 2010 4:41 pm

    This works like a charm on the emulator, but as soon as I get my app up and running on my HTC Hero, contacts photos aren’t retrieved. Actually, photos retrieved by the phone via Facebook are not. Manually set photos are correctly displayed (I got one in my whole phonebook).

    Any idea? Some HTC-owned API or something?

    A.

  5. March 19, 2010 1:52 am

    hey thnx for such a good blog.. it helped me a lot……… 🙂

  6. December 14, 2010 2:42 am

    Hi,

    I search a way to get the contact’s photo when it is a facebook photo because I get null with the loadContactPhoto…

    Thanks

  7. Ofer permalink
    December 23, 2010 4:59 am

    Very helpful! Thanks alot!

  8. January 20, 2011 11:17 am

    Thanks for the helpful post. Any idea what the maximum number of bytes you can store as a contacts Photo?

  9. sampson permalink
    August 15, 2011 1:00 am

    thanks jwei512,it’s very helpful!!

  10. March 5, 2012 9:00 am

    Thanks Dude, your tutorial help me to make my first game: http://goo.gl/PfAEF
    Thx

Trackbacks

  1. PN.Truong Phuc's Blog

Leave a comment