Merge "Ensure auto-initialization works with pseudolocales" into androidx-master-dev
diff --git a/activity/activity/api/api_lint.ignore b/activity/activity/api/api_lint.ignore
index 64382c3..060fbf1 100644
--- a/activity/activity/api/api_lint.ignore
+++ b/activity/activity/api/api_lint.ignore
@@ -7,6 +7,10 @@
     ComponentActivity should not extend `Activity`. Activity subclasses are impossible to compose. Expose a composable API instead.
 
 
+KotlinOperator: androidx.activity.result.ActivityResultRegistry#invoke(int, androidx.activity.result.contract.ActivityResultContract<I,O>, I, androidx.core.app.ActivityOptionsCompat):
+    Method can be invoked with function call syntax from Kotlin: `invoke` (this is usually desirable; just make sure it makes sense for this type of object)
+
+
 MissingNullability: androidx.activity.ComponentActivity#startActivityForResult(android.content.Intent, int) parameter #0:
     Missing nullability on parameter `intent` in method `startActivityForResult`
 MissingNullability: androidx.activity.ComponentActivity#startActivityForResult(android.content.Intent, int, android.os.Bundle) parameter #0:
diff --git a/camera/camera-core/build.gradle b/camera/camera-core/build.gradle
index 0949f82..47e0793 100644
--- a/camera/camera-core/build.gradle
+++ b/camera/camera-core/build.gradle
@@ -38,6 +38,7 @@
 
     annotationProcessor(AUTO_VALUE)
 
+    testImplementation(KOTLIN_STDLIB)
     testImplementation(ANDROIDX_TEST_CORE)
     testImplementation(ANDROIDX_TEST_RUNNER)
     testImplementation(JUNIT)
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/AndroidImageProxyTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/AndroidImageProxyTest.java
index 3a78141..4bf8df3 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/AndroidImageProxyTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/AndroidImageProxyTest.java
@@ -36,6 +36,9 @@
 
 import java.nio.ByteBuffer;
 
+/**
+ * Unit tests for {@link AndroidImageProxy}.
+ */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public final class AndroidImageProxyTest {
@@ -130,4 +133,42 @@
     public void getImage_returnsWrappedImage() {
         assertThat(mImageProxy.getImage()).isEqualTo(mImage);
     }
+
+    @Test
+    public void getViewPortRect_returnsPreviouslySetValue() {
+        // Arrange.
+        Rect rect = new Rect();
+
+        // Act.
+        mImageProxy.setViewPortRect(rect);
+
+        // Assert.
+        assertThat(mImageProxy.getViewPortRect()).isEqualTo(rect);
+    }
+
+    @Test
+    public void getNullViewPortRect_returnsWrappedCropRect() {
+        // Arrange.
+        Rect rect = new Rect();
+        when(mImage.getCropRect()).thenReturn(rect);
+
+        // Assert.
+        assertThat(mImageProxy.getViewPortRect()).isEqualTo(rect);
+    }
+
+    @Test
+    public void setNullViewPort_returnsWrappedCropRect() {
+        // Arrange.
+        Rect wrappedImageRect = new Rect();
+        when(mImage.getCropRect()).thenReturn(wrappedImageRect);
+        Rect viewPortRect = new Rect();
+        mImageProxy.setViewPortRect(viewPortRect);
+        assertThat(mImageProxy.getViewPortRect()).isEqualTo(viewPortRect);
+
+        // Act.
+        mImageProxy.setViewPortRect(null);
+
+        // Assert.
+        assertThat(mImageProxy.getViewPortRect()).isEqualTo(wrappedImageRect);
+    }
 }
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraXTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraXTest.java
index c81f0f9..927876d 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraXTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/CameraXTest.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.core;
 
+import static androidx.camera.core.CameraX.getScaledRect;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertFalse;
@@ -27,6 +29,7 @@
 import android.app.Instrumentation;
 import android.content.Context;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.util.Rational;
 import android.util.Size;
 
@@ -80,6 +83,10 @@
             new CameraSelector.Builder().requireLensFacing(CAMERA_LENS_FACING).build();
     private static final String CAMERA_ID = "0";
     private static final String CAMERA_ID_FRONT = "1";
+    // A container rect that is 4:3.
+    private static final RectF CONTAINER_RECT = new RectF(10, 10, 50, 40);
+    // 1:1 narrow aspect ratio
+    private static final Rational FIT_ASPECT_RATIO = new Rational(100, 100);
 
     private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
     private Context mContext;
@@ -121,12 +128,13 @@
     }
 
     @Test
