package eu.faircode.email; import android.Manifest; import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.net.Uri; import android.provider.ContactsContract; import android.util.TypedValue; import androidx.preference.PreferenceManager; import java.io.InputStream; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.mail.Address; import javax.mail.internet.InternetAddress; public class ContactInfo { private String email; private Bitmap bitmap; private String displayName; private Uri lookupUri; private long time; private static Map emailLookupInfo = new HashMap<>(); private static Map emailContactInfo = new HashMap<>(); private static final long CACHE_LOOKUP_DURATION = 120 * 60 * 1000L; private static final long CACHE_CONTACT_DURATION = 60 * 1000L; private ContactInfo() { } boolean hasPhoto() { return (bitmap != null); } Bitmap getPhotoBitmap() { return bitmap; } String getDisplayName(boolean name_email) { if (!name_email && displayName != null) return displayName; else if (displayName == null) return (email == null ? "" : email); else return displayName + " <" + email + ">"; } boolean hasLookupUri() { return (lookupUri != null); } Uri getLookupUri() { return lookupUri; } private boolean isExpired() { return (new Date().getTime() - time > CACHE_CONTACT_DURATION); } static void clearCache() { synchronized (emailLookupInfo) { emailLookupInfo.clear(); } synchronized (emailContactInfo) { emailContactInfo.clear(); } } static ContactInfo get(Context context, Address[] addresses, boolean cacheOnly) { if (addresses == null || addresses.length == 0) return new ContactInfo(); InternetAddress address = (InternetAddress) addresses[0]; String key = MessageHelper.formatAddresses(new Address[]{address}); synchronized (emailContactInfo) { ContactInfo info = emailContactInfo.get(key); if (info != null && !info.isExpired()) return info; } if (cacheOnly) return null; ContactInfo info = new ContactInfo(); info.email = address.getAddress(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); if (Helper.hasPermission(context, Manifest.permission.READ_CONTACTS)) try { ContentResolver resolver = context.getContentResolver(); try (Cursor cursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, new String[]{ ContactsContract.CommonDataKinds.Photo.CONTACT_ID, ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts.DISPLAY_NAME }, ContactsContract.CommonDataKinds.Email.ADDRESS + " = ? COLLATE NOCASE", new String[]{ address.getAddress() }, null)) { if (cursor != null && cursor.moveToNext()) { int colContactId = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Photo.CONTACT_ID); int colLookupKey = cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY); int colDisplayName = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); long contactId = cursor.getLong(colContactId); String lookupKey = cursor.getString(colLookupKey); Uri lookupUri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey); boolean avatars = prefs.getBoolean("avatars", true); if (avatars) { InputStream is = ContactsContract.Contacts.openContactPhotoInputStream(resolver, lookupUri); info.bitmap = BitmapFactory.decodeStream(is); } info.displayName = cursor.getString(colDisplayName); info.lookupUri = lookupUri; } } } catch (Throwable ex) { Log.e(ex); } if (info.bitmap == null) { TypedValue tv = new TypedValue(); context.getTheme().resolveAttribute(R.attr.themeName, tv, true); int dp = Helper.dp2pixels(context, 48); boolean dark = !"light".equals(tv.string); boolean identicons = prefs.getBoolean("identicons", false); if (identicons && BuildConfig.DEBUG) info.bitmap = Identicon.icon(key, dp, 5, dark); else info.bitmap = Identicon.letter(key, dp, dark); } boolean circular = prefs.getBoolean("circular", true); if (info.bitmap != null && circular) { int w = info.bitmap.getWidth(); int h = info.bitmap.getHeight(); Rect source; if (w > h) { int off = (w - h) / 2; source = new Rect(off, 0, w - off, h); } else if (w < h) { int off = (h - w) / 2; source = new Rect(0, off, w, h - off); } else source = new Rect(0, 0, w, h); Rect dest = new Rect(0, 0, source.width(), source.height()); Bitmap round = Bitmap.createBitmap(source.width(), source.height(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(round); Paint paint = new Paint(); paint.setAntiAlias(true); canvas.drawARGB(0, 0, 0, 0); paint.setColor(Color.GRAY); canvas.drawOval(new RectF(dest), paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(info.bitmap, source, dest, paint); info.bitmap.recycle(); info.bitmap = round; } if (info.displayName == null) info.displayName = address.getPersonal(); synchronized (emailContactInfo) { emailContactInfo.put(key, info); } info.time = new Date().getTime(); return info; } static Uri getLookupUri(Context context, Address[] addresses) { if (!Helper.hasPermission(context, Manifest.permission.READ_CONTACTS)) return null; if (addresses == null || addresses.length == 0) return null; InternetAddress address = (InternetAddress) addresses[0]; synchronized (emailLookupInfo) { LookupInfo info = emailLookupInfo.get(address.getAddress()); if (info != null && !info.isExpired()) return info.uri; } try { ContentResolver resolver = context.getContentResolver(); try (Cursor cursor = resolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, new String[]{ ContactsContract.CommonDataKinds.Photo.CONTACT_ID, ContactsContract.Contacts.LOOKUP_KEY }, ContactsContract.CommonDataKinds.Email.ADDRESS + " = ?", new String[]{ address.getAddress() }, null)) { Uri uri = null; if (cursor != null && cursor.moveToNext()) { int colContactId = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Photo.CONTACT_ID); int colLookupKey = cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY); long contactId = cursor.getLong(colContactId); String lookupKey = cursor.getString(colLookupKey); uri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey); } LookupInfo info = new LookupInfo(); info.uri = uri; info.time = new Date().getTime(); synchronized (emailLookupInfo) { emailLookupInfo.put(address.getAddress(), info); } return info.uri; } } catch (Throwable ex) { Log.e(ex); return null; } } private static class LookupInfo { private Uri uri; private long time; private boolean isExpired() { return (new Date().getTime() - time > CACHE_LOOKUP_DURATION); } } }