Merge "Supply the sensor-to-buffer transform in SurfaceOutput" into androidx-main
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
index b1020d6..6321d77 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
@@ -16,8 +16,10 @@
 
 package androidx.camera.core.processing
 
+import android.graphics.Bitmap.createBitmap
 import android.graphics.BitmapFactory
 import android.graphics.ImageFormat
+import android.graphics.Matrix
 import android.graphics.Rect
 import android.graphics.SurfaceTexture
 import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
@@ -427,7 +429,8 @@
             Rect(0, 0, WIDTH, HEIGHT),
             /*rotationDegrees=*/0,
             /*mirroring=*/false,
-            FakeCamera()
+            FakeCamera(),
+            Matrix()
         )
 
     private fun createCustomShaderProvider(
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
index 0cde5ad..a41d1a7 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
@@ -18,7 +18,9 @@
 
 import static androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE;
 
+import android.graphics.Matrix;
 import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraCharacteristics;
 import android.util.Size;
 import android.view.Surface;
 
@@ -150,6 +152,33 @@
     void updateTransformMatrix(@NonNull float[] updated, @NonNull float[] original);
 
     /**
+     * Returns the sensor to image buffer transform matrix.
+     *
+     * <p>The value is a mapping from sensor coordinates to buffer coordinates, which is,
+     * from the rect of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE} to the
+     * rect defined by {@code (0, 0, SurfaceRequest#getResolution#getWidth(),
+     * SurfaceRequest#getResolution#getHeight())}. The matrix can
+     * be used to map the coordinates from one {@link UseCase} to another. For example,
+     * detecting face with {@link ImageAnalysis}, and then highlighting the face in
+     * {@link Preview}.
+     *
+     * <p>Code sample
+     * <code><pre>
+     *  // Get the transformation from sensor to effect output.
+     *  Matrix sensorToEffect = surfaceOutput.getSensorToBufferTransform();
+     *  // Get the transformation from sensor to ImageAnalysis.
+     *  Matrix sensorToAnalysis = imageProxy.getSensorToBufferTransform();
+     *  // Concatenate the two matrices to get the transformation from ImageAnalysis to effect.
+     *  Matrix analysisToEffect = Matrix()
+     *  sensorToAnalysis.invert(analysisToEffect);
+     *  analysisToEffect.postConcat(sensorToEffect);
+     * </pre></code>
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @NonNull
+    Matrix getSensorToBufferTransform();
+
+    /**
      * Events of the {@link Surface} retrieved from
      * {@link SurfaceOutput#getSurface(Executor, Consumer)}.
      */
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEdge.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEdge.java
index a7c936a..d237eda 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEdge.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceEdge.java
@@ -320,7 +320,7 @@
                     }
                     SurfaceOutputImpl surfaceOutputImpl = new SurfaceOutputImpl(surface,
                             getTargets(), format, mStreamSpec.getResolution(), inputSize, cropRect,
-                            rotationDegrees, mirroring, cameraInternal);
+                            rotationDegrees, mirroring, cameraInternal, mSensorToBufferTransform);
                     surfaceOutputImpl.getCloseFuture().addListener(
                             settableSurface::decrementUseCount,
                             directExecutor());
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java
index 8038235..aae6698 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SurfaceOutputImpl.java
@@ -95,6 +95,8 @@
     private CallbackToFutureAdapter.Completer<Void> mCloseFutureCompleter;
     @Nullable
     private CameraInternal mCameraInternal;