-    public void cropRect_twoSurfacesIntersectCropWide() {
+    public void viewPortRectWithTwoSurfacesIntersectWide() {
         assertThat(
-                getCropRects(
+                getViewPortRects(
                         new Size(800, 800),
-                        180,
                         new Rational(1, 2),
+                        180,
+                        ViewPort.FILL_CENTER,
                         new Size(400, 800),
                         new Size(800, 400)))
                 .isEqualTo(
@@ -137,12 +145,13 @@
     }
 
     @Test
-    public void cropRect_twoSurfacesIntersectCropNarrow() {
+    public void viewPortRectWithTwoSurfacesIntersectNarrow() {
         assertThat(
-                getCropRects(
+                getViewPortRects(
                         new Size(800, 800),
-                        180,
                         new Rational(2, 1),
+                        180,
+                        ViewPort.FILL_CENTER,
                         new Size(400, 800),
                         new Size(800, 400)))
                 .isEqualTo(
@@ -153,24 +162,40 @@
     }
 
     @Test
-    public void cropRectRotation90_landscapeCropForPortraitMode() {
+    public void viewPortRectLandscapeForPortraitModeAndRotate90Degrees() {
         assertThat(
-                getCropRects(
+                getViewPortRects(
                         new Size(800, 600),
-                        90,
                         new Rational(400, 300),
+                        90,
+                        ViewPort.FILL_CENTER,
                         new Size(400, 300)))
                 .isEqualTo(new Rect[]{
                         new Rect(88, 0, 313, 300)
                 });
     }
 
+    @Test
+    public void viewPortRectFitStart() {
+        assertThat(
+                getViewPortRects(
+                        new Size(800, 600),
+                        new Rational(1, 1),
+                        0,
+                        ViewPort.FIT_END,
+                        new Size(400, 300)))
+                .isEqualTo(new Rect[]{
+                        new Rect(0, -100, 400, 300)
+                });
+    }
+
     /**
-     * Calls {@link CameraX#calculateCropRects(Rect, Rational, int, Map)}.
+     * Calls {@link CameraX#calculateViewPortRects(Rect, Rational, int, int, Map)}.
      */
-    private Rect[] getCropRects(Size sensorSize,
-            @IntRange(from = 0, to = 359) int rotationDegree,
+    private Rect[] getViewPortRects(Size sensorSize,
             Rational aspectRatio,
+            @IntRange(from = 0, to = 359) int rotationDegree,
+            @ViewPort.ScaleType int scaleType,
             Size... sizes) {
         // Convert the sizes into a UseCase map.
         List<UseCase> orderedUseCases = new ArrayList<>();
@@ -184,10 +209,11 @@
             }
         };
 
-        Map<UseCase, Rect> useCaseCropRects = CameraX.calculateCropRects(
+        Map<UseCase, Rect> useCaseCropRects = CameraX.calculateViewPortRects(
                 new Rect(0, 0, sensorSize.getWidth(), sensorSize.getHeight()),
                 aspectRatio,
                 rotationDegree,
+                scaleType,
                 useCaseSizeMap);
 
         // Converts the map back to sizes array.
@@ -200,6 +226,42 @@
     }
 
     @Test
+    public void viewPortRectFillCenter() {
+        assertThat(getScaledRect(CONTAINER_RECT, FIT_ASPECT_RATIO, ViewPort.FILL_CENTER))
+                .isEqualTo(new RectF(15, 10, 45, 40));
+    }
+
+    @Test
+    public void getScaledRectFillStart() {
+        assertThat(getScaledRect(CONTAINER_RECT, FIT_ASPECT_RATIO, ViewPort.FILL_START))
+                .isEqualTo(new RectF(10, 10, 40, 40));
+    }
+
+    @Test
+    public void getScaledRectFillEnd() {
+        assertThat(getScaledRect(CONTAINER_RECT, FIT_ASPECT_RATIO, ViewPort.FILL_END))
+                .isEqualTo(new RectF(20, 10, 50, 40));
+    }
+
+    @Test
+    public void getScaledRectFitCenter() {
+        assertThat(getScaledRect(CONTAINER_RECT, FIT_ASPECT_RATIO, ViewPort.FIT_CENTER))
+                .isEqualTo(new RectF(10, 5, 50, 45));
+    }
+
+    @Test
+    public void getScaledRectFitStart() {
+        assertThat(getScaledRect(CONTAINER_RECT, FIT_ASPECT_RATIO, ViewPort.FIT_START))
+                .isEqualTo(new RectF(10, 10, 50, 50));
+    }
+
+    @Test
+    public void getScaledRectFitEnd() {
+        assertThat(getScaledRect(CONTAINER_RECT, FIT_ASPECT_RATIO, ViewPort.FIT_END))
+                .isEqualTo(new RectF(10, 0, 50, 40));
+    }
+
+    @Test
     public void rotateUseCasesWith180Degrees_unchanged() {
         // Arrange.
         UseCase useCase1 = mock(UseCase.class);
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ForwardingImageProxyTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/ForwardingImageProxyTest.java
index 01120e0..2c84f8c 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ForwardingImageProxyTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ForwardingImageProxyTest.java
@@ -36,6 +36,9 @@
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.atomic.AtomicReference;
 
+/**
+ * Unit tests for {@link ForwardingImageProxy}.
+ */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public final class ForwardingImageProxyTest {
@@ -131,6 +134,16 @@
         assertThat(mImageProxy.getImage()).isEqualTo(mBaseImageProxy.getImage());
     }
 
+    @Test
+    public void getViewPortRect_returnsViewPortRectForWrappedImage() {
+        // Arrange.
+        Rect rect = new Rect();
+        when(mBaseImageProxy.getViewPortRect()).thenReturn(rect);
+
+        // Assert.
+        assertThat(mImageProxy.getViewPortRect()).isEqualTo(rect);
+    }
+
     private static final class ConcreteImageProxy extends ForwardingImageProxy {
         private ConcreteImageProxy(ImageProxy baseImageProxy) {
             super(baseImageProxy);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/AndroidImageProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/AndroidImageProxy.java
index a75f56b..d0bb7b0 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/AndroidImageProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/AndroidImageProxy.java
@@ -35,6 +35,9 @@
 
     private final ImageInfo mImageInfo;
 
+    @Nullable
+    private Rect mViewPortRect;
+
     /**
      * Creates a new instance which wraps the given image.
      *
@@ -73,6 +76,17 @@
         mImage.setCropRect(rect);
     }
 
+    @NonNull
+    @Override
+    public Rect getViewPortRect() {
+        return mViewPortRect != null ? mViewPortRect : getCropRect();
+    }
+
+    @Override
+    public void setViewPortRect(@Nullable Rect viewPortRect) {
+        mViewPortRect = viewPortRect;
+    }
+
     @Override
     public synchronized int getFormat() {
         return mImage.getFormat();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
index e539cc8..98acfd3 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
@@ -16,6 +16,7 @@
 
 package androidx.camera.core;
 
+import android.annotation.SuppressLint;
 import android.app.Application;
 import android.content.Context;
 import android.content.res.Resources;
@@ -37,6 +38,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
+import androidx.annotation.VisibleForTesting;
 import androidx.camera.core.impl.CameraDeviceSurfaceManager;
 import androidx.camera.core.impl.CameraFactory;
 import androidx.camera.core.impl.CameraFilter;
@@ -896,21 +898,23 @@
      * </ul>
      *
      * @param fullSensorRect        full sensor rect of the camera.
-     * @param outputAspectRatio     aspect ratio of the output crop rect specified by caller.
+     * @param viewPortAspectRatio   aspect ratio of the {@link ViewPort} specified by the
+     *                              caller.
      * @param outputRotationDegrees output rotation degrees in relation to the sensor orientation.
      * @param useCaseSizes          the {@link Surface} size of each use case.
-     * @return the crop rect in each {@link UseCase}'s output coordinates.
+     * @return the {@link ViewPort} rect in each {@link UseCase}'s output coordinates.
      */
     @NonNull
-    static Map<UseCase, Rect> calculateCropRects(
+    static Map<UseCase, Rect> calculateViewPortRects(
             @NonNull Rect fullSensorRect,
-            @NonNull Rational outputAspectRatio,
+            @NonNull Rational viewPortAspectRatio,
             @IntRange(from = 0, to = 359) int outputRotationDegrees,
+            @ViewPort.ScaleType int scaleType,
             @NonNull Map<UseCase, Size> useCaseSizes) {
         // Transform aspect ratio to sensor orientation. The the rest of the method is in sensor
         // orientation.
-        Rational aspectRatio = ImageUtil.getRotatedAspectRatio(outputRotationDegrees,
-                outputAspectRatio);
+        Rational rotatedViewPortAspectRatio = ImageUtil.getRotatedAspectRatio(
+                outputRotationDegrees, viewPortAspectRatio);
         RectF fullSensorRectF = new RectF(fullSensorRect);
 
         // Calculate the transformation for each UseCase.
@@ -931,8 +935,9 @@
             sensorIntersectionRect.intersect(useCaseSensorRect);
         }
 
-        // Get the max shared sensor rect by the given aspect ratio.
-        sensorIntersectionRect = ImageUtil.fitCenter(sensorIntersectionRect, aspectRatio);
+        // Get the shared sensor rect by the given aspect ratio.
+        sensorIntersectionRect = getScaledRect(
+                sensorIntersectionRect, rotatedViewPortAspectRatio, scaleType);
 
         // Map the max shared sensor rect to UseCase coordinates.
         Map<UseCase, Rect> useCaseOutputRects = new HashMap<>();
@@ -950,6 +955,70 @@
     }
 
     /**
+     * Returns the scaled surface rect that fits/fills the given view port aspect ratio.
+     *
+     * <p> Scale type represents the transformation from surface to view port. For FIT types,
+     * this method returns the smallest rectangle that is larger the surface; For FILL types,
+     * returns the largest rectangle that is smaller than the view port. The returned rectangle
+     * is also required to 1) have the view port's aspect ratio and 2) be in the surface
+     * coordinates.
+     */
+    @SuppressLint("SwitchIntDef")
+    @VisibleForTesting
+    @NonNull
+    static RectF getScaledRect(
+            @NonNull RectF surfaceRect,
+            @NonNull Rational viewPortAspectRatio,
+            @ViewPort.ScaleType int scaleType) {
+        Matrix viewPortToSurfaceTransformation = new Matrix();
+        RectF viewPortRect = new RectF(0, 0, viewPortAspectRatio.getNumerator(),
+                viewPortAspectRatio.getDenominator());
+        if (scaleType == ViewPort.FIT_CENTER || scaleType == ViewPort.FIT_END
+                || scaleType == ViewPort.FIT_START) {
+            Matrix surfaceToViewPortTransformation = new Matrix();
+            switch (scaleType) {
+                // To workaround the limitation that Matrix doesn't not support FILL types
+                // natively, use inverted backward FIT mapping to achieve forward FILL mapping.
+                case ViewPort.FIT_CENTER:
+                    surfaceToViewPortTransformation.setRectToRect(
+                            surfaceRect, viewPortRect, Matrix.ScaleToFit.CENTER);
+                    break;
+                case ViewPort.FIT_START:
+                    surfaceToViewPortTransformation.setRectToRect(
+                            surfaceRect, viewPortRect, Matrix.ScaleToFit.START);
+                    break;
+                case ViewPort.FIT_END:
+                    surfaceToViewPortTransformation.setRectToRect(
+                            surfaceRect, viewPortRect, Matrix.ScaleToFit.END);
+                    break;
+            }
+            surfaceToViewPortTransformation.invert(viewPortToSurfaceTransformation);
+        } else if (scaleType == ViewPort.FILL_CENTER || scaleType == ViewPort.FILL_END
+                || scaleType == ViewPort.FILL_START) {
+            switch (scaleType) {
+                case ViewPort.FILL_CENTER:
+                    viewPortToSurfaceTransformation.setRectToRect(
+                            viewPortRect, surfaceRect, Matrix.ScaleToFit.CENTER);
+                    break;
+                case ViewPort.FILL_START:
+                    viewPortToSurfaceTransformation.setRectToRect(
+                            viewPortRect, surfaceRect, Matrix.ScaleToFit.START);
+                    break;
+                case ViewPort.FILL_END:
+                    viewPortToSurfaceTransformation.setRectToRect(
+                            viewPortRect, surfaceRect, Matrix.ScaleToFit.END);
+                    break;
+            }
+        } else {
+            throw new IllegalStateException("Unexpected scale type: " + scaleType);
+        }
+
+        RectF viewPortRectInSurfaceCoordinates = new RectF();
+        viewPortToSurfaceTransformation.mapRect(viewPortRectInSurfaceCoordinates, viewPortRect);
+        return viewPortRectInSurfaceCoordinates;
+    }
+
+    /**
      * Returns a map of {@link Size} based on rotation degrees.
      */
     static Map<UseCase, Size> getRotatedUseCaseSizes(
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageProxy.java
index 65a280c..cad4dac 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ForwardingImageProxy.java
@@ -70,6 +70,17 @@
         mImage.setCropRect(rect);
     }
 
+    @NonNull
+    @Override
+    public synchronized Rect getViewPortRect() {
+        return mImage.getViewPortRect();
+    }
+
+    @Override
+    public synchronized void setViewPortRect(@NonNull Rect viewPortRect) {
+        mImage.setViewPortRect(viewPortRect);
+    }
+
     @Override
     public synchronized int getFormat() {
         return mImage.getFormat();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageProxy.java
index fe02d35..5df36fb 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageProxy.java
@@ -19,9 +19,11 @@
 import android.annotation.SuppressLint;
 import android.graphics.Rect;
 import android.media.Image;
+import android.util.Rational;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
 
 import java.nio.ByteBuffer;
 
@@ -51,6 +53,29 @@
     void setCropRect(@Nullable Rect rect);
 
     /**
+     * Returns the rectangle defined by {@link ViewPort}.
+     *
+     * <p> Returns the value of {@link #getCropRect()} if {@link ViewPort} is not provided.
+     *
+     * @hide
+     * @see ViewPort.Builder#setAspectRatio(Rational)
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @NonNull
+    Rect getViewPortRect();
+
+    /**
+     * Sets the rectangle based on {@link ViewPort}.
+     *
+     * <p> If the value is null, {@link #getViewPortRect()} will return the value of
+     * {@link #getCropRect()}.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    void setViewPortRect(@Nullable Rect viewPortRect);
+
+    /**
      * Returns the image format.
      *
      * @see android.media.Image#getFormat()
@@ -122,9 +147,8 @@
      * {@link android.media.ImageReader}.  So an {@link Image} obtained with this method will behave
      * as such.
      *
-     * @see android.media.Image#close()
-     *
      * @return the android image.
+     * @see android.media.Image#close()
      */
     @Nullable
     @ExperimentalGetImage
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageUtil.java
index 27b5efc..18a318e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageUtil.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageUtil.java
@@ -20,9 +20,7 @@
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
 import android.graphics.ImageFormat;
-import android.graphics.Matrix;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.graphics.YuvImage;
 import android.util.Log;
 import android.util.Rational;
@@ -58,20 +56,6 @@
         return new Rational(aspectRatio.getNumerator(), aspectRatio.getDenominator());
     }
 
-    /**
-     * Returns the max containing {@link RectF} with the given aspect ratio.
-     */
-    @NonNull
-    public static RectF fitCenter(@NonNull RectF dstRect, @NonNull Rational sourceAspectRatio) {
-        Matrix matrix = new Matrix();
-        RectF srcRect = new RectF(0, 0, sourceAspectRatio.getNumerator(),
-                sourceAspectRatio.getDenominator());
-        matrix.setRectToRect(srcRect, dstRect, Matrix.ScaleToFit.CENTER);
-        RectF result = new RectF();
-        matrix.mapRect(result, srcRect);
-        return result;
-    }
-
     /** {@link android.media.Image} to JPEG byte array. */
     @Nullable
     public static byte[] imageToJpegByteArray(@NonNull ImageProxy image)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
index 8e3fbc6..0d1dfd9 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.core;
 
+import static androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
@@ -37,6 +39,8 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.google.common.collect.Iterables;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -70,23 +74,21 @@
     private Handler mCallbackHandler;
     private Handler mBackgroundHandler;
     private Executor mBackgroundExecutor;
-    private List<Image> mImagesReceived;
+    private List<ImageProxy> mImageProxiesReceived;
     private ImageAnalysis mImageAnalysis;
 
-    private HandlerThread mBackgroundThread;
-
     @Before
     public void setUp() throws ExecutionException, InterruptedException {
         HandlerThread callbackThread = new HandlerThread("Callback");
         callbackThread.start();
         mCallbackHandler = new Handler(callbackThread.getLooper());
 
-        mBackgroundThread = new HandlerThread("Background");
-        mBackgroundThread.start();
-        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
+        HandlerThread backgroundThread = new HandlerThread("Background");
+        backgroundThread.start();
+        mBackgroundHandler = new Handler(backgroundThread.getLooper());
         mBackgroundExecutor = CameraXExecutors.newHandlerExecutor(mBackgroundHandler);
 
-        mImagesReceived = new ArrayList<>();
+        mImageProxiesReceived = new ArrayList<>();
 
         ShadowImageReader.clear();
 
@@ -107,36 +109,53 @@
     @After
     public void tearDown() throws ExecutionException, InterruptedException {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(CameraX::unbindAll);
-        mImagesReceived.clear();
+        mImageProxiesReceived.clear();
         CameraX.shutdown().get();
     }
 
     @Test
+    public void resultSize_isEqualToSurfaceSize() {
+        // Arrange.
+        setUpImageAnalysisWithStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST);
+
+        // Act.
+        ShadowImageReader.triggerCallbackWithMockImage(MOCK_IMAGE_1);
+        flushHandler(mBackgroundHandler);
+        flushHandler(mCallbackHandler);
+
+        // Assert.
+        assertThat(Iterables.getOnlyElement(mImageProxiesReceived).getHeight()).isEqualTo(
+                MAX_OUTPUT_SIZE.getHeight());
+        assertThat(Iterables.getOnlyElement(mImageProxiesReceived).getWidth()).isEqualTo(
+                MAX_OUTPUT_SIZE.getWidth());
+    }
+
+    @Test
     public void nonBlockingAnalyzerClosed_imageNotAnalyzed() {
         // Arrange.
         setUpImageAnalysisWithStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST);
 
         // Act.
         // Receive images from camera feed.
-        ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_1);
+        ShadowImageReader.triggerCallbackWithMockImage(MOCK_IMAGE_1);
         flushHandler(mBackgroundHandler);
-        ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_2);
+        ShadowImageReader.triggerCallbackWithMockImage(MOCK_IMAGE_2);
         flushHandler(mBackgroundHandler);
 
         // Assert.
         // No image is received because callback handler is blocked.
-        assertThat(mImagesReceived).isEmpty();
+        assertThat(mImageProxiesReceived).isEmpty();
 
         // Flush callback handler and image1 is received.
         flushHandler(mCallbackHandler);
-        assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1);
+        assertThat(getImagesReceived()).containsExactly(MOCK_IMAGE_1);
 
         // Clear ImageAnalysis and flush both handlers. No more image should be received because
         // it's closed.
         mImageAnalysis.clear();
         flushHandler(mBackgroundHandler);
         flushHandler(mCallbackHandler);
-        assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1);
+        assertThat(getImagesReceived()).containsExactly(MOCK_IMAGE_1);
     }
 
     @Test
@@ -146,17 +165,17 @@
 
         // Act.
         // Receive images from camera feed.
-        ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_1);
+        ShadowImageReader.triggerCallbackWithMockImage(MOCK_IMAGE_1);
         flushHandler(mBackgroundHandler);
 
         // Assert.
         // No image is received because callback handler is blocked.
-        assertThat(mImagesReceived).isEmpty();
+        assertThat(mImageProxiesReceived).isEmpty();
 
         // Flush callback handler and it's still empty because it's close.
         mImageAnalysis.clear();
         flushHandler(mCallbackHandler);
-        assertThat(mImagesReceived).isEmpty();
+        assertThat(mImageProxiesReceived).isEmpty();
     }
 
     @Test
@@ -166,31 +185,31 @@
 
         // Act.
         // Receive images from camera feed.
-        ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_1);
+        ShadowImageReader.triggerCallbackWithMockImage(MOCK_IMAGE_1);
         flushHandler(mBackgroundHandler);
-        ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_2);
+        ShadowImageReader.triggerCallbackWithMockImage(MOCK_IMAGE_2);
         flushHandler(mBackgroundHandler);
-        ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_3);
+        ShadowImageReader.triggerCallbackWithMockImage(MOCK_IMAGE_3);
         flushHandler(mBackgroundHandler);
 
         // Assert.
         // No image is received because callback handler is blocked.
-        assertThat(mImagesReceived).isEmpty();
+        assertThat(mImageProxiesReceived).isEmpty();
 
         // Flush callback handler and image1 is received.
         flushHandler(mCallbackHandler);
-        assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1);
+        assertThat(getImagesReceived()).containsExactly(MOCK_IMAGE_1);
 
         // Flush both handlers and the previous cached image3 is received (image2 was dropped). The
         // code alternates the 2 threads so they have to be both flushed to proceed.
         flushHandler(mBackgroundHandler);
         flushHandler(mCallbackHandler);
