1d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/* 2d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Copyright (C) 2015 The Android Open Source Project 3d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 4d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Licensed under the Apache License, Version 2.0 (the "License"); 5d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * you may not use this file except in compliance with the License. 6d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * You may obtain a copy of the License at 7d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 8d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * http://www.apache.org/licenses/LICENSE-2.0 9d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 10d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Unless required by applicable law or agreed to in writing, software 11d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * distributed under the License is distributed on an "AS IS" BASIS, 12d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * See the License for the specific language governing permissions and 14d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * limitations under the License. 15d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 16d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpackage com.android.messaging.datamodel.media; 17d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 18d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.graphics.Bitmap; 19d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.graphics.BitmapFactory; 20d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.graphics.Color; 21d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.os.SystemClock; 22d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.support.annotation.NonNull; 23d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.util.SparseArray; 24d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 25d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.Factory; 26d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.Assert; 27d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.LogUtil; 28d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 29d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.io.IOException; 30d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.io.InputStream; 31d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.util.LinkedList; 32d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 33d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/** 34d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * A media cache that holds image resources, which doubles as a bitmap pool that allows the 35d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * consumer to optionally decode image resources using unused bitmaps stored in the cache. 36d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 37d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpublic class PoolableImageCache extends MediaCache<ImageResource> { 38d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final int MIN_TIME_IN_POOL = 5000; 39d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 40d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** Encapsulates bitmap pool representation of the image cache */ 41d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final ReusableImageResourcePool mReusablePoolAccessor = new ReusableImageResourcePool(); 42d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 43d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public PoolableImageCache(final int id, final String name) { 44d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd this(DEFAULT_MEDIA_RESOURCE_CACHE_SIZE_IN_KILOBYTES, id, name); 45d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 46d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 47d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public PoolableImageCache(final int maxSize, final int id, final String name) { 48d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd super(maxSize, id, name); 49d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 50d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 51d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 52d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Creates a new BitmapFactory.Options for using the self-contained bitmap pool. 53d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 54d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static BitmapFactory.Options getBitmapOptionsForPool(final boolean scaled, 55d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int inputDensity, final int targetDensity) { 56d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final BitmapFactory.Options options = new BitmapFactory.Options(); 57d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd options.inScaled = scaled; 58d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd options.inDensity = inputDensity; 59d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd options.inTargetDensity = targetDensity; 60d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd options.inSampleSize = 1; 61d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd options.inJustDecodeBounds = false; 62d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd options.inMutable = true; 63d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return options; 64d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 65d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 66d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 67d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public synchronized ImageResource addResourceToCache(final String key, 68d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final ImageResource imageResource) { 69d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mReusablePoolAccessor.onResourceEnterCache(imageResource); 70d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return super.addResourceToCache(key, imageResource); 71d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 72d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 73d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 74d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd protected synchronized void entryRemoved(final boolean evicted, final String key, 75d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final ImageResource oldValue, final ImageResource newValue) { 76d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mReusablePoolAccessor.onResourceLeaveCache(oldValue); 77d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd super.entryRemoved(evicted, key, oldValue, newValue); 78d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 79d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 80d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 81d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Returns a representation of the image cache as a reusable bitmap pool. 82d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 83d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public ReusableImageResourcePool asReusableBitmapPool() { 84d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return mReusablePoolAccessor; 85d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 86d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 87d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 88d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * A bitmap pool representation built on top of the image cache. It treats the image resources 89d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * stored in the image cache as a self-contained bitmap pool and is able to create or 90d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * reclaim bitmap resource as needed. 91d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 92d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public class ReusableImageResourcePool { 93d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final int MAX_SUPPORTED_IMAGE_DIMENSION = 0xFFFF; 94d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final int INVALID_POOL_KEY = 0; 95d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 96d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 97d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Number of reuse failures to skip before reporting. 98d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * For debugging purposes, change to a lower number for more frequent reporting. 99d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 100d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final int FAILED_REPORTING_FREQUENCY = 100; 101d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 102d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 103d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Count of reuse failures which have occurred. 104d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 105d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private volatile int mFailedBitmapReuseCount = 0; 106d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 107d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 108d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Count of reuse successes which have occurred. 109d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 110d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private volatile int mSucceededBitmapReuseCount = 0; 111d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 112d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 113d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * A sparse array from bitmap size to a list of image cache entries that match the 114d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * given size. This map is used to quickly retrieve a usable bitmap to be reused by an 115d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * incoming ImageRequest. We need to ensure that this sparse array always contains only 116d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * elements currently in the image cache with no other consumer. 117d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 118d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final SparseArray<LinkedList<ImageResource>> mImageListSparseArray; 119d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 120d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public ReusableImageResourcePool() { 121d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mImageListSparseArray = new SparseArray<LinkedList<ImageResource>>(); 122d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 123d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 124d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 125d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Load an input stream into a bitmap. Uses a bitmap from the pool if possible to reduce 126d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * memory turnover. 127d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param inputStream InputStream load. Cannot be null. 128d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param optionsTmp Should be the same options returned from getBitmapOptionsForPool(). 129d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Cannot be null. 130d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param width The width of the bitmap. 131d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param height The height of the bitmap. 132d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @return The decoded Bitmap with the resource drawn in it. 133d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @throws IOException 134d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 135d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public Bitmap decodeSampledBitmapFromInputStream(@NonNull final InputStream inputStream, 136d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @NonNull final BitmapFactory.Options optionsTmp, 137d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int width, final int height) throws IOException { 138d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (width <= 0 || height <= 0) { 139d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // This is an invalid / corrupted image of zero size. 140d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache: Decoding bitmap with " + 141d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd "invalid size"); 142d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd throw new IOException("Invalid size / corrupted image"); 143d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 144d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.notNull(inputStream); 145d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd assignPoolBitmap(optionsTmp, width, height); 146d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Bitmap b = null; 147d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd try { 148d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd b = BitmapFactory.decodeStream(inputStream, null, optionsTmp); 149d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mSucceededBitmapReuseCount++; 150d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } catch (final IllegalArgumentException e) { 151d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap. 152d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (optionsTmp.inBitmap != null) { 153d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd optionsTmp.inBitmap.recycle(); 154d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd optionsTmp.inBitmap = null; 155d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd b = BitmapFactory.decodeStream(inputStream, null, optionsTmp); 156d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd onFailedToReuse(); 157d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 158d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } catch (final OutOfMemoryError e) { 159d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Oom decoding inputStream"); 160d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Factory.get().reclaimMemory(); 161d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 162d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return b; 163d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 164d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 165d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 166d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Turn encoded bytes into a bitmap. Uses a bitmap from the pool if possible to reduce 167d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * memory turnover. 168d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param bytes Encoded bytes to draw on the bitmap. Cannot be null. 169d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param optionsTmp The bitmap will set here and the input should be generated from 170d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * getBitmapOptionsForPool(). Cannot be null. 171d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param width The width of the bitmap. 172d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param height The height of the bitmap. 173d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @return A Bitmap with the encoded bytes drawn in it. 174d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @throws IOException 175d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 176d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public Bitmap decodeByteArray(@NonNull final byte[] bytes, 177d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @NonNull final BitmapFactory.Options optionsTmp, final int width, 178d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int height) throws OutOfMemoryError, IOException { 179d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (width <= 0 || height <= 0) { 180d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // This is an invalid / corrupted image of zero size. 181d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache: Decoding bitmap with " + 182d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd "invalid size"); 183d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd throw new IOException("Invalid size / corrupted image"); 184d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 185d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.notNull(bytes); 186d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.notNull(optionsTmp); 187d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd assignPoolBitmap(optionsTmp, width, height); 188d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Bitmap b = null; 189d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd try { 190d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp); 191d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mSucceededBitmapReuseCount++; 192d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } catch (final IllegalArgumentException e) { 193d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap. 194d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // (i.e. without the bitmap from the pool) 195d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (optionsTmp.inBitmap != null) { 196d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd optionsTmp.inBitmap.recycle(); 197d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd optionsTmp.inBitmap = null; 198d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp); 199d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd onFailedToReuse(); 200d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 201d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } catch (final OutOfMemoryError e) { 202d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Oom decoding inputStream"); 203d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Factory.get().reclaimMemory(); 204d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 205d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return b; 206d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 207d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 208d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 209d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Called when a new image resource is added to the cache. We add the resource to the 210d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * pool so it's properly keyed into the pool structure. 211d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 212d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd void onResourceEnterCache(final ImageResource imageResource) { 213d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (getPoolKey(imageResource) != INVALID_POOL_KEY) { 214d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd addResourceToPool(imageResource); 215d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 216d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 217d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 218d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 219d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Called when an image resource is evicted from the cache. Bitmap pool's entries are 220d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * strictly tied to their presence in the image cache. Once an image is evicted from the 221d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * cache, it should be removed from the pool. 222d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 223d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd void onResourceLeaveCache(final ImageResource imageResource) { 224d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (getPoolKey(imageResource) != INVALID_POOL_KEY) { 225d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd removeResourceFromPool(imageResource); 226d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 227d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 228d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 229d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void addResourceToPool(final ImageResource imageResource) { 230d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd synchronized (PoolableImageCache.this) { 231d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int poolKey = getPoolKey(imageResource); 232d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.isTrue(poolKey != INVALID_POOL_KEY); 233d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LinkedList<ImageResource> imageList = mImageListSparseArray.get(poolKey); 234d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (imageList == null) { 235d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd imageList = new LinkedList<ImageResource>(); 236d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mImageListSparseArray.put(poolKey, imageList); 237d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 238d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd imageList.addLast(imageResource); 239d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 240d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 241d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 242d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void removeResourceFromPool(final ImageResource imageResource) { 243d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd synchronized (PoolableImageCache.this) { 244d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int poolKey = getPoolKey(imageResource); 245d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.isTrue(poolKey != INVALID_POOL_KEY); 246d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final LinkedList<ImageResource> imageList = mImageListSparseArray.get(poolKey); 247d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (imageList != null) { 248d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd imageList.remove(imageResource); 249d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 250d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 251d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 252d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 253d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 254d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Try to get a reusable bitmap from the pool with the given width and height. As a 255d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * result of this call, the caller will assume ownership of the returned bitmap. 256d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 257d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private Bitmap getReusableBitmapFromPool(final int width, final int height) { 258d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd synchronized (PoolableImageCache.this) { 259d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int poolKey = getPoolKey(width, height); 260d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (poolKey != INVALID_POOL_KEY) { 261d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final LinkedList<ImageResource> images = mImageListSparseArray.get(poolKey); 262d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (images != null && images.size() > 0) { 263d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Try to reuse the first available bitmap from the pool list. We start from 264d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // the least recently added cache entry of the given size. 265d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ImageResource imageToUse = null; 266d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd for (int i = 0; i < images.size(); i++) { 267d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final ImageResource image = images.get(i); 268d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (image.getRefCount() == 1) { 269d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd image.acquireLock(); 270d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (image.getRefCount() == 1) { 271d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // The image is only used by the cache, so it's reusable. 272d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd imageToUse = images.remove(i); 273d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd break; 274d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else { 275d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Logically, this shouldn't happen, because as soon as the 276d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // cache is the only user of this resource, it will not be 277d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // used by anyone else until the next cache access, but we 278d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // currently hold on to the cache lock. But technically 279d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // future changes may violate this assumption, so warn about 280d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // this. 281d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Image refCount changed " + 282d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd "from 1 in getReusableBitmapFromPool()"); 283d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd image.releaseLock(); 284d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 285d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 286d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 287d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 288d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (imageToUse == null) { 289d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return null; 290d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 291d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 292d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd try { 293d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd imageToUse.assertLockHeldByCurrentThread(); 294d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 295d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Only reuse the bitmap if the last time we use was greater than 5s. 296d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // This allows the cache a chance to reuse instead of always taking the 297d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // oldest. 298d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final long timeSinceLastRef = SystemClock.elapsedRealtime() - 299d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd imageToUse.getLastRefAddTimestamp(); 300d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (timeSinceLastRef < MIN_TIME_IN_POOL) { 301d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE)) { 302d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "Not reusing reusing " + 303d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd "first available bitmap from the pool because it " + 304d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd "has not been in the pool long enough. " + 305d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd "timeSinceLastRef=" + timeSinceLastRef); 306d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 307d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Put back the image and return no reuseable bitmap. 308d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd images.addLast(imageToUse); 309d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return null; 310d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 311d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 312d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Add a temp ref on the image resource so it won't be GC'd after 313d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // being removed from the cache. 314d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd imageToUse.addRef(); 315d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 316d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Remove the image resource from the image cache. 317d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final ImageResource removed = remove(imageToUse.getKey()); 318d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Assert.isTrue(removed == imageToUse); 319d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 320d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Try to reuse the bitmap from the image resource. This will transfer 321d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // ownership of the bitmap object to the caller of this method. 322d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final Bitmap reusableBitmap = imageToUse.reuseBitmap(); 323d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 324d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd imageToUse.release(); 325d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return reusableBitmap; 326d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } finally { 327d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // We are either done with the reuse operation, or decided not to use 328d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // the image. Either way, release the lock. 329d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd imageToUse.releaseLock(); 330d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 331d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 332d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 333d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 334d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return null; 335d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 336d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 337d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 338d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Try to locate and return a reusable bitmap from the pool, or create a new bitmap. 339d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param width desired bitmap width 340d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param height desired bitmap height 341d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @return the created or reused mutable bitmap that has its background cleared to 342d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * {@value Color#TRANSPARENT} 343d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 344d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public Bitmap createOrReuseBitmap(final int width, final int height) { 345d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return createOrReuseBitmap(width, height, Color.TRANSPARENT); 346d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 347d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 348d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 349d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Try to locate and return a reusable bitmap from the pool, or create a new bitmap. 350d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param width desired bitmap width 351d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param height desired bitmap height 352d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param backgroundColor the background color for the returned bitmap 353d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @return the created or reused mutable bitmap with the requested background color 354d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 355d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public Bitmap createOrReuseBitmap(final int width, final int height, 356d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int backgroundColor) { 357d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Bitmap retBitmap = null; 358d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd try { 359d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final Bitmap poolBitmap = getReusableBitmapFromPool(width, height); 360d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd retBitmap = (poolBitmap != null) ? poolBitmap : 361d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 362d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd retBitmap.eraseColor(backgroundColor); 363d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } catch (final OutOfMemoryError e) { 364d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache:try to createOrReuseBitmap"); 365d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd Factory.get().reclaimMemory(); 366d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 367d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return retBitmap; 368d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 369d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 370d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void assignPoolBitmap(final BitmapFactory.Options optionsTmp, final int width, 371d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int height) { 372d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (optionsTmp.inJustDecodeBounds) { 373d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return; 374d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 375d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd optionsTmp.inBitmap = getReusableBitmapFromPool(width, height); 376d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 377d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 378d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 379d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @return The pool key for the provided image dimensions or 0 if either width or height is 380d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * greater than the max supported image dimension. 381d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 382d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private int getPoolKey(final int width, final int height) { 383d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (width > MAX_SUPPORTED_IMAGE_DIMENSION || height > MAX_SUPPORTED_IMAGE_DIMENSION) { 384d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return INVALID_POOL_KEY; 385d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 386d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return (width << 16) | height; 387d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 388d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 389d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 390d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @return the pool key for a given image resource. 391d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 392d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private int getPoolKey(final ImageResource imageResource) { 393d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (imageResource.supportsBitmapReuse()) { 394d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final Bitmap bitmap = imageResource.getBitmap(); 395d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (bitmap != null && bitmap.isMutable()) { 396d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int width = bitmap.getWidth(); 397d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int height = bitmap.getHeight(); 398d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (width > 0 && height > 0) { 399d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return getPoolKey(width, height); 400d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 401d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 402d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 403d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return INVALID_POOL_KEY; 404d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 405d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 406d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 407d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Called when bitmap reuse fails. Conditionally report the failure with statistics. 408d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 409d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void onFailedToReuse() { 410d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mFailedBitmapReuseCount++; 411d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) { 412d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, 413d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd "Pooled bitmap consistently not being reused. Failure count = " + 414d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mFailedBitmapReuseCount + ", success count = " + 415d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mSucceededBitmapReuseCount); 416d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 417d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 418d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 419d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd} 420