+    @NonNull
+    private android.graphics.Matrix mSensorToBufferTransform;
 
     SurfaceOutputImpl(
             @NonNull Surface surface,
@@ -105,7 +107,8 @@
             @NonNull Rect inputCropRect,
             int rotationDegree,
             boolean mirroring,
-            @Nullable CameraInternal cameraInternal) {
+            @Nullable CameraInternal cameraInternal,
+            @NonNull android.graphics.Matrix sensorToBufferTransform) {
         mSurface = surface;
         mTargets = targets;
         mFormat = format;
@@ -115,6 +118,7 @@
         mMirroring = mirroring;
         mRotationDegrees = rotationDegree;
         mCameraInternal = cameraInternal;
+        mSensorToBufferTransform = sensorToBufferTransform;
         calculateAdditionalTransform();
         mCloseFuture = CallbackToFutureAdapter.getFuture(
                 completer -> {
@@ -264,6 +268,15 @@
     }
 
     /**
+     * @inheritDoc
+     */
+    @NonNull
+    @Override
+    public android.graphics.Matrix getSensorToBufferTransform() {
+        return new android.graphics.Matrix(mSensorToBufferTransform);
+    }
+
+    /**
      * Calculates the additional GL transform and saves it to {@link #mAdditionalTransform}.
      *
      * <p>The effect implementation needs to apply this value on top of texture transform obtained
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEdgeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEdgeTest.kt
index eeda779..ad6dbd7 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEdgeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceEdgeTest.kt
@@ -293,25 +293,26 @@
 
     @Test
     fun createSurfaceOutputWithDisconnectedEdge_surfaceOutputNotCreated() {
-        // Arrange: create a SurfaceOutput future from a closed LinkableSurface
+        // Arrange: create a SurfaceOutput future from a closed Edge
+        surfaceEdge.setProvider(provider)
+        provider.setSurface(fakeSurface)
         surfaceEdge.disconnect()
-        val surfaceOutput = createSurfaceOutputFuture(surfaceEdge)
-
         // Act: wait for the SurfaceOutput to return.
-        var successful: Boolean? = null
-        Futures.addCallback(surfaceOutput, object : FutureCallback<SurfaceOutput> {
-            override fun onSuccess(result: SurfaceOutput?) {
-                successful = true
-            }
-
-            override fun onFailure(t: Throwable) {
-                successful = false
-            }
-        }, mainThreadExecutor())
-        shadowOf(getMainLooper()).idle()
+        val surfaceOutput = getSurfaceOutputFromFuture(createSurfaceOutputFuture(surfaceEdge))
 
         // Assert: the SurfaceOutput is not created.
-        assertThat(successful!!).isEqualTo(false)
+        assertThat(surfaceOutput).isNull()
+    }
+
+    @Test
+    fun createSurfaceOutput_inheritSurfaceEdgeTransformation() {
+        // Arrange: set the provider and create a SurfaceOutput future.
+        surfaceEdge.setProvider(provider)
+        provider.setSurface(fakeSurface)
+        // Act: create a SurfaceOutput from the SurfaceEdge
+        val surfaceOutput = getSurfaceOutputFromFuture(createSurfaceOutputFuture(surfaceEdge))
+        // Assert: the SurfaceOutput inherits the transformation from the SurfaceEdge.
+        assertThat(surfaceOutput!!.sensorToBufferTransform).isEqualTo(SENSOR_TO_BUFFER)
     }
 
     @Test
@@ -542,6 +543,22 @@
         assertThat(transformationInfo!!.rotationDegrees).isEqualTo(90)
     }
 
+    private fun getSurfaceOutputFromFuture(
+        future: ListenableFuture<SurfaceOutput>
+    ): SurfaceOutput? {
+        var surfaceOutput: SurfaceOutput? = null
+        Futures.addCallback(future, object : FutureCallback<SurfaceOutput> {
+            override fun onSuccess(result: SurfaceOutput?) {
+                surfaceOutput = result
+            }
+
+            override fun onFailure(t: Throwable) {
+            }
+        }, mainThreadExecutor())
+        shadowOf(getMainLooper()).idle()
+        return surfaceOutput
+    }
+
     private fun createSurfaceOutputFuture(surfaceEdge: SurfaceEdge) =
         surfaceEdge.createSurfaceOutputFuture(
             INPUT_SIZE,
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
index 21dd66c..2603f35 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt
@@ -186,7 +186,8 @@
         sizeToRect(INPUT_SIZE),
         /*rotationDegrees=*/180,
         /*mirroring=*/false,
-        camera
+        camera,
+        android.graphics.Matrix()
     ).apply {
         surfaceOutputsToCleanup.add(this)
     }