-        assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1, MOCK_IMAGE_3);
+        assertThat(getImagesReceived()).containsExactly(MOCK_IMAGE_1, MOCK_IMAGE_3);
 
         // Flush both handlers and no more frame.
         flushHandler(mBackgroundHandler);
         flushHandler(mCallbackHandler);
-        assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1, MOCK_IMAGE_3);
+        assertThat(getImagesReceived()).containsExactly(MOCK_IMAGE_1, MOCK_IMAGE_3);
     }
 
     @Test
@@ -200,20 +219,20 @@
 
         // Act.
         // Receive images from camera feed.
-        ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_1);
+        ShadowImageReader.triggerCallbackWithMockImage(MOCK_IMAGE_1);
         flushHandler(mBackgroundHandler);
-        ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_2);
+        ShadowImageReader.triggerCallbackWithMockImage(MOCK_IMAGE_2);
         flushHandler(mBackgroundHandler);
-        ShadowImageReader.triggerCallbackWithImage(MOCK_IMAGE_3);
+        ShadowImageReader.triggerCallbackWithMockImage(MOCK_IMAGE_3);
         flushHandler(mBackgroundHandler);
 
         // Assert.
         // No image is received because callback handler is blocked.
-        assertThat(mImagesReceived).isEmpty();
+        assertThat(mImageProxiesReceived).isEmpty();
 
         // Flush callback handler and 3 frames received.
         flushHandler(mCallbackHandler);
