package com.android.camera.debug;
import com.android.camera.debug.Log.Tag;
+import com.google.common.annotations.VisibleForTesting;
import javax.annotation.ParametersAreNonnullByDefault;
}
/**
- * This creates a factory that will use the standard android
- * static log methods.
+ * This creates a factory that will use the standard android static log
+ * methods.
*/
public static Logger.Factory tagFactory() {
return TagLoggerFactory.instance();
}
/**
+ * Creates a logger factory which always returns the given logger.
+ */
+ public static Logger.Factory factoryFor(final Logger logger) {
+ return new Logger.Factory() {
+ @Override
+ public Logger create(Tag tag) {
+ return logger;
+ }
+ };
+ }
+
+ /**
* Creates loggers that eat all input and does nothing.
*/
private static class NoOpLoggerFactory implements Logger.Factory {
private static final NoOpLoggerFactory INSTANCE = new NoOpLoggerFactory();
}
- public static NoOpLoggerFactory instance() { return Singleton.INSTANCE; }
+ public static NoOpLoggerFactory instance() {
+ return Singleton.INSTANCE;
+ }
private final NoOpLogger mNoOpLogger;
}
/**
- * Creates loggers that use tag objects to write to standard
- * android log output.
+ * Creates loggers that use tag objects to write to standard android log
+ * output.
*/
private static class TagLoggerFactory implements Logger.Factory {
private static class Singleton {
private static final TagLoggerFactory INSTANCE = new TagLoggerFactory();
}
- public static TagLoggerFactory instance() { return Singleton.INSTANCE; }
+ public static TagLoggerFactory instance() {
+ return Singleton.INSTANCE;
+ }
@Override
public Logger create(Tag tag) {
*/
private static class NoOpLogger implements Logger {
@Override
- public void d(String msg) { }
+ public void d(String msg) {
+ }
@Override
- public void d(String msg, Throwable tr) { }
+ public void d(String msg, Throwable tr) {
+ }
@Override
- public void e(String msg) { }
+ public void e(String msg) {
+ }
@Override
- public void e(String msg, Throwable tr) { }
+ public void e(String msg, Throwable tr) {
+ }
@Override
- public void i(String msg) { }
+ public void i(String msg) {
+ }
@Override
- public void i(String msg, Throwable tr) { }
+ public void i(String msg, Throwable tr) {
+ }
@Override
- public void v(String msg) { }
+ public void v(String msg) {
+ }
@Override
- public void v(String msg, Throwable tr) { }
+ public void v(String msg, Throwable tr) {
+ }
@Override
- public void w(String msg) { }
+ public void w(String msg) {
+ }
@Override
- public void w(String msg, Throwable tr) { }
+ public void w(String msg, Throwable tr) {
+ }
}
/**
- * TagLogger logger writes to the standard static log output with the
- * given tag object.
+ * TagLogger logger writes to the standard static log output with the given
+ * tag object.
*/
private static class TagLogger implements Logger {
private final Log.Tag mTag;
Log.w(mTag, msg, tr);
}
}
-}
\ No newline at end of file
+}
private final Object mLock;
@GuardedBy("mLock")
+ private boolean mClosePending;
+ @GuardedBy("mLock")
private boolean mClosed;
@GuardedBy("mLock")
private int mOpenImages;
private void decrementImageCount() {
synchronized (mLock) {
mOpenImages--;
- if (mClosed && mOpenImages == 0) {
+ if (mClosePending && !mClosed && mOpenImages == 0) {
+ mClosed = true;
super.close();
}
}
@Nullable
public ImageProxy acquireNextImage() {
synchronized (mLock) {
- if (!mClosed) {
+ if (!mClosePending && !mClosed) {
ImageProxy image = super.acquireNextImage();
if (image != null) {
mOpenImages++;
@Nullable
public ImageProxy acquireLatestImage() {
synchronized (mLock) {
- if (!mClosed) {
+ if (!mClosePending && !mClosed) {
ImageProxy image = super.acquireLatestImage();
if (image != null) {
mOpenImages++;
@Override
public void close() {
synchronized (mLock) {
- mClosed = true;
+ if (mClosed || mClosePending) {
+ return;
+ }
+ mClosePending = true;
if (mOpenImages == 0) {
+ mClosed = true;
super.close();
}
}
import com.android.camera.debug.Log.Tag;
import com.android.camera.debug.Logger;
-import com.android.camera.debug.Loggers;
import com.android.camera.one.v2.camera2proxy.ForwardingImageProxy;
import com.android.camera.one.v2.camera2proxy.ForwardingImageReader;
import com.android.camera.one.v2.camera2proxy.ImageProxy;
import com.android.camera.one.v2.camera2proxy.ImageReaderProxy;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
final class LoggingImageReader extends ForwardingImageReader {
- private static Tag TAG = new Tag("LoggingImageReader");
-
private class LoggingImageProxy extends ForwardingImageProxy {
+ private final AtomicBoolean mClosed;
+
public LoggingImageProxy(ImageProxy proxy) {
super(proxy);
+ mClosed = new AtomicBoolean(false);
}
@Override
public void close() {
- super.close();
- decrementOpenImageCount();
+ if (!mClosed.getAndSet(true)) {
+ super.close();
+ decrementOpenImageCount();
+ }
}
}
- private final Logger mLogger;
+
+ private final Logger mLog;
private final AtomicInteger mNumOpenImages;
- LoggingImageReader(ImageReaderProxy delegate, Logger.Factory logFactory) {
+ public LoggingImageReader(ImageReaderProxy delegate, Logger.Factory logFactory) {
super(delegate);
- mLogger = logFactory.create(TAG);
+ mLog = logFactory.create(new Tag("LoggingImageReader"));
mNumOpenImages = new AtomicInteger(0);
}
- public static LoggingImageReader create(ImageReaderProxy imageReader) {
- return new LoggingImageReader(imageReader, Loggers.tagFactory());
- }
-
@Override
@Nullable
public ImageProxy acquireNextImage() {
@Override
public void close() {
- mLogger.d("Closing: " + toString());
+ mLog.d("Closing: " + toString());
super.close();
}
private void incrementOpenImageCount() {
int numOpenImages = mNumOpenImages.incrementAndGet();
if (numOpenImages >= getMaxImages()) {
- mLogger.e(String.format("Open Image Count (%d) exceeds maximum (%d)!",
+ mLog.e(String.format("Open Image Count (%d) exceeds maximum (%d)!",
numOpenImages, getMaxImages()));
}
}
private void decrementOpenImageCount() {
int numOpenImages = mNumOpenImages.decrementAndGet();
- mLogger.v("Open Image Count = " + numOpenImages);
}
}
import com.android.camera.async.MainThread;
import com.android.camera.async.Observable;
import com.android.camera.async.Updatable;
+import com.android.camera.debug.Loggers;
import com.android.camera.one.OneCamera;
import com.android.camera.one.OneCameraCharacteristics;
import com.android.camera.one.v2.camera2proxy.AndroidImageReaderProxy;
final Observable<OneCamera.PhotoCaptureParameters.Flash> flashSetting) {
Lifetime lifetime = new Lifetime();
- final ImageReaderProxy imageReader = new CloseWhenDoneImageReader(
- LoggingImageReader.create(
- AndroidImageReaderProxy.newInstance(pictureSize.getWidth(),
- pictureSize.getHeight(), mImageFormat, mMaxImageCount)));
+ final ImageReaderProxy imageReader = new CloseWhenDoneImageReader(new LoggingImageReader(
+ AndroidImageReaderProxy.newInstance(
+ pictureSize.getWidth(), pictureSize.getHeight(),
+ mImageFormat, mMaxImageCount),
+ Loggers.tagFactory()));
+
lifetime.add(imageReader);
List<Surface> outputSurfaces = new ArrayList<>();
Lifetime lifetime = new Lifetime();
final ImageReaderProxy imageReader = new CloseWhenDoneImageReader(
- LoggingImageReader.create(AndroidImageReaderProxy.newInstance(
+ new LoggingImageReader(AndroidImageReaderProxy.newInstance(
pictureSize.getWidth(), pictureSize.getHeight(),
- mImageFormat, mMaxImageCount)));
+ mImageFormat, mMaxImageCount), Loggers.tagFactory()));
lifetime.add(imageReader);
lifetime.add(device);
import java.util.ArrayList;
import java.util.List;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
/**
* An {@link ImageProxy} backed by an {@link android.media.Image}.
*/
+@ThreadSafe
public class AndroidImageProxy implements ImageProxy {
/**
* {@link android.media.Image.Plane}.
*/
public class Plane implements ImageProxy.Plane {
+ /**
+ * {@link android.media.Image} is not thread-safe, and the resulting
+ * {@link Image.Plane} objects are not-necessarily thread-safe either,
+ * so all interaction must be guarded by {@link #mLock}.
+ */
+ @GuardedBy("mLock")
private final Image.Plane mPlane;
public Plane(Image.Plane imagePlane) {
*/
@Override
public int getRowStride() {
- return mPlane.getRowStride();
+ synchronized (mLock) {
+ return mPlane.getRowStride();
+ }
}
/**
*/
@Override
public int getPixelStride() {
- return mPlane.getPixelStride();
+ synchronized (mLock) {
+ return mPlane.getPixelStride();
+ }
}
/**
*/
@Override
public ByteBuffer getBuffer() {
- return mPlane.getBuffer();
+ synchronized (mLock) {
+ return mPlane.getBuffer();
+ }
}
}
+ private final Object mLock;
+ /**
+ * {@link android.media.Image} is not thread-safe, so all interactions must
+ * be guarded by {@link #mLock}.
+ */
+ @GuardedBy("mLock")
private final android.media.Image mImage;
public AndroidImageProxy(android.media.Image image) {
+ mLock = new Object();
mImage = image;
}
*/
@Override
public Rect getCropRect() {
- return mImage.getCropRect();
+ synchronized (mLock) {
+ return mImage.getCropRect();
+ }
}
/**
*/
@Override
public void setCropRect(Rect cropRect) {
- mImage.setCropRect(cropRect);
+ synchronized (mLock) {
+ mImage.setCropRect(cropRect);
+ }
}
/**
*/
@Override
public int getFormat() {
- return mImage.getFormat();
+ synchronized (mLock) {
+ return mImage.getFormat();
+ }
}
/**
*/
@Override
public int getHeight() {
- return mImage.getHeight();
+ synchronized (mLock) {
+ return mImage.getHeight();
+ }
}
/**
* object needs to be. So, just consider the performance when using this
* function wrapper.
* </p>
+ *
* @see {@link android.media.Image#getPlanes}
*/
@Override
public List<ImageProxy.Plane> getPlanes() {
- Image.Plane[] planes = mImage.getPlanes();
+ Image.Plane[] planes;
+
+ synchronized (mLock) {
+ planes = mImage.getPlanes();
+ }
List<ImageProxy.Plane> wrappedPlanes = new ArrayList<>(planes.length);
*/
@Override
public long getTimestamp() {
- return mImage.getTimestamp();
+ synchronized (mLock) {
+ return mImage.getTimestamp();
+ }
}
/**
*/
@Override
public int getWidth() {
- return mImage.getWidth();
+ synchronized (mLock) {
+ return mImage.getWidth();
+ }
}
/**
*/
@Override
public void close() {
- mImage.close();
+ synchronized (mLock) {
+ mImage.close();
+ }
}
@Override
public String toString() {
- return Objects.toStringHelper(mImage)
- .add("timestamp", getTimestamp())
+ Objects.ToStringHelper tsh;
+
+ synchronized (mImage) {
+ tsh = Objects.toStringHelper(mImage);
+ }
+ return tsh.add("timestamp", getTimestamp())
.toString();
}
}
package com.android.camera.one.v2.camera2proxy;
import android.graphics.ImageFormat;
+import android.media.Image;
import android.os.Handler;
import android.view.Surface;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
/**
* A replacement for {@link android.media.ImageReader}.
*/
public final class AndroidImageReaderProxy implements ImageReaderProxy {
+ private final Object mLock;
+ @GuardedBy("mLock")
private final android.media.ImageReader mDelegate;
public AndroidImageReaderProxy(android.media.ImageReader delegate) {
+ mLock = new Object();
mDelegate = delegate;
}
@Override
public int getWidth() {
- return mDelegate.getWidth();
+ synchronized (mLock) {
+ return mDelegate.getWidth();
+ }
}
@Override
public int getHeight() {
- return mDelegate.getHeight();
+ synchronized (mLock) {
+ return mDelegate.getHeight();
+ }
}
@Override
public int getImageFormat() {
- return mDelegate.getImageFormat();
+ synchronized (mLock) {
+ return mDelegate.getImageFormat();
+ }
}
@Override
public int getMaxImages() {
- return mDelegate.getMaxImages();
+ synchronized (mLock) {
+ return mDelegate.getMaxImages();
+ }
}
@Override
@Nonnull
public Surface getSurface() {
- return mDelegate.getSurface();
+ synchronized (mLock) {
+ return mDelegate.getSurface();
+ }
}
@Override
@Nullable
public ImageProxy acquireLatestImage() {
- return new AndroidImageProxy(mDelegate.acquireLatestImage());
+ synchronized (mLock) {
+ Image image = mDelegate.acquireLatestImage();
+ if (image == null) {
+ return null;
+ } else {
+ return new AndroidImageProxy(image);
+ }
+ }
}
@Override
@Nullable
public ImageProxy acquireNextImage() {
- return new AndroidImageProxy(mDelegate.acquireNextImage());
+ synchronized (mLock) {
+ Image image = mDelegate.acquireNextImage();
+ if (image == null) {
+ return null;
+ } else {
+ return new AndroidImageProxy(image);
+ }
+ }
}
@Override
public void setOnImageAvailableListener(@Nonnull
final ImageReaderProxy.OnImageAvailableListener listener,
Handler handler) {
- mDelegate.setOnImageAvailableListener(
- new android.media.ImageReader.OnImageAvailableListener() {
- @Override
- public void onImageAvailable(android.media.ImageReader imageReader) {
- listener.onImageAvailable(AndroidImageReaderProxy.this);
- }
- }, handler);
+ synchronized (mLock) {
+ mDelegate.setOnImageAvailableListener(
+ new android.media.ImageReader.OnImageAvailableListener() {
+ @Override
+ public void onImageAvailable(android.media.ImageReader imageReader) {
+ listener.onImageAvailable();
+ }
+ }, handler);
+ }
}
@Override
public void close() {
- mDelegate.close();
+ synchronized (mLock) {
+ mDelegate.close();
+ }
}
@Override
public String toString() {
- return Objects.toStringHelper(mDelegate)
- .add("width", getWidth())
+ Objects.ToStringHelper tsh;
+ synchronized (mLock) {
+ tsh = Objects.toStringHelper(mDelegate);
+ }
+ return tsh.add("width", getWidth())
.add("height", getHeight())
.add("format", imageFormatToString(getImageFormat()))
.toString();
import java.util.List;
+import javax.annotation.concurrent.ThreadSafe;
+
/**
* Forwards all {@link ImageProxy} methods.
*/
+@ThreadSafe
public abstract class ForwardingImageProxy implements ImageProxy {
private final ImageProxy mImpl;
package com.android.camera.one.v2.camera2proxy;
import android.graphics.Rect;
-import android.media.Image;
import com.android.camera.async.SafeCloseable;
import java.nio.ByteBuffer;
import java.util.List;
+import javax.annotation.concurrent.ThreadSafe;
+
/**
* Wraps {@link android.media.Image} with a mockable interface.
+ * <p>
+ * Implementations must be thread-safe.
*/
+@ThreadSafe
public interface ImageProxy extends SafeCloseable {
/**
- * Wraps the inner class {@link android.media.Image} with a mockable interface.
+ * Wraps the inner class {@link android.media.Image} with a mockable
+ * interface.
*/
public interface Plane {
/**
* See {@link ImageReader.OnImageAvailableListener#onImageAvailable}
*/
- public void onImageAvailable(@Nonnull ImageReaderProxy imageReader);
+ public void onImageAvailable();
}
/**
Handler imageReaderHandler = handlerFactory.create(lifetime, "ImageDistributor");
imageReader.setOnImageAvailableListener(
- new ImageDistributorOnImageAvailableListener(mImageDistributor),
+ new ImageDistributorOnImageAvailableListener(imageReader, mImageDistributor),
imageReaderHandler);
}
package com.android.camera.one.v2.sharedimagereader.imagedistributor;
-import com.android.camera.debug.Log;
-import com.android.camera.one.v2.camera2proxy.ForwardingImageProxy;
import com.android.camera.one.v2.camera2proxy.ImageProxy;
import com.android.camera.one.v2.camera2proxy.ImageReaderProxy;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
/**
- * Connects an {@link ImageReaderProxy} to an {@link ImageDistributor} with a new
- * thread to handle image availability callbacks.
+ * Connects an {@link ImageReaderProxy} to an {@link ImageDistributor} with a
+ * new thread to handle image availability callbacks.
*/
@ParametersAreNonnullByDefault
class ImageDistributorOnImageAvailableListener implements
ImageReaderProxy.OnImageAvailableListener {
private final ImageDistributorImpl mImageDistributor;
+ private final ImageReaderProxy mImageReader;
/**
+ * @param imageReader The image reader to retrieve images from.
* @param imageDistributor The image distributor to send images to.
*/
- public ImageDistributorOnImageAvailableListener(ImageDistributorImpl imageDistributor) {
+ public ImageDistributorOnImageAvailableListener(ImageReaderProxy imageReader,
+ ImageDistributorImpl imageDistributor) {
mImageDistributor = imageDistributor;
+ mImageReader = imageReader;
}
@Override
- public void onImageAvailable(@Nonnull ImageReaderProxy imageReader) {
- ImageProxy nextImage = imageReader.acquireNextImage();
+ public void onImageAvailable() {
+ ImageProxy nextImage = mImageReader.acquireNextImage();
if (nextImage != null) {
mImageDistributor.distributeImage(new SingleCloseImageProxy(nextImage));
}
import com.android.camera.one.v2.camera2proxy.ForwardingImageProxy;
import com.android.camera.one.v2.camera2proxy.ImageProxy;
+import javax.annotation.concurrent.ThreadSafe;
+
/**
* Wraps ImageProxy with reference counting, starting with a fixed number of
* references.
*/
+@ThreadSafe
class RefCountedImageProxy extends ForwardingImageProxy {
private final RefCountBase<ImageProxy> mRefCount;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
+import javax.annotation.concurrent.ThreadSafe;
/**
* Wraps an output queue of images by wrapping each image to track when they are
* closed. When images are closed, their associated metadata entry is freed to
* not leak memory.
*/
+@ThreadSafe
@ParametersAreNonnullByDefault
public class MetadataReleasingImageQueue implements BufferQueueController<ImageProxy> {
private class MetadataReleasingImageProxy extends ForwardingImageProxy {
import java.util.concurrent.atomic.AtomicBoolean;
+import javax.annotation.concurrent.ThreadSafe;
+
/**
* Combines an {@link ImageProxy} with a {@link Ticket}.
*/
+@ThreadSafe
public class TicketImageProxy extends ForwardingImageProxy {
private final Ticket mTicket;
private final AtomicBoolean mClosed;