Preview tests

pull/22/head
Mattia Iavarone 8 years ago
parent e77dc26cf6
commit c3b2527e88
  1. 4
      cameraview/src/androidTest/AndroidManifest.xml
  2. 15
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockPreview.java
  3. 137
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/PreviewTest.java
  4. 19
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/SurfaceViewPreviewTest.java
  5. 7
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/TestActivity.java
  6. 19
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/TextureViewPreviewTest.java
  7. 11
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  8. 68
      cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java
  9. 33
      cameraview/src/main/views/com/otaliastudios/cameraview/SurfaceViewPreview.java
  10. 36
      cameraview/src/main/views/com/otaliastudios/cameraview/TextureViewPreview.java

@ -4,4 +4,8 @@
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
<application>
<activity android:name=".TestActivity"/>
</application>
</manifest> </manifest>

@ -2,28 +2,35 @@ package com.otaliastudios.cameraview;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull;
import android.view.Surface; import android.view.Surface;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
public class MockPreview extends Preview { public class MockPreview extends Preview<View, Void> {
public MockPreview(Context context, ViewGroup parent) { MockPreview(Context context, ViewGroup parent) {
super(context, parent); super(context, parent);
} }
@NonNull
@Override
protected View onCreateView(Context context, ViewGroup parent) {
return new View(context);
}
@Override @Override
Surface getSurface() { Surface getSurface() {
return null; return null;
} }
@Override @Override
View getView() { Class<Void> getOutputClass() {
return null; return null;
} }
@Override @Override
Class getOutputClass() { Void getOutput() {
return null; return null;
} }

@ -0,0 +1,137 @@
package com.otaliastudios.cameraview;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
public abstract class PreviewTest {
protected abstract Preview createPreview(Context context, ViewGroup parent);
private Preview preview;
private ViewGroup parent;
private Preview.SurfaceCallback callback;
private final Semaphore lock = new Semaphore(1, true);
@Rule
public ActivityTestRule<TestActivity> rule = new ActivityTestRule<>(TestActivity.class);
@Before
public void setUp() {
Context context = rule.getActivity();
parent = new FrameLayout(context);
preview = createPreview(context, parent);
callback = mock(Preview.SurfaceCallback.class);
preview.setSurfaceCallback(callback);
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
rule.getActivity().setContentView(parent);
}
});
}
@Test
public void testDefaults() {
assertNotNull(preview.getView());
assertEquals(parent.getChildAt(0), preview.getView());
assertNotNull(preview.getOutputClass());
}
@Test
public void testDesiredSize() {
preview.setDesiredSize(160, 90);
assertEquals(160, preview.getDesiredSize().getWidth());
assertEquals(90, preview.getDesiredSize().getHeight());
}
@Test
public void testSurfaceSize() {
preview.onSurfaceAvailable(500, 1000);
assertEquals(500, preview.getSurfaceSize().getWidth());
assertEquals(1000, preview.getSurfaceSize().getHeight());
preview.onSurfaceSizeChanged(50, 100);
assertEquals(50, preview.getSurfaceSize().getWidth());
assertEquals(100, preview.getSurfaceSize().getHeight());
preview.onSurfaceDestroyed();
assertEquals(0, preview.getSurfaceSize().getWidth());
assertEquals(0, preview.getSurfaceSize().getHeight());
}
@Test
public void testCallbacks() {
preview.onSurfaceAvailable(500, 1000);
verify(callback, times(1)).onSurfaceAvailable();
preview.onSurfaceSizeChanged(50, 100);
verify(callback, times(1)).onSurfaceChanged();
}
@Test
public void testCropCenter() throws Exception {
preview.setCropListener(new Preview.CropListener() {
@Override
public void onPreCrop() {
try {
lock.acquire();
} catch (Exception e) {
e.printStackTrace();
};
}
@Override
public void onPostCrop() {
lock.release();
}
});
// If aspect ratio is different, there should be a crop.
Size s = new Size(1000, 1000);
preview.onSurfaceAvailable(s.getWidth(), s.getHeight());
// Not cropping.
preview.setDesiredSize(100, 100); // Wait...
lock.acquire();
assertEquals(AspectRatio.of(100, 100), getScaledAspectRatio());
assertFalse(preview.isCropping());
lock.release();
// Cropping.
preview.setDesiredSize(160, 50); // Wait...
lock.acquire();
assertEquals(AspectRatio.of(160, 50), getScaledAspectRatio());
assertTrue(preview.isCropping());
lock.release();
// Not cropping.
preview.onSurfaceSizeChanged(1600, 500); // Wait...
lock.acquire();
assertEquals(AspectRatio.of(160, 50), getScaledAspectRatio());
assertFalse(preview.isCropping());
lock.release();
}
private AspectRatio getScaledAspectRatio() {
Size size = preview.getSurfaceSize();
int newWidth = (int) ((float) size.getWidth() * preview.getView().getScaleX());
int newHeight = (int) ((float) size.getHeight() * preview.getView().getScaleY());
return AspectRatio.of(newWidth, newHeight);
}
}