-        assertThat(mImagesReceived).containsExactly(MOCK_IMAGE_1, MOCK_IMAGE_2, MOCK_IMAGE_3);
+        assertThat(getImagesReceived()).containsExactly(MOCK_IMAGE_1, MOCK_IMAGE_2, MOCK_IMAGE_3);
     }
 
     private void setUpImageAnalysisWithStrategy(
@@ -226,7 +245,7 @@
 
         mImageAnalysis.setAnalyzer(CameraXExecutors.newHandlerExecutor(mCallbackHandler),
                 (image) -> {
-                    mImagesReceived.add(image.getImage());
+                    mImageProxiesReceived.add(image);
                     image.close();
                 }
         );
@@ -239,6 +258,14 @@
         });
     }
 
+    private List<Image> getImagesReceived() {
+        List<Image> imagesReceived = new ArrayList<>();
+        for (ImageProxy imageProxy : mImageProxiesReceived) {
+            imagesReceived.add(imageProxy.getImage());
+        }
+        return imagesReceived;
+    }
+
     /**
      * Flushes a {@link Handler} to run all pending tasks.
      *
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
new file mode 100644
index 0000000..f7d2e66
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2020 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 android.content.Context
+import android.media.Image
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import android.view.Surface
+import androidx.camera.core.impl.CameraControlInternal.ControlUpdateCallback
+import androidx.camera.core.impl.CameraFactory
+import androidx.camera.core.impl.CameraThreadConfig
+import androidx.camera.core.impl.CaptureConfig
+import androidx.camera.core.impl.SessionConfig
+import androidx.camera.core.impl.UseCaseConfig
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.testing.fakes.FakeAppConfig
+import androidx.camera.testing.fakes.FakeCamera
+import androidx.camera.testing.fakes.FakeCameraCaptureResult
+import androidx.camera.testing.fakes.FakeCameraControl
+import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager
+import androidx.camera.testing.fakes.FakeCameraFactory
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
+import androidx.camera.testing.fakes.FakeLifecycleOwner
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadow.api.Shadow
+import org.robolectric.shadows.ShadowLooper
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+
+/**
+ * Unit tests for [ImageCapture].
+ */
+@MediumTest
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(
+    minSdk = Build.VERSION_CODES.LOLLIPOP,
+    shadows = [ShadowCameraX::class, ShadowImageReader::class]
+)
+class ImageCaptureTest {
+
+    private var executor: Executor? = null
+    private var callbackHandler: Handler? = null
+    private var fakeCameraControl: FakeCameraControl? = null
+
+    @Before
+    @Throws(ExecutionException::class, InterruptedException::class)
+    fun setUp() {
+        fakeCameraControl = FakeCameraControl(
+            object : ControlUpdateCallback {
+                override fun onCameraControlUpdateSessionConfig(
+                    sessionConfig: SessionConfig
+                ) {
+                }
+
+                override fun onCameraControlCaptureRequests(
+                    captureConfigs: List<CaptureConfig>
+                ) {
+                }
+            })
+        val cameraFactoryProvider =
+            CameraFactory.Provider { _: Context?, _: CameraThreadConfig? ->
+                val cameraFactory = FakeCameraFactory()
+                cameraFactory.insertDefaultBackCamera(ShadowCameraX.DEFAULT_CAMERA_ID) {
+                    FakeCamera(
+                        ShadowCameraX.DEFAULT_CAMERA_ID, fakeCameraControl,
+                        FakeCameraInfoInternal()
+                    )
+                }
+                cameraFactory
+            }
+        val cameraXConfig = CameraXConfig.Builder.fromConfig(
+            FakeAppConfig.create()
+        ).setCameraFactoryProvider(cameraFactoryProvider).build()
+        val context =
+            ApplicationProvider.getApplicationContext<Context>()
+        CameraX.initialize(context, cameraXConfig).get()
+        val callbackThread = HandlerThread("Callback")
+        callbackThread.start()
+        callbackHandler = Handler(callbackThread.looper)
+        executor = CameraXExecutors.newHandlerExecutor(callbackHandler!!)
+        ShadowImageReader.clear()
+    }
+
+    @After
+    @Throws(ExecutionException::class, InterruptedException::class)
+    fun tearDown() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync { CameraX.unbindAll() }
+        CameraX.shutdown().get()
+    }
+
+    @Test
+    fun capturedImageSize_isEqualToSurfaceSize() {
+        // Arrange.
+        val timestamp = 0L
+        val imageCapture = ImageCapture.Builder()
+            .setTargetRotation(Surface.ROTATION_0)
+            .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
+            .setFlashMode(ImageCapture.FLASH_MODE_OFF)
+            .setCaptureOptionUnpacker { _: UseCaseConfig<*>?, _: CaptureConfig.Builder? -> }
+            .build()
+        var capturedImage: ImageProxy? = null
+        val onImageCapturedCallback = object : ImageCapture.OnImageCapturedCallback() {
+            override fun onCaptureSuccess(image: ImageProxy) {
+                capturedImage = image
+            }
+
+            override fun onError(exception: ImageCaptureException) {
+            }
+        }
+
+        // Act.
+        // Bind UseCase amd take picture.
+        val lifecycleOwner = FakeLifecycleOwner()
+        InstrumentationRegistry.getInstrumentation()
+            .runOnMainSync {
+                CameraX.bindToLifecycle(
+                    lifecycleOwner,
+                    CameraSelector.DEFAULT_BACK_CAMERA,
+                    imageCapture
+                )
+                lifecycleOwner.startAndResume()
+            }
+        imageCapture.takePicture(executor!!, onImageCapturedCallback)
+        // Send mock image.
+        ShadowImageReader.triggerCallbackWithMockImage(createMockImage(timestamp))
+        flushHandler(callbackHandler)
+        // Send fake image info.
+        fakeCameraControl?.notifyAllRequestsOnCaptureCompleted(
+            FakeCameraCaptureResult.Builder().setTimestamp(timestamp).build()
+        )
+        flushHandler(callbackHandler)
+
+        // Assert.
+        Truth.assertThat(capturedImage?.width)
+            .isEqualTo(FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.width)
+        Truth.assertThat(capturedImage?.height)
+            .isEqualTo(FakeCameraDeviceSurfaceManager.MAX_OUTPUT_SIZE.height)
+    }
+
+    private fun flushHandler(handler: Handler?) {
+        (Shadow.extract<Any>(handler!!.looper) as ShadowLooper).idle()
+    }
+
+    private fun createMockImage(timestamp: Long): Image? {
+        val mockImage = mock(Image::class.java)
+        Mockito.`when`(mockImage.timestamp).thenReturn(timestamp)
+        return mockImage
+    }
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageUtilTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageUtilTest.java
index b19a8bb..5bbcc5d 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageUtilTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageUtilTest.java
@@ -28,7 +28,6 @@
 import android.graphics.ImageFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.os.Build;
 import android.util.Base64;
 import android.util.Rational;
