blob: b8564ca4078f027a6349f90ef15caba6fcb5c0e4 [file] [log] [blame]
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.S_V2;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.media.Image;
import android.media.ImageReader;
import android.media.ImageReader.OnImageAvailableListener;
import android.os.Handler;
import android.view.Surface;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;
/** Shadow for {@link android.media.ImageReader} */
@Implements(value = ImageReader.class, looseSignatures = true)
public class ShadowImageReader {
// Using same return codes as ImageReader.
private static final int ACQUIRE_SUCCESS = 0;
private static final int ACQUIRE_NO_BUFS = 1;
private static final int ACQUIRE_MAX_IMAGES = 2;
private final AtomicLong imageCount = new AtomicLong(0);
private final AtomicBoolean readerValid = new AtomicBoolean(true);
private final AtomicLong availableBuffers = new AtomicLong(0);
private final List<Image> openedImages = new ArrayList<>();
private Surface surface;
@RealObject private ImageReader imageReader;
private Canvas canvas;
@Implementation(minSdk = KITKAT)
protected void close() {
readerValid.set(false);
openedImages.clear();
}
@Implementation(minSdk = KITKAT, maxSdk = S_V2)
protected int nativeImageSetup(Image image) {
if (!readerValid.get()) {
throw new IllegalStateException("ImageReader closed.");
}
if (openedImages.size() >= imageReader.getMaxImages()) {
return ACQUIRE_MAX_IMAGES;
}
if (availableBuffers.get() == 0) {
return ACQUIRE_NO_BUFS;
}
availableBuffers.getAndDecrement();
openedImages.add(image);
ShadowSurfaceImage shadowSurfaceImage = Shadow.extract(image);
shadowSurfaceImage.setTimeStamp(imageCount.get());
return ACQUIRE_SUCCESS;
}
@Implementation(minSdk = TIRAMISU, maxSdk = TIRAMISU)
protected int nativeImageSetup(Image image, boolean useLegacyImageFormat) {
return nativeImageSetup(image);
}
@Implementation(minSdk = ShadowBuild.UPSIDE_DOWN_CAKE)
protected int nativeImageSetup(Object /* Image */ image) {
return nativeImageSetup((Image) image);
}
@Implementation(minSdk = KITKAT)
protected void nativeReleaseImage(Image i) {
openedImages.remove(i);
}
@Implementation(minSdk = KITKAT)
protected Surface nativeGetSurface() {
if (surface == null) {
surface = new FakeSurface();
}
return surface;
}
private class FakeSurface extends Surface {
public FakeSurface() {}
@Override
public Canvas lockCanvas(Rect inOutDirty) {
if (canvas == null) {
canvas = new Canvas();
}
return canvas;
}
@Override
public Canvas lockHardwareCanvas() {
if (canvas == null) {
canvas = new Canvas();
}
return canvas;
}
@Override
public void unlockCanvasAndPost(Canvas canvas) {
availableBuffers.getAndIncrement();
imageCount.getAndIncrement();
OnImageAvailableListener listener =
reflector(ImageReaderReflector.class, imageReader).getListener();
Handler handler = reflector(ImageReaderReflector.class, imageReader).getListenerHandler();
if (listener == null) {
return;
}
if (handler == null) {
Objects.requireNonNull(listener).onImageAvailable(imageReader);
return;
}
Objects.requireNonNull(handler)
.post(() -> Objects.requireNonNull(listener).onImageAvailable(imageReader));
}
}
/** Shadow for {@link android.media.Image} */
@Implements(className = "android.media.ImageReader$SurfaceImage")
public static class ShadowSurfaceImage {
@RealObject Object surfaceImage;
@Implementation(minSdk = KITKAT)
protected int getWidth() {
ImageReader reader = ReflectionHelpers.getField(surfaceImage, "this$0");
return reader.getWidth();
}
@Implementation(minSdk = KITKAT)
protected int getHeight() {
ImageReader reader = ReflectionHelpers.getField(surfaceImage, "this$0");
return reader.getHeight();
}
@Implementation(minSdk = KITKAT)
protected int getFormat() {
ImageReader reader = ReflectionHelpers.getField(surfaceImage, "this$0");
return reader.getImageFormat();
}
public void setTimeStamp(long timestamp) {
ReflectionHelpers.setField(surfaceImage, "mTimestamp", timestamp);
}
}
@ForType(ImageReader.class)
interface ImageReaderReflector {
@Accessor("mListener")
OnImageAvailableListener getListener();
@Accessor("mListenerHandler")
Handler getListenerHandler();
}
}