107ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor/*
207ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor * Copyright (C) 2012 The Android Open Source Project
307ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor *
407ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor * Licensed under the Apache License, Version 2.0 (the "License");
507ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor * you may not use this file except in compliance with the License.
607ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor * You may obtain a copy of the License at
707ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor *
807ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor *      http://www.apache.org/licenses/LICENSE-2.0
907ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor *
1007ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor * Unless required by applicable law or agreed to in writing, software
1107ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor * distributed under the License is distributed on an "AS IS" BASIS,
1207ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1307ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor * See the License for the specific language governing permissions and
1407ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor * limitations under the License.
1507ddb5c577a10e5aa3b4442426a055f3b95d5202Tom Taylor */
1651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
1751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorpackage com.android.mms.util;
1851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
1951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport android.content.Context;
2051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport android.graphics.Bitmap;
21d64419030e1fec1e751695dab3bd7236e2fb0214Roger Chenimport android.graphics.Bitmap.Config;
2251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport android.graphics.BitmapFactory;
23d64419030e1fec1e751695dab3bd7236e2fb0214Roger Chenimport android.graphics.BitmapFactory.Options;
24721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylorimport android.graphics.Canvas;
25721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylorimport android.graphics.Paint;
2679bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylorimport android.media.MediaMetadataRetriever;
2751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport android.net.Uri;
2851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport android.util.Log;
2951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
3051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport com.android.mms.LogTag;
31721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylorimport com.android.mms.R;
32b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylorimport com.android.mms.TempFileProvider;
3351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorimport com.android.mms.ui.UriImage;
34721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylorimport com.android.mms.util.ImageCacheService.ImageData;
3551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
36e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylorimport java.io.ByteArrayOutputStream;
37e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylorimport java.io.Closeable;
38e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylorimport java.io.FileNotFoundException;
39e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylorimport java.io.InputStream;
40e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylorimport java.util.Set;
41e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylor
4251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor/**
4351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * Primary {@link ThumbnailManager} implementation used by {@link MessagingApplication}.
4451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * <p>
4551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * Public methods should only be used from a single thread (typically the UI
4651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * thread). Callbacks will be invoked on the thread where the ThumbnailManager
4751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * was instantiated.
4851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * <p>
4951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * Uses a thread-pool ExecutorService instead of AsyncTasks since clients may
5051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * request lots of pdus around the same time, and AsyncTask may reject tasks
5151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * in that case and has no way of bounding the number of threads used by those
5251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * tasks.
5351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * <p>
5451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * ThumbnailManager is used to asynchronously load pictures and create thumbnails. The thumbnails
5551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * are stored in a local cache with SoftReferences. Once a thumbnail is loaded, it will call the
5651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * passed in callback with the result. If a thumbnail is immediately available in the cache,
5751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * the callback will be called immediately as well.
5851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor *
5951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor * Based on BooksImageManager by Virgil King.
6051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor */
6151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylorpublic class ThumbnailManager extends BackgroundLoaderManager {
6251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    private static final String TAG = "ThumbnailManager";
6351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
64721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    private static final boolean DEBUG_DISABLE_CACHE = false;
65721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    private static final boolean DEBUG_DISABLE_CALLBACK = false;
6651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    private static final boolean DEBUG_DISABLE_LOAD = false;
6751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    private static final boolean DEBUG_LONG_WAIT = false;
6851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
69721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    private static final int COMPRESS_JPEG_QUALITY = 90;
7051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
7151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    private final SimpleCache<Uri, Bitmap> mThumbnailCache;
7251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    private final Context mContext;
73721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    private ImageCacheService mImageCacheService;
74721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    private static Bitmap mEmptyImageBitmap;
75721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    private static Bitmap mEmptyVideoBitmap;
76721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
77721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    // NOTE: These type numbers are stored in the image cache, so it should not
78721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    // not be changed without resetting the cache.
79721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    public static final int TYPE_THUMBNAIL = 1;
80721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    public static final int TYPE_MICROTHUMBNAIL = 2;
81721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
82721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    public static final int THUMBNAIL_TARGET_SIZE = 640;
8351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
8451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    public ThumbnailManager(final Context context) {
8551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        super(context);
8651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
87721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        mThumbnailCache = new SimpleCache<Uri, Bitmap>(8, 16, 0.75f, true);
8851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        mContext = context;
89721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
90721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        mEmptyImageBitmap = BitmapFactory.decodeResource(context.getResources(),
91721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                R.drawable.ic_missing_thumbnail_picture);
92721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
93721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        mEmptyVideoBitmap = BitmapFactory.decodeResource(context.getResources(),
94721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                R.drawable.ic_missing_thumbnail_video);
9551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    }
9651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
9751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    /**
9851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * getThumbnail must be called on the same thread that created ThumbnailManager. This is
9951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * normally the UI thread.
10051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * @param uri the uri of the image
10151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * @param width the original full width of the image
10251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * @param height the original full height of the image
10351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * @param callback the callback to call when the thumbnail is fully loaded
10451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     * @return
10551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor     */
106721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    public ItemLoadedFuture getThumbnail(Uri uri,
10779bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor            final ItemLoadedCallback<ImageLoaded> callback) {
108721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        return getThumbnail(uri, false, callback);
10979bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor    }
11079bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor
11179bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor    /**
11279bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor     * getVideoThumbnail must be called on the same thread that created ThumbnailManager. This is
11379bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor     * normally the UI thread.
11479bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor     * @param uri the uri of the image
11579bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor     * @param callback the callback to call when the thumbnail is fully loaded
11679bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor     * @return
11779bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor     */
11879bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor    public ItemLoadedFuture getVideoThumbnail(Uri uri,
11979bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor            final ItemLoadedCallback<ImageLoaded> callback) {
120721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        return getThumbnail(uri, true, callback);
12179bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor    }
12279bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor
123721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    private ItemLoadedFuture getThumbnail(Uri uri, boolean isVideo,
12479bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor            final ItemLoadedCallback<ImageLoaded> callback) {
12551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        if (uri == null) {
12651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            throw new NullPointerException();
12751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
12851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
129721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        final Bitmap thumbnail = DEBUG_DISABLE_CACHE ? null : mThumbnailCache.get(uri);
13051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
13151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        final boolean thumbnailExists = (thumbnail != null);
13251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        final boolean taskExists = mPendingTaskUris.contains(uri);
13351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        final boolean newTaskRequired = !thumbnailExists && !taskExists;
13451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        final boolean callbackRequired = (callback != null);
13551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
13651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        if (Log.isLoggable(LogTag.THUMBNAIL_CACHE, Log.DEBUG)) {
13751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            Log.v(TAG, "getThumbnail mThumbnailCache.get for uri: " + uri + " thumbnail: " +
13851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    thumbnail + " callback: " + callback + " thumbnailExists: " +
13951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    thumbnailExists + " taskExists: " + taskExists +
14051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    " newTaskRequired: " + newTaskRequired +
14151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    " callbackRequired: " + callbackRequired);
14251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
14351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
14451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        if (thumbnailExists) {
145721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            if (callbackRequired && !DEBUG_DISABLE_CALLBACK) {
14679bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor                ImageLoaded imageLoaded = new ImageLoaded(thumbnail, isVideo);
14779bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor                callback.onItemLoaded(imageLoaded, null);
14851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
14951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            return new NullItemLoadedFuture();
15051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
15151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
15251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        if (callbackRequired) {
15351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            addCallback(uri, callback);
15451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
15551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
15651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        if (newTaskRequired) {
15751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            mPendingTaskUris.add(uri);
158721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            Runnable task = new ThumbnailTask(uri, isVideo);
15951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            mExecutor.execute(task);
16051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
16151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        return new ItemLoadedFuture() {
1629c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor            private boolean mIsDone;
1639c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor
164ddd31c4011b4191035bdfbba05a8edb1785f71afTodor Kalaydjiev            @Override
1659c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor            public void cancel(Uri uri) {
16651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                cancelCallback(callback);
1679c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor                removeThumbnail(uri);   // if the thumbnail is half loaded, force a reload next time
16851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
1699c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor
1709c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor            @Override
1719c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor            public void setIsDone(boolean done) {
1729c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor                mIsDone = done;
1739c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor            }
1749c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor
175ddd31c4011b4191035bdfbba05a8edb1785f71afTodor Kalaydjiev            @Override
17651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            public boolean isDone() {
1779c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor                return mIsDone;
17851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
17951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        };
18051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    }
18151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
18251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    @Override
1837b6fe946f2e1020432e3600c8863f72449cd4e68Tom Taylor    public synchronized void clear() {
18451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        super.clear();
18551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
1867b6fe946f2e1020432e3600c8863f72449cd4e68Tom Taylor        mThumbnailCache.clear();    // clear in-memory cache
1877b6fe946f2e1020432e3600c8863f72449cd4e68Tom Taylor        clearBackingStore();        // clear on-disk cache
1887b6fe946f2e1020432e3600c8863f72449cd4e68Tom Taylor    }
1897b6fe946f2e1020432e3600c8863f72449cd4e68Tom Taylor
1907b6fe946f2e1020432e3600c8863f72449cd4e68Tom Taylor    // Delete the on-disk cache, but leave the in-memory cache intact
1917b6fe946f2e1020432e3600c8863f72449cd4e68Tom Taylor    public synchronized void clearBackingStore() {
192ffe45d561b2d756d010face2018f411c7d69ae80Roger Chen        if (mImageCacheService == null) {
193ffe45d561b2d756d010face2018f411c7d69ae80Roger Chen            // No need to call getImageCacheService() to renew the instance if it's null.
194ffe45d561b2d756d010face2018f411c7d69ae80Roger Chen            // It's enough to only delete the image cache files for the sake of safety.
195ffe45d561b2d756d010face2018f411c7d69ae80Roger Chen            CacheManager.clear(mContext);
196ffe45d561b2d756d010face2018f411c7d69ae80Roger Chen        } else {
197ffe45d561b2d756d010face2018f411c7d69ae80Roger Chen            getImageCacheService().clear();
198ffe45d561b2d756d010face2018f411c7d69ae80Roger Chen
199ffe45d561b2d756d010face2018f411c7d69ae80Roger Chen            // force a re-init the next time getImageCacheService requested
200ffe45d561b2d756d010face2018f411c7d69ae80Roger Chen            mImageCacheService = null;
201ffe45d561b2d756d010face2018f411c7d69ae80Roger Chen        }
20251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    }
20351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
204b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor    public void removeThumbnail(Uri uri) {
2059c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor        if (Log.isLoggable(TAG, Log.DEBUG)) {
2069c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor            Log.d(TAG, "removeThumbnail: " + uri);
2079c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor        }
2089c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor        if (uri != null) {
2099c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor            mThumbnailCache.remove(uri);
2109c9df2773645d195dbf17df91d7cccdf5154fe84Tom Taylor        }
211b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor    }
212b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor
213ddd31c4011b4191035bdfbba05a8edb1785f71afTodor Kalaydjiev    @Override
21451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    public String getTag() {
21551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        return TAG;
21651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    }
21751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
218721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    private synchronized ImageCacheService getImageCacheService() {
219721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        if (mImageCacheService == null) {
220721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            mImageCacheService = new ImageCacheService(mContext);
221721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
222721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        return mImageCacheService;
223721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor    }
224721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
22551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    public class ThumbnailTask implements Runnable {
22651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        private final Uri mUri;
22779bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor        private final boolean mIsVideo;
22851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
229721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        public ThumbnailTask(Uri uri, boolean isVideo) {
23051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            if (uri == null) {
23151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                throw new NullPointerException();
23251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
23351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            mUri = uri;
23479bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor            mIsVideo = isVideo;
23551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
23651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
23751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        /** {@inheritDoc} */
238ddd31c4011b4191035bdfbba05a8edb1785f71afTodor Kalaydjiev        @Override
23951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        public void run() {
24051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            if (DEBUG_DISABLE_LOAD) {
24151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                return;
24251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
24351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            if (DEBUG_LONG_WAIT) {
24451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                try {
24551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    Thread.sleep(10000);
24651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                } catch (InterruptedException e) {
24751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                }
24851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            }
24951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
2505d403c7cb873004bbc203536e818075a7223c0a5Tom Taylor            Bitmap bitmap = null;
2515d403c7cb873004bbc203536e818075a7223c0a5Tom Taylor            try {
2525d403c7cb873004bbc203536e818075a7223c0a5Tom Taylor                bitmap = getBitmap(mIsVideo);
2535d403c7cb873004bbc203536e818075a7223c0a5Tom Taylor            } catch (IllegalArgumentException e) {
2545d403c7cb873004bbc203536e818075a7223c0a5Tom Taylor                Log.e(TAG, "Couldn't load bitmap for " + mUri, e);
255459773169711cec9c9677f425998fd5a6ddb1361Bin Li            } catch (OutOfMemoryError e) {
256459773169711cec9c9677f425998fd5a6ddb1361Bin Li                Log.e(TAG, "Couldn't load bitmap for " + mUri, e);
2575d403c7cb873004bbc203536e818075a7223c0a5Tom Taylor            }
2585d403c7cb873004bbc203536e818075a7223c0a5Tom Taylor            final Bitmap resultBitmap = bitmap;
259721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
26051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            mCallbackHandler.post(new Runnable() {
261ddd31c4011b4191035bdfbba05a8edb1785f71afTodor Kalaydjiev                @Override
26251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                public void run() {
26351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    final Set<ItemLoadedCallback> callbacks = mCallbacks.get(mUri);
26451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    if (callbacks != null) {
265721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                        Bitmap bitmap = resultBitmap == null ?
266721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                                (mIsVideo ? mEmptyVideoBitmap : mEmptyImageBitmap)
267721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                                : resultBitmap;
268721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
26951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        // Make a copy so that the callback can unregister itself
27079bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor                        for (final ItemLoadedCallback<ImageLoaded> callback : asList(callbacks)) {
271721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                            if (Log.isLoggable(LogTag.THUMBNAIL_CACHE, Log.DEBUG)) {
27251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                                Log.d(TAG, "Invoking item loaded callback " + callback);
27351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                            }
274721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                            if (!DEBUG_DISABLE_CALLBACK) {
275721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                                ImageLoaded imageLoaded = new ImageLoaded(bitmap, mIsVideo);
276721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                                callback.onItemLoaded(imageLoaded, null);
277721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                            }
27851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        }
27951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    } else {
28051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        if (Log.isLoggable(TAG, Log.DEBUG)) {
28151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                            Log.d(TAG, "No image callback!");
28251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        }
28351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    }
28451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
285721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    // Add the bitmap to the soft cache if the load succeeded. Don't cache the
286721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    // stand-ins for empty bitmaps.
28751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    if (resultBitmap != null) {
28851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        mThumbnailCache.put(mUri, resultBitmap);
289721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                        if (Log.isLoggable(LogTag.THUMBNAIL_CACHE, Log.DEBUG)) {
29051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                            Log.v(TAG, "in callback runnable: bitmap uri: " + mUri +
29151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                                    " width: " + resultBitmap.getWidth() + " height: " +
29251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                                    resultBitmap.getHeight() + " size: " +
29351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                                    resultBitmap.getByteCount());
29451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        }
29551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    }
29651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
29751e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    mCallbacks.remove(mUri);
29851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    mPendingTaskUris.remove(mUri);
29951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor
30051e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    if (Log.isLoggable(LogTag.THUMBNAIL_CACHE, Log.DEBUG)) {
30151e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                        Log.d(TAG, "Image task for " + mUri + "exiting " + mPendingTaskUris.size()
30251e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                                + " remain");
30351e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                    }
30451e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor                }
30551e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor            });
30651e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor        }
307721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
308721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private Bitmap getBitmap(boolean isVideo) {
309721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            ImageCacheService cacheService = getImageCacheService();
310721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
311721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            UriImage uriImage = new UriImage(mContext, mUri);
312721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            String path = uriImage.getPath();
313b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor
314f0190cdad812e64f3f3a67931cb2d0bf0edf4b05Tom Taylor            if (path == null) {
315f0190cdad812e64f3f3a67931cb2d0bf0edf4b05Tom Taylor                return null;
316f0190cdad812e64f3f3a67931cb2d0bf0edf4b05Tom Taylor            }
317f0190cdad812e64f3f3a67931cb2d0bf0edf4b05Tom Taylor
318b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor            // We never want to store thumbnails of temp files in the thumbnail cache on disk
319b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor            // because those temp filenames are recycled (and reused when capturing images
320b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor            // or videos).
321b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor            boolean isTempFile = TempFileProvider.isTempFile(path);
322b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor
323b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor            ImageData data = null;
324b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor            if (!isTempFile) {
325b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor                data = cacheService.getImageData(path, TYPE_THUMBNAIL);
326b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor            }
327721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
328721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            if (data != null) {
329721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                BitmapFactory.Options options = new BitmapFactory.Options();
330721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                options.inPreferredConfig = Bitmap.Config.ARGB_8888;
331721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                Bitmap bitmap = requestDecode(data.mData,
332721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                        data.mOffset, data.mData.length - data.mOffset, options);
333721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                if (bitmap == null) {
334721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    Log.w(TAG, "decode cached failed " + path);
335721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                }
336721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                return bitmap;
337721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            } else {
338721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                Bitmap bitmap;
339721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                if (isVideo) {
340721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    bitmap = getVideoBitmap();
341721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                } else {
342721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    bitmap = onDecodeOriginal(mUri, TYPE_THUMBNAIL);
343721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                }
344721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                if (bitmap == null) {
345721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    Log.w(TAG, "decode orig failed " + path);
346721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    return null;
347721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                }
348721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
349721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                bitmap = resizeDownBySideLength(bitmap, THUMBNAIL_TARGET_SIZE, true);
350721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
351b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor                if (!isTempFile) {
352b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor                    byte[] array = compressBitmap(bitmap);
353b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor                    cacheService.putImageData(path, TYPE_THUMBNAIL, array);
354b8eae066a7ba60722de07300bf990d519f5c81d8Tom Taylor                }
355721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                return bitmap;
356721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            }
357721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
358721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
359721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private Bitmap getVideoBitmap() {
360721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            MediaMetadataRetriever retriever = new MediaMetadataRetriever();
361721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            try {
362721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                retriever.setDataSource(mContext, mUri);
363721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                return retriever.getFrameAtTime(-1);
364721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            } catch (RuntimeException ex) {
365721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                // Assume this is a corrupt video file.
366721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            } finally {
367721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                try {
368721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    retriever.release();
369721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                } catch (RuntimeException ex) {
370721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    // Ignore failures while cleaning up.
371721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                }
372721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            }
373721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            return null;
374721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
375721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
376721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private byte[] compressBitmap(Bitmap bitmap) {
377721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            ByteArrayOutputStream os = new ByteArrayOutputStream();
378721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            bitmap.compress(Bitmap.CompressFormat.JPEG,
379721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    COMPRESS_JPEG_QUALITY, os);
380721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            return os.toByteArray();
381721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
382721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
383721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private Bitmap requestDecode(byte[] bytes, int offset,
384721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                int length, Options options) {
385721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            if (options == null) {
386721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                options = new Options();
387721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            }
388721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            return ensureGLCompatibleBitmap(
389721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    BitmapFactory.decodeByteArray(bytes, offset, length, options));
390721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
391721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
392721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private Bitmap resizeDownBySideLength(
393721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                Bitmap bitmap, int maxLength, boolean recycle) {
394721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            int srcWidth = bitmap.getWidth();
395721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            int srcHeight = bitmap.getHeight();
396721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            float scale = Math.min(
397721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    (float) maxLength / srcWidth, (float) maxLength / srcHeight);
398721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            if (scale >= 1.0f) return bitmap;
399721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            return resizeBitmapByScale(bitmap, scale, recycle);
400721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
401721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
402721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private Bitmap resizeBitmapByScale(
403721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                Bitmap bitmap, float scale, boolean recycle) {
404721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            int width = Math.round(bitmap.getWidth() * scale);
405721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            int height = Math.round(bitmap.getHeight() * scale);
406721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            if (width == bitmap.getWidth()
407721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    && height == bitmap.getHeight()) return bitmap;
408721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            Bitmap target = Bitmap.createBitmap(width, height, getConfig(bitmap));
409721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            Canvas canvas = new Canvas(target);
410721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            canvas.scale(scale, scale);
411721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
412721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            canvas.drawBitmap(bitmap, 0, 0, paint);
413721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            if (recycle) bitmap.recycle();
414721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            return target;
415721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
416721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
417721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private Bitmap.Config getConfig(Bitmap bitmap) {
418721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            Bitmap.Config config = bitmap.getConfig();
419721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            if (config == null) {
420721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                config = Bitmap.Config.ARGB_8888;
421721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            }
422721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            return config;
423721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
424721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
425721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        // TODO: This function should not be called directly from
426721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        // DecodeUtils.requestDecode(...), since we don't have the knowledge
427721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        // if the bitmap will be uploaded to GL.
428721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) {
429721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            if (bitmap == null || bitmap.getConfig() != null) return bitmap;
430721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false);
431721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            bitmap.recycle();
432721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            return newBitmap;
433721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
434721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
435721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private Bitmap onDecodeOriginal(Uri uri, int type) {
436721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            BitmapFactory.Options options = new BitmapFactory.Options();
437721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
438721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
439721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            return requestDecode(uri, options, THUMBNAIL_TARGET_SIZE);
440721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
441721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
442721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private void closeSilently(Closeable c) {
443721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            if (c == null) return;
444721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            try {
445721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                c.close();
446721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            } catch (Throwable t) {
447721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                Log.w(TAG, "close fail", t);
448721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            }
449721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
450721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
451721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private Bitmap requestDecode(final Uri uri, Options options, int targetSize) {
452721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            if (options == null) options = new Options();
453721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
454721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            InputStream inputStream;
455721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            try {
456721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                inputStream = mContext.getContentResolver().openInputStream(uri);
457721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            } catch (FileNotFoundException e) {
458721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                Log.e(TAG, "Can't open uri: " + uri, e);
459721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                return null;
460721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            }
461721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
462721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            options.inJustDecodeBounds = true;
463721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            BitmapFactory.decodeStream(inputStream, null, options);
464721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            closeSilently(inputStream);
465721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
466721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            // No way to reset the stream. Have to open it again :-(
467721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            try {
468721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                inputStream = mContext.getContentResolver().openInputStream(uri);
469721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            } catch (FileNotFoundException e) {
470721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                Log.e(TAG, "Can't open uri: " + uri, e);
471721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                return null;
472721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            }
473721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
474721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            options.inSampleSize = computeSampleSizeLarger(
475721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    options.outWidth, options.outHeight, targetSize);
476721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            options.inJustDecodeBounds = false;
477721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
478721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            Bitmap result = BitmapFactory.decodeStream(inputStream, null, options);
479721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            closeSilently(inputStream);
480721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
4815d403c7cb873004bbc203536e818075a7223c0a5Tom Taylor            if (result == null) {
4825d403c7cb873004bbc203536e818075a7223c0a5Tom Taylor                return null;
4835d403c7cb873004bbc203536e818075a7223c0a5Tom Taylor            }
4845d403c7cb873004bbc203536e818075a7223c0a5Tom Taylor
485721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            // We need to resize down if the decoder does not support inSampleSize.
486721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            // (For example, GIF images.)
487721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            result = resizeDownIfTooBig(result, targetSize, true);
488e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylor            result = ensureGLCompatibleBitmap(result);
489e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylor
490e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylor            int orientation = UriImage.getOrientation(mContext, uri);
491e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylor            // Rotate the bitmap if we need to.
492e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylor            if (result != null && orientation != 0) {
493e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylor                result = UriImage.rotateBitmap(result, orientation);
494e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylor            }
495e8a0d786fba1960818cb3da938cdbf2b2b11b340Tom Taylor            return result;
496721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
497721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
498721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        // This computes a sample size which makes the longer side at least
499721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        // minSideLength long. If that's not possible, return 1.
500721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private int computeSampleSizeLarger(int w, int h,
501721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                int minSideLength) {
502721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            int initialSize = Math.max(w / minSideLength, h / minSideLength);
503721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            if (initialSize <= 1) return 1;
504721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
505721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            return initialSize <= 8
506721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    ? prevPowerOf2(initialSize)
507721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    : initialSize / 8 * 8;
508721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
509721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
510721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        // Returns the previous power of two.
511721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        // Returns the input if it is already power of 2.
512721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        // Throws IllegalArgumentException if the input is <= 0
513721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private int prevPowerOf2(int n) {
514721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            if (n <= 0) throw new IllegalArgumentException();
515721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            return Integer.highestOneBit(n);
516721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
517721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor
518721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        // Resize the bitmap if each side is >= targetSize * 2
519721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        private Bitmap resizeDownIfTooBig(
520721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                Bitmap bitmap, int targetSize, boolean recycle) {
521721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            int srcWidth = bitmap.getWidth();
522721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            int srcHeight = bitmap.getHeight();
523721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            float scale = Math.max(
524721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor                    (float) targetSize / srcWidth, (float) targetSize / srcHeight);
525721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            if (scale > 0.5f) return bitmap;
526721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor            return resizeBitmapByScale(bitmap, scale, recycle);
527721ad07121cb9b0cd76bdbbc88494aa8f4d45a6dTom Taylor        }
52851e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor    }
52979bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor
53079bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor    public static class ImageLoaded {
53179bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor        public final Bitmap mBitmap;
53279bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor        public final boolean mIsVideo;
53379bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor
53479bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor        public ImageLoaded(Bitmap bitmap, boolean isVideo) {
53579bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor            mBitmap = bitmap;
53679bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor            mIsVideo = isVideo;
53779bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor        }
53879bf6f70c54d08dc9c3481b8461a3a46a3cefb83Tom Taylor    }
53951e4621fa12400b1e79cc18b7bb0f9a83af6b622Tom Taylor}
540