Add all Camera code from internal repo
This is a direct copy of our code from our internal repo. Still needs
to be cleaned up for code style and to match AndroidX build files.
Bug: 121003129
Test: All unit tests pass before copy.
Change-Id: Id18d2e13c53fc35a013c7709598ee59b53472576
diff --git a/camera/core/src/androidTest/java/androidx/camera/core/ImageSaverAndroidTest.java b/camera/core/src/androidTest/java/androidx/camera/core/ImageSaverAndroidTest.java
new file mode 100644
index 0000000..2b986b4
--- /dev/null
+++ b/camera/core/src/androidTest/java/androidx/camera/core/ImageSaverAndroidTest.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2019 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 androidx.camera.core;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.ImageFormat;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.support.annotation.Nullable;
+import android.util.Base64;
+import android.util.Rational;
+import androidx.camera.core.ImageSaver.OnImageSavedListener;
+import androidx.camera.core.ImageSaver.SaveError;
+import androidx.test.runner.AndroidJUnit4;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Semaphore;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+@RunWith(AndroidJUnit4.class)
+public class ImageSaverAndroidTest {
+
+ private static final int WIDTH = 160;
+ private static final int HEIGHT = 120;
+ private static final int Y_PIXEL_STRIDE = 1;
+ private static final int Y_ROW_STRIDE = WIDTH;
+ private static final int UV_PIXEL_STRIDE = 1;
+ private static final int UV_ROW_STRIDE = WIDTH / 2;
+
+ // The image used here has a YUV_420_888 format.
+ @Mock private final ImageProxy mockYuvImage = mock(ImageProxy.class);
+ @Mock private final ImageProxy.PlaneProxy yPlane = mock(ImageProxy.PlaneProxy.class);
+ @Mock private final ImageProxy.PlaneProxy uPlane = mock(ImageProxy.PlaneProxy.class);
+ @Mock private final ImageProxy.PlaneProxy vPlane = mock(ImageProxy.PlaneProxy.class);
+ private final ByteBuffer yBuffer = ByteBuffer.allocateDirect(WIDTH * HEIGHT);
+ private final ByteBuffer uBuffer = ByteBuffer.allocateDirect(WIDTH * HEIGHT / 4);
+ private final ByteBuffer vBuffer = ByteBuffer.allocateDirect(WIDTH * HEIGHT / 4);
+
+ @Mock private final ImageProxy mockJpegImage = mock(ImageProxy.class);
+ @Mock private final ImageProxy.PlaneProxy jpegDataPlane = mock(ImageProxy.PlaneProxy.class);
+ private final String jpegImageDataBase64 =
+ "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB"
+ + "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEB"
+ + "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAB4AKADASIA"
+ + "AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA"
+ + "AAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3"
+ + "ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm"
+ + "p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEA"
+ + "AwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSEx"
+ + "BhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElK"
+ + "U1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3"
+ + "uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD/AD/6"
+ + "KKK/8/8AP/P/AAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiii"
+ + "gAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKA"
+ + "CiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK"
+ + "KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoo"
+ + "ooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiii"
+ + "gAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//9k=";
+ private final ByteBuffer jpegDataBuffer =
+ ByteBuffer.wrap(Base64.decode(jpegImageDataBase64, Base64.DEFAULT));
+
+ private final Semaphore semaphore = new Semaphore(0);
+ private final ImageSaver.OnImageSavedListener mockListener =
+ mock(ImageSaver.OnImageSavedListener.class);
+ private final ImageSaver.OnImageSavedListener syncListener =
+ new OnImageSavedListener() {
+ @Override
+ public void onImageSaved(File file) {
+ mockListener.onImageSaved(file);
+ semaphore.release();
+ }
+
+ @Override
+ public void onError(SaveError saveError, String message, @Nullable Throwable cause) {
+ mockListener.onError(saveError, message, cause);
+ semaphore.release();
+ }
+ };
+
+ private HandlerThread backgroundThread;
+ private Handler backgroundHandler;
+
+ @Before
+ public void setup() {
+ // The YUV image's behavior.
+ when(mockYuvImage.getFormat()).thenReturn(ImageFormat.YUV_420_888);
+ when(mockYuvImage.getWidth()).thenReturn(WIDTH);
+ when(mockYuvImage.getHeight()).thenReturn(HEIGHT);
+
+ when(yPlane.getBuffer()).thenReturn(yBuffer);
+ when(yPlane.getPixelStride()).thenReturn(Y_PIXEL_STRIDE);
+ when(yPlane.getRowStride()).thenReturn(Y_ROW_STRIDE);
+
+ when(uPlane.getBuffer()).thenReturn(uBuffer);
+ when(uPlane.getPixelStride()).thenReturn(UV_PIXEL_STRIDE);
+ when(uPlane.getRowStride()).thenReturn(UV_ROW_STRIDE);
+
+ when(vPlane.getBuffer()).thenReturn(vBuffer);
+ when(vPlane.getPixelStride()).thenReturn(UV_PIXEL_STRIDE);
+ when(vPlane.getRowStride()).thenReturn(UV_ROW_STRIDE);
+ when(mockYuvImage.getPlanes()).thenReturn(new ImageProxy.PlaneProxy[] {yPlane, uPlane, vPlane});
+
+ // The JPEG image's behavior
+ when(mockJpegImage.getFormat()).thenReturn(ImageFormat.JPEG);
+ when(mockJpegImage.getWidth()).thenReturn(WIDTH);
+ when(mockJpegImage.getHeight()).thenReturn(HEIGHT);
+
+ when(jpegDataPlane.getBuffer()).thenReturn(jpegDataBuffer);
+ when(mockJpegImage.getPlanes()).thenReturn(new ImageProxy.PlaneProxy[] {jpegDataPlane});
+
+ // Set up a background thread/handler for callbacks
+ backgroundThread = new HandlerThread("CallbackThread");
+ backgroundThread.start();
+ backgroundHandler = new Handler(backgroundThread.getLooper());
+ }
+
+ @After
+ public void tearDown() {
+ backgroundThread.quitSafely();
+ }
+
+ private ImageSaver getDefaultImageSaver(ImageProxy image, File file) {
+ return new ImageSaver(
+ image,
+ file,
+ /*orientation=*/ 0,
+ /*reversedHorizontal=*/ false,
+ /*reversedVertical=*/ false,
+ /*location=*/ null,
+ /*cropAspectRatio=*/ null,
+ syncListener,
+ backgroundHandler);
+ }
+
+ @Test
+ public void canSaveYuvImage() throws InterruptedException, IOException {
+ File saveLocation = File.createTempFile("test", ".jpg");
+ saveLocation.deleteOnExit();
+
+ ImageSaver imageSaver = getDefaultImageSaver(mockYuvImage, saveLocation);
+
+ imageSaver.run();
+
+ semaphore.acquire();
+
+ verify(mockListener).onImageSaved(anyObject());
+ }
+
+ @Test
+ public void canSaveJpegImage() throws InterruptedException, IOException {
+ File saveLocation = File.createTempFile("test", ".jpg");
+ saveLocation.deleteOnExit();
+
+ ImageSaver imageSaver = getDefaultImageSaver(mockJpegImage, saveLocation);
+
+ imageSaver.run();
+
+ semaphore.acquire();
+
+ verify(mockListener).onImageSaved(anyObject());
+ }
+
+ @Test
+ public void errorCallbackWillBeCalledOnInvalidPath() throws InterruptedException, IOException {
+ // Invalid filename should cause error
+ File saveLocation = new File("/not/a/real/path.jpg");
+
+ ImageSaver imageSaver = getDefaultImageSaver(mockJpegImage, saveLocation);
+
+ imageSaver.run();
+
+ semaphore.acquire();
+
+ verify(mockListener).onError(eq(SaveError.FILE_IO_FAILED), anyString(), anyObject());
+ }
+
+ @Test
+ public void imageIsClosedOnSuccess() throws InterruptedException, IOException {
+ File saveLocation = File.createTempFile("test", ".jpg");
+ saveLocation.deleteOnExit();
+
+ ImageSaver imageSaver = getDefaultImageSaver(mockJpegImage, saveLocation);
+
+ imageSaver.run();
+
+ semaphore.acquire();
+
+ verify(mockJpegImage).close();
+ }
+
+ @Test
+ public void imageIsClosedOnError() throws InterruptedException, IOException {
+ // Invalid filename should cause error
+ File saveLocation = new File("/not/a/real/path.jpg");
+
+ ImageSaver imageSaver = getDefaultImageSaver(mockJpegImage, saveLocation);
+
+ imageSaver.run();
+
+ semaphore.acquire();
+
+ verify(mockJpegImage).close();
+ }
+
+ private void imageCanBeCropped(ImageProxy image) throws InterruptedException, IOException {
+ File saveLocation = File.createTempFile("test", ".jpg");
+ saveLocation.deleteOnExit();
+
+ Rational viewRatio = new Rational(1, 1);
+
+ ImageSaver imageSaver = new ImageSaver(
+ image,
+ saveLocation,
+ /*orientation=*/ 0,
+ /*reversedHorizontal=*/ false,
+ /*reversedVertical=*/ false,
+ /*location=*/ null,
+ /*cropAspectRatio=*/ viewRatio,
+ syncListener,
+ backgroundHandler
+ );
+ imageSaver.run();
+
+ semaphore.acquire();
+
+ Bitmap bitmap = BitmapFactory.decodeFile(saveLocation.getPath());
+ assertThat(bitmap.getWidth()).isEqualTo(bitmap.getHeight());
+ }
+
+ @Test
+ public void jpegImageCanBeCropped() throws InterruptedException, IOException {
+ imageCanBeCropped(mockJpegImage);
+ }
+
+ @Test
+ public void yuvImageCanBeCropped() throws InterruptedException, IOException {
+ imageCanBeCropped(mockYuvImage);
+ }
+}