diff --git a/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientAdapter.java b/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientAdapter.java
index 4ba12e306..75f8f6de2 100644
--- a/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientAdapter.java
+++ b/k9mail/src/main/java/com/fsck/k9/activity/compose/RecipientAdapter.java
@@ -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;
@@ -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
diff --git a/k9mail/src/main/java/com/fsck/k9/activity/misc/ContactPictureLoader.java b/k9mail/src/main/java/com/fsck/k9/activity/misc/ContactPictureLoader.java
index 46ca7f895..53494e78b 100644
--- a/k9mail/src/main/java/com/fsck/k9/activity/misc/ContactPictureLoader.java
+++ b/k9mail/src/main/java/com/fsck/k9/activity/misc/ContactPictureLoader.java
@@ -1,34 +1,43 @@
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 {
/**
@@ -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
mBitmapCache;
-
/**
* @see Color palette used
*/
@@ -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);
@@ -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);
@@ -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;
-
- mBitmapCache = new LruCache(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();
- }
- };
}
- /**
- * Load a contact picture and display it using the supplied {@link ImageView} instance.
- *
- *
- * 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.
- *
- *
- * @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));
- }
+ public void loadContactPicture(final Address address, final ImageView imageView) {
+ Uri photoUri = mContactsHelper.getPhotoUri(address.getAddress());
+ loadContactPicture(photoUri, address, imageView);
+ }
+
+ 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);
+ }
+
+ private void loadContactPicture(Uri photoUri, final Address address, final ImageView imageView) {
+ if (photoUri != null) {
+ RequestListener noPhotoListener = new RequestListener() {
+ @Override
+ public boolean onException(Exception e, Uri model, Target target,
+ boolean isFirstResource) {
+ loadFallbackPicture(address, imageView);
+ return true;
+ }
+
+ @Override
+ public boolean onResourceReady(GlideDrawable resource, Uri model,
+ Target 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);
}
}
@@ -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);
+ private Bitmap drawTextAndBgColorOnBitmap(Bitmap bitmap, FallbackGlideParams params) {
+ Canvas canvas = new Canvas(bitmap);
- Canvas canvas = new Canvas(result);
+ int rgb = calcUnknownContactColor(params.address);
+ bitmap.eraseColor(rgb);
- int rgb = calcUnknownContactColor(address);
- result.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();
@@ -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 {
+ private final Context context;
- private Bitmap getBitmapFromCache(Address key) {
- return mBitmapCache.get(key);
- }
-
- /**
- * 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;
- }
- }
-
- // No task associated with the ContactBadge, or an existing task was cancelled
- return true;
- }
-
- private ContactPictureRetrievalTask getContactPictureRetrievalTask(ImageView imageView) {
- if (imageView != null) {
- Drawable drawable = imageView.getDrawable();
- if (drawable instanceof AsyncDrawable) {
- AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
- return asyncDrawable.getContactPictureRetrievalTask();
- }
- }
-
- return null;
- }
-
-
- /**
- * Load a contact picture in a background thread.
- */
- class ContactPictureRetrievalTask extends AsyncTask {
- private final WeakReference mImageViewReference;
- private final Address mAddress;
-
- ContactPictureRetrievalTask(ImageView imageView, Address address) {
- mImageViewReference = new WeakReference(imageView);
- mAddress = new Address(address);
- }
-
- public Address getAddress() {
- return mAddress;
+ FallbackGlideBitmapDecoder(Context context) {
+ this.context = context;
}
@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 Resource 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);
+ }
+ drawTextAndBgColorOnBitmap(bitmap, source);
+ return BitmapResource.obtain(bitmap, pool);
+ }
+
+ @Override
+ public String getId() {
+ return "fallback-photo";
+ }
+ }
+
+ private class FallbackGlideParams {
+ final Address address;
+
+ FallbackGlideParams(Address address) {
+ this.address = address;
+ }
+
+ public String getId() {
+ return String.format(Locale.ROOT, "%s-%s", address.getAddress(), address.getPersonal());
+ }
+ }
+
+ private class FallbackGlideModelLoader implements ModelLoader {
+ @Override
+ public DataFetcher getResourceFetcher(final FallbackGlideParams model, int width,
+ int height) {
+
+ return new DataFetcher() {
+
+ @Override
+ public FallbackGlideParams loadData(Priority priority) throws Exception {
+ return model;
}
- }
+ @Override
+ public void cleanup() {
- if (bitmap == null) {
- bitmap = calculateFallbackBitmap(mAddress);
- }
+ }
- // Save the picture of the contact with that email address in the bitmap cache
- addBitmapToCache(mAddress, bitmap);
+ @Override
+ public String getId() {
+ return model.getId();
+ }
- return bitmap;
- }
+ @Override
+ public void cancel() {
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- ImageView imageView = mImageViewReference.get();
- if (imageView != null && getContactPictureRetrievalTask(imageView) == this) {
- imageView.setImageBitmap(bitmap);
- }
+ }
+ };
}
}
- /**
- * {@code Drawable} subclass that stores a reference to the {@link ContactPictureRetrievalTask}
- * that is trying to load the contact picture.
- *
- *
- * 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}.
- *
- */
- static class AsyncDrawable extends BitmapDrawable {
- private final WeakReference mAsyncTaskReference;
-
- public AsyncDrawable(Resources res, Bitmap bitmap, ContactPictureRetrievalTask task) {
- super(res, bitmap);
- mAsyncTaskReference = new WeakReference(task);
- }
-
- public ContactPictureRetrievalTask getContactPictureRetrievalTask() {
- return mAsyncTaskReference.get();
- }
- }
}
diff --git a/k9mail/src/main/java/com/fsck/k9/helper/Contacts.java b/k9mail/src/main/java/com/fsck/k9/helper/Contacts.java
index 0759e9ec5..066c59c77 100644
--- a/k9mail/src/main/java/com/fsck/k9/helper/Contacts.java
+++ b/k9mail/src/main/java/com/fsck/k9/helper/Contacts.java
@@ -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;
/**
@@ -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) {
@@ -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;