10

This seems to work for small images:

ContentValues values = new ContentValues();

values.put(ContactsContract.Data.RAW_CONTACT_ID, id);
values.put(ContactsContract.Data.IS_SUPER_PRIMARY, 1);
values.put(ContactsContract.CommonDataKinds.Photo.PHOTO, photo);
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE);
if (photoRow >= 0) {
    context.getContentResolver().update(ContactsContract.Data.CONTENT_URI, values, ContactsContract.Data._ID + " = " + photoRow, null);
} else {
    context.getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values);
}

From the docs I realise that for large images I need to set the PHOTO_FILE_ID, so I can replace:

ContactsContract.CommonDataKinds.Photo.PHOTO

with:

ContactsContract.CommonDataKinds.Photo.PHOTO_FILE_ID

However, then I need to supply a PHOTO_FILE_ID rather than raw data. My question:

  1. How do I save the photo (byte []) and get a PHOTO_FILE_ID?
  2. If there is already a photo available (PHOTO not PHOTO_FILE_ID). Do I need to delete it for the big image to be seen or does the big image take precedence, if not, how do I delete it?
Sнаđошƒаӽ
  • 16,753
  • 12
  • 73
  • 90
Guy
  • 12,488
  • 16
  • 79
  • 119

4 Answers4

20

Your own answer will work, but it's not very efficient because the photo needs to be encoded into an SQL query and piped through Android IPC. That also makes it a subject to Android's IPC size limit of 1MB (i.e. if your photo is too large the content provider operation will fail).

The most efficient way to set (create or override) a RawContact's (primary) photo is by using openAssetFileDescriptor and a ContactsContract.RawContacts.DisplayPhoto URI like so (example copied from Android docs):

public void writeDisplayPhoto(long rawContactId, byte[] photo) {
    Uri rawContactPhotoUri = Uri.withAppendedPath(
            ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
            RawContacts.DisplayPhoto.CONTENT_DIRECTORY);
    try {
        AssetFileDescriptor fd =
            getContentResolver().openAssetFileDescriptor(rawContactPhotoUri, "rw");
        OutputStream os = fd.createOutputStream();
        os.write(photo);
        os.close();
        fd.close();
    } catch (IOException e) {
        // Handle error cases.
    }
}

The only drawback of this approach is that it always creates/replaces the primary photo of the RawContact. If the RawContact doesn't have a photo yet this will add one.

Unfortunately there is no way to use openAssetFileDescriptor with a PHOTO_FILE_ID, so you can't override a specific photo identified by its ID using this method. However, in real life most contacts probably have at most one photo, so that's not a real limitation.

This will automatically update the Photo.PHOTO column with a thumbnail of the large photo and assign a PHOTO_FILE_ID.

Marten
  • 3,802
  • 1
  • 17
  • 26
  • @androiddeveloper there is no "best way that will always work". It depends on your use case. If you update the image of an existing contact you're probably mostly best off with this method (`PHOTO_FILE_ID` + `openAssetFileDescriptor`). At times it's preferable to send the image in a content provider operations batch. For instance, it allows you to create a contact including the image in a single transaction. We use this method when we insert new contacts, but we also pre-scale images to make sure they won't blow the IPC limits. – Marten Sep 04 '17 at 14:09
  • I mean. What if you queried a contact (existing one), and got its contact id and lookup key. Now you wish to add/update a photo to it. Will this method always work (assuming you get a raw contact id) ? You wrote that if it has multiple photos, this won't work. How come? Just that I asked about it here: https://stackoverflow.com/q/45894991/878126 , and got a different answer in code, and wonder if there is a better way. – android developer Sep 04 '17 at 16:08
  • If you have the raw contact id of an existing contact this method will always create/update the (first) primary photo of that raw contact. If a raw contact already has multiple photos, only the first one is updated. If the contact is linked to other accounts, the photos of the other raw contacts are not touched. So if the photo of another linked raw contact is the super primary display photo, the user won't notice any difference. If you want to make sure the user sees your photo you have to make it the super primary one. – Marten Sep 04 '17 at 18:53
  • Is it possible I get all rawContacts of a person, but none of them allow to update/insert the photo ? If so, what should be done in this case? – android developer Sep 04 '17 at 23:25
  • Yes, that's perfectly possible. Just imagine a Facebook and a Whatsapp account are linked without any writable account. In such case you can not replace the photo of this contact. The only option would be to add another raw contact of your own account type, link it to the existing contact (if not done automatically) and make its photo the super primary photo. Depending on your use case you might also be able to solve this by adding a local, unsynced raw contact. You probably should start another question outlining your use case and the actual problem you're facing. – Marten Sep 05 '17 at 10:50
  • How do I add "unsynced raw contact" ? Also, I did create this question, here: https://stackoverflow.com/q/45894991/878126 , as I've written above... :) – android developer Sep 05 '17 at 22:18
  • 1
    unsynced raw contacts (aka local contacts) have account type `null`. I've read your question but it didn't really state what your app is doing. It might be better to actually implement your own account type. Creating local contacts should only be the last resort. – Marten Sep 08 '17 at 12:52
  • I see. Can you please show me how to create a new RawContact data just for the photo, of the local storage, given contactId and lookupKey ? – android developer Sep 08 '17 at 22:58
  • We've developed a library called [ContentPal](https://blog.dmfs.org/2017-08-02/Introducing-ContentPal/) which makes it easier to deal with these operations. There are a few pending pull requests in our repo which should provide pretty much everything you need. You can open an issue to get help there. I can't describe that in a comment. Also be aware, that every raw contact must have at least a name. – Marten Sep 10 '17 at 19:41
