Merge "Device quirk: preview stretched on Samsung J5 Prime" into androidx-main
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewTransformationDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewTransformationDeviceTest.kt
index ba1c76f..9bc5023 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewTransformationDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewTransformationDeviceTest.kt
@@ -113,7 +113,7 @@
SURFACE_SIZE,
BACK_CAMERA
)
- return mPreviewTransform.isCropRectAspectRatioMatchPreviewView(PREVIEW_VIEW_SIZE)
+ return mPreviewTransform.isViewportAspectRatioMatchPreviewView(PREVIEW_VIEW_SIZE)
}
@Test
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/PreviewTransformation.java b/camera/camera-view/src/main/java/androidx/camera/view/PreviewTransformation.java
index ce02f39..39c7819 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/PreviewTransformation.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/PreviewTransformation.java
@@ -55,7 +55,7 @@
import androidx.camera.core.SurfaceRequest;
import androidx.camera.core.ViewPort;
import androidx.camera.view.internal.compat.quirk.DeviceQuirks;
-import androidx.camera.view.internal.compat.quirk.PreviewStretchedQuirk;
+import androidx.camera.view.internal.compat.quirk.PreviewOneThirdWiderQuirk;
import androidx.core.util.Preconditions;
/**
@@ -104,8 +104,12 @@
// SurfaceRequest.getResolution().
private Size mResolution;
- // TransformationInfo.getCropRect().
+ // This represents the area of the Surface that should be visible to end users. The value
+ // is based on TransformationInfo.getCropRect() with possible corrections due to device quirks.
private Rect mSurfaceCropRect;
+ // This rect represents the size of the viewport in preview. It's always the same as
+ // TransformationInfo.getCropRect().
+ private Rect mViewportRect;
// TransformationInfo.getRotationDegrees().
private int mPreviewRotationDegrees;
// TransformationInfo.getTargetRotation.
@@ -128,7 +132,8 @@
Size resolution, boolean isFrontCamera) {
Logger.d(TAG, "Transformation info set: " + transformationInfo + " " + resolution + " "
+ isFrontCamera);
- mSurfaceCropRect = transformationInfo.getCropRect();
+ mSurfaceCropRect = getCorrectedCropRect(transformationInfo.getCropRect());
+ mViewportRect = transformationInfo.getCropRect();
mPreviewRotationDegrees = transformationInfo.getRotationDegrees();
mTargetRotation = transformationInfo.getTargetRotation();
mResolution = resolution;
@@ -237,7 +242,7 @@
// Get the target of the mapping, the vertices of the crop rect in PreviewView.
float[] previewViewCropRectVertices;
- if (isCropRectAspectRatioMatchPreviewView(previewViewSize)) {
+ if (isViewportAspectRatioMatchPreviewView(previewViewSize)) {
// If crop rect has the same aspect ratio as PreviewView, scale the crop rect to fill
// the entire PreviewView. This happens if the scale type is FILL_* AND a
// PreviewView-based viewport is used.
@@ -245,7 +250,7 @@
} else {
// If the aspect ratios don't match, it could be 1) scale type is FIT_*, 2) the
// Viewport is not based on the PreviewView or 3) both.
- RectF previewViewCropRect = getPreviewViewCropRectForMismatchedAspectRatios(
+ RectF previewViewCropRect = getPreviewViewViewportRectForMismatchedAspectRatios(
previewViewSize, layoutDirection);
previewViewCropRectVertices = rectToVertices(previewViewCropRect);
}
@@ -253,7 +258,7 @@
previewViewCropRectVertices, mPreviewRotationDegrees);
// Get the source of the mapping, the vertices of the crop rect in Surface.
- float[] surfaceCropRectVertices = getSurfaceCropRectVertices();
+ float[] surfaceCropRectVertices = rectToVertices(new RectF(mSurfaceCropRect));
// Map source to target.
matrix.setPolyToPoly(surfaceCropRectVertices, 0, rotatedPreviewViewCropRectVertices, 0, 4);
@@ -282,43 +287,46 @@
/**
* Gets the vertices of the crop rect in Surface.
*/
- private float[] getSurfaceCropRectVertices() {
- RectF cropRectF = new RectF(mSurfaceCropRect);
- PreviewStretchedQuirk quirk = DeviceQuirks.get(PreviewStretchedQuirk.class);
+ private Rect getCorrectedCropRect(Rect surfaceCropRect) {
+ PreviewOneThirdWiderQuirk quirk = DeviceQuirks.get(PreviewOneThirdWiderQuirk.class);
if (quirk != null) {
// Correct crop rect if the device has a quirk.
+ RectF cropRectF = new RectF(surfaceCropRect);
Matrix correction = new Matrix();
correction.setScale(
quirk.getCropRectScaleX(),
- quirk.getCropRectScaleY(),
- mSurfaceCropRect.centerX(),
- mSurfaceCropRect.centerY());
+ 1f,
+ surfaceCropRect.centerX(),
+ surfaceCropRect.centerY());
correction.mapRect(cropRectF);
+ Rect correctRect = new Rect();
+ cropRectF.round(correctRect);
+ return correctRect;
}
- return rectToVertices(cropRectF);
+ return surfaceCropRect;
}
/**
- * Gets the crop rect in {@link PreviewView} coordinates for the case where crop rect's aspect
- * ratio doesn't match {@link PreviewView}'s aspect ratio.
+ * Gets the viewport rect in {@link PreviewView} coordinates for the case where viewport's
+ * aspect ratio doesn't match {@link PreviewView}'s aspect ratio.
*
* <p> When aspect ratios don't match, additional calculation is needed to figure out how to
* fit crop rect into the{@link PreviewView}.
*/
- RectF getPreviewViewCropRectForMismatchedAspectRatios(Size previewViewSize,
+ RectF getPreviewViewViewportRectForMismatchedAspectRatios(Size previewViewSize,
int layoutDirection) {
RectF previewViewRect = new RectF(0, 0, previewViewSize.getWidth(),
previewViewSize.getHeight());
- Size rotatedCropRectSize = getRotatedCropRectSize();
- RectF rotatedSurfaceCropRect = new RectF(0, 0, rotatedCropRectSize.getWidth(),
- rotatedCropRectSize.getHeight());
+ Size rotatedViewportSize = getRotatedViewportSize();
+ RectF rotatedViewportRect = new RectF(0, 0, rotatedViewportSize.getWidth(),
+ rotatedViewportSize.getHeight());
Matrix matrix = new Matrix();
- setMatrixRectToRect(matrix, rotatedSurfaceCropRect, previewViewRect, mScaleType);
- matrix.mapRect(rotatedSurfaceCropRect);
+ setMatrixRectToRect(matrix, rotatedViewportRect, previewViewRect, mScaleType);
+ matrix.mapRect(rotatedViewportRect);
if (layoutDirection == LayoutDirection.RTL) {
- return flipHorizontally(rotatedSurfaceCropRect, (float) previewViewSize.getWidth() / 2);
+ return flipHorizontally(rotatedViewportRect, (float) previewViewSize.getWidth() / 2);
}
- return rotatedSurfaceCropRect;
+ return rotatedViewportRect;
}
/**
@@ -374,29 +382,29 @@
}
/**
- * Returns crop rect size with target rotation applied.
+ * Returns viewport size with target rotation applied.
*/
- private Size getRotatedCropRectSize() {
- Preconditions.checkNotNull(mSurfaceCropRect);
+ private Size getRotatedViewportSize() {
if (is90or270(mPreviewRotationDegrees)) {
- return new Size(mSurfaceCropRect.height(), mSurfaceCropRect.width());
+ return new Size(mViewportRect.height(), mViewportRect.width());
}
- return new Size(mSurfaceCropRect.width(), mSurfaceCropRect.height());
+ return new Size(mViewportRect.width(), mViewportRect.height());
}
/**
- * Checks if the crop rect's aspect ratio matches that of the {@link PreviewView}.
+ * Checks if the viewport's aspect ratio matches that of the {@link PreviewView}.
*
* <p> The mismatch could happen if the {@link ViewPort} is not based on the
* {@link PreviewView}, or the {@link PreviewView#getScaleType()} is FIT_*. In this case, we
* need to calculate how the crop rect should be fitted.
*/
@VisibleForTesting
- boolean isCropRectAspectRatioMatchPreviewView(Size previewViewSize) {
- Size rotatedSize = getRotatedCropRectSize();
+ boolean isViewportAspectRatioMatchPreviewView(Size previewViewSize) {
+ // Using viewport rect to check if the viewport is based on the PreviewView.
+ Size rotatedViewportSize = getRotatedViewportSize();
return isAspectRatioMatchingWithRoundingError(
previewViewSize, /* isAccurate1= */ true,
- rotatedSize, /* isAccurate2= */ false);
+ rotatedViewportSize, /* isAccurate2= */ false);
}
/**
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/DeviceQuirksLoader.java
index 1fca5fe0..d73a851 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/DeviceQuirksLoader.java
@@ -39,8 +39,8 @@
final List<Quirk> quirks = new ArrayList<>();
// Load all device specific quirks
- if (PreviewStretchedQuirk.load()) {
- quirks.add(new PreviewStretchedQuirk());
+ if (PreviewOneThirdWiderQuirk.load()) {
+ quirks.add(new PreviewOneThirdWiderQuirk());
}
if (SurfaceViewStretchedQuirk.load()) {
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/PreviewOneThirdWiderQuirk.java b/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/PreviewOneThirdWiderQuirk.java
new file mode 100644
index 0000000..316e4db
--- /dev/null
+++ b/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/PreviewOneThirdWiderQuirk.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2021 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.view.internal.compat.quirk;
+
+import android.os.Build;
+
+import androidx.camera.core.impl.Quirk;
+
+/**
+ * A quirk where the preview buffer is stretched.
+ *
+ * <p> The symptom is, the preview's FOV is always 1/3 wider than intended. For example, if the
+ * preview Surface is 800x600, it's actually has a FOV of 1066x600 with the same center point,
+ * but squeezed to fit the 800x600 buffer.
+ */
+public class PreviewOneThirdWiderQuirk implements Quirk {
+
+ private static final String SAMSUNG_A3_2017 = "A3Y17LTE"; // b/180121821
+ private static final String SAMSUNG_J5_PRIME = "ON5XELTE"; // b/183329599
+
+ static boolean load() {
+ boolean isSamsungJ5PrimeAndApi26 =
+ SAMSUNG_J5_PRIME.equals(Build.DEVICE.toUpperCase()) && Build.VERSION.SDK_INT >= 26;
+ boolean isSamsungA3 = SAMSUNG_A3_2017.equals(Build.DEVICE.toUpperCase());
+ return isSamsungJ5PrimeAndApi26 || isSamsungA3;
+ }
+
+ /**
+ * The mount that the crop rect needs to be scaled in x.
+ */
+ public float getCropRectScaleX() {
+ return 0.75f;
+ }
+}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/PreviewStretchedQuirk.java b/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/PreviewStretchedQuirk.java
deleted file mode 100644
index fd274fa..0000000
--- a/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/PreviewStretchedQuirk.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2021 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.view.internal.compat.quirk;
-
-import android.os.Build;
-
-import androidx.camera.core.impl.Quirk;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * A quirk where the preview buffer is stretched.
- *
- * <p> This is similar to the SamsungPreviewTargetAspectRatioQuirk in camera-camera2 artifact.
- * The difference is, the other quirk can be fixed by choosing a different resolution,
- * while for this one the preview is always stretched no matter what resolution is selected.
- */
-public class PreviewStretchedQuirk implements Quirk {
-
- private static final String SAMSUNG_A3_2017 = "A3Y17LTE"; // b/180121821
-
- private static final List<String> KNOWN_AFFECTED_DEVICES = Arrays.asList(SAMSUNG_A3_2017);
-
- static boolean load() {
- return KNOWN_AFFECTED_DEVICES.contains(Build.DEVICE.toUpperCase());
- }
-
- /**
- * The mount that the crop rect needs to be scaled in x.
- */
- public float getCropRectScaleX() {
- if (SAMSUNG_A3_2017.equals(Build.DEVICE.toUpperCase())) {
- // For Samsung A3 2017, the symptom seems to be that the preview's FOV is always 1/3
- // wider than it's supposed to be. For example, if the preview Surface is 800x600, it's
- // actually has a FOV of 1066x600, but stretched to fit the 800x600 buffer. To correct
- // the preview, we need to crop out the extra 25% FOV.
- return 0.75f;
- }
- // No scale.
- return 1;
- }
-
- /**
- * The mount that the crop rect needs to be scaled in y.
- */
- public float getCropRectScaleY() {
- // No scale.
- return 1;
- }
-}
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/PreviewTransformationTest.java b/camera/camera-view/src/test/java/androidx/camera/view/PreviewTransformationTest.java
new file mode 100644
index 0000000..803a3a3
--- /dev/null
+++ b/camera/camera-view/src/test/java/androidx/camera/view/PreviewTransformationTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2021 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.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.Size;
+
+import androidx.camera.core.SurfaceRequest;
+import androidx.camera.view.internal.compat.quirk.PreviewOneThirdWiderQuirk;
+import androidx.camera.view.internal.compat.quirk.QuirkInjector;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/**
+ * Unit tests for {@link PreviewTransformation}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+public class PreviewTransformationTest {
+
+ private static final Rect CROP_RECT = new Rect(0, 0, 600, 400);
+
+ private final PreviewTransformation mPreviewTransformation = new PreviewTransformation();
+
+ @Test
+ public void withPreviewStretchedQuirk_cropRectIsAdjusted() {
+ // Arrange.
+ QuirkInjector.inject(new PreviewOneThirdWiderQuirk());
+
+ // Act.
+ mPreviewTransformation.setTransformationInfo(
+ SurfaceRequest.TransformationInfo.of(CROP_RECT, 0, 0),
+ new Size(CROP_RECT.width(), CROP_RECT.height()),
+ /*isFrontCamera*/ false);
+
+ // Assert: the crop rect is corrected.
+ assertThat(mPreviewTransformation.getSurfaceCropRect()).isEqualTo(new Rect(75, 0, 525,
+ 400));
+ }
+}
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/internal/compat/quirk/PreviewStretchedQuirkTest.java b/camera/camera-view/src/test/java/androidx/camera/view/internal/compat/quirk/PreviewOneThirdWiderQuirkTest.java
similarity index 61%
rename from camera/camera-view/src/test/java/androidx/camera/view/internal/compat/quirk/PreviewStretchedQuirkTest.java
rename to camera/camera-view/src/test/java/androidx/camera/view/internal/compat/quirk/PreviewOneThirdWiderQuirkTest.java
index 0b42944..17c02f5 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/internal/compat/quirk/PreviewStretchedQuirkTest.java
+++ b/camera/camera-view/src/test/java/androidx/camera/view/internal/compat/quirk/PreviewOneThirdWiderQuirkTest.java
@@ -28,24 +28,36 @@
import org.robolectric.util.ReflectionHelpers;
/**
- * Unit tests for {@link PreviewStretchedQuirk}.
+ * Unit tests for {@link PreviewOneThirdWiderQuirk}.
*/
@RunWith(RobolectricTestRunner.class)
@DoNotInstrument
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class PreviewStretchedQuirkTest {
+public class PreviewOneThirdWiderQuirkTest {
@Test
public void quirkExistsOnSamsungA3() {
- // Arrange.
ReflectionHelpers.setStaticField(Build.class, "DEVICE", "A3Y17LTE");
+ assertPreviewShouldBeCroppedBy25Percent();
+ }
- // Act.
- final PreviewStretchedQuirk quirk = DeviceQuirks.get(PreviewStretchedQuirk.class);
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.O)
+ public void quirkExistsOnSamsungJ5PrimeApi26AndAbove() {
+ ReflectionHelpers.setStaticField(Build.class, "DEVICE", "ON5XELTE");
+ assertPreviewShouldBeCroppedBy25Percent();
+ }
- // Assert.
+ @Test
+ @Config(maxSdk = Build.VERSION_CODES.N_MR1)
+ public void quirkDoesNotExistOnSamsungJ5PrimeApi25AndBelow() {
+ ReflectionHelpers.setStaticField(Build.class, "DEVICE", "ON5XELTE");
+ assertThat(DeviceQuirks.get(PreviewOneThirdWiderQuirk.class)).isNull();
+ }
+
+ private void assertPreviewShouldBeCroppedBy25Percent() {
+ final PreviewOneThirdWiderQuirk quirk = DeviceQuirks.get(PreviewOneThirdWiderQuirk.class);
assertThat(quirk).isNotNull();
assertThat(quirk.getCropRectScaleX()).isEqualTo(0.75F);
- assertThat(quirk.getCropRectScaleY()).isEqualTo(1F);
}
}
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/TransformFragment.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/TransformFragment.java
index d15867e..5b18e9d 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/TransformFragment.java
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/TransformFragment.java
@@ -161,9 +161,13 @@
// Loop through the y plane and get the sum of the luminance for each tile.
byte[] bytes = new byte[image.getPlanes()[0].getBuffer().remaining()];
image.getPlanes()[0].getBuffer().get(bytes);
+ int tileX;
+ int tileY;
for (int x = 0; x < cropRect.width(); x++) {
for (int y = 0; y < cropRect.height(); y++) {
- tiles[x / tileWidth][y / tileHeight] +=
+ tileX = Math.min(x / tileWidth, TILE_COUNT - 1);
+ tileY = Math.min(y / tileHeight, TILE_COUNT - 1);
+ tiles[tileX][tileY] +=
bytes[(y + cropRect.top) * image.getWidth() + cropRect.left + x] & 0xFF;
}
}