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:
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
.
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());
}
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With