Improve CameraOptions APIs, cleanup (#101)

* Improve CameraOptions APIs, cleanup

* Test flip

* Remove unwanted API
pull/105/head
Mattia Iavarone 7 years ago committed by GitHub
parent 9e6c4c0919
commit 33162f0e31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 96
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraOptions1Test.java
  2. 25
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java
  3. 9
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java
  4. 48
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/OrientationHelperTest.java
  5. 122
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  6. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java
  7. 126
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
  8. 32
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java
  9. 34
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  10. 13
      cameraview/src/main/java/com/otaliastudios/cameraview/Size.java
  11. 62
      cameraview/src/main/utils/com/otaliastudios/cameraview/OrientationHelper.java
  12. 6
      cameraview/src/test/java/com/otaliastudios/cameraview/SizeTest.java

@ -4,8 +4,6 @@ package com.otaliastudios.cameraview;
import android.hardware.Camera; import android.hardware.Camera;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import android.test.ActivityInstrumentationTestCase2;
import android.test.InstrumentationTestCase;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -16,8 +14,11 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static org.mockito.Mockito.*; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @SmallTest
@ -25,7 +26,9 @@ public class CameraOptions1Test extends BaseTest {
@Test @Test
public void testEmpty() { public void testEmpty() {
CameraOptions o = new CameraOptions(mock(Camera.Parameters.class)); CameraOptions o = new CameraOptions(mock(Camera.Parameters.class), false);
assertTrue(o.getSupportedPictureAspectRatios().isEmpty());
assertTrue(o.getSupportedPictureSizes().isEmpty());
assertTrue(o.getSupportedWhiteBalance().isEmpty()); assertTrue(o.getSupportedWhiteBalance().isEmpty());
assertTrue(o.getSupportedFlash().isEmpty()); assertTrue(o.getSupportedFlash().isEmpty());
assertTrue(o.getSupportedHdr().isEmpty()); assertTrue(o.getSupportedHdr().isEmpty());
@ -37,6 +40,75 @@ public class CameraOptions1Test extends BaseTest {
assertEquals(o.getExposureCorrectionMinValue(), 0f, 0); assertEquals(o.getExposureCorrectionMinValue(), 0f, 0);
} }
private Camera.Size mockCameraSize(int width, int height) {
Camera.Size cs = mock(Camera.Size.class);
cs.width = width;
cs.height = height;
return cs;
}
@Test
public void testPictureSizes() {
List<Camera.Size> sizes = Arrays.asList(
mockCameraSize(100, 200),
mockCameraSize(50, 50),
mockCameraSize(1600, 900),
mockCameraSize(1000, 2000)
);
Camera.Parameters params = mock(Camera.Parameters.class);
when(params.getSupportedPictureSizes()).thenReturn(sizes);
CameraOptions o = new CameraOptions(params, false);
Set<Size> supportedSizes = o.getSupportedPictureSizes();
assertEquals(supportedSizes.size(), sizes.size());
for (Camera.Size size : sizes) {
Size internalSize = new Size(size.width, size.height);
assertTrue(supportedSizes.contains(internalSize));
}
}
@Test
public void testPictureSizesFlip() {
List<Camera.Size> sizes = Arrays.asList(
mockCameraSize(100, 200),
mockCameraSize(50, 50),
mockCameraSize(1600, 900),
mockCameraSize(1000, 2000)
);
Camera.Parameters params = mock(Camera.Parameters.class);
when(params.getSupportedPictureSizes()).thenReturn(sizes);
CameraOptions o = new CameraOptions(params, true);
Set<Size> supportedSizes = o.getSupportedPictureSizes();
assertEquals(supportedSizes.size(), sizes.size());
for (Camera.Size size : sizes) {
Size internalSize = new Size(size.width, size.height).flip();
assertTrue(supportedSizes.contains(internalSize));
}
}
@Test
public void testPictureAspectRatio() {
List<Camera.Size> sizes = Arrays.asList(
mockCameraSize(100, 200),
mockCameraSize(50, 50),
mockCameraSize(1600, 900),
mockCameraSize(1000, 2000)
);
Set<AspectRatio> expected = new HashSet<>();
expected.add(AspectRatio.of(1, 2));
expected.add(AspectRatio.of(1, 1));
expected.add(AspectRatio.of(16, 9));
Camera.Parameters params = mock(Camera.Parameters.class);
when(params.getSupportedPictureSizes()).thenReturn(sizes);
CameraOptions o = new CameraOptions(params, false);
Set<AspectRatio> supportedRatios = o.getSupportedPictureAspectRatios();
assertEquals(supportedRatios.size(), expected.size());
for (AspectRatio ratio : expected) {
assertTrue(supportedRatios.contains(ratio));
}
}
@Test @Test
public void testFacing() { public void testFacing() {
Set<Integer> supported = new HashSet<>(); Set<Integer> supported = new HashSet<>();
@ -46,7 +118,7 @@ public class CameraOptions1Test extends BaseTest {
supported.add(cameraInfo.facing); supported.add(cameraInfo.facing);
} }
CameraOptions o = new CameraOptions(mock(Camera.Parameters.class)); CameraOptions o = new CameraOptions(mock(Camera.Parameters.class), false);
Mapper m = new Mapper.Mapper1(); Mapper m = new Mapper.Mapper1();
Set<Facing> s = o.getSupportedFacing(); Set<Facing> s = o.getSupportedFacing();
assertEquals(o.getSupportedFacing().size(), supported.size()); assertEquals(o.getSupportedFacing().size(), supported.size());
@ -64,7 +136,7 @@ public class CameraOptions1Test extends BaseTest {
when(params.getMaxExposureCompensation()).thenReturn(0); when(params.getMaxExposureCompensation()).thenReturn(0);
when(params.getMinExposureCompensation()).thenReturn(0); when(params.getMinExposureCompensation()).thenReturn(0);
CameraOptions o = new CameraOptions(params); CameraOptions o = new CameraOptions(params, false);
assertFalse(o.supports(GestureAction.FOCUS)); assertFalse(o.supports(GestureAction.FOCUS));
assertFalse(o.supports(GestureAction.FOCUS_WITH_MARKER)); assertFalse(o.supports(GestureAction.FOCUS_WITH_MARKER));
assertTrue(o.supports(GestureAction.CAPTURE)); assertTrue(o.supports(GestureAction.CAPTURE));
@ -82,7 +154,7 @@ public class CameraOptions1Test extends BaseTest {
Camera.Parameters.WHITE_BALANCE_SHADE // Not supported Camera.Parameters.WHITE_BALANCE_SHADE // Not supported
)); ));
CameraOptions o = new CameraOptions(params); CameraOptions o = new CameraOptions(params, false);
assertEquals(o.getSupportedWhiteBalance().size(), 2); assertEquals(o.getSupportedWhiteBalance().size(), 2);
assertTrue(o.getSupportedWhiteBalance().contains(WhiteBalance.AUTO)); assertTrue(o.getSupportedWhiteBalance().contains(WhiteBalance.AUTO));
assertTrue(o.getSupportedWhiteBalance().contains(WhiteBalance.CLOUDY)); assertTrue(o.getSupportedWhiteBalance().contains(WhiteBalance.CLOUDY));
@ -99,7 +171,7 @@ public class CameraOptions1Test extends BaseTest {
Camera.Parameters.FLASH_MODE_RED_EYE // Not supported Camera.Parameters.FLASH_MODE_RED_EYE // Not supported
)); ));
CameraOptions o = new CameraOptions(params); CameraOptions o = new CameraOptions(params, false);
assertEquals(o.getSupportedFlash().size(), 2); assertEquals(o.getSupportedFlash().size(), 2);
assertTrue(o.getSupportedFlash().contains(Flash.AUTO)); assertTrue(o.getSupportedFlash().contains(Flash.AUTO));
assertTrue(o.getSupportedFlash().contains(Flash.TORCH)); assertTrue(o.getSupportedFlash().contains(Flash.TORCH));
@ -116,7 +188,7 @@ public class CameraOptions1Test extends BaseTest {
Camera.Parameters.SCENE_MODE_FIREWORKS // Not supported Camera.Parameters.SCENE_MODE_FIREWORKS // Not supported
)); ));
CameraOptions o = new CameraOptions(params); CameraOptions o = new CameraOptions(params, false);
assertEquals(o.getSupportedHdr().size(), 2); assertEquals(o.getSupportedHdr().size(), 2);
assertTrue(o.getSupportedHdr().contains(Hdr.OFF)); assertTrue(o.getSupportedHdr().contains(Hdr.OFF));
assertTrue(o.getSupportedHdr().contains(Hdr.ON)); assertTrue(o.getSupportedHdr().contains(Hdr.ON));
@ -130,7 +202,7 @@ public class CameraOptions1Test extends BaseTest {
when(params.isVideoSnapshotSupported()).thenReturn(true); when(params.isVideoSnapshotSupported()).thenReturn(true);
when(params.isZoomSupported()).thenReturn(true); when(params.isZoomSupported()).thenReturn(true);
when(params.getSupportedFocusModes()).thenReturn(Arrays.asList(Camera.Parameters.FOCUS_MODE_AUTO)); when(params.getSupportedFocusModes()).thenReturn(Arrays.asList(Camera.Parameters.FOCUS_MODE_AUTO));
CameraOptions o = new CameraOptions(params); CameraOptions o = new CameraOptions(params, false);
assertTrue(o.isVideoSnapshotSupported()); assertTrue(o.isVideoSnapshotSupported());
assertTrue(o.isZoomSupported()); assertTrue(o.isZoomSupported());
assertTrue(o.isAutoFocusSupported()); assertTrue(o.isAutoFocusSupported());
@ -142,7 +214,7 @@ public class CameraOptions1Test extends BaseTest {
when(params.getMaxExposureCompensation()).thenReturn(10); when(params.getMaxExposureCompensation()).thenReturn(10);
when(params.getMinExposureCompensation()).thenReturn(-10); when(params.getMinExposureCompensation()).thenReturn(-10);
when(params.getExposureCompensationStep()).thenReturn(0.5f); when(params.getExposureCompensationStep()).thenReturn(0.5f);
CameraOptions o = new CameraOptions(params); CameraOptions o = new CameraOptions(params, false);
assertTrue(o.isExposureCorrectionSupported()); assertTrue(o.isExposureCorrectionSupported());
assertEquals(o.getExposureCorrectionMinValue(), -10f * 0.5f, 0f); assertEquals(o.getExposureCorrectionMinValue(), -10f * 0.5f, 0f);
assertEquals(o.getExposureCorrectionMaxValue(), 10f * 0.5f, 0f); assertEquals(o.getExposureCorrectionMaxValue(), 10f * 0.5f, 0f);

@ -194,31 +194,8 @@ public class CameraViewCallbacksTest extends BaseTest {
} }
@Test @Test
public void testOrientationCallbacks_deviceOnly() { public void testOrientationCallbacks() {
completeTask().when(listener).onOrientationChanged(anyInt()); completeTask().when(listener).onOrientationChanged(anyInt());
// Assert not called. Both methods must be called.
camera.mCameraCallbacks.onDeviceOrientationChanged(0);
assertNull(task.await(200));
verify(listener, never()).onOrientationChanged(anyInt());
}
@Test
public void testOrientationCallbacks_displayOnly() {
completeTask().when(listener).onOrientationChanged(anyInt());
// Assert not called. Both methods must be called.
camera.mCameraCallbacks.onDisplayOffsetChanged(0);
assertNull(task.await(200));
verify(listener, never()).onOrientationChanged(anyInt());
}
@Test
public void testOrientationCallbacks_both() {
completeTask().when(listener).onOrientationChanged(anyInt());
// Assert called.
camera.mCameraCallbacks.onDisplayOffsetChanged(0);
camera.mCameraCallbacks.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());

@ -20,7 +20,7 @@ public class MockCameraController extends CameraController {
} }
void setMockCameraOptions(CameraOptions options) { void setMockCameraOptions(CameraOptions options) {
mOptions = options; mCameraOptions = options;
} }
void setMockPreviewSize(Size size) { void setMockPreviewSize(Size size) {
@ -108,13 +108,6 @@ public class MockCameraController extends CameraController {
void endVideo() { void endVideo() {
} }
@Override
boolean shouldFlipSizes() {
return false;
}
@Override @Override
void startAutoFocus(@Nullable Gesture gesture, PointF point) { void startAutoFocus(@Nullable Gesture gesture, PointF point) {
mFocusStarted = true; mFocusStarted = true;

@ -1,10 +1,6 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import android.annotation.TargetApi;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import android.view.OrientationEventListener; import android.view.OrientationEventListener;
@ -22,44 +18,44 @@ import static org.mockito.Mockito.*;
public class OrientationHelperTest extends BaseTest { public class OrientationHelperTest extends BaseTest {
private OrientationHelper helper; private OrientationHelper helper;
private OrientationHelper.Callbacks callbacks; private OrientationHelper.Callback callback;
@Before @Before
public void setUp() { public void setUp() {
ui(new Runnable() { ui(new Runnable() {
@Override @Override
public void run() { public void run() {
callbacks = mock(OrientationHelper.Callbacks.class); callback = mock(OrientationHelper.Callback.class);
helper = new OrientationHelper(context(), callbacks); helper = new OrientationHelper(context(), callback);
} }
}); });
} }
@After @After
public void tearDown() { public void tearDown() {
callbacks = null; callback = null;
helper = null; helper = null;
} }
@Test @Test
public void testEnable() { public void testEnable() {
assertNotNull(helper.mListener); assertNotNull(helper.mListener);
assertNull(helper.mDisplay); assertEquals(helper.getDisplayOffset(), -1);
assertEquals(helper.getDeviceOrientation(), -1);
helper.enable(context()); helper.enable(context());
assertNotNull(helper.mListener); assertNotNull(helper.mListener);
assertNotNull(helper.mDisplay); assertNotEquals(helper.getDisplayOffset(), -1); // Don't know about device orientation.
// Ensure nothing bad if called twice. // Ensure nothing bad if called twice.
helper.enable(context()); helper.enable(context());
assertNotNull(helper.mListener); assertNotNull(helper.mListener);
assertNotNull(helper.mDisplay); assertNotEquals(helper.getDisplayOffset(), -1);
helper.disable(); helper.disable();
assertNotNull(helper.mListener); assertNotNull(helper.mListener);
assertNull(helper.mDisplay); assertEquals(helper.getDisplayOffset(), -1);
assertEquals(helper.getDeviceOrientation(), -1);
verify(callbacks, atLeastOnce()).onDisplayOffsetChanged(anyInt());
} }
@Test @Test
@ -69,30 +65,30 @@ public class OrientationHelperTest extends BaseTest {
// right after enabling. But that's fine for us, times(1) will be OK either way. // right after enabling. But that's fine for us, times(1) will be OK either way.
helper.enable(context()); helper.enable(context());
helper.mListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN); helper.mListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN);
assertEquals(helper.mLastOrientation, 0); assertEquals(helper.getDeviceOrientation(), 0);
helper.mListener.onOrientationChanged(10); helper.mListener.onOrientationChanged(10);
assertEquals(helper.mLastOrientation, 0); assertEquals(helper.getDeviceOrientation(), 0);
helper.mListener.onOrientationChanged(-10); helper.mListener.onOrientationChanged(-10);
assertEquals(helper.mLastOrientation, 0); assertEquals(helper.getDeviceOrientation(), 0);
helper.mListener.onOrientationChanged(44); helper.mListener.onOrientationChanged(44);
assertEquals(helper.mLastOrientation, 0); assertEquals(helper.getDeviceOrientation(), 0);
helper.mListener.onOrientationChanged(360); helper.mListener.onOrientationChanged(360);
assertEquals(helper.mLastOrientation, 0); assertEquals(helper.getDeviceOrientation(), 0);
// Callback called just once. // Callback called just once.
verify(callbacks, times(1)).onDeviceOrientationChanged(0); verify(callback, times(1)).onDeviceOrientationChanged(0);
helper.mListener.onOrientationChanged(90); helper.mListener.onOrientationChanged(90);
helper.mListener.onOrientationChanged(91); helper.mListener.onOrientationChanged(91);
assertEquals(helper.mLastOrientation, 90); assertEquals(helper.getDeviceOrientation(), 90);
verify(callbacks, times(1)).onDeviceOrientationChanged(90); verify(callback, times(1)).onDeviceOrientationChanged(90);
helper.mListener.onOrientationChanged(180); helper.mListener.onOrientationChanged(180);
assertEquals(helper.mLastOrientation, 180); assertEquals(helper.getDeviceOrientation(), 180);
verify(callbacks, times(1)).onDeviceOrientationChanged(180); verify(callback, times(1)).onDeviceOrientationChanged(180);
helper.mListener.onOrientationChanged(270); helper.mListener.onOrientationChanged(270);
assertEquals(helper.mLastOrientation, 270); assertEquals(helper.getDeviceOrientation(), 270);
verify(callbacks, times(1)).onDeviceOrientationChanged(270); verify(callback, times(1)).onDeviceOrientationChanged(270);
} }
} }

@ -9,7 +9,6 @@ import android.hardware.Camera;
import android.location.Location; import android.location.Location;
import android.media.CamcorderProfile; import android.media.CamcorderProfile;
import android.media.MediaRecorder; import android.media.MediaRecorder;
import android.os.Build;
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;
@ -18,7 +17,6 @@ import android.view.SurfaceHolder;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
@ -29,10 +27,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
private Camera mCamera; private Camera mCamera;
private MediaRecorder mMediaRecorder; private boolean mIsBound = false;
private File mVideoFile;
private int mSensorOffset;
private final int mPostFocusResetDelay = 3000; private final int mPostFocusResetDelay = 3000;
private Runnable mPostFocusResetRunnable = new Runnable() { private Runnable mPostFocusResetRunnable = new Runnable() {
@ -48,11 +43,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
} }
}; };
private Mapper mMapper;
private boolean mIsBound = false;
private boolean mIsCapturingImage = false;
private boolean mIsCapturingVideo = false;
Camera1(CameraView.CameraCallbacks callback) { Camera1(CameraView.CameraCallbacks callback) {
super(callback); super(callback);
mMapper = new Mapper.Mapper1(); mMapper = new Mapper.Mapper1();
@ -135,7 +125,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
throw new CameraException(e); throw new CameraException(e);
} }
mPictureSize = computePictureSize(sizesFromList(mCamera.getParameters().getSupportedPictureSizes())); mPictureSize = computePictureSize();
mPreviewSize = computePreviewSize(sizesFromList(mCamera.getParameters().getSupportedPreviewSizes())); mPreviewSize = computePreviewSize(sizesFromList(mCamera.getParameters().getSupportedPreviewSizes()));
applySizesAndStartPreview("bindToSurface:"); applySizesAndStartPreview("bindToSurface:");
mIsBound = true; mIsBound = true;
@ -182,7 +172,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
LOG.i("onStart:", "Applying default parameters."); LOG.i("onStart:", "Applying default parameters.");
Camera.Parameters params = mCamera.getParameters(); Camera.Parameters params = mCamera.getParameters();
mExtraProperties = new ExtraProperties(params); mExtraProperties = new ExtraProperties(params);
mOptions = new CameraOptions(params); mCameraOptions = new CameraOptions(params, shouldFlipSizes());
applyDefaultFocus(params); applyDefaultFocus(params);
mergeFlash(params, Flash.DEFAULT); mergeFlash(params, Flash.DEFAULT);
mergeLocation(params, null); mergeLocation(params, null);
@ -192,7 +182,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mCamera.setParameters(params); mCamera.setParameters(params);
// Try starting preview. // Try starting preview.
mCamera.setDisplayOrientation(computeSensorToDisplayOffset()); // <- not allowed during preview mCamera.setDisplayOrientation(computeSensorToViewOffset()); // <- not allowed during preview
if (shouldBindToSurface()) bindToSurface(); if (shouldBindToSurface()) bindToSurface();
LOG.i("onStart:", "Ended"); LOG.i("onStart:", "Ended");
} }
@ -230,7 +220,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
} }
} }
mExtraProperties = null; mExtraProperties = null;
mOptions = null; mCameraOptions = null;
mCamera = null; mCamera = null;
mPreviewSize = null; mPreviewSize = null;
mPictureSize = null; mPictureSize = null;
@ -346,7 +336,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
} }
private boolean mergeWhiteBalance(Camera.Parameters params, WhiteBalance oldWhiteBalance) { private boolean mergeWhiteBalance(Camera.Parameters params, WhiteBalance oldWhiteBalance) {
if (mOptions.supports(mWhiteBalance)) { if (mCameraOptions.supports(mWhiteBalance)) {
params.setWhiteBalance((String) mMapper.map(mWhiteBalance)); params.setWhiteBalance((String) mMapper.map(mWhiteBalance));
return true; return true;
} }
@ -368,7 +358,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
} }
private boolean mergeHdr(Camera.Parameters params, Hdr oldHdr) { private boolean mergeHdr(Camera.Parameters params, Hdr oldHdr) {
if (mOptions.supports(mHdr)) { if (mCameraOptions.supports(mHdr)) {
params.setSceneMode((String) mMapper.map(mHdr)); params.setSceneMode((String) mMapper.map(mHdr));
return true; return true;
} }
@ -403,7 +393,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
private boolean mergeFlash(Camera.Parameters params, Flash oldFlash) { private boolean mergeFlash(Camera.Parameters params, Flash oldFlash) {
if (mOptions.supports(mFlash)) { if (mCameraOptions.supports(mFlash)) {
params.setFlashMode((String) mMapper.map(mFlash)); params.setFlashMode((String) mMapper.map(mFlash));
return true; return true;
} }
@ -456,7 +446,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
if (mSessionType == SessionType.VIDEO) { if (mSessionType == SessionType.VIDEO) {
// Change capture size to a size that fits the video aspect ratio. // Change capture size to a size that fits the video aspect ratio.
Size oldSize = mPictureSize; Size oldSize = mPictureSize;
mPictureSize = computePictureSize(sizesFromList(mCamera.getParameters().getSupportedPictureSizes())); mPictureSize = computePictureSize();
if (!mPictureSize.equals(oldSize)) { if (!mPictureSize.equals(oldSize)) {
// New video quality triggers a new aspect ratio. // New video quality triggers a new aspect ratio.
// Go on and see if preview size should change also. // Go on and see if preview size should change also.
@ -480,20 +470,16 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
public void run() { public void run() {
LOG.v("capturePicture: performing.", mIsCapturingImage); LOG.v("capturePicture: performing.", mIsCapturingImage);
if (mIsCapturingImage) return; if (mIsCapturingImage) return;
if (mIsCapturingVideo && !mOptions.isVideoSnapshotSupported()) return; if (mIsCapturingVideo && !mCameraOptions.isVideoSnapshotSupported()) return;
mIsCapturingImage = true; mIsCapturingImage = true;
final int exifRotation = computeExifRotation(); final int sensorToOutput = computeSensorToOutputOffset();
final boolean exifFlip = computeExifFlip(); final int sensorToView = computeSensorToViewOffset();
final int sensorToDisplay = computeSensorToDisplayOffset(); final boolean outputMatchesView = (sensorToOutput + sensorToView + 180) % 180 == 0;
final boolean outputFlip = mFacing == Facing.FRONT;
Camera.Parameters params = mCamera.getParameters(); Camera.Parameters params = mCamera.getParameters();
params.setRotation(exifRotation); params.setRotation(sensorToOutput);
mCamera.setParameters(params); mCamera.setParameters(params);
// Is the final picture (decoded respecting EXIF) consistent with CameraView orientation?
// We must consider exifOrientation to bring back the picture in the sensor world.
// Then use sensorToDisplay to move to the display world, where CameraView lives.
final boolean consistentWithView = (exifRotation + sensorToDisplay + 180) % 180 == 0;
mCamera.takePicture( mCamera.takePicture(
new Camera.ShutterCallback() { new Camera.ShutterCallback() {
@Override @Override
@ -507,7 +493,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
@Override @Override
public void onPictureTaken(byte[] data, final Camera camera) { public void onPictureTaken(byte[] data, final Camera camera) {
mIsCapturingImage = false; mIsCapturingImage = false;
mCameraCallbacks.processImage(data, consistentWithView, exifFlip); mCameraCallbacks.processImage(data, outputMatchesView, outputFlip);
camera.startPreview(); // This is needed, read somewhere in the docs. camera.startPreview(); // This is needed, read somewhere in the docs.
} }
} }
@ -540,10 +526,11 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
// Got to rotate the preview frame, since byte[] data here does not include // Got to rotate the preview frame, since byte[] data here does not include
// EXIF tags automatically set by camera. So either we add EXIF, or we rotate. // EXIF tags automatically set by camera. So either we add EXIF, or we rotate.
// Adding EXIF to a byte array, unfortunately, is hard. // Adding EXIF to a byte array, unfortunately, is hard.
final int sensorToDevice = computeExifRotation(); final int sensorToOutput = computeSensorToOutputOffset();
final int sensorToDisplay = computeSensorToDisplayOffset(); final int sensorToView = computeSensorToViewOffset();
final boolean exifFlip = computeExifFlip(); final boolean outputMatchesView = (sensorToOutput + sensorToView + 180) % 180 == 0;
final boolean flip = sensorToDevice % 180 != 0; final boolean outputFlip = mFacing == Facing.FRONT;
final boolean flip = sensorToOutput % 180 != 0;
final int preWidth = mPreviewSize.getWidth(); final int preWidth = mPreviewSize.getWidth();
final int preHeight = mPreviewSize.getHeight(); final int preHeight = mPreviewSize.getHeight();
final int postWidth = flip ? preHeight : preWidth; final int postWidth = flip ? preHeight : preWidth;
@ -554,11 +541,10 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
public void run() { public void run() {
LOG.v("captureSnapshot: rotating."); LOG.v("captureSnapshot: rotating.");
final boolean consistentWithView = (sensorToDevice + sensorToDisplay + 180) % 180 == 0; byte[] rotatedData = RotationHelper.rotate(data, preWidth, preHeight, sensorToOutput);
byte[] rotatedData = RotationHelper.rotate(data, preWidth, preHeight, sensorToDevice);
LOG.v("captureSnapshot: rotated."); LOG.v("captureSnapshot: rotated.");
YuvImage yuv = new YuvImage(rotatedData, format, postWidth, postHeight, null); YuvImage yuv = new YuvImage(rotatedData, format, postWidth, postHeight, null);
mCameraCallbacks.processSnapshot(yuv, consistentWithView, exifFlip); mCameraCallbacks.processSnapshot(yuv, outputMatchesView, outputFlip);
mIsCapturingImage = false; mIsCapturingImage = false;
} }
}); });
@ -577,20 +563,12 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
public void onPreviewFrame(byte[] data, Camera camera) { public void onPreviewFrame(byte[] data, Camera camera) {
Frame frame = mFrameManager.getFrame(data, Frame frame = mFrameManager.getFrame(data,
System.currentTimeMillis(), System.currentTimeMillis(),
computeExifRotation(), computeSensorToOutputOffset(),
mPreviewSize, mPreviewSize,
mPreviewFormat); mPreviewFormat);
mCameraCallbacks.dispatchFrame(frame); mCameraCallbacks.dispatchFrame(frame);
} }
@Override
boolean shouldFlipSizes() {
int offset = computeSensorToDisplayOffset();
LOG.i("shouldFlipSizes:", "mDeviceOrientation=", mDeviceOrientation, "mSensorOffset=", mSensorOffset);
LOG.i("shouldFlipSizes:", "sensorToDisplay=", offset);
return offset % 180 != 0;
}
private boolean isCameraAvailable() { private boolean isCameraAvailable() {
switch (mState) { switch (mState) {
// If we are stopped, don't. // If we are stopped, don't.
@ -607,44 +585,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
return false; return false;
} }
// Internal:
/**
* Returns how much should the sensor image be rotated before being shown.
* It is meant to be fed to Camera.setDisplayOrientation().
*/
private int computeSensorToDisplayOffset() {
if (mFacing == Facing.FRONT) {
// or: (360 - ((mSensorOffset + mDisplayOffset) % 360)) % 360;
return ((mSensorOffset - mDisplayOffset) + 360 + 180) % 360;
} else {
return (mSensorOffset - mDisplayOffset + 360) % 360;
}
}
/**
* Returns the orientation to be set as a exif tag. This is already managed by
* the camera APIs as long as you call {@link Camera.Parameters#setRotation(int)}.
* This ignores flipping for facing camera.
*/
private int computeExifRotation() {
if (mFacing == Facing.FRONT) {
return (mSensorOffset - mDeviceOrientation + 360) % 360;
} else {
return (mSensorOffset + mDeviceOrientation) % 360;
}
}
/**
* Whether the exif tag should include a 'flip' operation.
*/
private boolean computeExifFlip() {
return mFacing == Facing.FRONT;
}
// ----------------- // -----------------
// Video recording stuff. // Video recording stuff.
@ -735,7 +675,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
} }
mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath()); mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath());
mMediaRecorder.setOrientationHint(computeExifRotation()); mMediaRecorder.setOrientationHint(computeSensorToOutputOffset());
// Not needed. mMediaRecorder.setPreviewDisplay(mPreview.getSurface()); // Not needed. mMediaRecorder.setPreviewDisplay(mPreview.getSurface());
} }
@ -748,7 +688,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
schedule(mZoomTask, true, new Runnable() { schedule(mZoomTask, true, new Runnable() {
@Override @Override
public void run() { public void run() {
if (!mOptions.isZoomSupported()) return; if (!mCameraOptions.isZoomSupported()) return;
mZoomValue = zoom; mZoomValue = zoom;
Camera.Parameters params = mCamera.getParameters(); Camera.Parameters params = mCamera.getParameters();
@ -769,11 +709,11 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
schedule(mExposureCorrectionTask, true, new Runnable() { schedule(mExposureCorrectionTask, true, new Runnable() {
@Override @Override
public void run() { public void run() {
if (!mOptions.isExposureCorrectionSupported()) return; if (!mCameraOptions.isExposureCorrectionSupported()) return;
float value = EVvalue; float value = EVvalue;
float max = mOptions.getExposureCorrectionMaxValue(); float max = mCameraOptions.getExposureCorrectionMaxValue();
float min = mOptions.getExposureCorrectionMinValue(); float min = mCameraOptions.getExposureCorrectionMinValue();
value = value < min ? min : value > max ? max : value; // cap value = value < min ? min : value > max ? max : value; // cap
mExposureCorrectionValue = value; mExposureCorrectionValue = value;
Camera.Parameters params = mCamera.getParameters(); Camera.Parameters params = mCamera.getParameters();
@ -806,10 +746,10 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
schedule(null, true, new Runnable() { schedule(null, true, new Runnable() {
@Override @Override
public void run() { public void run() {
if (!mOptions.isAutoFocusSupported()) return; if (!mCameraOptions.isAutoFocusSupported()) return;
final PointF p = new PointF(point.x, point.y); // copy. final PointF p = new PointF(point.x, point.y); // copy.
List<Camera.Area> meteringAreas2 = computeMeteringAreas(p.x, p.y, List<Camera.Area> meteringAreas2 = computeMeteringAreas(p.x, p.y,
viewWidthF, viewHeightF, computeSensorToDisplayOffset()); viewWidthF, viewHeightF, computeSensorToViewOffset());
List<Camera.Area> meteringAreas1 = meteringAreas2.subList(0, 1); List<Camera.Area> meteringAreas1 = meteringAreas2.subList(0, 1);
// At this point we are sure that camera supports auto focus... right? Look at CameraView.onTouchEvent(). // At this point we are sure that camera supports auto focus... right? Look at CameraView.onTouchEvent().

@ -116,11 +116,6 @@ class Camera2 extends CameraController {
} }
@Override
boolean shouldFlipSizes() {
return false;
}
@Override @Override
void startAutoFocus(@Nullable Gesture gesture, PointF point) { void startAutoFocus(@Nullable Gesture gesture, PointF point) {

@ -5,6 +5,7 @@ import android.location.Location;
import android.media.CamcorderProfile; import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
@ -13,6 +14,7 @@ import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread; import android.support.annotation.WorkerThread;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.List; import java.util.List;
abstract class CameraController implements abstract class CameraController implements
@ -32,7 +34,6 @@ abstract class CameraController implements
protected CameraPreview mPreview; protected CameraPreview mPreview;
protected WorkerHandler mHandler; protected WorkerHandler mHandler;
/* for tests */ Handler mCrashHandler; /* for tests */ Handler mCrashHandler;
protected int mCameraId;
protected Facing mFacing; protected Facing mFacing;
protected Flash mFlash; protected Flash mFlash;
@ -42,21 +43,28 @@ abstract class CameraController implements
protected Hdr mHdr; protected Hdr mHdr;
protected Location mLocation; protected Location mLocation;
protected Audio mAudio; protected Audio mAudio;
protected float mZoomValue; protected float mZoomValue;
protected float mExposureCorrectionValue; protected float mExposureCorrectionValue;
protected int mCameraId;
protected ExtraProperties mExtraProperties;
protected CameraOptions mCameraOptions;
protected Mapper mMapper;
protected FrameManager mFrameManager;
protected SizeSelector mPictureSizeSelector;
protected MediaRecorder mMediaRecorder;
protected File mVideoFile;
protected Size mPictureSize; protected Size mPictureSize;
protected Size mPreviewSize; protected Size mPreviewSize;
protected int mPreviewFormat; protected int mPreviewFormat;
protected ExtraProperties mExtraProperties; protected int mSensorOffset;
protected CameraOptions mOptions; private int mDisplayOffset;
protected FrameManager mFrameManager; private int mDeviceOrientation;
protected SizeSelector mPictureSizeSelector;
protected boolean mIsCapturingImage = false;
protected boolean mIsCapturingVideo = false;
protected int mDisplayOffset;
protected int mDeviceOrientation;
protected int mState = STATE_STOPPED; protected int mState = STATE_STOPPED;
// Used for testing. // Used for testing.
@ -167,7 +175,7 @@ abstract class CameraController implements
onStart(); onStart();
LOG.i("Start:", "returned from onStart().", "Dispatching.", ss()); LOG.i("Start:", "returned from onStart().", "Dispatching.", ss());
mState = STATE_STARTED; mState = STATE_STARTED;
mCameraCallbacks.dispatchOnCameraOpened(mOptions); mCameraCallbacks.dispatchOnCameraOpened(mCameraOptions);
} }
}); });
} }
@ -228,7 +236,7 @@ abstract class CameraController implements
onStart(); onStart();
mState = STATE_STARTED; mState = STATE_STARTED;
LOG.i("Restart: returned from start. Dispatching. State:", ss()); LOG.i("Restart: returned from start. Dispatching. State:", ss());
mCameraCallbacks.dispatchOnCameraOpened(mOptions); mCameraCallbacks.dispatchOnCameraOpened(mCameraOptions);
} }
}); });
} }
@ -251,22 +259,23 @@ abstract class CameraController implements
//region Simple setters //region Simple setters
void onDisplayOffset(int displayOrientation) { // This is called before start() and never again.
// I doubt this will ever change. final void setDisplayOffset(int displayOffset) {
mDisplayOffset = displayOrientation; mDisplayOffset = displayOffset;
} }
void onDeviceOrientation(int deviceOrientation) { // This can be called multiple times.
final void setDeviceOrientation(int deviceOrientation) {
mDeviceOrientation = deviceOrientation; mDeviceOrientation = deviceOrientation;
} }
void setPictureSizeSelector(SizeSelector selector) { final void setPictureSizeSelector(SizeSelector selector) {
mPictureSizeSelector = selector; mPictureSizeSelector = selector;
} }
//endregion //endregion
//region Abstract setters //region Abstract setters and APIs
// Should restart the session if active. // Should restart the session if active.
abstract void setSessionType(SessionType sessionType); abstract void setSessionType(SessionType sessionType);
@ -298,11 +307,6 @@ abstract class CameraController implements
// Throw if capturing. If in video session, recompute capture size, and, if needed, preview size. // Throw if capturing. If in video session, recompute capture size, and, if needed, preview size.
abstract void setVideoQuality(VideoQuality videoQuality); abstract void setVideoQuality(VideoQuality videoQuality);
//endregion
//region APIs
abstract void capturePicture(); abstract void capturePicture();
abstract void captureSnapshot(); abstract void captureSnapshot();
@ -311,8 +315,6 @@ abstract class CameraController implements
abstract void endVideo(); abstract void endVideo();
abstract boolean shouldFlipSizes(); // Wheter the Sizes should be flipped to match the view orientation.
abstract void startAutoFocus(@Nullable Gesture gesture, PointF point); abstract void startAutoFocus(@Nullable Gesture gesture, PointF point);
//endregion //endregion
@ -326,7 +328,7 @@ abstract class CameraController implements
@Nullable @Nullable
final CameraOptions getCameraOptions() { final CameraOptions getCameraOptions() {
return mOptions; return mCameraOptions;
} }
final Facing getFacing() { final Facing getFacing() {
@ -383,6 +385,47 @@ abstract class CameraController implements
//endregion //endregion
//region Orientation utils
/**
* The result of this should not change after start() is called: the sensor offset is the same,
* and the display offset does not change.
*/
final boolean shouldFlipSizes() {
int offset = computeSensorToViewOffset();
LOG.i("shouldFlipSizes:", "displayOffset=", mDisplayOffset, "sensorOffset=", mSensorOffset);
LOG.i("shouldFlipSizes:", "sensorToDisplay=", offset);
return offset % 180 != 0;
}
/**
* Returns how much should the sensor image be rotated before being shown.
* It is meant to be fed to Camera.setDisplayOrientation().
* The result of this should not change after start() is called: the sensor offset is the same,
* and the display offset does not change.
*/
protected final int computeSensorToViewOffset() {
if (mFacing == Facing.FRONT) {
// or: (360 - ((mSensorOffset + mDisplayOffset) % 360)) % 360;
return ((mSensorOffset - mDisplayOffset) + 360 + 180) % 360;
} else {
return (mSensorOffset - mDisplayOffset + 360) % 360;
}
}
/**
* Returns the orientation to be set as a exif tag.
*/
protected final int computeSensorToOutputOffset() {
if (mFacing == Facing.FRONT) {
return (mSensorOffset - mDeviceOrientation + 360) % 360;
} else {
return (mSensorOffset + mDeviceOrientation) % 360;
}
}
//endregion
//region Size utils //region Size utils
/** /**
@ -392,24 +435,14 @@ abstract class CameraController implements
* But when it does, the {@link CameraPreview.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.
*/ */
protected Size computePictureSize(List<Size> captureSizes) { protected final Size computePictureSize() {
SizeSelector selector;
// The external selector is expecting stuff in the view world, not in the sensor world. // The external selector is expecting stuff in the view world, not in the sensor world.
// Flip before starting, and then flip again. // Use the list in the camera options, then flip the result if needed.
boolean flip = shouldFlipSizes(); boolean flip = shouldFlipSizes();
LOG.i("computePictureSize:", "flip:", flip); SizeSelector selector;
if (flip) {
for (Size size : captureSizes) {
size.flip();
}
}
if (mSessionType == SessionType.PICTURE) { if (mSessionType == SessionType.PICTURE) {
selector = SizeSelectors.or( selector = SizeSelectors.or(mPictureSizeSelector, SizeSelectors.biggest());
mPictureSizeSelector,
SizeSelectors.biggest() // Fallback to biggest.
);
} else { } else {
// The Camcorder internally checks for cameraParameters.getSupportedVideoSizes() etc. // The Camcorder internally checks for cameraParameters.getSupportedVideoSizes() etc.
// And we want the picture size to be the biggest picture consistent with the video aspect ratio. // And we want the picture size to be the biggest picture consistent with the video aspect ratio.
@ -426,19 +459,20 @@ abstract class CameraController implements
); );
} }
Size result = selector.select(captureSizes).get(0); List<Size> list = new ArrayList<>(mCameraOptions.getSupportedPictureSizes());
LOG.i("computePictureSize:", "result:", result); Size result = selector.select(list).get(0);
if (flip) result.flip(); LOG.i("computePictureSize:", "result:", result, "flip:", flip);
if (flip) result = result.flip();
return result; return result;
} }
protected Size computePreviewSize(List<Size> previewSizes) { protected final Size computePreviewSize(List<Size> previewSizes) {
// instead of flipping everything to the view world, we can just flip the // instead of flipping everything to the view world, we can just flip the
// surface size to the sensor world // surface size to the sensor world
boolean flip = shouldFlipSizes(); boolean flip = shouldFlipSizes();
AspectRatio targetRatio = AspectRatio.of(mPictureSize.getWidth(), mPictureSize.getHeight()); AspectRatio targetRatio = AspectRatio.of(mPictureSize.getWidth(), mPictureSize.getHeight());
Size targetMinSize = mPreview.getSurfaceSize(); Size targetMinSize = mPreview.getSurfaceSize();
if (flip) targetMinSize.flip(); if (flip) targetMinSize = targetMinSize.flip();
LOG.i("size:", "computePreviewSize:", "targetRatio:", targetRatio, "targetMinSize:", targetMinSize); LOG.i("size:", "computePreviewSize:", "targetRatio:", targetRatio, "targetMinSize:", targetMinSize);
SizeSelector matchRatio = SizeSelectors.aspectRatio(targetRatio, 0); SizeSelector matchRatio = SizeSelectors.aspectRatio(targetRatio, 0);
SizeSelector matchSize = SizeSelectors.and( SizeSelector matchSize = SizeSelectors.and(
@ -450,14 +484,12 @@ abstract class CameraController implements
SizeSelectors.biggest() // If couldn't match any, take the biggest. SizeSelectors.biggest() // If couldn't match any, take the biggest.
); );
Size result = matchAll.select(previewSizes).get(0); Size result = matchAll.select(previewSizes).get(0);
LOG.i("computePreviewSize:", "result:", result); LOG.i("computePreviewSize:", "result:", result, "flip:", flip);
// Flip back what we flipped.
if (flip) targetMinSize.flip();
return result; return result;
} }
@NonNull @NonNull
protected CamcorderProfile getCamcorderProfile() { protected final CamcorderProfile getCamcorderProfile() {
switch (mVideoQuality) { switch (mVideoQuality) {
case HIGHEST: case HIGHEST:
return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_HIGH); return CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_HIGH);

@ -20,6 +20,8 @@ public class CameraOptions {
private Set<Facing> supportedFacing = new HashSet<>(2); private Set<Facing> supportedFacing = new HashSet<>(2);
private Set<Flash> supportedFlash = new HashSet<>(4); private Set<Flash> supportedFlash = new HashSet<>(4);
private Set<Hdr> supportedHdr = new HashSet<>(2); private Set<Hdr> supportedHdr = new HashSet<>(2);
private Set<Size> supportedPictureSizes = new HashSet<>(15);
private Set<AspectRatio> supportedPictureAspectRatio = new HashSet<>(4);
private boolean zoomSupported; private boolean zoomSupported;
private boolean videoSnapshotSupported; private boolean videoSnapshotSupported;
@ -31,7 +33,7 @@ public class CameraOptions {
// Camera1 constructor. // Camera1 constructor.
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
CameraOptions(Camera.Parameters params) { CameraOptions(Camera.Parameters params, boolean flipSizes) {
List<String> strings; List<String> strings;
Mapper mapper = new Mapper.Mapper1(); Mapper mapper = new Mapper.Mapper1();
@ -80,6 +82,15 @@ public class CameraOptions {
exposureCorrectionMaxValue = (float) params.getMaxExposureCompensation() * step; exposureCorrectionMaxValue = (float) params.getMaxExposureCompensation() * step;
exposureCorrectionSupported = params.getMinExposureCompensation() != 0 exposureCorrectionSupported = params.getMinExposureCompensation() != 0
|| params.getMaxExposureCompensation() != 0; || params.getMaxExposureCompensation() != 0;
// Sizes
List<Camera.Size> sizes = params.getSupportedPictureSizes();
for (Camera.Size size : sizes) {
int width = flipSizes ? size.height : size.width;
int height = flipSizes ? size.width : size.height;
supportedPictureSizes.add(new Size(width, height));
supportedPictureAspectRatio.add(AspectRatio.of(width, height));
}
} }
@ -155,6 +166,25 @@ public class CameraOptions {
return false; return false;
} }
/**
* Set of supported picture sizes for the currently opened camera.
*
* @return a set of supported values.
*/
@NonNull
public Set<Size> getSupportedPictureSizes() {
return Collections.unmodifiableSet(supportedPictureSizes);
}
/**
* Set of supported picture aspect ratios for the currently opened camera.
*
* @return a set of supported values.
*/
@NonNull
public Set<AspectRatio> getSupportedPictureAspectRatios() {
return Collections.unmodifiableSet(supportedPictureAspectRatio);
}
/** /**
* Set of supported facing values. * Set of supported facing values.

@ -537,6 +537,7 @@ public class CameraView extends FrameLayout {
if (checkPermissions(getSessionType(), getAudio())) { if (checkPermissions(getSessionType(), getAudio())) {
// Update display orientation for current CameraController // Update display orientation for current CameraController
mOrientationHelper.enable(getContext()); mOrientationHelper.enable(getContext());
mCameraController.setDisplayOffset(mOrientationHelper.getDisplayOffset());
mCameraController.start(); mCameraController.start();
} }
} }
@ -1338,7 +1339,7 @@ public class CameraView extends FrameLayout {
//region Callbacks and dispatching //region Callbacks and dispatching
interface CameraCallbacks extends OrientationHelper.Callbacks { interface CameraCallbacks extends OrientationHelper.Callback {
void dispatchOnCameraOpened(CameraOptions options); void dispatchOnCameraOpened(CameraOptions options);
void dispatchOnCameraClosed(); void dispatchOnCameraClosed();
void onCameraPreviewSizeChanged(); void onCameraPreviewSizeChanged();
@ -1356,13 +1357,8 @@ public class CameraView extends FrameLayout {
private class Callbacks implements CameraCallbacks { private class Callbacks implements CameraCallbacks {
// Outer listeners
private CameraLogger mLogger = CameraLogger.create(CameraCallbacks.class.getSimpleName()); private CameraLogger mLogger = CameraLogger.create(CameraCallbacks.class.getSimpleName());
// Orientation TODO: move this logic into OrientationHelper
private Integer mDisplayOffset;
private Integer mDeviceOrientation;
Callbacks() {} Callbacks() {}
@Override @Override
@ -1477,7 +1473,6 @@ public class CameraView extends FrameLayout {
}); });
} }
private void dispatchOnPictureTaken(byte[] jpeg) { private void dispatchOnPictureTaken(byte[] jpeg) {
mLogger.i("dispatchOnPictureTaken"); mLogger.i("dispatchOnPictureTaken");
final byte[] data = jpeg; final byte[] data = jpeg;
@ -1544,31 +1539,12 @@ public class CameraView extends FrameLayout {
}); });
} }
@Override
public void onDisplayOffsetChanged(int displayOffset) {
mLogger.i("onDisplayOffsetChanged", displayOffset);
mCameraController.onDisplayOffset(displayOffset);
mDisplayOffset = displayOffset;
if (mDeviceOrientation != null) {
int value = (mDeviceOrientation + mDisplayOffset) % 360;
dispatchOnOrientationChanged(value);
}
}
@Override @Override
public void onDeviceOrientationChanged(int deviceOrientation) { public void onDeviceOrientationChanged(int deviceOrientation) {
mLogger.i("onDeviceOrientationChanged", deviceOrientation); mLogger.i("onDeviceOrientationChanged", deviceOrientation);
mCameraController.onDeviceOrientation(deviceOrientation); mCameraController.setDeviceOrientation(deviceOrientation);
mDeviceOrientation = deviceOrientation; int displayOffset = mOrientationHelper.getDisplayOffset();
if (mDisplayOffset != null) { final int value = (deviceOrientation + displayOffset) % 360;
int value = (mDeviceOrientation + mDisplayOffset) % 360;
dispatchOnOrientationChanged(value);
}
}
private void dispatchOnOrientationChanged(final int value) {
mLogger.i("dispatchOnOrientationChanged", value);
mUiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {

@ -7,8 +7,8 @@ import android.support.annotation.NonNull;
*/ */
public class Size implements Comparable<Size> { public class Size implements Comparable<Size> {
private int mWidth; private final int mWidth;
private int mHeight; private final int mHeight;
Size(int width, int height) { Size(int width, int height) {
mWidth = width; mWidth = width;
@ -23,14 +23,9 @@ public class Size implements Comparable<Size> {
return mHeight; return mHeight;
} }
/**
* Flips width and height altogether.
*/
@SuppressWarnings("SuspiciousNameCombination") @SuppressWarnings("SuspiciousNameCombination")
public void flip() { Size flip() {
int temp = mWidth; return new Size(mHeight, mWidth);
mWidth = mHeight;
mHeight = temp;
} }
@Override @Override

@ -3,7 +3,6 @@ package com.otaliastudios.cameraview;
import android.content.Context; import android.content.Context;
import android.hardware.SensorManager; import android.hardware.SensorManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.util.SparseIntArray;
import android.view.Display; import android.view.Display;
import android.view.OrientationEventListener; import android.view.OrientationEventListener;
import android.view.Surface; import android.view.Surface;
@ -11,28 +10,18 @@ import android.view.WindowManager;
class OrientationHelper { class OrientationHelper {
private static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray();
static {
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0);
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90);
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180);
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270);
}
final OrientationEventListener mListener; final OrientationEventListener mListener;
Display mDisplay;
private final Callbacks mCallbacks; private final Callback mCallback;
int mLastKnownDisplayOffset = -1; private int mDeviceOrientation = -1;
int mLastOrientation = -1; private int mDisplayOffset = -1;
interface Callbacks { interface Callback {
void onDisplayOffsetChanged(int displayOffset);
void onDeviceOrientationChanged(int deviceOrientation); void onDeviceOrientationChanged(int deviceOrientation);
} }
OrientationHelper(Context context, @NonNull Callbacks callbacks) { OrientationHelper(Context context, @NonNull Callback callback) {
mCallbacks = callbacks; mCallback = callback;
mListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) { mListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) {
@Override @Override
@ -50,34 +39,37 @@ class OrientationHelper {
or = 270; or = 270;
} }
if (or != mLastOrientation) { if (or != mDeviceOrientation) {
mLastOrientation = or; mDeviceOrientation = or;
mCallbacks.onDeviceOrientationChanged(mLastOrientation); mCallback.onDeviceOrientationChanged(mDeviceOrientation);
}
// Let's see if display rotation has changed.. but how could it ever change...??
// This makes no sense apparently. I'll leave it for now.
if (mDisplay != null) {
final int offset = mDisplay.getRotation();
if (mLastKnownDisplayOffset != offset) {
mLastKnownDisplayOffset = offset;
mCallbacks.onDisplayOffsetChanged(DISPLAY_ORIENTATIONS.get(offset));
}
} }
} }
}; };
} }
void enable(Context context) { void enable(Context context) {
mDisplay = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
switch (display.getRotation()) {
case Surface.ROTATION_0: mDisplayOffset = 0; break;
case Surface.ROTATION_90: mDisplayOffset = 90; break;
case Surface.ROTATION_180: mDisplayOffset = 180; break;
case Surface.ROTATION_270: mDisplayOffset = 270; break;
default: mDisplayOffset = 0; break;
}
mListener.enable(); mListener.enable();
mLastKnownDisplayOffset = DISPLAY_ORIENTATIONS.get(mDisplay.getRotation());
mCallbacks.onDisplayOffsetChanged(mLastKnownDisplayOffset);
} }
void disable() { void disable() {
mListener.disable(); mListener.disable();
mDisplay = null; mDisplayOffset = -1;
mDeviceOrientation = -1;
}
int getDeviceOrientation() {
return mDeviceOrientation;
}
int getDisplayOffset() {
return mDisplayOffset;
} }
} }

@ -19,9 +19,9 @@ public class SizeTest {
@Test @Test
public void testFlip() { public void testFlip() {
Size size = new Size(10, 20); Size size = new Size(10, 20);
size.flip(); Size flipped = size.flip();
assertEquals(size.getWidth(), 20); assertEquals(size.getWidth(), flipped.getHeight());
assertEquals(size.getHeight(), 10); assertEquals(size.getHeight(), flipped.getWidth());
} }
@Test @Test

Loading…
Cancel
Save