Refactor some classes, fix hardware acceleration bug, disable cropping for SurfaceView

pull/54/head
Mattia Iavarone 8 years ago
parent 83ec114726
commit fa65c5247e
  1. 83
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraCallbacksTest.java
  2. 12
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
  3. 6
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java
  4. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java
  5. 14
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraPreview.java
  6. 14
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/PreviewTest.java
  7. 6
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/SurfaceCameraPreviewTest.java
  8. 6
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/TextureCameraPreviewTest.java
  9. 9
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  10. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java
  11. 14
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
  12. 69
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  13. 14
      cameraview/src/main/res/layout/surface_view.xml
  14. 30
      cameraview/src/main/views/com/otaliastudios/cameraview/CameraPreview.java
  15. 107
      cameraview/src/main/views/com/otaliastudios/cameraview/SurfaceCameraPreview.java
  16. 74
      cameraview/src/main/views/com/otaliastudios/cameraview/SurfaceViewPreview.java
  17. 4
      cameraview/src/main/views/com/otaliastudios/cameraview/TextureCameraPreview.java
  18. 1
      demo/src/main/AndroidManifest.xml
  19. 14
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  20. 8
      demo/src/main/java/com/otaliastudios/cameraview/demo/ControlView.java
  21. 4
      demo/src/main/res/layout/activity_camera.xml

