diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java index 09d7fbd1df36a..c6f66d7592ee7 100644 --- a/media/java/android/media/ImageReader.java +++ b/media/java/android/media/ImageReader.java @@ -43,6 +43,7 @@ import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.StampedLock; /** *
The ImageReader class allows direct application access to image data @@ -675,7 +676,8 @@ public class ImageReader implements AutoCloseable { * If no handler specified and the calling thread has no looper. */ public void setOnImageAvailableListener(OnImageAvailableListener listener, Handler handler) { - synchronized (mListenerLock) { + long writeStamp = mListenerLock.writeLock(); + try { if (listener != null) { Looper looper = handler != null ? handler.getLooper() : Looper.myLooper(); if (looper == null) { @@ -691,6 +693,8 @@ public class ImageReader implements AutoCloseable { mListenerExecutor = null; } mListener = listener; + } finally { + mListenerLock.unlockWrite(writeStamp); } } @@ -713,9 +717,12 @@ public class ImageReader implements AutoCloseable { throw new IllegalArgumentException("executor must not be null"); } - synchronized (mListenerLock) { + long writeStamp = mListenerLock.writeLock(); + try { mListenerExecutor = executor; mListener = listener; + } finally { + mListenerLock.unlockWrite(writeStamp); } } @@ -731,6 +738,8 @@ public class ImageReader implements AutoCloseable { /** * Callback that is called when a new image is available from ImageReader. * + * This callback must not modify or close the passed {@code reader}. + * * @param reader the ImageReader the callback is associated with. * @see ImageReader * @see Image @@ -889,28 +898,41 @@ public class ImageReader implements AutoCloseable { return; } - final Executor executor; - final OnImageAvailableListener listener; - synchronized (ir.mListenerLock) { - executor = ir.mListenerExecutor; - listener = ir.mListener; - } - final boolean isReaderValid; synchronized (ir.mCloseLock) { - isReaderValid = ir.mIsReaderValid; + if (!ir.mIsReaderValid) { + // It's dangerous to fire onImageAvailable() callback when the ImageReader + // is being closed, as application could acquire next image in the + // onImageAvailable() callback. + return; + } } - // It's dangerous to fire onImageAvailable() callback when the ImageReader - // is being closed, as application could acquire next image in the - // onImageAvailable() callback. - if (executor != null && listener != null && isReaderValid) { - executor.execute(new Runnable() { - @Override - public void run() { - listener.onImageAvailable(ir); - } - }); + final Executor executor; + final long readStamp = ir.mListenerLock.readLock(); + try { + executor = ir.mListenerExecutor; + if (executor == null) { + return; + } + } finally { + ir.mListenerLock.unlockRead(readStamp); } + + executor.execute(() -> { + // Acquire readlock to ensure that the ImageReader does not change its + // state while a listener is actively processing. + final long rStamp = ir.mListenerLock.readLock(); + try { + // Fire onImageAvailable of the latest non-null listener + // This ensures that if the listener changes while messages are in queue, the + // in-flight messages will call onImageAvailable of the new listener instead + if (ir.mListener != null) { + ir.mListener.onImageAvailable(ir); + } + } finally { + ir.mListenerLock.unlockRead(rStamp); + } + }); } /** @@ -1070,7 +1092,7 @@ public class ImageReader implements AutoCloseable { private Surface mSurface; private int mEstimatedNativeAllocBytes; - private final Object mListenerLock = new Object(); + private final StampedLock mListenerLock = new StampedLock(); private final Object mCloseLock = new Object(); private boolean mIsReaderValid = false; private OnImageAvailableListener mListener;