blob: ddd6a0e84d90a7f433e9d33d2dbb239920beee56 [file] [log] [blame]
Dmitri Plotnikove8643852010-02-17 10:49:05 -08001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Dmitri Plotnikov022b62d2011-01-28 12:16:07 -080017package com.android.contacts;
Dmitri Plotnikove8643852010-02-17 10:49:05 -080018
Dmitri Plotnikov34b24ef2011-01-28 13:41:07 -080019import com.android.contacts.model.AccountTypeManager;
Dmitri Plotnikove8643852010-02-17 10:49:05 -080020import com.google.android.collect.Lists;
21
22import android.content.ContentResolver;
23import android.content.Context;
24import android.database.Cursor;
25import android.graphics.Bitmap;
26import android.graphics.BitmapFactory;
Dmitri Plotnikoveb689432010-09-24 10:10:57 -070027import android.net.Uri;
Dmitri Plotnikove8643852010-02-17 10:49:05 -080028import android.os.Handler;
Dmitri Plotnikoveb689432010-09-24 10:10:57 -070029import android.os.Handler.Callback;
Dmitri Plotnikove8643852010-02-17 10:49:05 -080030import android.os.HandlerThread;
31import android.os.Message;
Dmitri Plotnikove8643852010-02-17 10:49:05 -080032import android.provider.ContactsContract.Contacts.Photo;
Dmitri Plotnikoveb689432010-09-24 10:10:57 -070033import android.provider.ContactsContract.Data;
34import android.util.Log;
Dmitri Plotnikove8643852010-02-17 10:49:05 -080035import android.widget.ImageView;
36
Dmitri Plotnikoveb689432010-09-24 10:10:57 -070037import java.io.ByteArrayOutputStream;
38import java.io.InputStream;
Dmitri Plotnikove8643852010-02-17 10:49:05 -080039import java.lang.ref.SoftReference;
40import java.util.ArrayList;
41import java.util.Iterator;
42import java.util.concurrent.ConcurrentHashMap;
43
44/**
Dmitri Plotnikov34b24ef2011-01-28 13:41:07 -080045 * Asynchronously loads contact photos and maintains a cache of photos.
Dmitri Plotnikove8643852010-02-17 10:49:05 -080046 */
Dmitri Plotnikov34b24ef2011-01-28 13:41:07 -080047public abstract class ContactPhotoManager {
48
49 static final String TAG = "ContactPhotoManager";
50
51 public static final String CONTACT_PHOTO_SERVICE = "contactPhotos";
52
53 /**
54 * The resource ID of the image to be used when the photo is unavailable or being
55 * loaded.
56 */
57 protected final int mDefaultResourceId = R.drawable.ic_contact_picture;
58
59 /**
60 * Requests the singleton instance of {@link AccountTypeManager} with data bound from
61 * the available authenticators. This method can safely be called from the UI thread.
62 */
63 public static ContactPhotoManager getInstance(Context context) {
64 ContactPhotoManager service =
65 (ContactPhotoManager) context.getSystemService(CONTACT_PHOTO_SERVICE);
66 if (service == null) {
67 service = createContactPhotoManager(context);
68 Log.e(TAG, "No contact photo service in context: " + context);
69 }
70 return service;
71 }
72
73 public static synchronized ContactPhotoManager createContactPhotoManager(Context context) {
74 return new ContactPhotoManagerImpl(context);
75 }
76
77 /**
78 * Load photo into the supplied image view. If the photo is already cached,
79 * it is displayed immediately. Otherwise a request is sent to load the photo
80 * from the database.
81 */
82 public abstract void loadPhoto(ImageView view, long photoId);
83
84 /**
85 * Load photo into the supplied image view. If the photo is already cached,
86 * it is displayed immediately. Otherwise a request is sent to load the photo
87 * from the location specified by the URI.
88 */
89 public abstract void loadPhoto(ImageView view, Uri photoUri);
90
91 /**
92 * Temporarily stops loading photos from the database.
93 */
94 public abstract void pause();
95
96 /**
97 * Resumes loading photos from the database.
98 */
99 public abstract void resume();
100
101 /**
102 * Marks all cached photos for reloading. We can continue using cache but should
103 * also make sure the photos haven't changed in the background and notify the views
104 * if so.
105 */
106 public abstract void refreshCache();
107}
108
109class ContactPhotoManagerImpl extends ContactPhotoManager implements Callback {
Dmitri Plotnikov33409852010-02-20 15:02:32 -0800110 private static final String LOADER_THREAD_NAME = "ContactPhotoLoader";
111
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800112 /**
113 * Type of message sent by the UI thread to itself to indicate that some photos
114 * need to be loaded.
115 */
116 private static final int MESSAGE_REQUEST_LOADING = 1;
117
118 /**
119 * Type of message sent by the loader thread to indicate that some photos have
120 * been loaded.
121 */
122 private static final int MESSAGE_PHOTOS_LOADED = 2;
123
124 private static final String[] EMPTY_STRING_ARRAY = new String[0];
125
Dmitri Plotnikov33409852010-02-20 15:02:32 -0800126 private final String[] COLUMNS = new String[] { Photo._ID, Photo.PHOTO };
127
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800128 /**
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800129 * Maintains the state of a particular photo.
130 */
131 private static class BitmapHolder {
132 private static final int NEEDED = 0;
133 private static final int LOADING = 1;
134 private static final int LOADED = 2;
Dmitri Plotnikov437babb2010-11-23 18:29:50 -0800135 private static final int LOADED_NEEDS_RELOAD = 3;
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800136
137 int state;
Dmitri Plotnikov37dc7cf2010-12-17 19:25:08 -0800138 Bitmap bitmap;
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800139 SoftReference<Bitmap> bitmapRef;
140 }
141
Dmitri Plotnikov34b24ef2011-01-28 13:41:07 -0800142 private final Context mContext;
143
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800144 /**
145 * A soft cache for photos.
146 */
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700147 private final ConcurrentHashMap<Object, BitmapHolder> mBitmapCache =
148 new ConcurrentHashMap<Object, BitmapHolder>();
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800149
150 /**
151 * A map from ImageView to the corresponding photo ID. Please note that this
152 * photo ID may change before the photo loading request is started.
153 */
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700154 private final ConcurrentHashMap<ImageView, Object> mPendingRequests =
155 new ConcurrentHashMap<ImageView, Object>();
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800156
157 /**
158 * Handler for messages sent to the UI thread.
159 */
160 private final Handler mMainThreadHandler = new Handler(this);
161
162 /**
163 * Thread responsible for loading photos from the database. Created upon
164 * the first request.
165 */
166 private LoaderThread mLoaderThread;
167
168 /**
169 * A gate to make sure we only send one instance of MESSAGE_PHOTOS_NEEDED at a time.
170 */
171 private boolean mLoadingRequested;
172
173 /**
174 * Flag indicating if the image loading is paused.
175 */
176 private boolean mPaused;
177
Dmitri Plotnikov34b24ef2011-01-28 13:41:07 -0800178 public ContactPhotoManagerImpl(Context context) {
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800179 mContext = context;
180 }
181
Dmitri Plotnikov34b24ef2011-01-28 13:41:07 -0800182 @Override
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800183 public void loadPhoto(ImageView view, long photoId) {
184 if (photoId == 0) {
185 // No photo is needed
186 view.setImageResource(mDefaultResourceId);
187 mPendingRequests.remove(view);
188 } else {
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700189 loadPhotoByIdOrUri(view, photoId);
190 }
191 }
192
Dmitri Plotnikov34b24ef2011-01-28 13:41:07 -0800193 @Override
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700194 public void loadPhoto(ImageView view, Uri photoUri) {
195 if (photoUri == null) {
196 // No photo is needed
197 view.setImageResource(mDefaultResourceId);
198 mPendingRequests.remove(view);
199 } else {
200 loadPhotoByIdOrUri(view, photoUri);
201 }
202 }
203
204 private void loadPhotoByIdOrUri(ImageView view, Object key) {
205 boolean loaded = loadCachedPhoto(view, key);
206 if (loaded) {
207 mPendingRequests.remove(view);
208 } else {
209 mPendingRequests.put(view, key);
210 if (!mPaused) {
211 // Send a request to start loading photos
212 requestLoading();
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800213 }
214 }
215 }
216
Dmitri Plotnikov34b24ef2011-01-28 13:41:07 -0800217 @Override
Dmitri Plotnikov718a2502010-11-23 17:56:28 -0800218 public void refreshCache() {
219 for (BitmapHolder holder : mBitmapCache.values()) {
Dmitri Plotnikov437babb2010-11-23 18:29:50 -0800220 if (holder.state == BitmapHolder.LOADED) {
221 holder.state = BitmapHolder.LOADED_NEEDS_RELOAD;
Dmitri Plotnikov718a2502010-11-23 17:56:28 -0800222 }
223 }
224 }
225
226 /**
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800227 * Checks if the photo is present in cache. If so, sets the photo on the view,
228 * otherwise sets the state of the photo to {@link BitmapHolder#NEEDED} and
229 * temporarily set the image to the default resource ID.
230 */
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700231 private boolean loadCachedPhoto(ImageView view, Object key) {
232 BitmapHolder holder = mBitmapCache.get(key);
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800233 if (holder == null) {
234 holder = new BitmapHolder();
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700235 mBitmapCache.put(key, holder);
Dmitri Plotnikov718a2502010-11-23 17:56:28 -0800236 } else {
Dmitri Plotnikov437babb2010-11-23 18:29:50 -0800237 boolean loaded = (holder.state == BitmapHolder.LOADED);
238 boolean loadedNeedsReload = (holder.state == BitmapHolder.LOADED_NEEDS_RELOAD);
239 if (loadedNeedsReload) {
Dmitri Plotnikov718a2502010-11-23 17:56:28 -0800240 holder.state = BitmapHolder.NEEDED;
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800241 }
242
Dmitri Plotnikov99f9d532010-12-17 14:58:51 -0800243 // Null bitmap reference means that database contains no bytes for the photo
244 if ((loaded || loadedNeedsReload) && holder.bitmapRef == null) {
245 view.setImageResource(mDefaultResourceId);
246 return loaded;
247 }
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800248
Dmitri Plotnikov99f9d532010-12-17 14:58:51 -0800249 if (holder.bitmapRef != null) {
Dmitri Plotnikov718a2502010-11-23 17:56:28 -0800250 Bitmap bitmap = holder.bitmapRef.get();
251 if (bitmap != null) {
252 view.setImageBitmap(bitmap);
Dmitri Plotnikov437babb2010-11-23 18:29:50 -0800253 return loaded;
Dmitri Plotnikov718a2502010-11-23 17:56:28 -0800254 }
255
256 // Null bitmap means that the soft reference was released by the GC
257 // and we need to reload the photo.
258 holder.bitmapRef = null;
259 }
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800260 }
261
262 // The bitmap has not been loaded - should display the placeholder image.
263 view.setImageResource(mDefaultResourceId);
264 holder.state = BitmapHolder.NEEDED;
265 return false;
266 }
267
Dmitri Plotnikovb369c492010-03-24 18:11:24 -0700268 public void clear() {
269 mPendingRequests.clear();
270 mBitmapCache.clear();
271 }
272
Dmitri Plotnikov34b24ef2011-01-28 13:41:07 -0800273 @Override
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800274 public void pause() {
275 mPaused = true;
276 }
277
Dmitri Plotnikov34b24ef2011-01-28 13:41:07 -0800278 @Override
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800279 public void resume() {
280 mPaused = false;
281 if (!mPendingRequests.isEmpty()) {
282 requestLoading();
283 }
284 }
285
286 /**
287 * Sends a message to this thread itself to start loading images. If the current
288 * view contains multiple image views, all of those image views will get a chance
289 * to request their respective photos before any of those requests are executed.
290 * This allows us to load images in bulk.
291 */
292 private void requestLoading() {
293 if (!mLoadingRequested) {
294 mLoadingRequested = true;
295 mMainThreadHandler.sendEmptyMessage(MESSAGE_REQUEST_LOADING);
296 }
297 }
298
299 /**
300 * Processes requests on the main thread.
301 */
302 public boolean handleMessage(Message msg) {
303 switch (msg.what) {
304 case MESSAGE_REQUEST_LOADING: {
305 mLoadingRequested = false;
306 if (!mPaused) {
307 if (mLoaderThread == null) {
308 mLoaderThread = new LoaderThread(mContext.getContentResolver());
309 mLoaderThread.start();
310 }
311
312 mLoaderThread.requestLoading();
313 }
314 return true;
315 }
316
317 case MESSAGE_PHOTOS_LOADED: {
318 if (!mPaused) {
319 processLoadedImages();
320 }
321 return true;
322 }
323 }
324 return false;
325 }
326
327 /**
328 * Goes over pending loading requests and displays loaded photos. If some of the
329 * photos still haven't been loaded, sends another request for image loading.
330 */
331 private void processLoadedImages() {
332 Iterator<ImageView> iterator = mPendingRequests.keySet().iterator();
333 while (iterator.hasNext()) {
334 ImageView view = iterator.next();
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700335 Object key = mPendingRequests.get(view);
336 boolean loaded = loadCachedPhoto(view, key);
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800337 if (loaded) {
338 iterator.remove();
339 }
340 }
341
Dmitri Plotnikov37dc7cf2010-12-17 19:25:08 -0800342 softenCache();
343
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800344 if (!mPendingRequests.isEmpty()) {
345 requestLoading();
346 }
347 }
348
349 /**
Dmitri Plotnikov37dc7cf2010-12-17 19:25:08 -0800350 * Removes strong references to loaded bitmaps to allow them to be garbage collected
351 * if needed.
352 */
353 private void softenCache() {
354 for (BitmapHolder holder : mBitmapCache.values()) {
355 holder.bitmap = null;
356 }
357 }
358
359 /**
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800360 * Stores the supplied bitmap in cache.
361 */
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700362 private void cacheBitmap(Object key, byte[] bytes) {
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800363 if (mPaused) {
364 return;
365 }
366
367 BitmapHolder holder = new BitmapHolder();
368 holder.state = BitmapHolder.LOADED;
369 if (bytes != null) {
370 try {
371 Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, null);
Dmitri Plotnikov37dc7cf2010-12-17 19:25:08 -0800372 holder.bitmap = bitmap;
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800373 holder.bitmapRef = new SoftReference<Bitmap>(bitmap);
374 } catch (OutOfMemoryError e) {
375 // Do nothing - the photo will appear to be missing
376 }
377 }
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700378 mBitmapCache.put(key, holder);
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800379 }
380
381 /**
382 * Populates an array of photo IDs that need to be loaded.
383 */
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700384 private void obtainPhotoIdsAndUrisToLoad(ArrayList<Long> photoIds,
385 ArrayList<String> photoIdsAsStrings, ArrayList<Uri> uris) {
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800386 photoIds.clear();
Dmitri Plotnikov33409852010-02-20 15:02:32 -0800387 photoIdsAsStrings.clear();
Dmitri Plotnikov0f7462c2010-10-20 14:41:18 -0700388 uris.clear();
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800389
390 /*
391 * Since the call is made from the loader thread, the map could be
392 * changing during the iteration. That's not really a problem:
393 * ConcurrentHashMap will allow those changes to happen without throwing
394 * exceptions. Since we may miss some requests in the situation of
395 * concurrent change, we will need to check the map again once loading
396 * is complete.
397 */
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700398 Iterator<Object> iterator = mPendingRequests.values().iterator();
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800399 while (iterator.hasNext()) {
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700400 Object key = iterator.next();
401 BitmapHolder holder = mBitmapCache.get(key);
Dmitri Plotnikov92dfa112010-04-02 11:32:50 -0700402 if (holder != null && holder.state == BitmapHolder.NEEDED) {
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800403 // Assuming atomic behavior
404 holder.state = BitmapHolder.LOADING;
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700405 if (key instanceof Long) {
406 photoIds.add((Long)key);
407 photoIdsAsStrings.add(key.toString());
408 } else {
409 uris.add((Uri)key);
410 }
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800411 }
412 }
413 }
414
415 /**
416 * The thread that performs loading of photos from the database.
417 */
418 private class LoaderThread extends HandlerThread implements Callback {
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700419 private static final int BUFFER_SIZE = 1024*16;
420
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800421 private final ContentResolver mResolver;
422 private final StringBuilder mStringBuilder = new StringBuilder();
Dmitri Plotnikov33409852010-02-20 15:02:32 -0800423 private final ArrayList<Long> mPhotoIds = Lists.newArrayList();
424 private final ArrayList<String> mPhotoIdsAsStrings = Lists.newArrayList();
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700425 private final ArrayList<Uri> mPhotoUris = Lists.newArrayList();
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800426 private Handler mLoaderThreadHandler;
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700427 private byte mBuffer[];
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800428
429 public LoaderThread(ContentResolver resolver) {
430 super(LOADER_THREAD_NAME);
431 mResolver = resolver;
432 }
433
434 /**
435 * Sends a message to this thread to load requested photos.
436 */
437 public void requestLoading() {
438 if (mLoaderThreadHandler == null) {
439 mLoaderThreadHandler = new Handler(getLooper(), this);
440 }
441 mLoaderThreadHandler.sendEmptyMessage(0);
442 }
443
444 /**
445 * Receives the above message, loads photos and then sends a message
446 * to the main thread to process them.
447 */
448 public boolean handleMessage(Message msg) {
449 loadPhotosFromDatabase();
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800450 return true;
451 }
452
453 private void loadPhotosFromDatabase() {
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700454 obtainPhotoIdsAndUrisToLoad(mPhotoIds, mPhotoIdsAsStrings, mPhotoUris);
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800455
456 int count = mPhotoIds.size();
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700457 if (count != 0) {
458 mStringBuilder.setLength(0);
459 mStringBuilder.append(Photo._ID + " IN(");
460 for (int i = 0; i < count; i++) {
461 if (i != 0) {
462 mStringBuilder.append(',');
463 }
464 mStringBuilder.append('?');
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800465 }
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700466 mStringBuilder.append(')');
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800467
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700468 Cursor cursor = null;
469 try {
470 cursor = mResolver.query(Data.CONTENT_URI,
471 COLUMNS,
472 mStringBuilder.toString(),
473 mPhotoIdsAsStrings.toArray(EMPTY_STRING_ARRAY),
474 null);
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800475
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700476 if (cursor != null) {
477 while (cursor.moveToNext()) {
478 Long id = cursor.getLong(0);
479 byte[] bytes = cursor.getBlob(1);
480 cacheBitmap(id, bytes);
481 mPhotoIds.remove(id);
482 }
483 }
484 } finally {
485 if (cursor != null) {
486 cursor.close();
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800487 }
488 }
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700489
490 // Remaining photos were not found in the database - mark the cache accordingly.
491 count = mPhotoIds.size();
492 for (int i = 0; i < count; i++) {
493 cacheBitmap(mPhotoIds.get(i), null);
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800494 }
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700495 mMainThreadHandler.sendEmptyMessage(MESSAGE_PHOTOS_LOADED);
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800496 }
Dmitri Plotnikov33409852010-02-20 15:02:32 -0800497
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700498 count = mPhotoUris.size();
Dmitri Plotnikov33409852010-02-20 15:02:32 -0800499 for (int i = 0; i < count; i++) {
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700500 Uri uri = mPhotoUris.get(i);
501 if (mBuffer == null) {
502 mBuffer = new byte[BUFFER_SIZE];
503 }
504 try {
505 InputStream is = mResolver.openInputStream(uri);
506 if (is != null) {
507 ByteArrayOutputStream baos = new ByteArrayOutputStream();
508 try {
509 int size;
510 while ((size = is.read(mBuffer)) != -1) {
511 baos.write(mBuffer, 0, size);
512 }
513 } finally {
514 is.close();
515 }
516 cacheBitmap(uri, baos.toByteArray());
517 mMainThreadHandler.sendEmptyMessage(MESSAGE_PHOTOS_LOADED);
Dmitri Plotnikov0f7462c2010-10-20 14:41:18 -0700518 } else {
519 Log.v(TAG, "Cannot load photo " + uri);
520 cacheBitmap(uri, null);
Dmitri Plotnikoveb689432010-09-24 10:10:57 -0700521 }
522 } catch (Exception ex) {
523 Log.v(TAG, "Cannot load photo " + uri, ex);
524 cacheBitmap(uri, null);
525 }
Dmitri Plotnikov33409852010-02-20 15:02:32 -0800526 }
Dmitri Plotnikove8643852010-02-17 10:49:05 -0800527 }
528 }
529}