ThumbnailManager.java revision 51e4621fa12400b1e79cc18b7bb0f9a83af6b622
151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor// Copyright 2012 Google Inc. All Rights Reserved.
251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorpackage com.android.mms.util;
451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport java.util.Set;
651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport android.content.Context;
851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport android.graphics.Bitmap;
951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport android.graphics.BitmapFactory;
1051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport android.net.Uri;
1151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport android.util.Log;
1251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
1351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport com.android.mms.LogTag;
1451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport com.android.mms.ui.UriImage;
1551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
1651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor/**
1751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * Primary {@link ThumbnailManager} implementation used by {@link MessagingApplication}.
1851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * <p>
1951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * Public methods should only be used from a single thread (typically the UI
2051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * thread). Callbacks will be invoked on the thread where the ThumbnailManager
2151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * was instantiated.
2251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * <p>
2351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * Uses a thread-pool ExecutorService instead of AsyncTasks since clients may
2451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * request lots of pdus around the same time, and AsyncTask may reject tasks
2551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * in that case and has no way of bounding the number of threads used by those
2651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * tasks.
2751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * <p>
2851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * ThumbnailManager is used to asynchronously load pictures and create thumbnails. The thumbnails
2951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * are stored in a local cache with SoftReferences. Once a thumbnail is loaded, it will call the
3051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * passed in callback with the result. If a thumbnail is immediately available in the cache,
3151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * the callback will be called immediately as well.
3251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor *
3351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * Based on BooksImageManager by Virgil King.
3451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor */
3551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorpublic class ThumbnailManager extends BackgroundLoaderManager {
3651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    private static final String TAG = "ThumbnailManager";
3751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
3851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    private static final boolean DEBUG_DISABLE_LOAD = false;
3951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    private static final boolean DEBUG_LONG_WAIT = false;
4051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
4151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    private static final int THUMBNAIL_BOUNDS_LIMIT = 480;
4251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    private static final int PICTURE_SIZE_LIMIT = 20 * 1024;
4351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
4451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    private final SimpleCache<Uri, Bitmap> mThumbnailCache;
4551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    private final Context mContext;
4651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
4751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    public ThumbnailManager(final Context context) {
4851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        super(context);
4951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
5051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        mThumbnailCache = new SimpleCache<Uri, Bitmap>(32, 256, 0.75f);
5151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        mContext = context;
5251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    }
5351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
5451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    /**
5551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * getThumbnail must be called on the same thread that created ThumbnailManager. This is
5651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * normally the UI thread.
5751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * @param uri the uri of the image
5851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * @param width the original full width of the image
5951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * @param height the original full height of the image
6051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * @param callback the callback to call when the thumbnail is fully loaded
6151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * @return
6251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     */
6351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    public ItemLoadedFuture getThumbnail(Uri uri, int width, int height,
6451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            final ItemLoadedCallback<Bitmap> callback) {
6551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        if (uri == null) {
6651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            throw new NullPointerException();
6751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
6851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
6951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        final Bitmap thumbnail = mThumbnailCache.get(uri);
7051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
7151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        final boolean thumbnailExists = (thumbnail != null);
7251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        final boolean taskExists = mPendingTaskUris.contains(uri);
7351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        final boolean newTaskRequired = !thumbnailExists && !taskExists;
7451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        final boolean callbackRequired = (callback != null);
7551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
7651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        if (Log.isLoggable(LogTag.THUMBNAIL_CACHE, Log.DEBUG)) {
7751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            Log.v(TAG, "getThumbnail mThumbnailCache.get for uri: " + uri + " thumbnail: " +
7851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    thumbnail + " callback: " + callback + " thumbnailExists: " +
7951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    thumbnailExists + " taskExists: " + taskExists +
8051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    " newTaskRequired: " + newTaskRequired +
8151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    " callbackRequired: " + callbackRequired);
8251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
8351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
8451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        if (thumbnailExists) {
8551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            if (callbackRequired) {
8651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                callback.onItemLoaded(thumbnail, null);
8751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
8851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            return new NullItemLoadedFuture();
8951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
9051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
9151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        if (callbackRequired) {
9251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            addCallback(uri, callback);
9351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
9451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
9551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        if (newTaskRequired) {
9651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            mPendingTaskUris.add(uri);
9751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            Runnable task = new ThumbnailTask(uri, width, height);
9851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            mExecutor.execute(task);
9951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
10051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        return new ItemLoadedFuture() {
10151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            public void cancel() {
10251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                cancelCallback(callback);
10351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
10451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            public boolean isDone() {
10551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                return false;
10651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
10751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        };
10851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    }
10951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
11051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    @Override
11151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    public void clear() {
11251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        super.clear();
11351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
11451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        mThumbnailCache.clear();
11551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    }
11651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
11751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    public String getTag() {
11851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        return TAG;
11951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    }
12051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
12151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    public class ThumbnailTask implements Runnable {
12251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        private final Uri mUri;
12351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        private final int mWidth;
12451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        private final int mHeight;
12551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
12651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        public ThumbnailTask(Uri uri, int width, int height) {
12751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            if (uri == null) {
12851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                throw new NullPointerException();
12951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
13051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            mUri = uri;
13151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            mWidth = width;
13251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            mHeight = height;
13351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
13451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
13551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        /** {@inheritDoc} */
13651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        public void run() {
13751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            if (DEBUG_DISABLE_LOAD) {
13851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                return;
13951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
14051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            if (DEBUG_LONG_WAIT) {
14151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                try {
14251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    Thread.sleep(10000);
14351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                } catch (InterruptedException e) {
14451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                }
14551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
14651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
14751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            byte[] data = UriImage.getResizedImageData(mWidth, mHeight,
14851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    THUMBNAIL_BOUNDS_LIMIT, THUMBNAIL_BOUNDS_LIMIT,
14951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    PICTURE_SIZE_LIMIT, mUri, mContext);
15051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            if (Log.isLoggable(LogTag.THUMBNAIL_CACHE, Log.DEBUG)) {
15151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                Log.v(TAG, "createBitmap size: " + (data == null ? data : data.length));
15251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
15351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            Bitmap bitmap = data == null ? null :
15451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                BitmapFactory.decodeByteArray(data, 0, data.length);
15551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            if (Log.isLoggable(LogTag.THUMBNAIL_CACHE, Log.DEBUG) && bitmap == null) {
15651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                Log.v(TAG, "DECODED BITMAP IS NULL!!!");
15751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
15851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
15951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            final Bitmap resultBitmap = bitmap;
16051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            mCallbackHandler.post(new Runnable() {
16151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                public void run() {
16251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    final Set<ItemLoadedCallback> callbacks = mCallbacks.get(mUri);
16351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    if (callbacks != null) {
16451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        // Make a copy so that the callback can unregister itself
16551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        for (final ItemLoadedCallback<Bitmap> callback : asList(callbacks)) {
16651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                            if (Log.isLoggable(TAG, Log.DEBUG)) {
16751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                                Log.d(TAG, "Invoking item loaded callback " + callback);
16851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                            }
16951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                            callback.onItemLoaded(resultBitmap, null);
17051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        }
17151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    } else {
17251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        if (Log.isLoggable(TAG, Log.DEBUG)) {
17351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                            Log.d(TAG, "No image callback!");
17451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        }
17551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    }
17651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
17751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    // Add the bitmap to the soft cache if the load succeeded
17851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    if (resultBitmap != null) {
17951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        mThumbnailCache.put(mUri, resultBitmap);
18051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        if (Log.isLoggable(TAG, Log.DEBUG)) {
18151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                            Log.v(TAG, "in callback runnable: bitmap uri: " + mUri +
18251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                                    " width: " + resultBitmap.getWidth() + " height: " +
18351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                                    resultBitmap.getHeight() + " size: " +
18451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                                    resultBitmap.getByteCount());
18551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        }
18651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    }
18751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
18851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    mCallbacks.remove(mUri);
18951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    mPendingTaskUris.remove(mUri);
19051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
19151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    if (Log.isLoggable(LogTag.THUMBNAIL_CACHE, Log.DEBUG)) {
19251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        Log.d(TAG, "Image task for " + mUri + "exiting " + mPendingTaskUris.size()
19351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                                + " remain");
19451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    }
19551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                }
19651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            });
19751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
19851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    }
19951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor}
200