Merge "Add a memory pool for reusing direct bytebuffers." into ub-camera-haleakala
diff --git a/src/com/android/camera/processing/imagebackend/ImageBackend.java b/src/com/android/camera/processing/imagebackend/ImageBackend.java
index 98422a9..3297ff5 100644
--- a/src/com/android/camera/processing/imagebackend/ImageBackend.java
+++ b/src/com/android/camera/processing/imagebackend/ImageBackend.java
@@ -18,11 +18,13 @@
import com.android.camera.debug.Log;
import com.android.camera.processing.ProcessingTaskConsumer;
+import com.android.camera.processing.memory.ByteBufferDirectPool;
+import com.android.camera.processing.memory.LruResourcePool;
import com.android.camera.session.CaptureSession;
import com.android.camera.util.Size;
-
import com.google.common.base.Optional;
+import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -82,6 +84,7 @@
* already been completed should return immediately on its process call.
*/
public class ImageBackend implements ImageConsumer, ImageTaskManager {
+ private final static Log.Tag TAG = new Log.Tag("ImageBackend");
protected static final int FAST_THREAD_PRIORITY = Thread.MAX_PRIORITY;
protected static final int AVERAGE_THREAD_PRIORITY = Thread.NORM_PRIORITY - 1;
@@ -91,6 +94,8 @@
protected static final int NUM_THREADS_AVERAGE = 2;
protected static final int NUM_THREADS_SLOW = 2;
+ private static final int IMAGE_BACKEND_HARD_REF_POOL_SIZE = 2;
+
protected final ProcessingTaskConsumer mProcessingTaskConsumer;
/**
@@ -108,7 +113,7 @@
protected final ExecutorService mThreadPoolAverage;
protected final ExecutorService mThreadPoolSlow;
- private final static Log.Tag TAG = new Log.Tag("ImageBackend");
+ private final LruResourcePool<Integer, ByteBuffer> mByteBufferDirectPool;
/**
* Approximate viewable size (in pixels) for the fast thumbnail in the
@@ -143,6 +148,7 @@
mThreadPoolAverage = Executors.newFixedThreadPool(NUM_THREADS_AVERAGE,
new AverageThreadFactory());
mThreadPoolSlow = Executors.newFixedThreadPool(NUM_THREADS_SLOW, new SlowThreadFactory());
+ mByteBufferDirectPool = new ByteBufferDirectPool(IMAGE_BACKEND_HARD_REF_POOL_SIZE);
mProxyListener = new ImageProcessorProxyListener();
mImageSemaphoreMap = new HashMap<>();
mShadowTaskMap = new HashMap<>();
@@ -158,13 +164,17 @@
* @param slowService Service where Tasks of SLOW Priority are placed.
* @param imageProcessorProxyListener iamge proxy listener to be used
*/
- public ImageBackend(ExecutorService fastService, ExecutorService averageService,
+ public ImageBackend(ExecutorService fastService,
+ ExecutorService averageService,
ExecutorService slowService,
+ LruResourcePool<Integer, ByteBuffer> byteBufferDirectPool,
ImageProcessorProxyListener imageProcessorProxyListener,
- ProcessingTaskConsumer processingTaskConsumer, int tinyThumbnailSize) {
+ ProcessingTaskConsumer processingTaskConsumer,
+ int tinyThumbnailSize) {
mThreadPoolFast = fastService;
mThreadPoolAverage = averageService;
mThreadPoolSlow = slowService;
+ mByteBufferDirectPool = byteBufferDirectPool;
mProxyListener = imageProcessorProxyListener;
mImageSemaphoreMap = new HashMap<>();
mShadowTaskMap = new HashMap<>();
@@ -487,11 +497,12 @@
// JPEG compression of the YUV Image, and writes the result to
// disk
tasksToExecute.add(new TaskPreviewChainedJpeg(img, executor, this, session,
- FILMSTRIP_THUMBNAIL_TARGET_SIZE));
+ FILMSTRIP_THUMBNAIL_TARGET_SIZE, mByteBufferDirectPool));
} else {
// Request job that only does JPEG compression and writes the
// result to disk
- tasksToExecute.add(new TaskCompressImageToJpeg(img, executor, this, session));
+ tasksToExecute.add(new TaskCompressImageToJpeg(img, executor, this, session,
+ mByteBufferDirectPool));
}
}
@@ -546,7 +557,8 @@
public TaskCompressImageToJpeg createTaskCompressImageToJpeg(ImageToProcess image,
Executor executor, ImageBackend imageBackend, CaptureSession session) {
- return new TaskCompressImageToJpeg(image, executor, imageBackend, session);
+ return new TaskCompressImageToJpeg(image, executor, imageBackend, session,
+ mByteBufferDirectPool);
}
/**
diff --git a/src/com/android/camera/processing/imagebackend/TaskCompressImageToJpeg.java b/src/com/android/camera/processing/imagebackend/TaskCompressImageToJpeg.java
index b5120e5..118cfed 100644
--- a/src/com/android/camera/processing/imagebackend/TaskCompressImageToJpeg.java
+++ b/src/com/android/camera/processing/imagebackend/TaskCompressImageToJpeg.java
@@ -29,6 +29,8 @@
import com.android.camera.one.v2.camera2proxy.CaptureResultProxy;
import com.android.camera.one.v2.camera2proxy.ImageProxy;
import com.android.camera.one.v2.camera2proxy.TotalCaptureResultProxy;
+import com.android.camera.processing.memory.LruResourcePool;
+import com.android.camera.processing.memory.LruResourcePool.Resource;
import com.android.camera.session.CaptureSession;
import com.android.camera.util.ExifUtil;
import com.android.camera.util.JpegUtilNative;
@@ -51,6 +53,15 @@
* that the JPEG is already encoded in the proper orientation.
*/
public class TaskCompressImageToJpeg extends TaskJpegEncode {
+
+ /**
+ * Loss-less JPEG compression is usually about a factor of 5,
+ * and is a safe lower bound for this value to use to reduce the memory
+ * footprint for encoding the final jpg.
+ */
+ private static final int MINIMUM_EXPECTED_JPG_COMPRESSION_FACTOR = 5;
+ private final LruResourcePool<Integer, ByteBuffer> mByteBufferDirectPool;
+
/**
* Constructor
*
@@ -61,8 +72,10 @@
*/
TaskCompressImageToJpeg(ImageToProcess image, Executor executor,
ImageTaskManager imageTaskManager,
- CaptureSession captureSession) {
+ CaptureSession captureSession,
+ LruResourcePool<Integer, ByteBuffer> byteBufferResourcePool) {
super(image, executor, imageTaskManager, ProcessingPriority.SLOW, captureSession);
+ mByteBufferDirectPool = byteBufferResourcePool;
}
/**
@@ -105,6 +118,7 @@
int numBytes;
ByteBuffer compressedData;
ExifInterface exifData = null;
+ Resource<ByteBuffer> byteBufferResource = null;
switch (img.proxy.getFormat()) {
case ImageFormat.JPEG:
@@ -240,13 +254,27 @@
onStart(mId, inputImage, resultImage, TaskInfo.Destination.FINAL_IMAGE);
- compressedData = ByteBuffer.allocateDirect(3 * resultImage.width
- * resultImage.height);
+ // WARNING:
+ // This reduces the size of the buffer that is created
+ // to hold the final jpg. It is reduced by the "Minimum expected
+ // jpg compression factor" to reduce memory allocation consumption.
+ // If the final jpg is more than this size the image will be
+ // corrupted. The maximum size of an image is width * height *
+ // number_of_channels. We artificially reduce this number based on
+ // what we expect the compression ratio to be to reduce the
+ // amount of memory we are required to allocate.
+ int maxPossibleJpgSize = 3 * resultImage.width * resultImage.height;
+ int jpgBufferSize = maxPossibleJpgSize /
+ MINIMUM_EXPECTED_JPG_COMPRESSION_FACTOR;
+
+ byteBufferResource = mByteBufferDirectPool.acquire(jpgBufferSize);
+ compressedData = byteBufferResource.get();
// On memory allocation failure, fail gracefully.
if (compressedData == null) {
// TODO: Put memory allocation failure code here.
mSession.finishWithFailure(-1, true);
+ byteBufferResource.close();
return;
}
@@ -255,7 +283,28 @@
img.proxy, compressedData, getJpegCompressionQuality(),
img.crop, inputImage.orientation.getDegrees());
+ // If the compression overflows the size of the buffer, the
+ // actual number of bytes will be returned.
+ if (numBytes > jpgBufferSize) {
+ byteBufferResource.close();
+ mByteBufferDirectPool.acquire(maxPossibleJpgSize);
+ compressedData = byteBufferResource.get();
+
+ // On memory allocation failure, fail gracefully.
+ if (compressedData == null) {
+ // TODO: Put memory allocation failure code here.
+ mSession.finishWithFailure(-1, true);
+ byteBufferResource.close();
+ return;
+ }
+
+ numBytes = compressJpegFromYUV420Image(
+ img.proxy, compressedData, getJpegCompressionQuality(),
+ img.crop, inputImage.orientation.getDegrees());
+ }
+
if (numBytes < 0) {
+ byteBufferResource.close();
throw new RuntimeException("Error compressing jpeg.");
}
compressedData.limit(numBytes);
@@ -275,6 +324,10 @@
compressedData.get(writeOut);
compressedData.rewind();
+ if (byteBufferResource != null) {
+ byteBufferResource.close();
+ }
+
onJpegEncodeDone(mId, inputImage, resultImage, writeOut,
TaskInfo.Destination.FINAL_IMAGE);
diff --git a/src/com/android/camera/processing/imagebackend/TaskJpegEncode.java b/src/com/android/camera/processing/imagebackend/TaskJpegEncode.java
index 333c693..c818401 100644
--- a/src/com/android/camera/processing/imagebackend/TaskJpegEncode.java
+++ b/src/com/android/camera/processing/imagebackend/TaskJpegEncode.java
@@ -23,11 +23,9 @@
import android.graphics.YuvImage;
import android.net.Uri;
-import com.android.camera.app.OrientationManager;
import com.android.camera.debug.Log;
import com.android.camera.one.v2.camera2proxy.ImageProxy;
import com.android.camera.session.CaptureSession;
-import com.android.camera.util.Size;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
diff --git a/src/com/android/camera/processing/imagebackend/TaskPreviewChainedJpeg.java b/src/com/android/camera/processing/imagebackend/TaskPreviewChainedJpeg.java
index 3fb57b2..b20b764 100644
--- a/src/com/android/camera/processing/imagebackend/TaskPreviewChainedJpeg.java
+++ b/src/com/android/camera/processing/imagebackend/TaskPreviewChainedJpeg.java
@@ -17,10 +17,12 @@
package com.android.camera.processing.imagebackend;
import android.graphics.Rect;
-import com.android.camera.debug.Log;
+
+import com.android.camera.processing.memory.LruResourcePool;
import com.android.camera.session.CaptureSession;
import com.android.camera.util.Size;
+import java.nio.ByteBuffer;
import java.util.concurrent.Executor;
/**
@@ -28,6 +30,8 @@
* inscribed in a circle.
*/
public class TaskPreviewChainedJpeg extends TaskConvertImageToRGBPreview {
+ private final LruResourcePool<Integer, ByteBuffer> mByteBufferDirectPool;
+
/**
* Constructor
*
@@ -38,10 +42,15 @@
* @param captureSession Capture session that bound to this image
* @param targetSize Approximate viewable pixel demensions of the desired
* preview Image */
- TaskPreviewChainedJpeg(ImageToProcess image, Executor executor,
- ImageTaskManager imageTaskManager, CaptureSession captureSession, Size targetSize) {
+ TaskPreviewChainedJpeg(ImageToProcess image,
+ Executor executor,
+ ImageTaskManager imageTaskManager,
+ CaptureSession captureSession,
+ Size targetSize,
+ LruResourcePool<Integer, ByteBuffer> byteBufferResourcePool) {
super(image, executor, imageTaskManager, ProcessingPriority.SLOW, captureSession,
targetSize , ThumbnailShape.MAINTAIN_ASPECT_NO_INSET);
+ mByteBufferDirectPool = byteBufferResourcePool;
}
public void logWrapper(String message) {
@@ -72,7 +81,7 @@
// Chain JPEG task
TaskImageContainer jpegTask = new TaskCompressImageToJpeg(img, mExecutor,
- mImageTaskManager, mSession);
+ mImageTaskManager, mSession, mByteBufferDirectPool);
mImageTaskManager.appendTasks(img, jpegTask);
} finally {
// Signal backend that reference has been released
diff --git a/src/com/android/camera/processing/memory/ByteBufferDirectPool.java b/src/com/android/camera/processing/memory/ByteBufferDirectPool.java
new file mode 100644
index 0000000..f87e589
--- /dev/null
+++ b/src/com/android/camera/processing/memory/ByteBufferDirectPool.java
@@ -0,0 +1,42 @@
+/*
+ * 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.camera.processing.memory;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Resource pool for large, directly allocated byte buffers. The integer key
+ * represents the size of the bytebuffer.
+ */
+public final class ByteBufferDirectPool extends SimpleLruResourcePool<Integer, ByteBuffer> {
+ public ByteBufferDirectPool(int lruSize) {
+ super(lruSize);
+ }
+
+ @Override
+ protected ByteBuffer create(Integer bytes) {
+ return ByteBuffer.allocateDirect(bytes);
+ }
+
+ @Override
+ protected ByteBuffer recycle(Integer integer, ByteBuffer byteBuffer) {
+ // Reset byte buffer location and limits
+ byteBuffer.rewind();
+ byteBuffer.limit(byteBuffer.capacity());
+ return byteBuffer;
+ }
+}
diff --git a/src/com/android/camera/processing/memory/LruPool.java b/src/com/android/camera/processing/memory/LruPool.java
new file mode 100644
index 0000000..93a1cdf
--- /dev/null
+++ b/src/com/android/camera/processing/memory/LruPool.java
@@ -0,0 +1,255 @@
+/*
+ * 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.camera.processing.memory;
+
+import com.google.common.base.Preconditions;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Queue;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * LruPool that will evict items from the pool after reaching maximum size in
+ * order of Least Recently Used (LRU). This code is based on the Android
+ * Lru implementation but removes the hard requirement that keys must only
+ * exist once. Different values may be returned for the same key, and there is
+ * no guarantee that adding and then immediately acquiring the same key will
+ * return the same value instance.
+ *
+ * The size of the pool is generally equal to the number of items, but can be
+ * reconfigured by a subclass to be proportional to some other computed value.
+ *
+ * This class has multiple moving parts and should only be use to track and
+ * reuse objects that are expensive to create or re-create.
+ *
+ * WARNING:
+ * {@link #acquire(TKey)} is currently linear time, pending a better
+ * implementation.
+ *
+ * TODO: Build a constant time acquire(TKey) method implementation.
+ *
+ */
+public class LruPool<TKey, TValue> {
+ public static class Configuration<TKey, TValue> {
+ /**
+ * Called for entries that have been evicted or removed. This method is
+ * invoked when a value is evicted to make space, but NOT when an item is
+ * removed via {@link #acquire}. The default
+ * implementation does nothing.
+ *
+ * <p>The method is called without synchronization: other threads may
+ * access the cache while this method is executing.
+ */
+ void entryEvicted(TKey key, TValue value) { }
+
+ /**
+ * Called after a cache miss to compute a value for the corresponding key.
+ * Returns the computed value or null if no value can be computed. The
+ * default implementation returns null.
+ *
+ * <p>The method is called without synchronization: other threads may
+ * access the cache while this method is executing.
+ */
+ TValue create(TKey key) {
+ return null;
+ }
+
+ /**
+ * Returns the size of the entry for {@code key} and {@code value} in
+ * user-defined units. The default implementation returns 1 so that size
+ * is the number of entries and max size is the maximum number of entries.
+ *
+ * <p>An entry's size must not change while it is in the cache.
+ */
+ int sizeOf(TKey key, TValue value) {
+ return 1;
+ }
+ }
+
+ private final Object mLock;
+
+ /**
+ * Maintains an ordered list of keys by "most recently added". Duplicate
+ * keys can exist in the list.
+ */
+ @GuardedBy("mLock")
+ private final LinkedList<TKey> mLruKeyList;
+
+ /**
+ * Maintains individual pools for each distinct key type.
+ */
+ @GuardedBy("mLock")
+ private final HashMap<TKey, Queue<TValue>> mValuePool;
+ private final Configuration<TKey, TValue> mConfiguration;
+
+ private final int mMaxSize;
+
+ /**
+ * Size may be configured to represent quantities other than the number of
+ * items in the pool. By default, it represents the number of items
+ * in the pool.
+ */
+ @GuardedBy("mLock")
+ private int mSize;
+
+ /**
+ * Creates and sets the size of the Lru Pool
+ *
+ * @param maxSize Sets the size of the Lru Pool.
+ */
+ public LruPool(int maxSize) {
+ this(maxSize, new Configuration<TKey, TValue>());
+ }
+
+ public LruPool(int maxSize, Configuration<TKey, TValue> configuration) {
+ Preconditions.checkArgument(maxSize > 0, "maxSize must be > 0.");
+
+ mMaxSize = maxSize;
+ mConfiguration = configuration;
+
+ mLock = new Object();
+ mLruKeyList = new LinkedList<>();
+ mValuePool = new HashMap<>();
+ }
+
+ /**
+ * Acquire a value from the pool, or attempt to create a new one if the create
+ * method is overridden. If an item cannot be retrieved or created, this method
+ * will return null.
+ *
+ * WARNING:
+ * This implementation is currently linear time, pending a better
+ * implementation.
+ *
+ * TODO: Build a constant time acquire(TKey) method implementation.
+ *
+ * @param key the type of object to retrieve from the pool.
+ * @return a value or null if none exists or can be created.
+ */
+ public final TValue acquire(TKey key) {
+ Preconditions.checkNotNull(key);
+
+ // We must remove the item we acquire from the list
+ TValue value;
+
+ synchronized (mLock) {
+ if (mLruKeyList.removeLastOccurrence(key)) {
+ value = mValuePool.get(key).remove();
+ mSize -= checkedSizeOf(key, value);
+ } else {
+ value = mConfiguration.create(key);
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * Add a new or previously existing value to the pool. The most recently added
+ * item will be placed at the top of the Lru list, and will trim existing items
+ * off the list, if the list exceeds the maximum size.
+ *
+ * @param key the type of object to add to the pool.
+ * @param value the object to add into the pool.
+ */
+ public final void add(TKey key, TValue value) {
+ Preconditions.checkNotNull(key);
+ Preconditions.checkNotNull(value);
+
+ synchronized (mLock) {
+ final Queue<TValue> pool;
+
+ mLruKeyList.push(key);
+ if (!mValuePool.containsKey(key)) {
+ pool = new LinkedList<>();
+ mValuePool.put(key, pool);
+ } else {
+ pool = mValuePool.get(key);
+ }
+ pool.add(value);
+ mSize += checkedSizeOf(key, value);
+
+ unsafeTrimToSize(mMaxSize);
+ }
+ }
+
+ /**
+ * Remove the oldest entries until the total of remaining entries is at or
+ * below the configured size.
+ *
+ * @param trimToSize the maximum size of the cache before returning. May
+ * be -1 to evict even 0-sized elements.
+ */
+ public final void trimToSize(int trimToSize) {
+ synchronized (mLock) {
+ unsafeTrimToSize(trimToSize);
+ }
+ }
+
+ /**
+ * For pools that do not override {@link Configuration#sizeOf}, this
+ * returns the number of items in the pool. For custom sizes, this returns
+ * the sum of the sizes of the entries in this pool.
+ */
+ public final int getSize() {
+ synchronized (mLock) {
+ return mSize;
+ }
+ }
+
+ /**
+ * For pools that do not override {@link Configuration#sizeOf}, this
+ * returns the maximum number of entries in the pool. For all other pools,
+ * this returns the maximum sum of the sizes of the entries in this pool.
+ */
+ public final int getMaxSize() {
+ return mMaxSize;
+ }
+
+ @GuardedBy("mLock")
+ private void unsafeTrimToSize(int trimToSize) {
+ while (mSize > trimToSize && !mLruKeyList.isEmpty()) {
+ TKey key = mLruKeyList.removeLast();
+ if (key == null) {
+ break;
+ }
+
+ Queue<TValue> pool = mValuePool.get(key);
+ TValue value = pool.remove();
+
+ if (pool.size() <= 0) {
+ mValuePool.remove(key);
+ }
+
+ mSize = mSize - checkedSizeOf(key, value);
+ mConfiguration.entryEvicted(key, value);
+ }
+
+ if (mSize < 0 || (mLruKeyList.isEmpty() && mSize != 0)) {
+ throw new IllegalStateException("LruPool.sizeOf() is reporting "
+ + "inconsistent results!");
+ }
+ }
+
+ private int checkedSizeOf(TKey key, TValue value) {
+ int result = mConfiguration.sizeOf(key, value);
+ Preconditions.checkArgument(result >= 0, "Size was < 0.");
+ return result;
+ }
+}
diff --git a/src/com/android/camera/processing/memory/LruResourcePool.java b/src/com/android/camera/processing/memory/LruResourcePool.java
new file mode 100644
index 0000000..6488951
--- /dev/null
+++ b/src/com/android/camera/processing/memory/LruResourcePool.java
@@ -0,0 +1,50 @@
+/*
+ * 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.camera.processing.memory;
+
+import com.android.camera.async.SafeCloseable;
+
+import javax.annotation.Nullable;
+
+/**
+ * Pool for and/or creating or reusing expensive resources.
+ */
+public interface LruResourcePool<TKey, TValue> {
+ /**
+ * Returns a wrapped reference to a resource.
+ *
+ * @param key size of memory.
+ * @return T object representing a reusable n-bytes of memory.
+ */
+ public Resource<TValue> acquire(TKey key);
+
+ /**
+ * Closeable resource object that will release the underlying object back to the
+ * source resource pool. A resource may be released or closed multiple times,
+ * and calls to get will return null if the resource has already been released.
+ */
+ public interface Resource<T> extends SafeCloseable {
+ /**
+ * Get the underlying resource. This will return null if the resource has been
+ * closed and returned to the pool.
+ *
+ * @return the resource represented by this instance.
+ */
+ @Nullable
+ public T get();
+ }
+}
diff --git a/src/com/android/camera/processing/memory/SimpleLruResourcePool.java b/src/com/android/camera/processing/memory/SimpleLruResourcePool.java
new file mode 100644
index 0000000..9cb5c01
--- /dev/null
+++ b/src/com/android/camera/processing/memory/SimpleLruResourcePool.java
@@ -0,0 +1,125 @@
+/*
+ * 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.camera.processing.memory;
+
+import com.google.common.base.Preconditions;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Simple resource based memory pool that can automatically return
+ * items back to the memory pool when closed.
+ */
+@ThreadSafe
+public abstract class SimpleLruResourcePool<TKey, TValue> implements LruResourcePool<TKey, TValue> {
+ @GuardedBy("mLock")
+ private final LruPool<TKey, TValue> mLruPool;
+
+ private final Object mLock;
+
+ public SimpleLruResourcePool(int lruSize) {
+ Preconditions.checkArgument(lruSize > 0);
+
+ mLock = new Object();
+ mLruPool = new LruPool<>(lruSize);
+ }
+
+ @Override
+ public Resource<TValue> acquire(TKey key) {
+ TValue value;
+ synchronized (mLock) {
+ value = mLruPool.acquire(key);
+ }
+
+ // We may not reach a point where we have have a value to reuse,
+ // create a new one.
+ if(value == null) {
+ value = create(key);
+ }
+
+ return new SynchronizedResource<>(this, key, value);
+ }
+
+ /**
+ * Create a new value for a given key.
+ */
+ protected abstract TValue create(TKey key);
+
+ /**
+ * Recycle or reset a given value before it is added back to the pool,
+ * by default, this does nothing.
+ */
+ protected TValue recycle(TKey key, TValue value) {
+ return value;
+ }
+
+ /**
+ * Returns an item to the LruPool.
+ */
+ private void release(TKey key, TValue value) {
+ mLruPool.add(key, recycle(key, value));
+ }
+
+ /**
+ * This is a closable resource that returns the underlying value to the pool
+ * when the object is closed.
+ */
+ @ThreadSafe
+ private static final class SynchronizedResource<TKey, TValue> implements Resource<TValue> {
+ private final Object mLock;
+ private final SimpleLruResourcePool<TKey, TValue> mPool;
+
+ @GuardedBy("mLock")
+ private TKey mKey;
+
+ @GuardedBy("mLock")
+ private TValue mValue;
+
+ public SynchronizedResource(SimpleLruResourcePool<TKey, TValue> pool,
+ TKey key, TValue value) {
+ mPool = pool;
+ mKey = key;
+ mValue = value;
+
+ mLock = new Object();
+ }
+
+ @Nullable
+ @Override
+ public TValue get() {
+ synchronized (mLock) {
+ if (mValue != null) {
+ return mValue;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void close() {
+ synchronized (mLock) {
+ if (mValue != null) {
+ mPool.release(mKey, mValue);
+ mValue = null;
+ mKey = null;
+ }
+ }
+ }
+ }
+}