4

Finally was able to solve it with:

public void changeContactImage(String contactId, byte[] b) {
 ArrayList < ContentProviderOperation > ops = new ArrayList < > ();

 ops.add(ContentProviderOperation
  .newUpdate(
   ContactsContract.Data.CONTENT_URI)
  .withSelection(
   ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?",
   new String[] {
    contactId,
    ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE
   })
  .withValue(ContactsContract.CommonDataKinds.Photo.DATA15, b).build());


 // Do something with the phone number...
 try {

  context.getContentResolver().
  applyBatch(ContactsContract.AUTHORITY, ops);

 } catch (RemoteException e) {
  Log.d("RemoteException", e.toString());
 } catch (OperationApplicationException e) {
  Log.d("OperationException", e.toString());
 }

}
Amitsharma
  • 1,577
  • 1
  • 17
  • 29
Guy
  • 12,488
  • 16
  • 79
  • 119
3

The PHOTO_FILE_ID is the ID not of a file (confusingly) but of a row in the database which contains your raw photo data. According to the docs I've looked at you can actually avoid using it (from the docs) :

Under PHOTO_FILE_ID

If present, this will be used to populate PHOTO_URI

and Under PHOTO_ID (which is guaranteed populated if PHOTO_FILE_ID exists)

Reference to the row in the data table holding the photo. A photo can be referred to either by ID (this field) or by URI (see PHOTO_THUMBNAIL_URI and PHOTO_URI)

This implies that if you just use PHOTO_URI you will get the same resulting image as if you made the method openDisplayPhoto. It also suggests that the URI methods are better compatible with 3rd part directories so are probably preferable to work with

Nick Cardoso
  • 20,807
  • 14
  • 73
  • 124
  • Actually `PHOTO_FILE_ID` is the ID (the file name, to be more precise) of a file. It's the file name of the photo as stored under `/data/data/com.android.providers.contacts/files/photos`. Unfortunately you can not write photos using this ID, you can only read them. – Marten Apr 29 '16 at 20:29
  • Maybe this was unclear. What I meant was that you can use PHOTO_URI with which you can open an output stream to save your new data and not worry about using the ID's directly. The docs say PHOTO_FILE_ID is an integer, so can't be the file name. – Nick Cardoso May 04 '16 at 11:37
  • You can not write to a `PHOTO_URI`. This is only for reading the primary photo of the (aggregated) contact. It doesn't operate on `RawContacts` Trying to write to this URI will fail (see [ContactsProvider2.java, line 8033](http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android-apps/5.1.1_r1/com/android/providers/contacts/ContactsProvider2.java#8033)). Regarding the file name, you're right, the actual file name is the decimal string representation of the `PHOTO_FILE_ID`. But in the end that doesn't matter since it's just an internal implementation detail anyway. – Marten May 04 '16 at 11:56
0

You can get the contact photo uri without using ContactsContract.CommonDataKinds.Email.PHOTO_URI this way: Read Full Answer here More

Where! to get Contact URI as you said try this:

import android.provider.ContactsContract.PhoneLookup;

public String fetchContactIdFromPhoneNumber(String phoneNumber) {
    Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
        Uri.encode(phoneNumber));
    Cursor cursor = this.getContentResolver().query(uri,
        new String[] { PhoneLookup.DISPLAY_NAME, PhoneLookup._ID },
        null, null, null);

    String contactId = "";

    if (cursor.moveToFirst()) {
        do {
        contactId = cursor.getString(cursor
            .getColumnIndex(PhoneLookup._ID));
        } while (cursor.moveToNext());
    }

    return contactId;
  }

To get the conatct id using the phone number use the following code:

import android.provider.ContactsContract.PhoneLookup;

public String fetchContactIdFromPhoneNumber(String phoneNumber) {
    Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
        Uri.encode(phoneNumber));
    Cursor cursor = this.getContentResolver().query(uri,
        new String[] { PhoneLookup.DISPLAY_NAME, PhoneLookup._ID },
        null, null, null);

    String contactId = "";

if (cursor.moveToFirst()) {
    do {
    contactId = cursor.getString(cursor
        .getColumnIndex(PhoneLookup._ID));
    } while (cursor.moveToNext());
}

return contactId;

}

and use the contact id obtained to get the contatc photo URI. Use the following code for getting photo URI:

import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;

public Uri getPhotoUri(long contactId) {
    ContentResolver contentResolver = getContentResolver();

    try {
        Cursor cursor = contentResolver
            .query(ContactsContract.Data.CONTENT_URI,
                null,
                ContactsContract.Data.CONTACT_ID
                    + "="
                    + contactId
                    + " AND "

                    + ContactsContract.Data.MIMETYPE
                    + "='"
                    + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE
                    + "'", null, null);

        if (cursor != null) {
        if (!cursor.moveToFirst()) {
            return null; // no photo
        }
        } else {
        return null; // error in cursor process
        }

    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }

    Uri person = ContentUris.withAppendedId(
        ContactsContract.Contacts.CONTENT_URI, contactId);
    return Uri.withAppendedPath(person,
        ContactsContract.Contacts.Photo.CONTENT_DIRECTORY);
  }

Please let me know if this does not answer your question

Community
  • 1
  • 1
Michael
  • 588
  • 11
  • 19