@ -0,0 +1,19 @@
package com.otaliastudios.cameraview;
import android.content.Context;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.ViewGroup;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SurfaceViewPreviewTest extends PreviewTest {
@Override
protected Preview createPreview(Context context, ViewGroup parent) {
return new SurfaceViewPreview(context, parent);
}
}

@ -0,0 +1,7 @@
package com.otaliastudios.cameraview;
import android.app.Activity;
public class TestActivity extends Activity {
}

@ -0,0 +1,19 @@
package com.otaliastudios.cameraview;
import android.content.Context;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.ViewGroup;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TextureViewPreviewTest extends PreviewTest {
@Override
protected Preview createPreview(Context context, ViewGroup parent) {
return new TextureViewPreview(context, parent);
}
}

@ -2,6 +2,7 @@ package com.otaliastudios.cameraview;
import android.graphics.PointF; import android.graphics.PointF;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.graphics.YuvImage; import android.graphics.YuvImage;
import android.hardware.Camera; import android.hardware.Camera;
import android.location.Location; import android.location.Location;
@ -110,12 +111,14 @@ class Camera1 extends CameraController {
// These can happen at different times but we want to end up here. // These can happen at different times but we want to end up here.
private void setup() { private void setup() {
try { try {
Object output = mPreview.getOutput();
if (mPreview.getOutputClass() == SurfaceHolder.class) { if (mPreview.getOutputClass() == SurfaceHolder.class) {
mCamera.setPreviewDisplay(mPreview.getSurfaceHolder()); mCamera.setPreviewDisplay((SurfaceHolder) output);
} else { } else {
mCamera.setPreviewTexture(mPreview.getSurfaceTexture()); mCamera.setPreviewTexture((SurfaceTexture) output);
} }
} catch (IOException e) { } catch (IOException e) {
LOG.e("Error while trying to setup Camera1.", e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -450,8 +453,10 @@ class Camera1 extends CameraController {
@Override @Override
boolean shouldFlipSizes() { boolean shouldFlipSizes() {
int offset = computeSensorToDisplayOffset();
LOG.i("shouldFlip:", "mDeviceOrientation=", mDeviceOrientation, "mSensorOffset=", mSensorOffset); LOG.i("shouldFlip:", "mDeviceOrientation=", mDeviceOrientation, "mSensorOffset=", mSensorOffset);
return (mDeviceOrientation + mSensorOffset) % 180 != 0; LOG.i("shouldFlip:", "sensorToDisplay=", offset);
return offset % 180 != 0;
} }
@Override @Override

@ -1,15 +1,14 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import android.content.Context; import android.content.Context;
import android.graphics.SurfaceTexture; import android.support.annotation.NonNull;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
abstract class Preview { abstract class Preview<T extends View, Output> {
private final static CameraLogger LOG = CameraLogger.create(Preview.class.getSimpleName()); protected final static CameraLogger LOG = CameraLogger.create(Preview.class.getSimpleName());
// This is used to notify CameraImpl to recompute its camera Preview size. // This is used to notify CameraImpl to recompute its camera Preview size.
// After that, CameraView will need a new layout pass to adapt to the Preview size. // After that, CameraView will need a new layout pass to adapt to the Preview size.
@ -19,6 +18,7 @@ abstract class Preview {
} }
private SurfaceCallback mSurfaceCallback; private SurfaceCallback mSurfaceCallback;
private T mView;
// As far as I can see, these are the view/surface dimensions. // As far as I can see, these are the view/surface dimensions.
// This live in the 'View' orientation. // This live in the 'View' orientation.
@ -29,19 +29,25 @@ abstract class Preview {
private int mDesiredWidth; private int mDesiredWidth;
private int mDesiredHeight; private int mDesiredHeight;
Preview(Context context, ViewGroup parent) {} Preview(Context context, ViewGroup parent) {
mView = onCreateView(context, parent);
}
@NonNull
protected abstract T onCreateView(Context context, ViewGroup parent);
abstract Surface getSurface(); abstract Surface getSurface();
abstract View getView();
abstract Class getOutputClass(); @NonNull
abstract boolean isReady(); final T getView() {
SurfaceHolder getSurfaceHolder() { return mView;
return null;
}
SurfaceTexture getSurfaceTexture() {
return null;
} }
abstract Class<Output> getOutputClass();
abstract Output getOutput();
abstract boolean isReady();
// As far as I can see, these are the actual preview dimensions, as set in CameraParameters. // As far as I can see, these are the actual preview dimensions, as set in CameraParameters.
// This is called by the CameraImpl. // This is called by the CameraImpl.
@ -53,6 +59,10 @@ abstract class Preview {
crop(); crop();
} }
final Size getDesiredSize() {
return new Size(mDesiredWidth, mDesiredHeight);
}
final Size getSurfaceSize() { final Size getSurfaceSize() {
return new Size(mSurfaceWidth, mSurfaceHeight); return new Size(mSurfaceWidth, mSurfaceHeight);
} }
@ -87,7 +97,6 @@ abstract class Preview {
protected final void onSurfaceDestroyed() { protected final void onSurfaceDestroyed() {
mSurfaceWidth = 0; mSurfaceWidth = 0;
mSurfaceHeight = 0; mSurfaceHeight = 0;
crop();
} }
@ -100,11 +109,16 @@ abstract class Preview {
* However that should be already managed by the framework. * However that should be already managed by the framework.
*/ */
private final void crop() { private final void crop() {
onPreCrop();
getView().post(new Runnable() { getView().post(new Runnable() {
@Override @Override
public void run() { public void run() {
if (mDesiredHeight == 0 || mDesiredWidth == 0) return; if (mDesiredHeight == 0 || mDesiredWidth == 0 ||
if (mSurfaceHeight == 0 || mSurfaceWidth == 0) return; mSurfaceHeight == 0 || mSurfaceWidth == 0) {
onPostCrop();
return;
}
float scaleX = 1f, scaleY = 1f; float scaleX = 1f, scaleY = 1f;
AspectRatio current = AspectRatio.of(mSurfaceWidth, mSurfaceHeight); AspectRatio current = AspectRatio.of(mSurfaceWidth, mSurfaceHeight);
AspectRatio target = AspectRatio.of(mDesiredWidth, mDesiredHeight); AspectRatio target = AspectRatio.of(mDesiredWidth, mDesiredHeight);
@ -119,6 +133,7 @@ abstract class Preview {
getView().setScaleY(scaleY); getView().setScaleY(scaleY);
LOG.i("crop:", "applied scaleX=", scaleX); LOG.i("crop:", "applied scaleX=", scaleX);
LOG.i("crop:", "applied scaleY=", scaleY); LOG.i("crop:", "applied scaleY=", scaleY);
onPostCrop();
} }
}); });
} }
@ -132,4 +147,25 @@ abstract class Preview {
final boolean isCropping() { final boolean isCropping() {
return getView().getScaleX() > 1 || getView().getScaleY() > 1; return getView().getScaleX() > 1 || getView().getScaleY() > 1;
} }
// Utils for testing.
interface CropListener {
void onPreCrop();
void onPostCrop();
}
private CropListener cropListener;
private void onPreCrop() {
if (cropListener != null) cropListener.onPreCrop();
}
private void onPostCrop() {
if (cropListener != null) cropListener.onPostCrop();
}
void setCropListener(CropListener listener) {
cropListener = listener;
}
} }

@ -1,6 +1,7 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
@ -8,20 +9,24 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
// This is not used. // This is not used.
class SurfaceViewPreview extends Preview { class SurfaceViewPreview extends Preview<SurfaceView, SurfaceHolder> {
private final SurfaceView mSurfaceView;
SurfaceViewPreview(Context context, ViewGroup parent) { SurfaceViewPreview(Context context, ViewGroup parent) {
super(context, parent); super(context, parent);
final View view = View.inflate(context, R.layout.surface_view, parent); // MATCH_PARENT }
mSurfaceView = (SurfaceView) view.findViewById(R.id.surface_view);
final SurfaceHolder holder = mSurfaceView.getHolder(); @NonNull
@Override
protected SurfaceView onCreateView(Context context, ViewGroup parent) {
final View root = View.inflate(context, R.layout.surface_view, parent); // MATCH_PARENT
SurfaceView surface = root.findViewById(R.id.surface_view);
final SurfaceHolder holder = surface.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new SurfaceHolder.Callback() { holder.addCallback(new SurfaceHolder.Callback() {
@Override @Override
public void surfaceCreated(SurfaceHolder holder) { public void surfaceCreated(SurfaceHolder holder) {
onSurfaceAvailable(mSurfaceView.getWidth(), mSurfaceView.getHeight()); onSurfaceAvailable(getView().getWidth(), getView().getHeight());
} }
@Override @Override
@ -34,31 +39,27 @@ class SurfaceViewPreview extends Preview {
onSurfaceDestroyed(); onSurfaceDestroyed();
} }
}); });
return surface;
} }
@Override @Override
Surface getSurface() { Surface getSurface() {
return getSurfaceHolder().getSurface(); return getOutput().getSurface();
}
@Override
View getView() {
return mSurfaceView;
} }
@Override @Override
SurfaceHolder getSurfaceHolder() { SurfaceHolder getOutput() {
return mSurfaceView.getHolder(); return getView().getHolder();
} }
@Override @Override
Class getOutputClass() { Class<SurfaceHolder> getOutputClass() {
return SurfaceHolder.class; return SurfaceHolder.class;
} }
@Override @Override
boolean isReady() { boolean isReady() {
return mSurfaceView.getWidth() != 0 && mSurfaceView.getHeight() != 0; return getView().getWidth() != 0 && getView().getHeight() != 0;
} }
} }

@ -3,22 +3,26 @@ package com.otaliastudios.cameraview;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.support.annotation.NonNull;
import android.view.Surface; import android.view.Surface;
import android.view.TextureView; import android.view.TextureView;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@TargetApi(14) class TextureViewPreview extends Preview<TextureView, SurfaceTexture> {
class TextureViewPreview extends Preview {
private final TextureView mTextureView;
private Surface mSurface; private Surface mSurface;
TextureViewPreview(Context context, ViewGroup parent) { TextureViewPreview(Context context, ViewGroup parent) {
super(context, parent); super(context, parent);
final View view = View.inflate(context, R.layout.texture_view, parent); // MATCH_PARENT }
mTextureView = (TextureView) view.findViewById(R.id.texture_view);
mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @NonNull
@Override
protected TextureView onCreateView(Context context, ViewGroup parent) {
final View root = View.inflate(context, R.layout.texture_view, parent); // MATCH_PARENT
TextureView texture = root.findViewById(R.id.texture_view);
texture.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override @Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
@ -40,42 +44,38 @@ class TextureViewPreview extends Preview {
public void onSurfaceTextureUpdated(SurfaceTexture surface) { public void onSurfaceTextureUpdated(SurfaceTexture surface) {
} }
}); });
return texture;
} }
@Override @Override
Surface getSurface() { Surface getSurface() {
if (mSurface == null) { // Check if valid? if (mSurface == null) { // Check if valid?
mSurface = new Surface(getSurfaceTexture()); mSurface = new Surface(getOutput());
} }
return mSurface; return mSurface;
} }
@Override @Override
View getView() { Class<SurfaceTexture> getOutputClass() {
return mTextureView;
}
@Override
Class getOutputClass() {
return SurfaceTexture.class; return SurfaceTexture.class;
} }
@Override @Override
boolean isReady() { boolean isReady() {
return getSurfaceTexture() != null; return getOutput() != null;
} }
@Override @Override
SurfaceTexture getSurfaceTexture() { SurfaceTexture getOutput() {
return mTextureView.getSurfaceTexture(); return getView().getSurfaceTexture();
} }
@TargetApi(15) @TargetApi(15)
@Override @Override
void setDesiredSize(int width, int height) { void setDesiredSize(int width, int height) {
super.setDesiredSize(width, height); super.setDesiredSize(width, height);
if (mTextureView.getSurfaceTexture() != null) { if (getView().getSurfaceTexture() != null) {
mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height); getView().getSurfaceTexture().setDefaultBufferSize(width, height);
} }
} }

Loading…
Cancel
Save