7

I am trying to show contact pictures in my application but I am getting pictures of those who were added manually only and not those which are synced with facebook. How to work around this? Here is my code below:

Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, Long.parseLong(PhotoId));
InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), uri);
return BitmapFactory.decodeStream(input);
Janusz
  • 187,060
  • 113
  • 301
  • 369
Yash
  • 2,259
  • 4
  • 26
  • 33
  • I am experiencing the same problem. It seems that the issue occurs with contacts that are FB-synced only. For contacts that are synced from multiple sources, i.e. twitter and FB, it's fine. – Mathias Conradt Dec 22 '10 at 03:17

5 Answers5

4

It doesn't work for contacts that are synced from FB only. You'll need to use the FB graph API and fetch the photo from there; and you need to know the contacts facebook name.

 Bitmap contactPhoto = getImageBitmap("http://graph.facebook.com/mathiaslin/picture?type=square");

 final private static Bitmap getImageBitmap(String url) {
    Bitmap bm = null;
    try {
        URLConnection conn = new URL(url).openConnection();
        conn.connect();
        InputStream is = conn.getInputStream();
        BufferedInputStream bis = new BufferedInputStream(is);
        bm = BitmapFactory.decodeStream(bis);
        bis.close();
        is.close();
    } catch (IOException e) {
        Log.e(TAG, "Error getting bitmap", e);
    }
    return bm;
}
Mathias Conradt
  • 28,420
  • 21
  • 138
  • 192
2

I have tried every solution found on Stack Overflow at the time of writing this and nothing will correctly retrieve a photo that came from Facebook through the official Facebook app account sync adapter.

To be clear, people posting solutions who "think" they work are probably using HTC sense phones that come with a Facebook sync adapter written by HTC that doesn't have the same security requirements as the official Facebook app.

It's a security thing and one of the methods did manage to try to access the bytes of the facebook photo and you will end up with an SqliteException saying that the content requested is restricted.

Same code run as a system process will pull the photo fine. It is just not possible as of right now.

Eric
  • 1,033
  • 1
  • 10
  • 8
0

The code you have given should only access the default photo. Also, you should be appending the contact ID to that URI, not the photo ID (assuming you're using the photo's id from the data table).

If there are multiple photos you might want to try accessing them directly from the Data table. You'll need to parse a database cursor and convert the raw byte data into a bitmap manually as shown below:

String[] projection = {ContactsContract.CommonDataKinds.Photo.PHOTO};
Uri uri = Uri. ContactsContract.Data.CONTENT_URI;
String where = ContactsContract.Data.MIMETYPE 
       + "=" + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + " AND " 
       + ContactsContract.Data.CONTACT_ID + " = " + mContactId;
Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);

if(cursor!=null&&cursor.moveToFirst()){
    do{
        byte[] photoData = photoCursor.getBlob(0);
        Bitmap photo = BitmapFactory.decodeByteArray(photoData, 0,
                photoData.length, null);

        //Do whatever with your photo here...
    }while(cursor.moveToNext());
}

You would want mContactId to correspond with the contact that you want photos for.

If you want to limit to only facebook photos, you'll need to use ContactsContract.Data.RAW_CONTACT_ID instead, which you should get from the RawContacts table using your contact Id and a filter based on the facebook account (assuming you know what account you're looking for... that can vary by sync provider implementation...)

Charles B
  • 1,533
  • 8
  • 14
  • This method still results in a null byte for pictures from facebook for android. I guess it is because the api of facebook for android has a whitelist system which allows data to be shared to only android default applications. – Yash Oct 04 '10 at 19:48
  • I could have sworn that I've loaded facebook photos before using a similar method... it may depend on your sync provider though. I believe there are several different ones out there. Have you tried switching to a different facebook client app? – Charles B Oct 05 '10 at 00:04
  • Yeah that is the exact problem. The official app for facebook (facebook for android) seems to have a stupid policy that prevents other apps from using the data. I am pretty sure that another sync app for facebook will do the job. – Yash Oct 05 '10 at 08:50
0

I get the picture for all the contacts even some that are synced from facebook with this code:

 * @param context
 *            The context used to retrieve the image.
 * @return The image of the user that is saved in the address book or null if
 *         the user does not exists or has no image.
 */
