Loading contact photo from URI if ID is unavailable
Change-Id: Ibfb1de5d1a6bf6789179af330413003a1a8d9342
diff --git a/src/com/android/contacts/ContactPhotoLoader.java b/src/com/android/contacts/ContactPhotoLoader.java
index 96f55a6..b95d680 100644
--- a/src/com/android/contacts/ContactPhotoLoader.java
+++ b/src/com/android/contacts/ContactPhotoLoader.java
@@ -23,14 +23,18 @@
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.net.Uri;
import android.os.Handler;
+import android.os.Handler.Callback;
import android.os.HandlerThread;
import android.os.Message;
-import android.os.Handler.Callback;
-import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Contacts.Photo;
+import android.provider.ContactsContract.Data;
+import android.util.Log;
import android.widget.ImageView;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Iterator;
@@ -39,11 +43,11 @@
/**
* Asynchronously loads contact photos and maintains cache of photos. The class is
* mostly single-threaded. The only two methods accessed by the loader thread are
- * {@link #cacheBitmap} and {@link #obtainPhotoIdsToLoad}. Those methods access concurrent
+ * {@link #cacheBitmap} and {@link #obtainPhotoIdsAndUrisToLoad}. Those methods access concurrent
* hash maps shared with the main thread.
*/
public class ContactPhotoLoader implements Callback {
-
+ private static final String TAG = "ContactPhotoLoader";
private static final String LOADER_THREAD_NAME = "ContactPhotoLoader";
/**
@@ -83,15 +87,15 @@
/**
* A soft cache for photos.
*/
- private final ConcurrentHashMap<Long, BitmapHolder> mBitmapCache =
- new ConcurrentHashMap<Long, BitmapHolder>();
+ private final ConcurrentHashMap<Object, BitmapHolder> mBitmapCache =
+ new ConcurrentHashMap<Object, BitmapHolder>();
/**
* A map from ImageView to the corresponding photo ID. Please note that this
* photo ID may change before the photo loading request is started.
*/
- private final ConcurrentHashMap<ImageView, Long> mPendingRequests =
- new ConcurrentHashMap<ImageView, Long>();
+ private final ConcurrentHashMap<ImageView, Object> mPendingRequests =
+ new ConcurrentHashMap<ImageView, Object>();
/**
* Handler for messages sent to the UI thread.
@@ -139,15 +143,34 @@
view.setImageResource(mDefaultResourceId);
mPendingRequests.remove(view);
} else {
- boolean loaded = loadCachedPhoto(view, photoId);
- if (loaded) {
- mPendingRequests.remove(view);
- } else {
- mPendingRequests.put(view, photoId);
- if (!mPaused) {
- // Send a request to start loading photos
- requestLoading();
- }
+ loadPhotoByIdOrUri(view, photoId);
+ }
+ }
+
+ /**
+ * Load photo into the supplied image view. If the photo is already cached,
+ * it is displayed immediately. Otherwise a request is sent to load the photo
+ * from the location specified by the URI.
+ */
+ public void loadPhoto(ImageView view, Uri photoUri) {
+ if (photoUri == null) {
+ // No photo is needed
+ view.setImageResource(mDefaultResourceId);
+ mPendingRequests.remove(view);
+ } else {
+ loadPhotoByIdOrUri(view, photoUri);
+ }
+ }
+
+ private void loadPhotoByIdOrUri(ImageView view, Object key) {
+ boolean loaded = loadCachedPhoto(view, key);
+ if (loaded) {
+ mPendingRequests.remove(view);
+ } else {
+ mPendingRequests.put(view, key);
+ if (!mPaused) {
+ // Send a request to start loading photos
+ requestLoading();
}
}
}
@@ -157,11 +180,11 @@
* otherwise sets the state of the photo to {@link BitmapHolder#NEEDED} and
* temporarily set the image to the default resource ID.
*/
- private boolean loadCachedPhoto(ImageView view, long photoId) {
- BitmapHolder holder = mBitmapCache.get(photoId);
+ private boolean loadCachedPhoto(ImageView view, Object key) {
+ BitmapHolder holder = mBitmapCache.get(key);
if (holder == null) {
holder = new BitmapHolder();
- mBitmapCache.put(photoId, holder);
+ mBitmapCache.put(key, holder);
} else if (holder.state == BitmapHolder.LOADED) {
// Null bitmap reference means that database contains no bytes for the photo
if (holder.bitmapRef == null) {
@@ -272,8 +295,8 @@
Iterator<ImageView> iterator = mPendingRequests.keySet().iterator();
while (iterator.hasNext()) {
ImageView view = iterator.next();
- long photoId = mPendingRequests.get(view);
- boolean loaded = loadCachedPhoto(view, photoId);
+ Object key = mPendingRequests.get(view);
+ boolean loaded = loadCachedPhoto(view, key);
if (loaded) {
iterator.remove();
}
@@ -287,7 +310,7 @@
/**
* Stores the supplied bitmap in cache.
*/
- private void cacheBitmap(long id, byte[] bytes) {
+ private void cacheBitmap(Object key, byte[] bytes) {
if (mPaused) {
return;
}
@@ -302,14 +325,14 @@
// Do nothing - the photo will appear to be missing
}
}
- mBitmapCache.put(id, holder);
+ mBitmapCache.put(key, holder);
}
/**
* Populates an array of photo IDs that need to be loaded.
*/
- private void obtainPhotoIdsToLoad(ArrayList<Long> photoIds,
- ArrayList<String> photoIdsAsStrings) {
+ private void obtainPhotoIdsAndUrisToLoad(ArrayList<Long> photoIds,
+ ArrayList<String> photoIdsAsStrings, ArrayList<Uri> uris) {
photoIds.clear();
photoIdsAsStrings.clear();
@@ -321,15 +344,19 @@
* concurrent change, we will need to check the map again once loading
* is complete.
*/
- Iterator<Long> iterator = mPendingRequests.values().iterator();
+ Iterator<Object> iterator = mPendingRequests.values().iterator();
while (iterator.hasNext()) {
- Long id = iterator.next();
- BitmapHolder holder = mBitmapCache.get(id);
+ Object key = iterator.next();
+ BitmapHolder holder = mBitmapCache.get(key);
if (holder != null && holder.state == BitmapHolder.NEEDED) {
// Assuming atomic behavior
holder.state = BitmapHolder.LOADING;
- photoIds.add(id);
- photoIdsAsStrings.add(id.toString());
+ if (key instanceof Long) {
+ photoIds.add((Long)key);
+ photoIdsAsStrings.add(key.toString());
+ } else {
+ uris.add((Uri)key);
+ }
}
}
}
@@ -338,11 +365,15 @@
* The thread that performs loading of photos from the database.
*/
private class LoaderThread extends HandlerThread implements Callback {
+ private static final int BUFFER_SIZE = 1024*16;
+
private final ContentResolver mResolver;
private final StringBuilder mStringBuilder = new StringBuilder();
private final ArrayList<Long> mPhotoIds = Lists.newArrayList();
private final ArrayList<String> mPhotoIdsAsStrings = Lists.newArrayList();
+ private final ArrayList<Uri> mPhotoUris = Lists.newArrayList();
private Handler mLoaderThreadHandler;
+ private byte mBuffer[];
public LoaderThread(ContentResolver resolver) {
super(LOADER_THREAD_NAME);
@@ -365,54 +396,79 @@
*/
public boolean handleMessage(Message msg) {
loadPhotosFromDatabase();
- mMainThreadHandler.sendEmptyMessage(MESSAGE_PHOTOS_LOADED);
return true;
}
private void loadPhotosFromDatabase() {
- obtainPhotoIdsToLoad(mPhotoIds, mPhotoIdsAsStrings);
+ obtainPhotoIdsAndUrisToLoad(mPhotoIds, mPhotoIdsAsStrings, mPhotoUris);
int count = mPhotoIds.size();
- if (count == 0) {
- return;
- }
-
- mStringBuilder.setLength(0);
- mStringBuilder.append(Photo._ID + " IN(");
- for (int i = 0; i < count; i++) {
- if (i != 0) {
- mStringBuilder.append(',');
+ if (count != 0) {
+ mStringBuilder.setLength(0);
+ mStringBuilder.append(Photo._ID + " IN(");
+ for (int i = 0; i < count; i++) {
+ if (i != 0) {
+ mStringBuilder.append(',');
+ }
+ mStringBuilder.append('?');
}
- mStringBuilder.append('?');
- }
- mStringBuilder.append(')');
+ mStringBuilder.append(')');
- Cursor cursor = null;
- try {
- cursor = mResolver.query(Data.CONTENT_URI,
- COLUMNS,
- mStringBuilder.toString(),
- mPhotoIdsAsStrings.toArray(EMPTY_STRING_ARRAY),
- null);
+ Cursor cursor = null;
+ try {
+ cursor = mResolver.query(Data.CONTENT_URI,
+ COLUMNS,
+ mStringBuilder.toString(),
+ mPhotoIdsAsStrings.toArray(EMPTY_STRING_ARRAY),
+ null);
- if (cursor != null) {
- while (cursor.moveToNext()) {
- Long id = cursor.getLong(0);
- byte[] bytes = cursor.getBlob(1);
- cacheBitmap(id, bytes);
- mPhotoIds.remove(id);
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ Long id = cursor.getLong(0);
+ byte[] bytes = cursor.getBlob(1);
+ cacheBitmap(id, bytes);
+ mPhotoIds.remove(id);
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
}
}
- } finally {
- if (cursor != null) {
- cursor.close();
+
+ // Remaining photos were not found in the database - mark the cache accordingly.
+ count = mPhotoIds.size();
+ for (int i = 0; i < count; i++) {
+ cacheBitmap(mPhotoIds.get(i), null);
}
+ mMainThreadHandler.sendEmptyMessage(MESSAGE_PHOTOS_LOADED);
}
- // Remaining photos were not found in the database - mark the cache accordingly.
- count = mPhotoIds.size();
+ count = mPhotoUris.size();
for (int i = 0; i < count; i++) {
- cacheBitmap(mPhotoIds.get(i), null);
+ Uri uri = mPhotoUris.get(i);
+ if (mBuffer == null) {
+ mBuffer = new byte[BUFFER_SIZE];
+ }
+ try {
+ InputStream is = mResolver.openInputStream(uri);
+ if (is != null) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ int size;
+ while ((size = is.read(mBuffer)) != -1) {
+ baos.write(mBuffer, 0, size);
+ }
+ } finally {
+ is.close();
+ }
+ cacheBitmap(uri, baos.toByteArray());
+ mMainThreadHandler.sendEmptyMessage(MESSAGE_PHOTOS_LOADED);
+ }
+ } catch (Exception ex) {
+ Log.v(TAG, "Cannot load photo " + uri, ex);
+ cacheBitmap(uri, null);
+ }
}
}
}