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);
}