public Bitmap getContactPhoto(Context context) {
    ContentResolver contentResolver = context.getContentResolver();
    Uri photoUri = getCurrentUri(contentResolver);

    if (photoUri != null) {
        InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(
                contentResolver, photoUri);
        if (input != null) {
            return BitmapFactory.decodeStream(input);
        }
    } else {
        Log.d(getClass().getSimpleName(), "No photo Uri");
    }
    return null;
}

private Uri getCurrentUri(ContentResolver contentResolver) {
    Cursor contact = contentResolver.query(lookUpUri,
            new String[] { ContactsContract.Contacts._ID }, null, null, null);

    if (contact.moveToFirst()) {
        long userId = contact.getLong(contact.getColumnIndex(ContactsContract.Contacts._ID));
        return ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, userId);
    }
    return null;
}

Maybe have a look at the code again that creates your photoId. Don't use the Id of an photo in the data table to create the Uri. I also tried that and retrieving the photo worked for me only if I use an uri that links directly to the merged user. You will then get the default image for this person regardless from where it was synced.

Janusz
  • 187,060
  • 113
  • 301
  • 369
  • Does this work for FB-synced-only contacts as well? I tried but it fails with input being null after trying to retrieve the photo input stream. – Mathias Conradt Dec 22 '10 at 03:18
  • I had a look at it and it seems that it is only working for some facebook pictures. It looks like some of the facebook pictures got copied into the google contact for the person and only then I'm able to load this pictures. – Janusz Dec 22 '10 at 07:54
0

There is definitely no way to do this in a standard way. So, we must use an SQL Injection in order to be able to hack the contacts database and get the Facebook avatars. The following code works on most Motorolas, which use Motoblur, on Android 2.2 or higher:

public static Bitmap loadFacebookAvatar(Context context, long personId) {
    String[] rawProjection = {ContactsContract.RawContacts._ID};
    String contactIdAssertion = ContactsContract.RawContacts.CONTACT_ID + " = " + personId;
    String rawWhere = new StringBuilder()
            .append(contactIdAssertion).append(") UNION ALL SELECT ")
            .append(ContactsContract.RawContacts._ID).append(" FROM view_raw_contacts WHERE (")
            .append(contactIdAssertion).toString();
    Cursor query = context.getContentResolver().query(ContactsContract.RawContacts.CONTENT_URI,
            rawProjection,
            rawWhere, null, null);
    if (query != null && query.moveToFirst()) {
        do {
            long id = query.getLong(query.getColumnIndex(ContactsContract.RawContacts._ID));
            String[] projection = {ContactsContract.CommonDataKinds.Photo.PHOTO};
            Uri uri = ContactsContract.Data.CONTENT_URI;

            String mimeTypeAssertion = ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "'";
            String photoAssertion = ContactsContract.CommonDataKinds.Photo.PHOTO + " IS NOT NULL";
            String rawContactIdAssertion = ContactsContract.CommonDataKinds.Photo.RAW_CONTACT_ID + " = " + id;

            String where = new StringBuilder().append(mimeTypeAssertion).append(" AND ")
                    .append(photoAssertion).append(" AND ").append(rawContactIdAssertion)
                    .append(") UNION ALL SELECT ").append(ContactsContract.CommonDataKinds.Photo.PHOTO)
                    .append(" FROM view_data WHERE (").append(photoAssertion).append(" AND ")
                    .append(rawContactIdAssertion).toString();

            Cursor photoQuery = context.getContentResolver().query(uri, projection, where, null, null);
            if (photoQuery != null && photoQuery.moveToFirst()) {
                do {
                    byte[] photoData = photoQuery.getBlob(photoQuery.getColumnIndex(ContactsContract.CommonDataKinds.Photo.PHOTO));
                    if (photoData != null) {
                        return BitmapFactory.decodeByteArray(photoData, 0, photoData.length, null);
                    }
                } while (photoQuery.moveToNext());
            }
        } while (query.moveToNext());
    }
    return null;
}

For other handsets you must get the contacts database and analyze it in order to determine how to apply the SQL Injection, which requires a rooted phone.

Cristian
  • 198,401
  • 62
  • 356
  • 264
  • This does not appear to work on Android 4.0.x devices any more. The generated SQL is escaped to prevent the injection. :( – Jeff Hubbard Apr 04 '12 at 21:51