@ -4,10 +4,7 @@ package com.otaliastudios.cameraview;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.PointF; import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.support.test.filters.MediumTest; import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -18,9 +15,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.mockito.stubbing.Stubber;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -33,19 +28,18 @@ import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@MediumTest @MediumTest
public class CameraCallbacksTest extends BaseTest { public class CameraCallbacksTest extends BaseTest {
private CameraView camera; private CameraView camera;
private CameraView.CameraCallbacks callbacks;
private CameraListener listener; private CameraListener listener;
private MockCameraController mockController; private MockCameraController mockController;
private MockPreview mockPreview; private MockCameraPreview mockPreview;
private Task<Boolean> task; private Task<Boolean> task;
@ -58,14 +52,14 @@ public class CameraCallbacksTest extends BaseTest {
listener = mock(CameraListener.class); listener = mock(CameraListener.class);
camera = new CameraView(context) { camera = new CameraView(context) {
@Override @Override
protected CameraController instantiateCameraController(CameraCallbacks callbacks, Preview preview) { protected CameraController instantiateCameraController(CameraCallbacks callbacks) {
mockController = new MockCameraController(callbacks, preview); mockController = new MockCameraController(callbacks);
return mockController; return mockController;
} }
@Override @Override
protected Preview instantiatePreview(Context context, ViewGroup container) { protected CameraPreview instantiatePreview(Context context, ViewGroup container) {
mockPreview = new MockPreview(context, container); mockPreview = new MockCameraPreview(context, container);
return mockPreview; return mockPreview;
} }
@ -74,8 +68,8 @@ public class CameraCallbacksTest extends BaseTest {
return true; return true;
} }
}; };
camera.instantiatePreview();
camera.addCameraListener(listener); camera.addCameraListener(listener);
callbacks = camera.mCameraCallbacks;
task = new Task<>(); task = new Task<>();
task.listen(); task.listen();
} }
@ -87,26 +81,25 @@ public class CameraCallbacksTest extends BaseTest {
camera = null; camera = null;
mockController = null; mockController = null;
mockPreview = null; mockPreview = null;
callbacks = null;
listener = null; listener = null;
} }
// Completes our task. // Completes our task.
private Answer completeTask() { private Stubber completeTask() {
return new Answer() { return doAnswer(new Answer() {
@Override @Override
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {
task.end(true); task.end(true);
return null; return null;
} }
}; });
} }
@Test @Test
public void testDontDispatchIfRemoved() { public void testDontDispatchIfRemoved() {
camera.removeCameraListener(listener); camera.removeCameraListener(listener);
doAnswer(completeTask()).when(listener).onCameraOpened(null); completeTask().when(listener).onCameraOpened(null);
callbacks.dispatchOnCameraOpened(null); camera.mCameraCallbacks.dispatchOnCameraOpened(null);
assertNull(task.await(200)); assertNull(task.await(200));
verify(listener, never()).onCameraOpened(null); verify(listener, never()).onCameraOpened(null);
@ -115,8 +108,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testDontDispatchIfCleared() { public void testDontDispatchIfCleared() {
camera.clearCameraListeners(); camera.clearCameraListeners();
doAnswer(completeTask()).when(listener).onCameraOpened(null); completeTask().when(listener).onCameraOpened(null);
callbacks.dispatchOnCameraOpened(null); camera.mCameraCallbacks.dispatchOnCameraOpened(null);
assertNull(task.await(200)); assertNull(task.await(200));
verify(listener, never()).onCameraOpened(null); verify(listener, never()).onCameraOpened(null);
@ -124,8 +117,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testDispatchOnCameraOpened() { public void testDispatchOnCameraOpened() {
doAnswer(completeTask()).when(listener).onCameraOpened(null); completeTask().when(listener).onCameraOpened(null);
callbacks.dispatchOnCameraOpened(null); camera.mCameraCallbacks.dispatchOnCameraOpened(null);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onCameraOpened(null); verify(listener, times(1)).onCameraOpened(null);
@ -133,8 +126,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testDispatchOnCameraClosed() { public void testDispatchOnCameraClosed() {
doAnswer(completeTask()).when(listener).onCameraClosed(); completeTask().when(listener).onCameraClosed();
callbacks.dispatchOnCameraClosed(); camera.mCameraCallbacks.dispatchOnCameraClosed();
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onCameraClosed(); verify(listener, times(1)).onCameraClosed();
@ -142,8 +135,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testDispatchOnVideoTaken() { public void testDispatchOnVideoTaken() {
doAnswer(completeTask()).when(listener).onVideoTaken(null); completeTask().when(listener).onVideoTaken(null);
callbacks.dispatchOnVideoTaken(null); camera.mCameraCallbacks.dispatchOnVideoTaken(null);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onVideoTaken(null); verify(listener, times(1)).onVideoTaken(null);
@ -151,8 +144,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testDispatchOnZoomChanged() { public void testDispatchOnZoomChanged() {
doAnswer(completeTask()).when(listener).onZoomChanged(anyFloat(), any(float[].class), any(PointF[].class)); completeTask().when(listener).onZoomChanged(anyFloat(), any(float[].class), any(PointF[].class));
callbacks.dispatchOnZoomChanged(0f, null); camera.mCameraCallbacks.dispatchOnZoomChanged(0f, null);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onZoomChanged(anyFloat(), any(float[].class), any(PointF[].class)); verify(listener, times(1)).onZoomChanged(anyFloat(), any(float[].class), any(PointF[].class));
@ -160,8 +153,8 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testDispatchOnExposureCorrectionChanged() { public void testDispatchOnExposureCorrectionChanged() {
doAnswer(completeTask()).when(listener).onExposureCorrectionChanged(0f, null, null); completeTask().when(listener).onExposureCorrectionChanged(0f, null, null);
callbacks.dispatchOnExposureCorrectionChanged(0f, null, null); camera.mCameraCallbacks.dispatchOnExposureCorrectionChanged(0f, null, null);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onExposureCorrectionChanged(0f, null, null); verify(listener, times(1)).onExposureCorrectionChanged(0f, null, null);
@ -174,8 +167,8 @@ public class CameraCallbacksTest extends BaseTest {
camera.mapGesture(Gesture.TAP, GestureAction.FOCUS_WITH_MARKER); camera.mapGesture(Gesture.TAP, GestureAction.FOCUS_WITH_MARKER);
PointF point = new PointF(); PointF point = new PointF();
doAnswer(completeTask()).when(listener).onFocusStart(point); completeTask().when(listener).onFocusStart(point);
callbacks.dispatchOnFocusStart(Gesture.TAP, point); camera.mCameraCallbacks.dispatchOnFocusStart(Gesture.TAP, point);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onFocusStart(point); verify(listener, times(1)).onFocusStart(point);
@ -190,8 +183,8 @@ public class CameraCallbacksTest extends BaseTest {
PointF point = new PointF(); PointF point = new PointF();
boolean success = true; boolean success = true;
doAnswer(completeTask()).when(listener).onFocusEnd(success, point); completeTask().when(listener).onFocusEnd(success, point);
callbacks.dispatchOnFocusEnd(Gesture.TAP, success, point); camera.mCameraCallbacks.dispatchOnFocusEnd(Gesture.TAP, success, point);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onFocusEnd(success, point); verify(listener, times(1)).onFocusEnd(success, point);
@ -200,31 +193,31 @@ public class CameraCallbacksTest extends BaseTest {
@Test @Test
public void testOrientationCallbacks_deviceOnly() { public void testOrientationCallbacks_deviceOnly() {
doAnswer(completeTask()).when(listener).onOrientationChanged(anyInt()); completeTask().when(listener).onOrientationChanged(anyInt());
// Assert not called. Both methods must be called. // Assert not called. Both methods must be called.
callbacks.onDeviceOrientationChanged(0); camera.mCameraCallbacks.onDeviceOrientationChanged(0);
assertNull(task.await(200)); assertNull(task.await(200));
verify(listener, never()).onOrientationChanged(anyInt()); verify(listener, never()).onOrientationChanged(anyInt());
} }
@Test @Test
public void testOrientationCallbacks_displayOnly() { public void testOrientationCallbacks_displayOnly() {
doAnswer(completeTask()).when(listener).onOrientationChanged(anyInt()); completeTask().when(listener).onOrientationChanged(anyInt());
// Assert not called. Both methods must be called. // Assert not called. Both methods must be called.
callbacks.onDisplayOffsetChanged(0); camera.mCameraCallbacks.onDisplayOffsetChanged(0);
assertNull(task.await(200)); assertNull(task.await(200));
verify(listener, never()).onOrientationChanged(anyInt()); verify(listener, never()).onOrientationChanged(anyInt());
} }
@Test @Test
public void testOrientationCallbacks_both() { public void testOrientationCallbacks_both() {
doAnswer(completeTask()).when(listener).onOrientationChanged(anyInt()); completeTask().when(listener).onOrientationChanged(anyInt());
// Assert called. // Assert called.
callbacks.onDisplayOffsetChanged(0); camera.mCameraCallbacks.onDisplayOffsetChanged(0);
callbacks.onDeviceOrientationChanged(90); camera.mCameraCallbacks.onDeviceOrientationChanged(90);
assertNotNull(task.await(200)); assertNotNull(task.await(200));
verify(listener, times(1)).onOrientationChanged(anyInt()); verify(listener, times(1)).onOrientationChanged(anyInt());
} }
@ -290,9 +283,9 @@ public class CameraCallbacksTest extends BaseTest {
// Create fake JPEG array and trigger the process. // Create fake JPEG array and trigger the process.
if (jpeg) { if (jpeg) {
callbacks.processImage(mockJpeg(imageDim[0], imageDim[1]), true, false); camera.mCameraCallbacks.processImage(mockJpeg(imageDim[0], imageDim[1]), true, false);
} else { } else {
callbacks.processSnapshot(mockYuv(imageDim[0], imageDim[1]), true, false); camera.mCameraCallbacks.processSnapshot(mockYuv(imageDim[0], imageDim[1]), true, false);
} }
// Wait for result and get out dimensions. // Wait for result and get out dimensions.

@ -26,7 +26,7 @@ public class CameraViewTest extends BaseTest {
private CameraView cameraView; private CameraView cameraView;
private MockCameraController mockController; private MockCameraController mockController;
private Preview mockPreview; private CameraPreview mockPreview;
private boolean hasPermissions; private boolean hasPermissions;
@Before @Before
@ -37,14 +37,14 @@ public class CameraViewTest extends BaseTest {
Context context = context(); Context context = context();
cameraView = new CameraView(context) { cameraView = new CameraView(context) {
@Override @Override
protected CameraController instantiateCameraController(CameraCallbacks callbacks, Preview preview) { protected CameraController instantiateCameraController(CameraCallbacks callbacks) {
mockController = new MockCameraController(callbacks, preview); mockController = new MockCameraController(callbacks);
return mockController; return mockController;
} }
@Override @Override
protected Preview instantiatePreview(Context context, ViewGroup container) { protected CameraPreview instantiatePreview(Context context, ViewGroup container) {
mockPreview = new MockPreview(context, container); mockPreview = new MockCameraPreview(context, container);
return mockPreview; return mockPreview;
} }
@ -53,6 +53,8 @@ public class CameraViewTest extends BaseTest {
return hasPermissions; return hasPermissions;
} }
}; };
// Instantiate preview now.
cameraView.instantiatePreview();
} }
}); });
} }

@ -33,7 +33,7 @@ import static org.junit.Assert.*;
*/ */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@MediumTest @MediumTest
@Ignore // @Ignore
public class IntegrationTest extends BaseTest { public class IntegrationTest extends BaseTest {
@Rule @Rule
@ -56,8 +56,8 @@ public class IntegrationTest extends BaseTest {
public void run() { public void run() {
camera = new CameraView(rule.getActivity()) { camera = new CameraView(rule.getActivity()) {
@Override @Override
protected CameraController instantiateCameraController(CameraCallbacks callbacks, Preview preview) { protected CameraController instantiateCameraController(CameraCallbacks callbacks) {
controller = new Camera1(callbacks, preview); controller = new Camera1(callbacks);
return controller; return controller;
} }
}; };

@ -15,8 +15,8 @@ public class MockCameraController extends CameraController {
boolean mZoomChanged; boolean mZoomChanged;
boolean mExposureCorrectionChanged; boolean mExposureCorrectionChanged;
MockCameraController(CameraView.CameraCallbacks callback, Preview preview) { MockCameraController(CameraView.CameraCallbacks callback) {
super(callback, preview); super(callback);
} }
void setMockCameraOptions(CameraOptions options) { void setMockCameraOptions(CameraOptions options) {

@ -7,15 +7,21 @@ 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<View, Void> { public class MockCameraPreview extends CameraPreview<View, Void> {
MockPreview(Context context, ViewGroup parent) { MockCameraPreview(Context context, ViewGroup parent) {
super(context, parent, null); super(context, parent, null);
} }
private boolean mCropping = false;
public void setIsCropping(boolean crop) { public void setIsCropping(boolean crop) {
getView().setScaleX(crop ? 2 : 1); mCropping = crop;
getView().setScaleY(crop ? 2 : 1); }
@Override
boolean isCropping() {
return mCropping;
} }
@NonNull @NonNull

@ -19,14 +19,14 @@ import static org.mockito.Mockito.*;
public abstract class PreviewTest extends BaseTest { public abstract class PreviewTest extends BaseTest {
protected abstract Preview createPreview(Context context, ViewGroup parent, Preview.SurfaceCallback callback); protected abstract CameraPreview createPreview(Context context, ViewGroup parent, CameraPreview.SurfaceCallback callback);
@Rule @Rule
public ActivityTestRule<TestActivity> rule = new ActivityTestRule<>(TestActivity.class); public ActivityTestRule<TestActivity> rule = new ActivityTestRule<>(TestActivity.class);
protected Preview preview; protected CameraPreview preview;
protected Size surfaceSize; protected Size surfaceSize;
private Preview.SurfaceCallback callback; private CameraPreview.SurfaceCallback callback;
private Task<Boolean> availability; private Task<Boolean> availability;
@Before @Before
@ -40,7 +40,7 @@ public abstract class PreviewTest extends BaseTest {
TestActivity a = rule.getActivity(); TestActivity a = rule.getActivity();
surfaceSize = a.getContentSize(); surfaceSize = a.getContentSize();
callback = mock(Preview.SurfaceCallback.class); callback = mock(CameraPreview.SurfaceCallback.class);
doAnswer(new Answer() { doAnswer(new Answer() {
@Override @Override
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {
@ -121,8 +121,10 @@ public abstract class PreviewTest extends BaseTest {
// If we apply a different aspect ratio, there should be cropping. // If we apply a different aspect ratio, there should be cropping.
float desired = view * 1.2f; float desired = view * 1.2f;
setDesiredAspectRatio(desired); if (preview.supportsCropping()) {
assertTrue(preview.isCropping()); setDesiredAspectRatio(desired);
assertTrue(preview.isCropping());
}
// Since desired is 'desired', let's fake a new view size that is consistent with it. // Since desired is 'desired', let's fake a new view size that is consistent with it.
// Ensure crop is not happening anymore. // Ensure crop is not happening anymore.

@ -10,10 +10,10 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @SmallTest
public class SurfaceViewPreviewTest extends PreviewTest { public class SurfaceCameraPreviewTest extends PreviewTest {
@Override @Override
protected Preview createPreview(Context context, ViewGroup parent, Preview.SurfaceCallback callback) { protected CameraPreview createPreview(Context context, ViewGroup parent, CameraPreview.SurfaceCallback callback) {
return new SurfaceViewPreview(context, parent, callback); return new SurfaceCameraPreview(context, parent, callback);
} }
} }

@ -10,11 +10,11 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @SmallTest
public class TextureViewPreviewTest extends PreviewTest { public class TextureCameraPreviewTest extends PreviewTest {
@Override @Override
protected Preview createPreview(Context context, ViewGroup parent, Preview.SurfaceCallback callback) { protected CameraPreview createPreview(Context context, ViewGroup parent, CameraPreview.SurfaceCallback callback) {
return new TextureViewPreview(context, parent, callback); return new TextureCameraPreview(context, parent, callback);
} }
@Override @Override

@ -15,7 +15,6 @@ import android.support.annotation.WorkerThread;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -57,8 +56,8 @@ class Camera1 extends CameraController {
private final Object mLock = new Object(); private final Object mLock = new Object();
Camera1(CameraView.CameraCallbacks callback, final Preview preview) { Camera1(CameraView.CameraCallbacks callback) {
super(callback, preview); super(callback);
} }
/** /**
@ -118,7 +117,7 @@ class Camera1 extends CameraController {
} }
private boolean shouldSetup() { private boolean shouldSetup() {
return isCameraAvailable() && mPreview.isReady() && !mIsSetup; return isCameraAvailable() && mPreview != null && mPreview.isReady() && !mIsSetup;
} }
// The act of binding an "open" camera to a "ready" preview. // The act of binding an "open" camera to a "ready" preview.
@ -557,7 +556,7 @@ class Camera1 extends CameraController {
* This is called either on cameraView.start(), or when the underlying surface changes. * This is called either on cameraView.start(), or when the underlying surface changes.
* It is possible that in the first call the preview surface has not already computed its * It is possible that in the first call the preview surface has not already computed its
* dimensions. * dimensions.
* But when it does, the {@link Preview.SurfaceCallback} should be called, * But when it does, the {@link CameraPreview.SurfaceCallback} should be called,
* and this should be refreshed. * and this should be refreshed.
*/ */
private Size computeCaptureSize() { private Size computeCaptureSize() {

@ -22,8 +22,8 @@ import java.util.List;
@TargetApi(21) @TargetApi(21)
class Camera2 extends CameraController { class Camera2 extends CameraController {
public Camera2(CameraView.CameraCallbacks callback, Preview preview) { public Camera2(CameraView.CameraCallbacks callback) {
super(callback, preview); super(callback);
} }
@Override @Override

@ -2,14 +2,13 @@ package com.otaliastudios.cameraview;
import android.graphics.PointF; import android.graphics.PointF;
import android.location.Location; import android.location.Location;
import android.os.Handler;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread; import android.support.annotation.WorkerThread;
import java.io.File; import java.io.File;
abstract class CameraController implements Preview.SurfaceCallback { abstract class CameraController implements CameraPreview.SurfaceCallback {
private static final String TAG = CameraController.class.getSimpleName(); private static final String TAG = CameraController.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
@ -20,7 +19,7 @@ abstract class CameraController implements Preview.SurfaceCallback {
static final int STATE_STARTED = 2; // Camera is available and we can set parameters. static final int STATE_STARTED = 2; // Camera is available and we can set parameters.
protected final CameraView.CameraCallbacks mCameraCallbacks; protected final CameraView.CameraCallbacks mCameraCallbacks;
protected final Preview mPreview; protected CameraPreview mPreview;
protected Facing mFacing; protected Facing mFacing;
protected Flash mFlash; protected Flash mFlash;
@ -43,10 +42,8 @@ abstract class CameraController implements Preview.SurfaceCallback {
protected WorkerHandler mHandler; protected WorkerHandler mHandler;
CameraController(CameraView.CameraCallbacks callback, Preview preview) { CameraController(CameraView.CameraCallbacks callback) {
mCameraCallbacks = callback; mCameraCallbacks = callback;
mPreview = preview;
mPreview.setSurfaceCallback(this);
mHandler = WorkerHandler.get("CameraViewController"); mHandler = WorkerHandler.get("CameraViewController");
mHandler.getThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { mHandler.getThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override @Override
@ -67,6 +64,11 @@ abstract class CameraController implements Preview.SurfaceCallback {
}); });
} }
void setPreview(CameraPreview cameraPreview) {
mPreview = cameraPreview;
mPreview.setSurfaceCallback(this);
}
//region Start&Stop //region Start&Stop
private String ss() { private String ss() {

@ -58,9 +58,9 @@ public class CameraView extends FrameLayout {
// Components // Components
/* for tests */ CameraCallbacks mCameraCallbacks; /* for tests */ CameraCallbacks mCameraCallbacks;
private CameraPreview mCameraPreview;
private OrientationHelper mOrientationHelper; private OrientationHelper mOrientationHelper;
private CameraController mCameraController; private CameraController mCameraController;
private Preview mPreviewImpl;
private ArrayList<CameraListener> mListeners = new ArrayList<>(2); private ArrayList<CameraListener> mListeners = new ArrayList<>(2);
private MediaActionSound mSound; private MediaActionSound mSound;
@ -89,6 +89,7 @@ public class CameraView extends FrameLayout {
@SuppressWarnings("WrongConstant") @SuppressWarnings("WrongConstant")
private void init(@NonNull Context context, @Nullable AttributeSet attrs) { private void init(@NonNull Context context, @Nullable AttributeSet attrs) {
setWillNotDraw(false);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CameraView, 0, 0); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CameraView, 0, 0);
// Self managed // Self managed
int jpegQuality = a.getInteger(R.styleable.CameraView_cameraJpegQuality, DEFAULT_JPEG_QUALITY); int jpegQuality = a.getInteger(R.styleable.CameraView_cameraJpegQuality, DEFAULT_JPEG_QUALITY);
@ -115,8 +116,7 @@ public class CameraView extends FrameLayout {
// Components // Components
mCameraCallbacks = new Callbacks(); mCameraCallbacks = new Callbacks();
mPreviewImpl = instantiatePreview(context, this); mCameraController = instantiateCameraController(mCameraCallbacks);
mCameraController = instantiateCameraController(mCameraCallbacks, mPreviewImpl);
mUiHandler = new Handler(Looper.getMainLooper()); mUiHandler = new Handler(Looper.getMainLooper());
mWorkerHandler = WorkerHandler.get("CameraViewWorker"); mWorkerHandler = WorkerHandler.get("CameraViewWorker");
@ -158,24 +158,31 @@ public class CameraView extends FrameLayout {
} }
} }
protected CameraController instantiateCameraController(CameraCallbacks callbacks) {
return new Camera1(callbacks);
}
protected Preview instantiatePreview(Context context, ViewGroup container) { protected CameraPreview instantiatePreview(Context context, ViewGroup container) {
// TextureView is not supported without hardware acceleration. // TextureView is not supported without hardware acceleration.
LOG.i("preview:", "isHardwareAccelerated:", isHardwareAccelerated()); LOG.w("preview:", "isHardwareAccelerated:", isHardwareAccelerated());
return isHardwareAccelerated() ? return isHardwareAccelerated() ?
new TextureViewPreview(context, container, null) : new TextureCameraPreview(context, container, null) :
new SurfaceViewPreview(context, container, null); new SurfaceCameraPreview(context, container, null);
} }
/* for tests */ void instantiatePreview() {
protected CameraController instantiateCameraController(CameraCallbacks callbacks, Preview preview) { mCameraPreview = instantiatePreview(getContext(), this);
return new Camera1(callbacks, preview); mCameraController.setPreview(mCameraPreview);
} }
@Override @Override
protected void onAttachedToWindow() { protected void onAttachedToWindow() {
super.onAttachedToWindow(); super.onAttachedToWindow();
if (mCameraPreview == null) {
// isHardwareAccelerated will return the real value only after we are
// attached. That's why we instantiate the preview here.
instantiatePreview();
}
if (!isInEditMode()) { if (!isInEditMode()) {
mOrientationHelper.enable(getContext()); mOrientationHelper.enable(getContext());
} }
@ -212,7 +219,7 @@ public class CameraView extends FrameLayout {
* aspect ratio. * aspect ratio.
* *
* When both dimensions are MATCH_PARENT, CameraView will fill its * When both dimensions are MATCH_PARENT, CameraView will fill its
* parent no matter the preview. Thanks to what happens in {@link Preview}, this acts like * parent no matter the preview. Thanks to what happens in {@link CameraPreview}, this acts like
* a CENTER CROP scale type. * a CENTER CROP scale type.
* *
* When both dimensions are WRAP_CONTENT, CameraView will take the biggest dimensions that * When both dimensions are WRAP_CONTENT, CameraView will take the biggest dimensions that
@ -236,18 +243,24 @@ public class CameraView extends FrameLayout {
final float previewWidth = flip ? previewSize.getHeight() : previewSize.getWidth(); final float previewWidth = flip ? previewSize.getHeight() : previewSize.getWidth();
final float previewHeight = flip ? previewSize.getWidth() : previewSize.getHeight(); final float previewHeight = flip ? previewSize.getWidth() : previewSize.getHeight();
// If MATCH_PARENT is interpreted as AT_MOST, transform to EXACTLY // Pre-process specs
// to be consistent with our semantics (and our docs).
final ViewGroup.LayoutParams lp = getLayoutParams(); final ViewGroup.LayoutParams lp = getLayoutParams();
if (widthMode == AT_MOST && lp.width == MATCH_PARENT) widthMode = EXACTLY; if (!mCameraPreview.supportsCropping()) {
if (heightMode == AT_MOST && lp.height == MATCH_PARENT) heightMode = EXACTLY; // We can't allow EXACTLY constraints in this case.
LOG.i("onMeasure:", "requested dimensions are", if (widthMode == EXACTLY) widthMode = AT_MOST;
"(" + widthValue + "[" + ms(widthMode) + "]x" + if (heightMode == EXACTLY) heightMode = AT_MOST;
} else {
// If MATCH_PARENT is interpreted as AT_MOST, transform to EXACTLY
// to be consistent with our semantics (and our docs).
if (widthMode == AT_MOST && lp.width == MATCH_PARENT) widthMode = EXACTLY;
if (heightMode == AT_MOST && lp.height == MATCH_PARENT) heightMode = EXACTLY;
}
LOG.i("onMeasure:", "requested dimensions are", "(" + widthValue + "[" + ms(widthMode) + "]x" +
heightValue + "[" + ms(heightMode) + "])"); heightValue + "[" + ms(heightMode) + "])");
LOG.i("onMeasure:", "previewSize is", "(" + previewWidth + "x" + previewHeight + ")"); LOG.i("onMeasure:", "previewSize is", "(" + previewWidth + "x" + previewHeight + ")");
// (1) If we have fixed dimensions (either 300dp or MATCH_PARENT), there's nothing we should do,
// If we have fixed dimensions (either 300dp or MATCH_PARENT), there's nothing we should do,
// other than respect it. The preview will eventually be cropped at the sides (by PreviewImpl scaling) // other than respect it. The preview will eventually be cropped at the sides (by PreviewImpl scaling)
// except the case in which these fixed dimensions manage to fit exactly the preview aspect ratio. // except the case in which these fixed dimensions manage to fit exactly the preview aspect ratio.
if (widthMode == EXACTLY && heightMode == EXACTLY) { if (widthMode == EXACTLY && heightMode == EXACTLY) {
@ -257,8 +270,8 @@ public class CameraView extends FrameLayout {
return; return;
} }
// If both dimensions are free, with no limits, then our size will be exactly the // (2) If both dimensions are free, with no limits, then our size will be exactly the
// preview size. This can happen rarely, for example in scrollable containers. // preview size. This can happen rarely, for example in 2d scrollable containers.
if (widthMode == UNSPECIFIED && heightMode == UNSPECIFIED) { if (widthMode == UNSPECIFIED && heightMode == UNSPECIFIED) {
LOG.i("onMeasure:", "both are completely free.", LOG.i("onMeasure:", "both are completely free.",
"We respect that and extend to the whole preview size.", "We respect that and extend to the whole preview size.",
@ -272,7 +285,7 @@ public class CameraView extends FrameLayout {
// It's sure now that at least one dimension can be determined (either because EXACTLY or AT_MOST). // It's sure now that at least one dimension can be determined (either because EXACTLY or AT_MOST).
// This starts to seem a pleasant situation. // This starts to seem a pleasant situation.
// If one of the dimension is completely free (e.g. in a scrollable container), // (3) If one of the dimension is completely free (e.g. in a scrollable container),
// take the other and fit the ratio. // take the other and fit the ratio.
// One of the two might be AT_MOST, but we use the value anyway. // One of the two might be AT_MOST, but we use the value anyway.
float ratio = previewHeight / previewWidth; float ratio = previewHeight / previewWidth;
@ -293,7 +306,7 @@ public class CameraView extends FrameLayout {
return; return;
} }
// At this point both dimensions are either AT_MOST-AT_MOST, EXACTLY-AT_MOST or AT_MOST-EXACTLY. // (4) At this point both dimensions are either AT_MOST-AT_MOST, EXACTLY-AT_MOST or AT_MOST-EXACTLY.
// Let's manage this sanely. If only one is EXACTLY, we can TRY to fit the aspect ratio, // Let's manage this sanely. If only one is EXACTLY, we can TRY to fit the aspect ratio,
// but it is not guaranteed to succeed. It depends on the AT_MOST value of the other dimensions. // but it is not guaranteed to succeed. It depends on the AT_MOST value of the other dimensions.
if (widthMode == EXACTLY || heightMode == EXACTLY) { if (widthMode == EXACTLY || heightMode == EXACTLY) {
@ -314,8 +327,8 @@ public class CameraView extends FrameLayout {
return; return;
} }
// Last case, AT_MOST and AT_MOST. Here we can SURELY fit the aspect ratio by filling one // (5) Last case, AT_MOST and AT_MOST. Here we can SURELY fit the aspect ratio by
// dimension and adapting the other. // filling one dimension and adapting the other.
int height, width; int height, width;
float atMostRatio = (float) heightValue / (float) widthValue; float atMostRatio = (float) heightValue / (float) widthValue;
if (atMostRatio >= ratio) { if (atMostRatio >= ratio) {
@ -1374,7 +1387,7 @@ public class CameraView extends FrameLayout {
@Override @Override
public void run() { public void run() {
byte[] jpeg2 = jpeg; byte[] jpeg2 = jpeg;
if (mCropOutput && mPreviewImpl.isCropping()) { if (mCropOutput && mCameraPreview.isCropping()) {
// If consistent, dimensions of the jpeg Bitmap and dimensions of getWidth(), getHeight() // If consistent, dimensions of the jpeg Bitmap and dimensions of getWidth(), getHeight()
// Live in the same reference system. // Live in the same reference system.
int w = consistentWithView ? getWidth() : getHeight(); int w = consistentWithView ? getWidth() : getHeight();
@ -1396,7 +1409,7 @@ public class CameraView extends FrameLayout {
@Override @Override
public void run() { public void run() {
byte[] jpeg; byte[] jpeg;
if (mCropOutput && mPreviewImpl.isCropping()) { if (mCropOutput && mCameraPreview.isCropping()) {
int w = consistentWithView ? getWidth() : getHeight(); int w = consistentWithView ? getWidth() : getHeight();
int h = consistentWithView ? getHeight() : getWidth(); int h = consistentWithView ? getHeight() : getWidth();
AspectRatio targetRatio = AspectRatio.of(w, h); AspectRatio targetRatio = AspectRatio.of(w, h);

@ -1,11 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"> <merge xmlns:android="http://schemas.android.com/apk/res/android">
<SurfaceView <!-- This FL could be removed, I added to test if we could implement
android:id="@+id/surface_view" proper cropping with SurfaceView. I was not easily able to. -->
<FrameLayout
android:id="@+id/surface_view_root"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
android:gravity="center" /> android:gravity="center">
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</merge> </merge>

@ -6,9 +6,9 @@ import android.view.Surface;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
abstract class Preview<T extends View, Output> { abstract class CameraPreview<T extends View, Output> {
private final static CameraLogger LOG = CameraLogger.create(Preview.class.getSimpleName()); private final static CameraLogger LOG = CameraLogger.create(CameraPreview.class.getSimpleName());
// Used for testing. // Used for testing.
Task<Void> mCropTask = new Task<>(); Task<Void> mCropTask = new Task<>();
@ -22,6 +22,7 @@ abstract class Preview<T extends View, Output> {
private SurfaceCallback mSurfaceCallback; private SurfaceCallback mSurfaceCallback;
private T mView; private T mView;
private boolean mCropping;
// 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.
@ -32,7 +33,7 @@ abstract class Preview<T extends View, Output> {
private int mDesiredWidth; private int mDesiredWidth;
private int mDesiredHeight; private int mDesiredHeight;
Preview(Context context, ViewGroup parent, SurfaceCallback callback) { CameraPreview(Context context, ViewGroup parent, SurfaceCallback callback) {
mView = onCreateView(context, parent); mView = onCreateView(context, parent);
mSurfaceCallback = callback; mSurfaceCallback = callback;
} }
@ -118,6 +119,12 @@ abstract class Preview<T extends View, Output> {
*/ */
private final void crop() { private final void crop() {
mCropTask.start(); mCropTask.start();
if (!supportsCropping()) {
mCropTask.end(null);
return;
};
getView().post(new Runnable() { getView().post(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -137,8 +144,8 @@ abstract class Preview<T extends View, Output> {
// We must increase width. // We must increase width.
scaleX = target.toFloat() / current.toFloat(); scaleX = target.toFloat() / current.toFloat();
} }
getView().setScaleX(scaleX); applyCrop(scaleX, scaleY);
getView().setScaleY(scaleY); mCropping = scaleX > 1.02f || scaleY > 1.02f;
LOG.i("crop:", "applied scaleX=", scaleX); LOG.i("crop:", "applied scaleX=", scaleX);
LOG.i("crop:", "applied scaleY=", scaleY); LOG.i("crop:", "applied scaleY=", scaleY);
mCropTask.end(null); mCropTask.end(null);
@ -146,14 +153,21 @@ abstract class Preview<T extends View, Output> {
}); });
} }
protected void applyCrop(float scaleX, float scaleY) {
getView().setScaleX(scaleX);
getView().setScaleY(scaleY);
}
boolean supportsCropping() {
return true;
}
/** /**
* Whether we are cropping the output. * Whether we are cropping the output.
* If false, this means that the output image will match the visible bounds. * If false, this means that the output image will match the visible bounds.
* @return true if cropping * @return true if cropping
*/ */
final boolean isCropping() { /* not final for tests */ boolean isCropping() {
// Account for some error return mCropping;
return getView().getScaleX() > 1.02f || getView().getScaleY() > 1.02f;
} }
} }

@ -0,0 +1,107 @@
package com.otaliastudios.cameraview;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
// Fallback preview when hardware acceleration is off.
// Currently this does NOT support cropping (e. g. the crop inside behavior),
// so we return false in supportsCropping() in order to have proper measuring.
// This means that CameraView is forced to be wrap_content.
class SurfaceCameraPreview extends CameraPreview<View, SurfaceHolder> {
private final static CameraLogger LOG = CameraLogger.create(SurfaceCameraPreview.class.getSimpleName());
SurfaceCameraPreview(Context context, ViewGroup parent, SurfaceCallback callback) {
super(context, parent, callback);
}
private SurfaceView mSurfaceView;
@NonNull
@Override
protected View onCreateView(Context context, ViewGroup parent) {
final View root = View.inflate(context, R.layout.surface_view, parent); // MATCH_PARENT
mSurfaceView = root.findViewById(R.id.surface_view);
final SurfaceHolder holder = mSurfaceView.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new SurfaceHolder.Callback() {
private boolean mFirstTime = true;
@Override
public void surfaceCreated(SurfaceHolder holder) {
// Looks like this is too early to call anything.
// surfaceChanged is guaranteed to be called after, with exact dimensions.
LOG.i("callback:", "surfaceCreated");
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
LOG.i("callback:", "surfaceChanged", "w:", width, "h:", height, "firstTime:", mFirstTime);
if (mFirstTime) {
onSurfaceAvailable(width, height);
mFirstTime = false;
} else {
onSurfaceSizeChanged(width, height);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
LOG.i("callback:", "surfaceDestroyed");
onSurfaceDestroyed();
mFirstTime = true;
}
});
return root.findViewById(R.id.surface_view_root);
}
@Override
Surface getSurface() {
return getOutput().getSurface();
}
@Override
SurfaceHolder getOutput() {
return mSurfaceView.getHolder();
}
@Override
Class<SurfaceHolder> getOutputClass() {
return SurfaceHolder.class;
}
@Override
boolean supportsCropping() {
return false;
}
@Override
protected void applyCrop(float scaleX, float scaleY) {
/* float currWidth = getView().getWidth();
float currHeight = getView().getHeight();
float cropX = currWidth * (scaleX - 1) / 2f;
float cropY = currHeight * (scaleX - 1) / 2f;
LOG.e("applyCrop:", "currWidth:", currWidth, "currHeight:", currHeight);
LOG.e("applyCrop:", "scaleX:", scaleX, "scaleY:", scaleY);
mSurfaceView.setTop((int) -cropY);
mSurfaceView.setLeft((int) -cropX);
mSurfaceView.setBottom((int) (currHeight + cropY));
mSurfaceView.setRight((int) (currWidth + cropX));
LOG.e("applyCrop:", "top:", -cropY, "left:", -cropX, "bottom:", currHeight+cropY, "right:", currWidth+cropX);
mSurfaceView.requestLayout(); */
// mSurfaceView.getLayoutParams().width = (int) (currWidth * scaleX);
// mSurfaceView.getLayoutParams().height = (int) (currHeight * scaleY);
// mSurfaceView.setTranslationY(-cropY / 2f);
// mSurfaceView.setTranslationX(-cropX / 2f);
// getView().setScrollX((int) (cropX / 2f));
// getView().setScrollY((int) (cropY / 2f));
// mSurfaceView.requestLayout();
// super.applyCrop(scaleX, scaleY);
}
}

@ -1,74 +0,0 @@
package com.otaliastudios.cameraview;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
// Fallback preview when hardware acceleration is off.
class SurfaceViewPreview extends Preview<SurfaceView, SurfaceHolder> {
private final static CameraLogger LOG = CameraLogger.create(SurfaceViewPreview.class.getSimpleName());
SurfaceViewPreview(Context context, ViewGroup parent, SurfaceCallback callback) {
super(context, parent, callback);
}
@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.addCallback(new SurfaceHolder.Callback() {
private boolean mFirstTime = true;
@Override
public void surfaceCreated(SurfaceHolder holder) {
LOG.i("callback:", "surfaceCreated");
// Looks like this is too early to call anything.
// onSurfaceAvailable(getView().getWidth(), getView().getHeight());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
LOG.i("callback:", "surfaceChanged", "w:", width, "h:", height, "firstTime:", mFirstTime);
if (mFirstTime) {
onSurfaceAvailable(width, height);
mFirstTime = false;
} else {
onSurfaceSizeChanged(width, height);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
LOG.i("callback:", "surfaceDestroyed");
onSurfaceDestroyed();
mFirstTime = true;
}
});
return surface;
}
@Override
Surface getSurface() {
return getOutput().getSurface();
}
@Override
SurfaceHolder getOutput() {
return getView().getHolder();
}
@Override
Class<SurfaceHolder> getOutputClass() {
return SurfaceHolder.class;
}
}

@ -9,11 +9,11 @@ import android.view.TextureView;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
class TextureViewPreview extends Preview<TextureView, SurfaceTexture> { class TextureCameraPreview extends CameraPreview<TextureView, SurfaceTexture> {
private Surface mSurface; private Surface mSurface;
TextureViewPreview(Context context, ViewGroup parent, SurfaceCallback callback) { TextureCameraPreview(Context context, ViewGroup parent, SurfaceCallback callback) {
super(context, parent, callback); super(context, parent, callback);
} }

@ -15,6 +15,7 @@
<activity <activity
android:name=".CameraActivity" android:name=".CameraActivity"
android:theme="@style/Theme.MainActivity" android:theme="@style/Theme.MainActivity"
android:hardwareAccelerated="true"
android:configChanges="orientation|screenLayout|keyboardHidden"> android:configChanges="orientation|screenLayout|keyboardHidden">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

@ -11,6 +11,7 @@ import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.Toast; import android.widget.Toast;
import com.otaliastudios.cameraview.Audio; import com.otaliastudios.cameraview.Audio;
@ -43,6 +44,8 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
setContentView(R.layout.activity_camera); setContentView(R.layout.activity_camera);
CameraLogger.setLogLevel(CameraLogger.LEVEL_VERBOSE); CameraLogger.setLogLevel(CameraLogger.LEVEL_VERBOSE);
@ -168,11 +171,20 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
} }
@Override @Override
public void onValueChanged(Control control, Object value, String name) { public boolean onValueChanged(Control control, Object value, String name) {
if (!camera.isHardwareAccelerated() && (control == Control.WIDTH || control == Control.HEIGHT)) {
if ((Integer) value > 0) {
message("This device does not support hardware acceleration. " +
"In this case you can not change width or height. " +
"The view will act as WRAP_CONTENT by default.", true);
return false;
}
}
control.applyValue(camera, value); control.applyValue(camera, value);
BottomSheetBehavior b = BottomSheetBehavior.from(controlPanel); BottomSheetBehavior b = BottomSheetBehavior.from(controlPanel);
b.setState(BottomSheetBehavior.STATE_HIDDEN); b.setState(BottomSheetBehavior.STATE_HIDDEN);
message("Changed " + control.getName() + " to " + name, false); message("Changed " + control.getName() + " to " + name, false);
return true;
} }
//region Boilerplate //region Boilerplate

@ -25,7 +25,7 @@ import java.util.Collection;
public class ControlView<Value> extends LinearLayout implements Spinner.OnItemSelectedListener { public class ControlView<Value> extends LinearLayout implements Spinner.OnItemSelectedListener {
interface Callback { interface Callback {
void onValueChanged(Control control, Object value, String name); boolean onValueChanged(Control control, Object value, String name);
} }
private Value value; private Value value;
@ -86,7 +86,11 @@ public class ControlView<Value> extends LinearLayout implements Spinner.OnItemSe
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) { public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
if (values.get(i) != value) { if (values.get(i) != value) {
Log.e("ControlView", "curr: " + value + " new: " + values.get(i)); Log.e("ControlView", "curr: " + value + " new: " + values.get(i));
callback.onValueChanged(control, values.get(i), valuesStrings.get(i)); if (!callback.onValueChanged(control, values.get(i), valuesStrings.get(i))) {
spinner.setSelection(values.indexOf(value)); // Go back.
} else {
value = values.get(i);
}
} }
} }

@ -11,8 +11,8 @@
<com.otaliastudios.cameraview.CameraView <com.otaliastudios.cameraview.CameraView
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/camera" android:id="@+id/camera"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
android:keepScreenOn="true" android:keepScreenOn="true"
app:cameraGrid="off" app:cameraGrid="off"

Loading…
Cancel
Save