@@ -119,28 +118,6 @@
     }
 
     @Test
-    public void fitNarrowRectIntoContainer() {
-        // Arrange.
-        RectF rect = new RectF(10, 10, 50, 40);
-
-        // Assert.
-        // The aspect ratio is narrower than the container.
-        assertThat(ImageUtil.fitCenter(rect, new Rational(1, 1))).isEqualTo(
-                new RectF(15, 10, 45, 40));
-    }
-
-    @Test
-    public void fitWideRectIntoContainer() {
-        // Arrange.
-        RectF rect = new RectF(10, 10, 50, 40);
-
-        // Assert.
-        // The aspect ratio is wider than the container.
-        assertThat(ImageUtil.fitCenter(rect, new Rational(2, 1))).isEqualTo(
-                new RectF(10, 15, 50, 35));
-    }
-
-    @Test
     public void canTransformImageToByteArray() throws ImageUtil.CodecFailedException {
         byte[] byteArray = ImageUtil.imageToJpegByteArray(mImage);
         assertThat(byteArray).isEqualTo(mDataByteArray);
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ShadowCameraX.java b/camera/camera-core/src/test/java/androidx/camera/core/ShadowCameraX.java
index b65c25c..50189ad6 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ShadowCameraX.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ShadowCameraX.java
@@ -16,10 +16,11 @@
 
 package androidx.camera.core;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.camera.core.impl.CameraDeviceSurfaceManager;
-import androidx.camera.core.impl.SessionConfig;
+import androidx.camera.core.impl.ImageAnalysisConfig;
+import androidx.camera.core.impl.ImageCaptureConfig;
+import androidx.camera.core.impl.PreviewConfig;
 import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.testing.fakes.FakeCameraDeviceSurfaceManager;
 import androidx.camera.testing.fakes.FakeCameraInfoInternal;
@@ -32,16 +33,21 @@
  */
 @Implements(CameraX.class)
 public class ShadowCameraX {
-    public static final String DEFAULT_CAMERA_ID = "DEFAULT_CAMERA_ID";
+    public static final String DEFAULT_CAMERA_ID = "0";
 
     private static final UseCaseConfig<ImageAnalysis> DEFAULT_IMAGE_ANALYSIS_CONFIG =
             new ImageAnalysis.Builder().setSessionOptionUnpacker(
-                    new SessionConfig.OptionUnpacker() {
-                        @Override
-                        public void unpack(@NonNull UseCaseConfig<?> config,
-                                @NonNull SessionConfig.Builder builder) {
-                            // no op.
-                        }
+                    (config, builder) -> {
+                    }).getUseCaseConfig();
+
+    private static final UseCaseConfig<Preview> DEFAULT_PREVIEW_CONFIG =
+            new Preview.Builder().setSessionOptionUnpacker(
+                    (config, builder) -> {
+                    }).getUseCaseConfig();
+
+    private static final UseCaseConfig<ImageCapture> DEFAULT_IMAGE_CAPTURE_CONFIG =
+            new ImageCapture.Builder().setSessionOptionUnpacker(
+                    (config, builder) -> {
                     }).getUseCaseConfig();
 
     private static final CameraInfo DEFAULT_CAMERA_INFO = new FakeCameraInfoInternal();
@@ -64,7 +70,15 @@
     @Implementation
     public static <C extends UseCaseConfig<?>> C getDefaultUseCaseConfig(Class<C> configType,
             @Nullable CameraInfo cameraInfo) {
-        return (C) DEFAULT_IMAGE_ANALYSIS_CONFIG;
+        if (configType.equals(PreviewConfig.class)) {
+            return (C) DEFAULT_PREVIEW_CONFIG;
+        } else if (configType.equals(ImageAnalysisConfig.class)) {
+            return (C) DEFAULT_IMAGE_ANALYSIS_CONFIG;
+        } else if (configType.equals(ImageCaptureConfig.class)) {
+            return (C) DEFAULT_IMAGE_CAPTURE_CONFIG;
+        }
+        throw new UnsupportedOperationException(
+                "Shadow UseCase config not implemented: " + configType);
     }
 
     /**
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java b/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java
index ed5d2c3..72f7481 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ShadowImageReader.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.core;
 
+import static org.mockito.Mockito.when;
+
 import android.media.Image;
 import android.media.ImageReader;
 import android.os.Build;
@@ -23,6 +25,7 @@
 
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.core.util.Preconditions;
 
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
@@ -43,12 +46,31 @@
     private static ImageReader sImageReader;
     private static ShadowImageReader sShadowImageReader;
 
+    private static int sWidth;
+    private static int sHeight;
+    private static int sMaxImages;
+
+    /**
+     * Shadow of {@link ImageReader#newInstance(int, int, int, int, long)}.
+     */
+    @Implementation
+    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+    public static ImageReader newInstance(int width, int height, int format, int maxImages,
+            long usage) {
+        throw new UnsupportedOperationException("Shadow method not implemented.");
+    }
+
     /**
      * Shadow of {@link ImageReader#newInstance(int, int, int, int)}.
      */
     @Implementation
     @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
     public static ImageReader newInstance(int width, int height, int format, int maxImages) {
+        Preconditions.checkState(sImageReader == null,
+                "Only support one shadow ImageReader instance");
+        sMaxImages = maxImages;
+        sWidth = width;
+        sHeight = height;
         sImageReader = Shadow.newInstanceOf(ImageReader.class);
         sShadowImageReader = Shadow.extract(sImageReader);
         return sImageReader;
@@ -58,7 +80,9 @@
      * Sets the incoming image and triggers
      * {@link ImageReader.OnImageAvailableListener#onImageAvailable(ImageReader)}.
      */
-    public static void triggerCallbackWithImage(Image mockImage) {
+    public static void triggerCallbackWithMockImage(Image mockImage) {
+        when(mockImage.getWidth()).thenReturn(sWidth);
+        when(mockImage.getHeight()).thenReturn(sHeight);
         sIncomingImage = mockImage;
         sShadowImageReader.getListener().onImageAvailable(sImageReader);
     }
@@ -68,6 +92,11 @@
         // no-op.
     }
 
+    @Implementation
+    public int getMaxImages() {
+        return sMaxImages;
+    }
+
     /**
      * Clears incoming images.
      */
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCaptureResult.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCaptureResult.java
index ed7ab86..8bc4baf 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCaptureResult.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCaptureResult.java
@@ -137,38 +137,52 @@
         }
 
         /** Set the {@link CameraCaptureMetaData.AfMode} **/
-        public void setAfMode(@Nullable CameraCaptureMetaData.AfMode mode) {
+        @NonNull
+        public Builder setAfMode(@Nullable CameraCaptureMetaData.AfMode mode) {
             mAfMode = mode;
+            return this;
         }
 
         /** Set the {@link CameraCaptureMetaData.AfState} **/
-        public void setAfState(@Nullable CameraCaptureMetaData.AfState state) {
+        @NonNull
+        public Builder setAfState(@Nullable CameraCaptureMetaData.AfState state) {
             mAfState = state;
+            return this;
         }
 
         /** Set the {@link CameraCaptureMetaData.AeState} **/
-        public void setAeState(@Nullable CameraCaptureMetaData.AeState state) {
+        @NonNull
+        public Builder setAeState(@Nullable CameraCaptureMetaData.AeState state) {
             mAeState = state;
+            return this;
         }
 
         /** Set the {@link CameraCaptureMetaData.AwbState} **/
-        public void setAwbState(@Nullable CameraCaptureMetaData.AwbState state) {
+        @NonNull
+        public Builder setAwbState(@Nullable CameraCaptureMetaData.AwbState state) {
             mAwbState = state;
+            return this;
         }
 
         /** Set the {@link CameraCaptureMetaData.FlashState} **/
-        public void setFlashState(@Nullable CameraCaptureMetaData.FlashState state) {
+        @NonNull
+        public Builder setFlashState(@Nullable CameraCaptureMetaData.FlashState state) {
             mFlashState = state;
+            return this;
         }
 
         /** Set the timestamp. */
-        public void setTimestamp(long timestamp) {
+        @NonNull
+        public Builder setTimestamp(long timestamp) {
             mTimestamp = timestamp;
+            return this;
         }
 
         /** Set the tag. */
-        public void setTag(@Nullable Object tag) {
+        @NonNull
+        public Builder setTag(@Nullable Object tag) {
             mTag = tag;
+            return this;
         }
     }
 }
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManager.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManager.java
index 84d5510..536c0a9 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManager.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraDeviceSurfaceManager.java
@@ -33,7 +33,7 @@
 /** A CameraDeviceSurfaceManager which has no supported SurfaceConfigs. */
 public final class FakeCameraDeviceSurfaceManager implements CameraDeviceSurfaceManager {
 
-    private static final Size MAX_OUTPUT_SIZE = new Size(4032, 3024); // 12.2 MP
+    public static final Size MAX_OUTPUT_SIZE = new Size(4032, 3024); // 12.2 MP
     private static final Size PREVIEW_SIZE = new Size(1920, 1080);
 
     private Map<String, Map<Class<? extends UseCaseConfig<?>>, Size>> mDefinedResolutions =
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeImageProxy.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeImageProxy.java
index 3f47890..9bd424c 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeImageProxy.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeImageProxy.java
@@ -39,6 +39,7 @@
     private int mHeight = 0;
     private int mWidth = 0;
     private PlaneProxy[] mPlaneProxy = new PlaneProxy[0];
+    private Rect mViewPortRect;
 
     @NonNull
     private ImageInfo mImageInfo;
@@ -77,6 +78,17 @@
         mCropRect = rect != null ? rect : new Rect();
     }
 
+    @NonNull
+    @Override
+    public Rect getViewPortRect() {
+        return mViewPortRect != null ? mViewPortRect : getCropRect();
+    }
+
+    @Override
+    public void setViewPortRect(@Nullable Rect viewPortRect) {
+        mViewPortRect = viewPortRect;
+    }
+
     @Override
     public int getFormat() {
         return mFormat;
diff --git a/fragment/fragment-ktx/src/androidTest/java/androidx/fragment/app/FragmentResultOwnerTest.kt b/fragment/fragment-ktx/src/androidTest/java/androidx/fragment/app/FragmentResultOwnerTest.kt
index 35328a4..df2c2a4 100644
--- a/fragment/fragment-ktx/src/androidTest/java/androidx/fragment/app/FragmentResultOwnerTest.kt
+++ b/fragment/fragment-ktx/src/androidTest/java/androidx/fragment/app/FragmentResultOwnerTest.kt
@@ -20,13 +20,13 @@
 import androidx.core.os.bundleOf
 import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
+import androidx.test.filters.LargeTest
 import androidx.testutils.withActivity
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@MediumTest
+@LargeTest
 @RunWith(AndroidJUnit4::class)
 class FragmentResultOwnerTest {
 
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt
index afcb7e3..6e195e3 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt
@@ -21,7 +21,9 @@
 import android.content.Context
 import android.content.DialogInterface
 import android.os.Bundle
+import android.view.LayoutInflater
 import android.view.View
+import android.view.ViewGroup
 import android.widget.EditText
 import android.widget.TextView
 import androidx.fragment.app.test.EmptyFragmentTestActivity
@@ -104,6 +106,28 @@
             .isTrue()
     }
 
+    @UiThreadTest
+    @Test
+    fun testDialogFragmentWithChild() {
+        val fragment = TestDialogFragmentWithChild(false)
+        fragment.showNow(activityTestRule.activity.supportFragmentManager, null)
+
+        assertWithMessage("Dialog was not being shown")
+            .that(fragment.dialog?.isShowing)
+            .isTrue()
+    }
+
+    @UiThreadTest
+    @Test
+    fun testDialogFragmentWithChildExecutePendingTransactions() {
+        val fragment = TestDialogFragmentWithChild(true)
+        fragment.showNow(activityTestRule.activity.supportFragmentManager, null)
+
+        assertWithMessage("Dialog was not being shown")
+            .that(fragment.dialog?.isShowing)
+            .isTrue()
+    }
+
     @Test
     fun testCancelDialog() {
         val dialogFragment = TestDialogFragment()
@@ -330,6 +354,26 @@
         }
     }
 
+    class TestDialogFragmentWithChild(
+        val executePendingTransactions: Boolean = false
+    ) : DialogFragment() {
+
+        override fun onCreateView(
+            inflater: LayoutInflater,
+            container: ViewGroup?,
+            savedInstanceState: Bundle?
+        ): View? {
+            val view = inflater.inflate(R.layout.simple_container, container, false)
+            childFragmentManager.beginTransaction()
+                .add(R.id.fragmentContainer, StrictViewFragment())
+                .commit()
+            if (executePendingTransactions) {
+                childFragmentManager.executePendingTransactions()
+            }
+            return view
+        }
+    }
+
     class TestLayoutDialogFragment : DialogFragment(R.layout.fragment_a) {
         var parentSetInStart = false
 
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java
index 9721ede..79a9ec6 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java
@@ -489,7 +489,10 @@
                 if (dialogView != null) {
                     return dialogView;
                 }
-                return fragmentContainer.onFindViewById(id);
+                if (fragmentContainer.onHasView()) {
+                    return fragmentContainer.onFindViewById(id);
+                }
+                return null;
             }
 
             @Override
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
index b4fd940..5615b64 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -2839,7 +2839,8 @@
             @Nullable
             public View onFindViewById(int id) {
                 if (mView == null) {
-                    throw new IllegalStateException("Fragment " + this + " does not have a view");
+                    throw new IllegalStateException("Fragment " + Fragment.this
+                            + " does not have a view");
                 }
                 return mView.findViewById(id);
             }