Background
Suppose I've found a contact by using some query on the address book. Found it by performing a query to get contact info from a specific account (like of WhatsApp), as I've written here.
Now I have an image that I wish to use it to update the contact's photo.
The problem
I have created code based on things I've found here on StackOverflow, that update a contact photo.
Thing is, some users claim it doesn't do anything. I'm not sure what causes it. Maybe bad way to access the contact (id/lookup-key?). Maybe I need to get an extra address-book field. Maybe the query itself is wrong...
I'm having problems figuring out the reason for this issue, because I can't reproduce it and so does most of the users.
The code
Here's what I did:
final ArrayList<ContentProviderOperation> ops = new ArrayList<>();
final String lookupKey=...;
try {
FileInputStream fileInputStream = new FileInputStream(file);
final byte[] photoByteArray = new byte[(int) file.length()];
fileInputStream.read(photoByteArray);
fileInputStream.close();
final Builder builder = ContentProviderOperation.newUpdate(Data.CONTENT_URI);
builder.withSelection(Data.LOOKUP_KEY + "=?" + " AND " + Data.MIMETYPE + "=?", new String[]{lookupKey, Photo.CONTENT_ITEM_TYPE});
builder.withValue(Photo.PHOTO, photoByteArray);
ops.add(builder.build());
file.delete();
} catch (IOException e) {
e.printStackTrace();
}
context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops )
The question
Is there anything wrong with the code?
Should I update the contact differently?
How come some users fail to update? Isn't the lookup-key enough? Can it update the wrong data?
I've read somewhere that I might need to use RAW-contact-ids, but couldn't find what to do exactly. Query and then update/insert?
Someone has suggested that developers should consider using an account of their app to save photos, instead of updating, as it might cause issues. Is it true?
I see a number of possible issues in the code:
- You're assuming there's an existing photo stored for that contact (you're using
newUpdate
), in case there is not currently a photo, you should do newInsert
instead.
- You should be updating/inserting on a specific RawContact, what you wrote updates all that Contact's RawContacts photo data raws, some RawContacts might have read-only status, and will not allow your app to modify it.
- Handling photos, especially when storing them into the Contacts DB is a very memory intense action, it might crash on
OutOfMemoryError
for some devices, either because they have very limited RAM or because the memory is currently heavily used by something else. You should wrap your entire code including the applyBatch with try/catch, and catch all Exceptions/Errors and report them to the log, and preferably to your error-reporting library as well (Firebase/crashlytics/etc)
- You're only updating the
PHOTO
field of the data raw, you should also update PHOTO_FILE_ID
field, as some apps may choose to read the PHOTO_FILE_ID
and get the photo from it instead.
- You're not setting your new photo as the
SUPER_PRIMARY
of its contact, so if some other RawContact in that same Contact also has a photo, it might "win", and be the primary photo for that contact.
Here's code that should help:
// To simplify the code i didn't worry about try/catch, freeing resources,
// or running slow queries on the main thread, make sure your code does!
public ContentProviderOperation setPhoto(int contactId, byte[] photoByteArray) {
long rawId = getRawId(contactId);
ContentProviderOperation.Builder builder;
String selection = Data.RAW_CONTACT_ID + " = '" + rawId + "' AND " + Data.MIMETYPE + "= '" + Photo.CONTENT_ITEM_TYPE + "'";
Cursor cur = contentResolver.query(Data.CONTENT_URI, new String[]{ Data._ID }, selection, null, null);
if (cur.moveToFirst()) {
// this RawContact already has a photo!
Long photoDataId = cur.getLong(0);
Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, photoDataId);
builder = ContentProviderOperation.newUpdate(uri);
} else {
// this RawContact has no photo.
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValue(Data.RAW_CONTACT_ID, rawId);
}
builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
builder.withValue(Data.IS_SUPER_PRIMARY, 1);
builder.withValue(Data.IS_PRIMARY, 1);
builder.withValue(Photo.PHOTO, photoByteArray);
builder.withValue(Photo.PHOTO_FILE_ID, null);
return builder.build();
}
private long getRawId(long contactId) {
String selection = RawContacts.CONTACT_ID + "='" + contactId + "'";
Cursor cur = contentResolver.query(RawContacts.CONTENT_URI, new String[]{ RawContacts._ID }, selection, null, null);
try {
if (cur.moveToNext()) {
return cur.getLong(0);
}
} finally {
cur.close();
}
return 0;
}