Loading k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientAdapter.java +1 −11 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import android.widget.Filterable; import android.widget.ImageView; import android.widget.TextView; import com.bumptech.glide.Glide; import com.fsck.k9.R; import com.fsck.k9.helper.ContactPicture; import com.fsck.k9.view.RecipientSelectView.Recipient; Loading Loading @@ -126,16 +125,7 @@ public class RecipientAdapter extends BaseAdapter implements Filterable { } public static void setContactPhotoOrPlaceholder(Context context, ImageView imageView, Recipient recipient) { // TODO don't use two different mechanisms for loading! if (recipient.photoThumbnailUri != null) { Glide.with(context).load(recipient.photoThumbnailUri) // for some reason, this fixes loading issues. .placeholder(null) .dontAnimate() .into(imageView); } else { ContactPicture.getContactPictureLoader(context).loadContactPicture(recipient.address, imageView); } ContactPicture.getContactPictureLoader(context).loadContactPicture(recipient, imageView); } @Override Loading k9mail/src/main/java/com/fsck/k9/activity/misc/ContactPictureLoader.java +124 −197 Original line number Diff line number Diff line package com.fsck.k9.activity.misc; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.Locale; import java.util.concurrent.RejectedExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.support.annotation.VisibleForTesting; import android.support.v4.util.LruCache; import android.text.TextUtils; import android.widget.ImageView; import com.bumptech.glide.Glide; import com.bumptech.glide.Priority; import com.bumptech.glide.load.ResourceDecoder; import com.bumptech.glide.load.data.DataFetcher; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.Resource; import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; import com.bumptech.glide.load.model.ModelLoader; import com.bumptech.glide.load.resource.bitmap.BitmapEncoder; import com.bumptech.glide.load.resource.bitmap.BitmapResource; import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder; import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.load.resource.file.FileToStreamDecoder; import com.bumptech.glide.load.resource.transcode.BitmapToGlideDrawableTranscoder; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; import com.fsck.k9.helper.Contacts; import com.fsck.k9.mail.Address; import com.fsck.k9.view.RecipientSelectView.Recipient; public class ContactPictureLoader { /** Loading @@ -47,18 +56,12 @@ public class ContactPictureLoader { private static final String FALLBACK_CONTACT_LETTER = "?"; private ContentResolver mContentResolver; private Resources mResources; private Contacts mContactsHelper; private int mPictureSizeInPx; private int mDefaultBackgroundColor; /** * LRU cache of contact pictures. */ private final LruCache<Address, Bitmap> mBitmapCache; /** * @see <a href="http://developer.android.com/design/style/color.html">Color palette used</a> */ Loading @@ -80,7 +83,6 @@ public class ContactPictureLoader { String letter = null; String personal = address.getPersonal(); String str = (personal != null) ? personal : address.getAddress(); Matcher m = EXTRACT_LETTER_PATTERN.matcher(str); if (m.find()) { letter = m.group(0).toUpperCase(Locale.US); Loading @@ -101,7 +103,6 @@ public class ContactPictureLoader { */ public ContactPictureLoader(Context context, int defaultBackgroundColor) { Context appContext = context.getApplicationContext(); mContentResolver = appContext.getContentResolver(); mResources = appContext.getResources(); mContactsHelper = Contacts.getInstance(appContext); Loading @@ -110,59 +111,64 @@ public class ContactPictureLoader { mDefaultBackgroundColor = defaultBackgroundColor; ActivityManager activityManager = (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE); int memClass = activityManager.getMemoryClass(); } // Use 1/16th of the available memory for this memory cache. final int cacheSize = 1024 * 1024 * memClass / 16; public void loadContactPicture(final Address address, final ImageView imageView) { Uri photoUri = mContactsHelper.getPhotoUri(address.getAddress()); loadContactPicture(photoUri, address, imageView); } mBitmapCache = new LruCache<Address, Bitmap>(cacheSize) { @Override protected int sizeOf(Address key, Bitmap bitmap) { // The cache size will be measured in bytes rather than number of items. return bitmap.getByteCount(); public void loadContactPicture(Recipient recipient, ImageView imageView) { loadContactPicture(recipient.photoThumbnailUri, recipient.address, imageView); } }; private void loadFallbackPicture(Address address, ImageView imageView) { Context context = imageView.getContext(); Glide.with(context) .using(new FallbackGlideModelLoader(), FallbackGlideParams.class) .from(FallbackGlideParams.class) .as(Bitmap.class) .transcode(new BitmapToGlideDrawableTranscoder(context), GlideDrawable.class) .decoder(new FallbackGlideBitmapDecoder(context)) .encoder(new BitmapEncoder(Bitmap.CompressFormat.PNG, 0)) .cacheDecoder(new FileToStreamDecoder<>(new StreamBitmapDecoder(context))) .diskCacheStrategy(DiskCacheStrategy.NONE) .load(new FallbackGlideParams(address)) // for some reason, following 2 lines fix loading issues. .dontAnimate() .override(mPictureSizeInPx, mPictureSizeInPx) .into(imageView); } /** * Load a contact picture and display it using the supplied {@link ImageView} instance. * * <p> * If a picture is found in the cache, it is displayed in the {@code ContactBadge} * immediately. Otherwise a {@link ContactPictureRetrievalTask} is started to try to load the * contact picture in a background thread. Depending on the result the contact picture or a * fallback picture is then stored in the bitmap cache. * </p> * * @param address * The {@link Address} instance holding the email address that is used to search the * contacts database. * @param imageView * The {@code ContactBadge} instance to receive the picture. * * @see #mBitmapCache * @see #calculateFallbackBitmap(Address) */ public void loadContactPicture(Address address, ImageView imageView) { Bitmap bitmap = getBitmapFromCache(address); if (bitmap != null) { // The picture was found in the bitmap cache imageView.setImageBitmap(bitmap); } else if (cancelPotentialWork(address, imageView)) { // Query the contacts database in a background thread and try to load the contact // picture, if there is one. ContactPictureRetrievalTask task = new ContactPictureRetrievalTask(imageView, address); AsyncDrawable asyncDrawable = new AsyncDrawable(mResources, calculateFallbackBitmap(address), task); imageView.setImageDrawable(asyncDrawable); try { task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } catch (RejectedExecutionException e) { // We flooded the thread pool queue... use a fallback picture imageView.setImageBitmap(calculateFallbackBitmap(address)); private void loadContactPicture(Uri photoUri, final Address address, final ImageView imageView) { if (photoUri != null) { RequestListener<Uri, GlideDrawable> noPhotoListener = new RequestListener<Uri, GlideDrawable>() { @Override public boolean onException(Exception e, Uri model, Target<GlideDrawable> target, boolean isFirstResource) { loadFallbackPicture(address, imageView); return true; } @Override public boolean onResourceReady(GlideDrawable resource, Uri model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) { return false; } }; Glide.with(imageView.getContext()) .load(photoUri) .diskCacheStrategy(DiskCacheStrategy.NONE) .listener(noPhotoListener) // for some reason, following 2 lines fix loading issues. .dontAnimate() .override(mPictureSizeInPx, mPictureSizeInPx) .into(imageView); } else { loadFallbackPicture(address, imageView); } } Loading @@ -176,23 +182,17 @@ public class ContactPictureLoader { return CONTACT_DUMMY_COLORS_ARGB[colorIndex]; } /** * Calculates a bitmap with a color and a capital letter for contacts without picture. */ private Bitmap calculateFallbackBitmap(Address address) { Bitmap result = Bitmap.createBitmap(mPictureSizeInPx, mPictureSizeInPx, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(result); private Bitmap drawTextAndBgColorOnBitmap(Bitmap bitmap, FallbackGlideParams params) { Canvas canvas = new Canvas(bitmap); int rgb = calcUnknownContactColor(address); result.eraseColor(rgb); int rgb = calcUnknownContactColor(params.address); bitmap.eraseColor(rgb); String letter = calcUnknownContactLetter(address); String letter = calcUnknownContactLetter(params.address); Paint paint = new Paint(); paint.setAntiAlias(true); paint.setStyle(Paint.Style.FILL); paint.setStyle(Style.FILL); paint.setARGB(255, 255, 255, 255); paint.setTextSize(mPictureSizeInPx * 3 / 4); // just scale this down a bit Rect rect = new Rect(); Loading @@ -202,146 +202,73 @@ public class ContactPictureLoader { (mPictureSizeInPx / 2f) - (width / 2f), (mPictureSizeInPx / 2f) + (rect.height() / 2f), paint); return result; return bitmap; } private void addBitmapToCache(Address key, Bitmap bitmap) { if (getBitmapFromCache(key) == null) { mBitmapCache.put(key, bitmap); } } private class FallbackGlideBitmapDecoder implements ResourceDecoder<FallbackGlideParams, Bitmap> { private final Context context; private Bitmap getBitmapFromCache(Address key) { return mBitmapCache.get(key); FallbackGlideBitmapDecoder(Context context) { this.context = context; } /** * Checks if a {@code ContactPictureRetrievalTask} was already created to load the contact * picture for the supplied {@code Address}. * * @param address * The {@link Address} instance holding the email address that is used to search the * contacts database. * @param imageView * The {@link ImageView} instance that will receive the picture. * * @return {@code true}, if the contact picture should be loaded in a background thread. * {@code false}, if another {@link ContactPictureRetrievalTask} was already scheduled * to load that contact picture. */ private boolean cancelPotentialWork(Address address, ImageView imageView) { final ContactPictureRetrievalTask task = getContactPictureRetrievalTask(imageView); if (task != null && address != null) { if (!address.equals(task.getAddress())) { // Cancel previous task task.cancel(true); } else { // The same work is already in progress return false; } @Override public Resource<Bitmap> decode(FallbackGlideParams source, int width, int height) throws IOException { BitmapPool pool = Glide.get(context).getBitmapPool(); Bitmap bitmap = pool.getDirty(mPictureSizeInPx, mPictureSizeInPx, Bitmap.Config.ARGB_8888); if (bitmap == null) { bitmap = Bitmap.createBitmap(mPictureSizeInPx, mPictureSizeInPx, Bitmap.Config.ARGB_8888); } // No task associated with the ContactBadge, or an existing task was cancelled return true; drawTextAndBgColorOnBitmap(bitmap, source); return BitmapResource.obtain(bitmap, pool); } private ContactPictureRetrievalTask getContactPictureRetrievalTask(ImageView imageView) { if (imageView != null) { Drawable drawable = imageView.getDrawable(); if (drawable instanceof AsyncDrawable) { AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; return asyncDrawable.getContactPictureRetrievalTask(); } @Override public String getId() { return "fallback-photo"; } return null; } private class FallbackGlideParams { final Address address; /** * Load a contact picture in a background thread. */ class ContactPictureRetrievalTask extends AsyncTask<Void, Void, Bitmap> { private final WeakReference<ImageView> mImageViewReference; private final Address mAddress; ContactPictureRetrievalTask(ImageView imageView, Address address) { mImageViewReference = new WeakReference<ImageView>(imageView); mAddress = new Address(address); FallbackGlideParams(Address address) { this.address = address; } public Address getAddress() { return mAddress; public String getId() { return String.format(Locale.ROOT, "%s-%s", address.getAddress(), address.getPersonal()); } } private class FallbackGlideModelLoader implements ModelLoader<FallbackGlideParams, FallbackGlideParams> { @Override protected Bitmap doInBackground(Void... args) { final String email = mAddress.getAddress(); final Uri photoUri = mContactsHelper.getPhotoUri(email); Bitmap bitmap = null; if (photoUri != null) { try { InputStream stream = mContentResolver.openInputStream(photoUri); if (stream != null) { try { Bitmap tempBitmap = BitmapFactory.decodeStream(stream); if (tempBitmap != null) { bitmap = Bitmap.createScaledBitmap(tempBitmap, mPictureSizeInPx, mPictureSizeInPx, true); if (tempBitmap != bitmap) { tempBitmap.recycle(); } } } finally { try { stream.close(); } catch (IOException e) { /* ignore */ } } } } catch (FileNotFoundException e) { /* ignore */ } public DataFetcher<FallbackGlideParams> getResourceFetcher(final FallbackGlideParams model, int width, int height) { } return new DataFetcher<FallbackGlideParams>() { if (bitmap == null) { bitmap = calculateFallbackBitmap(mAddress); @Override public FallbackGlideParams loadData(Priority priority) throws Exception { return model; } // Save the picture of the contact with that email address in the bitmap cache addBitmapToCache(mAddress, bitmap); @Override public void cleanup() { return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { ImageView imageView = mImageViewReference.get(); if (imageView != null && getContactPictureRetrievalTask(imageView) == this) { imageView.setImageBitmap(bitmap); } } public String getId() { return model.getId(); } /** * {@code Drawable} subclass that stores a reference to the {@link ContactPictureRetrievalTask} * that is trying to load the contact picture. * * <p> * The reference is used by {@link ContactPictureLoader#cancelPotentialWork(Address, * ImageView)} to find out if the contact picture is already being loaded by a * {@code ContactPictureRetrievalTask}. * </p> */ static class AsyncDrawable extends BitmapDrawable { private final WeakReference<ContactPictureRetrievalTask> mAsyncTaskReference; @Override public void cancel() { public AsyncDrawable(Resources res, Bitmap bitmap, ContactPictureRetrievalTask task) { super(res, bitmap); mAsyncTaskReference = new WeakReference<ContactPictureRetrievalTask>(task); } public ContactPictureRetrievalTask getContactPictureRetrievalTask() { return mAsyncTaskReference.get(); }; } } } k9mail/src/main/java/com/fsck/k9/helper/Contacts.java +5 −24 Original line number Diff line number Diff line Loading @@ -2,15 +2,14 @@ package com.fsck.k9.helper; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract; import timber.log.Timber; import android.provider.ContactsContract.CommonDataKinds.Photo; import com.fsck.k9.K9; import com.fsck.k9.mail.Address; /** Loading Loading @@ -229,7 +228,6 @@ public class Contacts { * no such contact could be found or the contact doesn't have a picture. */ public Uri getPhotoUri(String address) { Long contactId; try { final Cursor c = getContactByAddress(address); if (c == null) { Loading @@ -240,30 +238,13 @@ public class Contacts { if (!c.moveToFirst()) { return null; } contactId = c.getLong(CONTACT_ID_INDEX); final String uriString = c.getString(c.getColumnIndex(Photo.PHOTO_URI)); return Uri.parse(uriString); } catch (IllegalStateException e) { return null; } finally { c.close(); } Cursor cur = mContentResolver.query( ContactsContract.Data.CONTENT_URI, null, ContactsContract.Data.CONTACT_ID + "=" + contactId + " AND " + ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "'", null, null); if (cur == null) { return null; } if (!cur.moveToFirst()) { cur.close(); return null; // no photo } // Ok, they have a photo cur.close(); Uri person = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId); return Uri.withAppendedPath(person, ContactsContract.Contacts.Photo.CONTENT_DIRECTORY); } catch (Exception e) { Timber.e(e, "Couldn't fetch photo for contact with email %s", address); return null; Loading Loading
k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientAdapter.java +1 −11 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import android.widget.Filterable; import android.widget.ImageView; import android.widget.TextView; import com.bumptech.glide.Glide; import com.fsck.k9.R; import com.fsck.k9.helper.ContactPicture; import com.fsck.k9.view.RecipientSelectView.Recipient; Loading Loading @@ -126,16 +125,7 @@ public class RecipientAdapter extends BaseAdapter implements Filterable { } public static void setContactPhotoOrPlaceholder(Context context, ImageView imageView, Recipient recipient) { // TODO don't use two different mechanisms for loading! if (recipient.photoThumbnailUri != null) { Glide.with(context).load(recipient.photoThumbnailUri) // for some reason, this fixes loading issues. .placeholder(null) .dontAnimate() .into(imageView); } else { ContactPicture.getContactPictureLoader(context).loadContactPicture(recipient.address, imageView); } ContactPicture.getContactPictureLoader(context).loadContactPicture(recipient, imageView); } @Override Loading
k9mail/src/main/java/com/fsck/k9/activity/misc/ContactPictureLoader.java +124 −197 Original line number Diff line number Diff line package com.fsck.k9.activity.misc; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.Locale; import java.util.concurrent.RejectedExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.support.annotation.VisibleForTesting; import android.support.v4.util.LruCache; import android.text.TextUtils; import android.widget.ImageView; import com.bumptech.glide.Glide; import com.bumptech.glide.Priority; import com.bumptech.glide.load.ResourceDecoder; import com.bumptech.glide.load.data.DataFetcher; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.Resource; import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; import com.bumptech.glide.load.model.ModelLoader; import com.bumptech.glide.load.resource.bitmap.BitmapEncoder; import com.bumptech.glide.load.resource.bitmap.BitmapResource; import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder; import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.load.resource.file.FileToStreamDecoder; import com.bumptech.glide.load.resource.transcode.BitmapToGlideDrawableTranscoder; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; import com.fsck.k9.helper.Contacts; import com.fsck.k9.mail.Address; import com.fsck.k9.view.RecipientSelectView.Recipient; public class ContactPictureLoader { /** Loading @@ -47,18 +56,12 @@ public class ContactPictureLoader { private static final String FALLBACK_CONTACT_LETTER = "?"; private ContentResolver mContentResolver; private Resources mResources; private Contacts mContactsHelper; private int mPictureSizeInPx; private int mDefaultBackgroundColor; /** * LRU cache of contact pictures. */ private final LruCache<Address, Bitmap> mBitmapCache; /** * @see <a href="http://developer.android.com/design/style/color.html">Color palette used</a> */ Loading @@ -80,7 +83,6 @@ public class ContactPictureLoader { String letter = null; String personal = address.getPersonal(); String str = (personal != null) ? personal : address.getAddress(); Matcher m = EXTRACT_LETTER_PATTERN.matcher(str); if (m.find()) { letter = m.group(0).toUpperCase(Locale.US); Loading @@ -101,7 +103,6 @@ public class ContactPictureLoader { */ public ContactPictureLoader(Context context, int defaultBackgroundColor) { Context appContext = context.getApplicationContext(); mContentResolver = appContext.getContentResolver(); mResources = appContext.getResources(); mContactsHelper = Contacts.getInstance(appContext); Loading @@ -110,59 +111,64 @@ public class ContactPictureLoader { mDefaultBackgroundColor = defaultBackgroundColor; ActivityManager activityManager = (ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE); int memClass = activityManager.getMemoryClass(); } // Use 1/16th of the available memory for this memory cache. final int cacheSize = 1024 * 1024 * memClass / 16; public void loadContactPicture(final Address address, final ImageView imageView) { Uri photoUri = mContactsHelper.getPhotoUri(address.getAddress()); loadContactPicture(photoUri, address, imageView); } mBitmapCache = new LruCache<Address, Bitmap>(cacheSize) { @Override protected int sizeOf(Address key, Bitmap bitmap) { // The cache size will be measured in bytes rather than number of items. return bitmap.getByteCount(); public void loadContactPicture(Recipient recipient, ImageView imageView) { loadContactPicture(recipient.photoThumbnailUri, recipient.address, imageView); } }; private void loadFallbackPicture(Address address, ImageView imageView) { Context context = imageView.getContext(); Glide.with(context) .using(new FallbackGlideModelLoader(), FallbackGlideParams.class) .from(FallbackGlideParams.class) .as(Bitmap.class) .transcode(new BitmapToGlideDrawableTranscoder(context), GlideDrawable.class) .decoder(new FallbackGlideBitmapDecoder(context)) .encoder(new BitmapEncoder(Bitmap.CompressFormat.PNG, 0)) .cacheDecoder(new FileToStreamDecoder<>(new StreamBitmapDecoder(context))) .diskCacheStrategy(DiskCacheStrategy.NONE) .load(new FallbackGlideParams(address)) // for some reason, following 2 lines fix loading issues. .dontAnimate() .override(mPictureSizeInPx, mPictureSizeInPx) .into(imageView); } /** * Load a contact picture and display it using the supplied {@link ImageView} instance. * * <p> * If a picture is found in the cache, it is displayed in the {@code ContactBadge} * immediately. Otherwise a {@link ContactPictureRetrievalTask} is started to try to load the * contact picture in a background thread. Depending on the result the contact picture or a * fallback picture is then stored in the bitmap cache. * </p> * * @param address * The {@link Address} instance holding the email address that is used to search the * contacts database. * @param imageView * The {@code ContactBadge} instance to receive the picture. * * @see #mBitmapCache * @see #calculateFallbackBitmap(Address) */ public void loadContactPicture(Address address, ImageView imageView) { Bitmap bitmap = getBitmapFromCache(address); if (bitmap != null) { // The picture was found in the bitmap cache imageView.setImageBitmap(bitmap); } else if (cancelPotentialWork(address, imageView)) { // Query the contacts database in a background thread and try to load the contact // picture, if there is one. ContactPictureRetrievalTask task = new ContactPictureRetrievalTask(imageView, address); AsyncDrawable asyncDrawable = new AsyncDrawable(mResources, calculateFallbackBitmap(address), task); imageView.setImageDrawable(asyncDrawable); try { task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } catch (RejectedExecutionException e) { // We flooded the thread pool queue... use a fallback picture imageView.setImageBitmap(calculateFallbackBitmap(address)); private void loadContactPicture(Uri photoUri, final Address address, final ImageView imageView) { if (photoUri != null) { RequestListener<Uri, GlideDrawable> noPhotoListener = new RequestListener<Uri, GlideDrawable>() { @Override public boolean onException(Exception e, Uri model, Target<GlideDrawable> target, boolean isFirstResource) { loadFallbackPicture(address, imageView); return true; } @Override public boolean onResourceReady(GlideDrawable resource, Uri model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) { return false; } }; Glide.with(imageView.getContext()) .load(photoUri) .diskCacheStrategy(DiskCacheStrategy.NONE) .listener(noPhotoListener) // for some reason, following 2 lines fix loading issues. .dontAnimate() .override(mPictureSizeInPx, mPictureSizeInPx) .into(imageView); } else { loadFallbackPicture(address, imageView); } } Loading @@ -176,23 +182,17 @@ public class ContactPictureLoader { return CONTACT_DUMMY_COLORS_ARGB[colorIndex]; } /** * Calculates a bitmap with a color and a capital letter for contacts without picture. */ private Bitmap calculateFallbackBitmap(Address address) { Bitmap result = Bitmap.createBitmap(mPictureSizeInPx, mPictureSizeInPx, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(result); private Bitmap drawTextAndBgColorOnBitmap(Bitmap bitmap, FallbackGlideParams params) { Canvas canvas = new Canvas(bitmap); int rgb = calcUnknownContactColor(address); result.eraseColor(rgb); int rgb = calcUnknownContactColor(params.address); bitmap.eraseColor(rgb); String letter = calcUnknownContactLetter(address); String letter = calcUnknownContactLetter(params.address); Paint paint = new Paint(); paint.setAntiAlias(true); paint.setStyle(Paint.Style.FILL); paint.setStyle(Style.FILL); paint.setARGB(255, 255, 255, 255); paint.setTextSize(mPictureSizeInPx * 3 / 4); // just scale this down a bit Rect rect = new Rect(); Loading @@ -202,146 +202,73 @@ public class ContactPictureLoader { (mPictureSizeInPx / 2f) - (width / 2f), (mPictureSizeInPx / 2f) + (rect.height() / 2f), paint); return result; return bitmap; } private void addBitmapToCache(Address key, Bitmap bitmap) { if (getBitmapFromCache(key) == null) { mBitmapCache.put(key, bitmap); } } private class FallbackGlideBitmapDecoder implements ResourceDecoder<FallbackGlideParams, Bitmap> { private final Context context; private Bitmap getBitmapFromCache(Address key) { return mBitmapCache.get(key); FallbackGlideBitmapDecoder(Context context) { this.context = context; } /** * Checks if a {@code ContactPictureRetrievalTask} was already created to load the contact * picture for the supplied {@code Address}. * * @param address * The {@link Address} instance holding the email address that is used to search the * contacts database. * @param imageView * The {@link ImageView} instance that will receive the picture. * * @return {@code true}, if the contact picture should be loaded in a background thread. * {@code false}, if another {@link ContactPictureRetrievalTask} was already scheduled * to load that contact picture. */ private boolean cancelPotentialWork(Address address, ImageView imageView) { final ContactPictureRetrievalTask task = getContactPictureRetrievalTask(imageView); if (task != null && address != null) { if (!address.equals(task.getAddress())) { // Cancel previous task task.cancel(true); } else { // The same work is already in progress return false; } @Override public Resource<Bitmap> decode(FallbackGlideParams source, int width, int height) throws IOException { BitmapPool pool = Glide.get(context).getBitmapPool(); Bitmap bitmap = pool.getDirty(mPictureSizeInPx, mPictureSizeInPx, Bitmap.Config.ARGB_8888); if (bitmap == null) { bitmap = Bitmap.createBitmap(mPictureSizeInPx, mPictureSizeInPx, Bitmap.Config.ARGB_8888); } // No task associated with the ContactBadge, or an existing task was cancelled return true; drawTextAndBgColorOnBitmap(bitmap, source); return BitmapResource.obtain(bitmap, pool); } private ContactPictureRetrievalTask getContactPictureRetrievalTask(ImageView imageView) { if (imageView != null) { Drawable drawable = imageView.getDrawable(); if (drawable instanceof AsyncDrawable) { AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; return asyncDrawable.getContactPictureRetrievalTask(); } @Override public String getId() { return "fallback-photo"; } return null; } private class FallbackGlideParams { final Address address; /** * Load a contact picture in a background thread. */ class ContactPictureRetrievalTask extends AsyncTask<Void, Void, Bitmap> { private final WeakReference<ImageView> mImageViewReference; private final Address mAddress; ContactPictureRetrievalTask(ImageView imageView, Address address) { mImageViewReference = new WeakReference<ImageView>(imageView); mAddress = new Address(address); FallbackGlideParams(Address address) { this.address = address; } public Address getAddress() { return mAddress; public String getId() { return String.format(Locale.ROOT, "%s-%s", address.getAddress(), address.getPersonal()); } } private class FallbackGlideModelLoader implements ModelLoader<FallbackGlideParams, FallbackGlideParams> { @Override protected Bitmap doInBackground(Void... args) { final String email = mAddress.getAddress(); final Uri photoUri = mContactsHelper.getPhotoUri(email); Bitmap bitmap = null; if (photoUri != null) { try { InputStream stream = mContentResolver.openInputStream(photoUri); if (stream != null) { try { Bitmap tempBitmap = BitmapFactory.decodeStream(stream); if (tempBitmap != null) { bitmap = Bitmap.createScaledBitmap(tempBitmap, mPictureSizeInPx, mPictureSizeInPx, true); if (tempBitmap != bitmap) { tempBitmap.recycle(); } } } finally { try { stream.close(); } catch (IOException e) { /* ignore */ } } } } catch (FileNotFoundException e) { /* ignore */ } public DataFetcher<FallbackGlideParams> getResourceFetcher(final FallbackGlideParams model, int width, int height) { } return new DataFetcher<FallbackGlideParams>() { if (bitmap == null) { bitmap = calculateFallbackBitmap(mAddress); @Override public FallbackGlideParams loadData(Priority priority) throws Exception { return model; } // Save the picture of the contact with that email address in the bitmap cache addBitmapToCache(mAddress, bitmap); @Override public void cleanup() { return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { ImageView imageView = mImageViewReference.get(); if (imageView != null && getContactPictureRetrievalTask(imageView) == this) { imageView.setImageBitmap(bitmap); } } public String getId() { return model.getId(); } /** * {@code Drawable} subclass that stores a reference to the {@link ContactPictureRetrievalTask} * that is trying to load the contact picture. * * <p> * The reference is used by {@link ContactPictureLoader#cancelPotentialWork(Address, * ImageView)} to find out if the contact picture is already being loaded by a * {@code ContactPictureRetrievalTask}. * </p> */ static class AsyncDrawable extends BitmapDrawable { private final WeakReference<ContactPictureRetrievalTask> mAsyncTaskReference; @Override public void cancel() { public AsyncDrawable(Resources res, Bitmap bitmap, ContactPictureRetrievalTask task) { super(res, bitmap); mAsyncTaskReference = new WeakReference<ContactPictureRetrievalTask>(task); } public ContactPictureRetrievalTask getContactPictureRetrievalTask() { return mAsyncTaskReference.get(); }; } } }
k9mail/src/main/java/com/fsck/k9/helper/Contacts.java +5 −24 Original line number Diff line number Diff line Loading @@ -2,15 +2,14 @@ package com.fsck.k9.helper; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract; import timber.log.Timber; import android.provider.ContactsContract.CommonDataKinds.Photo; import com.fsck.k9.K9; import com.fsck.k9.mail.Address; /** Loading Loading @@ -229,7 +228,6 @@ public class Contacts { * no such contact could be found or the contact doesn't have a picture. */ public Uri getPhotoUri(String address) { Long contactId; try { final Cursor c = getContactByAddress(address); if (c == null) { Loading @@ -240,30 +238,13 @@ public class Contacts { if (!c.moveToFirst()) { return null; } contactId = c.getLong(CONTACT_ID_INDEX); final String uriString = c.getString(c.getColumnIndex(Photo.PHOTO_URI)); return Uri.parse(uriString); } catch (IllegalStateException e) { return null; } finally { c.close(); } Cursor cur = mContentResolver.query( ContactsContract.Data.CONTENT_URI, null, ContactsContract.Data.CONTACT_ID + "=" + contactId + " AND " + ContactsContract.Data.MIMETYPE + "='" + ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE + "'", null, null); if (cur == null) { return null; } if (!cur.moveToFirst()) { cur.close(); return null; // no photo } // Ok, they have a photo cur.close(); Uri person = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId); return Uri.withAppendedPath(person, ContactsContract.Contacts.Photo.CONTENT_DIRECTORY); } catch (Exception e) { Timber.e(e, "Couldn't fetch photo for contact with email %s", address); return null; Loading