summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/datamodel/media/PoolableImageCache.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/messaging/datamodel/media/PoolableImageCache.java')
-rw-r--r--src/com/android/messaging/datamodel/media/PoolableImageCache.java419
1 files changed, 0 insertions, 419 deletions
diff --git a/src/com/android/messaging/datamodel/media/PoolableImageCache.java b/src/com/android/messaging/datamodel/media/PoolableImageCache.java
deleted file mode 100644
index df814ba..0000000
--- a/src/com/android/messaging/datamodel/media/PoolableImageCache.java
+++ /dev/null
@@ -1,419 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.messaging.datamodel.media;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.os.SystemClock;
-import android.support.annotation.NonNull;
-import android.util.SparseArray;
-
-import com.android.messaging.Factory;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.LinkedList;
-
-/**
- * A media cache that holds image resources, which doubles as a bitmap pool that allows the
- * consumer to optionally decode image resources using unused bitmaps stored in the cache.
- */
-public class PoolableImageCache extends MediaCache<ImageResource> {
- private static final int MIN_TIME_IN_POOL = 5000;
-
- /** Encapsulates bitmap pool representation of the image cache */
- private final ReusableImageResourcePool mReusablePoolAccessor = new ReusableImageResourcePool();
-
- public PoolableImageCache(final int id, final String name) {
- this(DEFAULT_MEDIA_RESOURCE_CACHE_SIZE_IN_KILOBYTES, id, name);
- }
-
- public PoolableImageCache(final int maxSize, final int id, final String name) {
- super(maxSize, id, name);
- }
-
- /**
- * Creates a new BitmapFactory.Options for using the self-contained bitmap pool.
- */
- public static BitmapFactory.Options getBitmapOptionsForPool(final boolean scaled,
- final int inputDensity, final int targetDensity) {
- final BitmapFactory.Options options = new BitmapFactory.Options();
- options.inScaled = scaled;
- options.inDensity = inputDensity;
- options.inTargetDensity = targetDensity;
- options.inSampleSize = 1;
- options.inJustDecodeBounds = false;
- options.inMutable = true;
- return options;
- }
-
- @Override
- public synchronized ImageResource addResourceToCache(final String key,
- final ImageResource imageResource) {
- mReusablePoolAccessor.onResourceEnterCache(imageResource);
- return super.addResourceToCache(key, imageResource);
- }
-
- @Override
- protected synchronized void entryRemoved(final boolean evicted, final String key,
- final ImageResource oldValue, final ImageResource newValue) {
- mReusablePoolAccessor.onResourceLeaveCache(oldValue);
- super.entryRemoved(evicted, key, oldValue, newValue);
- }
-
- /**
- * Returns a representation of the image cache as a reusable bitmap pool.
- */
- public ReusableImageResourcePool asReusableBitmapPool() {
- return mReusablePoolAccessor;
- }
-
- /**
- * A bitmap pool representation built on top of the image cache. It treats the image resources
- * stored in the image cache as a self-contained bitmap pool and is able to create or
- * reclaim bitmap resource as needed.
- */
- public class ReusableImageResourcePool {
- private static final int MAX_SUPPORTED_IMAGE_DIMENSION = 0xFFFF;
- private static final int INVALID_POOL_KEY = 0;
-
- /**
- * Number of reuse failures to skip before reporting.
- * For debugging purposes, change to a lower number for more frequent reporting.
- */
- private static final int FAILED_REPORTING_FREQUENCY = 100;
-
- /**
- * Count of reuse failures which have occurred.
- */
- private volatile int mFailedBitmapReuseCount = 0;
-
- /**
- * Count of reuse successes which have occurred.
- */
- private volatile int mSucceededBitmapReuseCount = 0;
-
- /**
- * A sparse array from bitmap size to a list of image cache entries that match the
- * given size. This map is used to quickly retrieve a usable bitmap to be reused by an
- * incoming ImageRequest. We need to ensure that this sparse array always contains only
- * elements currently in the image cache with no other consumer.
- */
- private final SparseArray<LinkedList<ImageResource>> mImageListSparseArray;
-
- public ReusableImageResourcePool() {
- mImageListSparseArray = new SparseArray<LinkedList<ImageResource>>();
- }
-
- /**
- * Load an input stream into a bitmap. Uses a bitmap from the pool if possible to reduce
- * memory turnover.
- * @param inputStream InputStream load. Cannot be null.
- * @param optionsTmp Should be the same options returned from getBitmapOptionsForPool().
- * Cannot be null.
- * @param width The width of the bitmap.
- * @param height The height of the bitmap.
- * @return The decoded Bitmap with the resource drawn in it.
- * @throws IOException
- */
- public Bitmap decodeSampledBitmapFromInputStream(@NonNull final InputStream inputStream,
- @NonNull final BitmapFactory.Options optionsTmp,
- final int width, final int height) throws IOException {
- if (width <= 0 || height <= 0) {
- // This is an invalid / corrupted image of zero size.
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache: Decoding bitmap with " +
- "invalid size");
- throw new IOException("Invalid size / corrupted image");
- }
- Assert.notNull(inputStream);
- assignPoolBitmap(optionsTmp, width, height);
- Bitmap b = null;
- try {
- b = BitmapFactory.decodeStream(inputStream, null, optionsTmp);
- mSucceededBitmapReuseCount++;
- } catch (final IllegalArgumentException e) {
- // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
- if (optionsTmp.inBitmap != null) {
- optionsTmp.inBitmap.recycle();
- optionsTmp.inBitmap = null;
- b = BitmapFactory.decodeStream(inputStream, null, optionsTmp);
- onFailedToReuse();
- }
- } catch (final OutOfMemoryError e) {
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Oom decoding inputStream");
- Factory.get().reclaimMemory();
- }
- return b;
- }
-
- /**
- * Turn encoded bytes into a bitmap. Uses a bitmap from the pool if possible to reduce
- * memory turnover.
- * @param bytes Encoded bytes to draw on the bitmap. Cannot be null.
- * @param optionsTmp The bitmap will set here and the input should be generated from
- * getBitmapOptionsForPool(). Cannot be null.
- * @param width The width of the bitmap.
- * @param height The height of the bitmap.
- * @return A Bitmap with the encoded bytes drawn in it.
- * @throws IOException
- */
- public Bitmap decodeByteArray(@NonNull final byte[] bytes,
- @NonNull final BitmapFactory.Options optionsTmp, final int width,
- final int height) throws OutOfMemoryError, IOException {
- if (width <= 0 || height <= 0) {
- // This is an invalid / corrupted image of zero size.
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache: Decoding bitmap with " +
- "invalid size");
- throw new IOException("Invalid size / corrupted image");
- }
- Assert.notNull(bytes);
- Assert.notNull(optionsTmp);
- assignPoolBitmap(optionsTmp, width, height);
- Bitmap b = null;
- try {
- b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp);
- mSucceededBitmapReuseCount++;
- } catch (final IllegalArgumentException e) {
- // BitmapFactory couldn't decode the file, try again without an inputBufferBitmap.
- // (i.e. without the bitmap from the pool)
- if (optionsTmp.inBitmap != null) {
- optionsTmp.inBitmap.recycle();
- optionsTmp.inBitmap = null;
- b = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, optionsTmp);
- onFailedToReuse();
- }
- } catch (final OutOfMemoryError e) {
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Oom decoding inputStream");
- Factory.get().reclaimMemory();
- }
- return b;
- }
-
- /**
- * Called when a new image resource is added to the cache. We add the resource to the
- * pool so it's properly keyed into the pool structure.
- */
- void onResourceEnterCache(final ImageResource imageResource) {
- if (getPoolKey(imageResource) != INVALID_POOL_KEY) {
- addResourceToPool(imageResource);
- }
- }
-
- /**
- * Called when an image resource is evicted from the cache. Bitmap pool's entries are
- * strictly tied to their presence in the image cache. Once an image is evicted from the
- * cache, it should be removed from the pool.
- */
- void onResourceLeaveCache(final ImageResource imageResource) {
- if (getPoolKey(imageResource) != INVALID_POOL_KEY) {
- removeResourceFromPool(imageResource);
- }
- }
-
- private void addResourceToPool(final ImageResource imageResource) {
- synchronized (PoolableImageCache.this) {
- final int poolKey = getPoolKey(imageResource);
- Assert.isTrue(poolKey != INVALID_POOL_KEY);
- LinkedList<ImageResource> imageList = mImageListSparseArray.get(poolKey);
- if (imageList == null) {
- imageList = new LinkedList<ImageResource>();
- mImageListSparseArray.put(poolKey, imageList);
- }
- imageList.addLast(imageResource);
- }
- }
-
- private void removeResourceFromPool(final ImageResource imageResource) {
- synchronized (PoolableImageCache.this) {
- final int poolKey = getPoolKey(imageResource);
- Assert.isTrue(poolKey != INVALID_POOL_KEY);
- final LinkedList<ImageResource> imageList = mImageListSparseArray.get(poolKey);
- if (imageList != null) {
- imageList.remove(imageResource);
- }
- }
- }
-
- /**
- * Try to get a reusable bitmap from the pool with the given width and height. As a
- * result of this call, the caller will assume ownership of the returned bitmap.
- */
- private Bitmap getReusableBitmapFromPool(final int width, final int height) {
- synchronized (PoolableImageCache.this) {
- final int poolKey = getPoolKey(width, height);
- if (poolKey != INVALID_POOL_KEY) {
- final LinkedList<ImageResource> images = mImageListSparseArray.get(poolKey);
- if (images != null && images.size() > 0) {
- // Try to reuse the first available bitmap from the pool list. We start from
- // the least recently added cache entry of the given size.
- ImageResource imageToUse = null;
- for (int i = 0; i < images.size(); i++) {
- final ImageResource image = images.get(i);
- if (image.getRefCount() == 1) {
- image.acquireLock();
- if (image.getRefCount() == 1) {
- // The image is only used by the cache, so it's reusable.
- imageToUse = images.remove(i);
- break;
- } else {
- // Logically, this shouldn't happen, because as soon as the
- // cache is the only user of this resource, it will not be
- // used by anyone else until the next cache access, but we
- // currently hold on to the cache lock. But technically
- // future changes may violate this assumption, so warn about
- // this.
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "Image refCount changed " +
- "from 1 in getReusableBitmapFromPool()");
- image.releaseLock();
- }
- }
- }
-
- if (imageToUse == null) {
- return null;
- }
-
- try {
- imageToUse.assertLockHeldByCurrentThread();
-
- // Only reuse the bitmap if the last time we use was greater than 5s.
- // This allows the cache a chance to reuse instead of always taking the
- // oldest.
- final long timeSinceLastRef = SystemClock.elapsedRealtime() -
- imageToUse.getLastRefAddTimestamp();
- if (timeSinceLastRef < MIN_TIME_IN_POOL) {
- if (LogUtil.isLoggable(LogUtil.BUGLE_IMAGE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_IMAGE_TAG, "Not reusing reusing " +
- "first available bitmap from the pool because it " +
- "has not been in the pool long enough. " +
- "timeSinceLastRef=" + timeSinceLastRef);
- }
- // Put back the image and return no reuseable bitmap.
- images.addLast(imageToUse);
- return null;
- }
-
- // Add a temp ref on the image resource so it won't be GC'd after
- // being removed from the cache.
- imageToUse.addRef();
-
- // Remove the image resource from the image cache.
- final ImageResource removed = remove(imageToUse.getKey());
- Assert.isTrue(removed == imageToUse);
-
- // Try to reuse the bitmap from the image resource. This will transfer
- // ownership of the bitmap object to the caller of this method.
- final Bitmap reusableBitmap = imageToUse.reuseBitmap();
-
- imageToUse.release();
- return reusableBitmap;
- } finally {
- // We are either done with the reuse operation, or decided not to use
- // the image. Either way, release the lock.
- imageToUse.releaseLock();
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Try to locate and return a reusable bitmap from the pool, or create a new bitmap.
- * @param width desired bitmap width
- * @param height desired bitmap height
- * @return the created or reused mutable bitmap that has its background cleared to
- * {@value Color#TRANSPARENT}
- */
- public Bitmap createOrReuseBitmap(final int width, final int height) {
- return createOrReuseBitmap(width, height, Color.TRANSPARENT);
- }
-
- /**
- * Try to locate and return a reusable bitmap from the pool, or create a new bitmap.
- * @param width desired bitmap width
- * @param height desired bitmap height
- * @param backgroundColor the background color for the returned bitmap
- * @return the created or reused mutable bitmap with the requested background color
- */
- public Bitmap createOrReuseBitmap(final int width, final int height,
- final int backgroundColor) {
- Bitmap retBitmap = null;
- try {
- final Bitmap poolBitmap = getReusableBitmapFromPool(width, height);
- retBitmap = (poolBitmap != null) ? poolBitmap :
- Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- retBitmap.eraseColor(backgroundColor);
- } catch (final OutOfMemoryError e) {
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG, "PoolableImageCache:try to createOrReuseBitmap");
- Factory.get().reclaimMemory();
- }
- return retBitmap;
- }
-
- private void assignPoolBitmap(final BitmapFactory.Options optionsTmp, final int width,
- final int height) {
- if (optionsTmp.inJustDecodeBounds) {
- return;
- }
- optionsTmp.inBitmap = getReusableBitmapFromPool(width, height);
- }
-
- /**
- * @return The pool key for the provided image dimensions or 0 if either width or height is
- * greater than the max supported image dimension.
- */
- private int getPoolKey(final int width, final int height) {
- if (width > MAX_SUPPORTED_IMAGE_DIMENSION || height > MAX_SUPPORTED_IMAGE_DIMENSION) {
- return INVALID_POOL_KEY;
- }
- return (width << 16) | height;
- }
-
- /**
- * @return the pool key for a given image resource.
- */
- private int getPoolKey(final ImageResource imageResource) {
- if (imageResource.supportsBitmapReuse()) {
- final Bitmap bitmap = imageResource.getBitmap();
- if (bitmap != null && bitmap.isMutable()) {
- final int width = bitmap.getWidth();
- final int height = bitmap.getHeight();
- if (width > 0 && height > 0) {
- return getPoolKey(width, height);
- }
- }
- }
- return INVALID_POOL_KEY;
- }
-
- /**
- * Called when bitmap reuse fails. Conditionally report the failure with statistics.
- */
- private void onFailedToReuse() {
- mFailedBitmapReuseCount++;
- if (mFailedBitmapReuseCount % FAILED_REPORTING_FREQUENCY == 0) {
- LogUtil.w(LogUtil.BUGLE_IMAGE_TAG,
- "Pooled bitmap consistently not being reused. Failure count = " +
- mFailedBitmapReuseCount + ", success count = " +
- mSucceededBitmapReuseCount);
- }
- }
- }
-}