blob: a14f989d4ae09a541848906bcc8ddf44176e200a [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.view;
import static androidx.camera.testing.SurfaceTextureProvider.createSurfaceTextureProvider;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
import android.Manifest;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.util.Size;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.camera.camera2.Camera2Config;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraX;
import androidx.camera.core.CameraXConfig;
import androidx.camera.core.DisplayOrientedMeteringPointFactory;
import androidx.camera.core.MeteringPoint;
import androidx.camera.core.MeteringPointFactory;
import androidx.camera.core.Preview;
import androidx.camera.testing.CameraUtil;
import androidx.camera.testing.CoreAppTestUtil;
import androidx.camera.testing.SurfaceTextureProvider;
import androidx.camera.testing.fakes.FakeActivity;
import androidx.camera.testing.fakes.FakeLifecycleOwner;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.Suppress;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.rule.GrantPermissionRule;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@LargeTest
@Suppress // "Suppress these tests until they can be fixed, see b/143453238"
@RunWith(AndroidJUnit4.class)
public class TextureViewMeteringPointFactoryTest {
public static final float TOLERANCE = 0.000001f;
private static final CameraSelector FRONT_CAM =
new CameraSelector.Builder().requireLensFacing(
CameraSelector.LENS_FACING_FRONT).build();
private static final CameraSelector BACK_CAM =
new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(
Manifest.permission.CAMERA);
@Rule
public ActivityTestRule<FakeActivity> mActivityRule =
new ActivityTestRule<>(FakeActivity.class);
private void setContentView(View view) throws Throwable {
final Activity activity = mActivityRule.getActivity();
mActivityRule.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.setContentView(view);
}
});
}
private static final int WAIT_FRAMECOUNT = 1;
private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
private FakeLifecycleOwner mLifecycle;
private CountDownLatch mLatchForFrameReady;
private Context mContext;
private TextureView mTextureView;
private int mWidth;
private int mHeight;
@Before
public void setUp() throws Throwable {
assumeTrue(CameraUtil.deviceHasCamera());
CoreAppTestUtil.assumeCompatibleDevice();
mContext = ApplicationProvider.getApplicationContext();
CameraXConfig config = Camera2Config.defaultConfig(mContext);
CameraX.initialize(mContext, config);
mLifecycle = new FakeLifecycleOwner();
mLatchForFrameReady = new CountDownLatch(1);
mTextureView = new TextureView(mContext);
setContentView(mTextureView);
}
@After
public void tearDown() throws InterruptedException, ExecutionException {
if (CameraX.isInitialized()) {
mInstrumentation.runOnMainSync(CameraX::unbindAll);
}
CameraX.shutdown().get();
}
@Test
public void backCamera_translatedPoint_SameAsDisplayOriented() throws Throwable {
assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK));
startAndWaitForCameraReady(CameraSelector.LENS_FACING_BACK);
TextureViewMeteringPointFactory factory = new TextureViewMeteringPointFactory(mTextureView);
// Creates the DisplayOrientedMeteringPointFactory with same width / height as TextureView
DisplayOrientedMeteringPointFactory displayFactory =
new DisplayOrientedMeteringPointFactory(mContext, BACK_CAM,
mTextureView.getWidth(), mTextureView.getHeight());
// Uses DisplayOrientedMeteringPointFactory to verify if coordinates are correct.
// DisplayOrientedMeteringPointFactory has the same width / height as the TextureView and
// TextureViewMeteringPointFactory
assertFactoryOutputSamePoints(factory, displayFactory);
}
@Test
public void frontCamera_translatedPoint_SameAsDisplayOriented() throws Throwable {
assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_FRONT));
startAndWaitForCameraReady(CameraSelector.LENS_FACING_FRONT);
TextureViewMeteringPointFactory factory = new TextureViewMeteringPointFactory(mTextureView);
// Creates the DisplayOrientedMeteringPointFactory with same width / height as TextureView
DisplayOrientedMeteringPointFactory displayFactory =
new DisplayOrientedMeteringPointFactory(mContext, FRONT_CAM,
mWidth, mHeight);
// Uses DisplayOrientedMeteringPointFactory to verify if coordinates are correct.
// DisplayOrientedMeteringPointFactory has the same width / height as the TextureView and
// TextureViewMeteringPointFactory
assertFactoryOutputSamePoints(factory, displayFactory);
}
@Test
public void xy_OutOfRange() throws Throwable {
startAndWaitForCameraReady(CameraSelector.LENS_FACING_BACK);
TextureViewMeteringPointFactory factory = new TextureViewMeteringPointFactory(mTextureView);
// if x or y is not in range [0.. width or height], the output will not be valid normalized
// point ( value not in [0..1])
MeteringPoint pt1 = factory.createPoint(-1, 0);
assertThat(isValid(pt1)).isFalse();
pt1 = factory.createPoint(0, -1);
assertThat(isValid(pt1)).isFalse();
pt1 = factory.createPoint(mWidth + 1, 0);
assertThat(isValid(pt1)).isFalse();
pt1 = factory.createPoint(0, mHeight + 1);
assertThat(isValid(pt1)).isFalse();
pt1 = factory.createPoint(-1, mHeight + 1);
assertThat(isValid(pt1)).isFalse();
pt1 = factory.createPoint(mWidth + 1, mHeight + 1);
assertThat(isValid(pt1)).isFalse();
pt1 = factory.createPoint(-1, -1);
assertThat(isValid(pt1)).isFalse();
}
private boolean isValid(MeteringPoint pt) {
boolean xValid = pt.getX() >= 0 && pt.getX() <= 1f;
boolean yValid = pt.getY() >= 0 && pt.getY() <= 1f;
return xValid && yValid;
}
private void startAndWaitForCameraReady(@CameraSelector.LensFacing int lensFacing)
throws InterruptedException {
Preview preview = new Preview.Builder().build();
mInstrumentation.runOnMainSync(() -> {
preview.setPreviewSurfaceProvider(createSurfaceTextureProvider(
new SurfaceTextureProvider.SurfaceTextureCallback() {
@Override
public void onSurfaceTextureReady(@NonNull SurfaceTexture surfaceTexture,
@NonNull Size resolution) {
ViewGroup viewGroup = (ViewGroup) mTextureView.getParent();
viewGroup.removeView(mTextureView);
viewGroup.addView(mTextureView);
mTextureView.setSurfaceTexture(surfaceTexture);
surfaceTexture.setOnFrameAvailableListener(
new SurfaceTexture.OnFrameAvailableListener() {
int mFrameCount = 0;
@Override
public void onFrameAvailable(
SurfaceTexture surfaceTexture) {
mFrameCount++;
if (mFrameCount == WAIT_FRAMECOUNT) {
mLatchForFrameReady.countDown();
}
}
});
}
@Override
public void onSafeToRelease(@NonNull SurfaceTexture surfaceTexture) {
surfaceTexture.release();
}
}));
CameraSelector cameraSelector =
new CameraSelector.Builder().requireLensFacing(lensFacing).build();
// SurfaceTexture#getTransformMatrix is initialized properly when camera starts
// to output.
CameraX.bindToLifecycle(mLifecycle, cameraSelector, preview);
mLifecycle.startAndResume();
});
mLatchForFrameReady.await(3, TimeUnit.SECONDS);
mWidth = mTextureView.getWidth();
mHeight = mTextureView.getHeight();
}
private void assertFactoryOutputSamePoints(MeteringPointFactory factory1,
MeteringPointFactory factory2) {
MeteringPoint point1;
MeteringPoint point2;
// left-top corner
point1 = factory1.createPoint(0f, 0f);
point2 = factory2.createPoint(0f, 0f);
assertThat(isValid(point1)).isTrue();
Assert.assertEquals(point1.getX(), point2.getX(),
TOLERANCE);
Assert.assertEquals(point1.getY(), point2.getY(),
TOLERANCE);
// left-bottom corner
point1 = factory1.createPoint(0f, mHeight);
point2 = factory2.createPoint(0f, mHeight);
assertThat(isValid(point1)).isTrue();
Assert.assertEquals(point1.getX(), point2.getX(),
TOLERANCE);
Assert.assertEquals(point1.getY(), point2.getY(),
TOLERANCE);
// right-top corner
point1 = factory1.createPoint(mWidth, 0f);
point2 = factory2.createPoint(mWidth, 0f);
assertThat(isValid(point1)).isTrue();
Assert.assertEquals(point1.getX(), point2.getX(),
TOLERANCE);
Assert.assertEquals(point1.getY(), point2.getY(),
TOLERANCE);
// right-bottom corner
point1 = factory1.createPoint(mWidth, mHeight);
point2 = factory2.createPoint(mWidth, mHeight);
assertThat(isValid(point1)).isTrue();
Assert.assertEquals(point1.getX(), point2.getX(),
TOLERANCE);
Assert.assertEquals(point1.getY(), point2.getY(),
TOLERANCE);
// some random point
point1 = factory1.createPoint(100, 120);
point2 = factory2.createPoint(100, 120);
assertThat(isValid(point1)).isTrue();
Assert.assertEquals(point1.getX(), point2.getX(),
TOLERANCE);
Assert.assertEquals(point1.getY(), point2.getY(),
TOLERANCE);
}
}