Improve threading, add CameraOptions.supports() (#13)

* abstract WorkerHandler class, cleanup

* Start and stop in our thread

* Simplify Camera1 threading

* Add CameraOptions.supports() shorthands

* Improve supports() shorthands

* Rearrange orientation callbacks
pull/19/head
Mattia Iavarone 7 years ago committed by GitHub
parent cbac776f91
commit 57d234c05b
  1. 107
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  2. 74
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java
  3. 135
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
  4. 68
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java
  5. 180
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  6. 33
      cameraview/src/main/utils/com/otaliastudios/cameraview/OrientationHelper.java
  7. 30
      cameraview/src/main/utils/com/otaliastudios/cameraview/WorkerHandler.java
  8. 2
      cameraview/src/main/views/com/otaliastudios/cameraview/Preview.java

@ -11,6 +11,7 @@ import android.os.Build;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.view.SurfaceHolder;
@ -26,24 +27,31 @@ class Camera1 extends CameraController {
private static final String TAG = Camera1.class.getSimpleName();
private static final int DELAY_MILLIS_BEFORE_RESETTING_FOCUS = 3000;
private int mCameraId;
private Camera mCamera;
private ExtraProperties mExtraProperties;
private CameraOptions mOptions;
private Size mPreviewSize;
private Size mCaptureSize;
private MediaRecorder mMediaRecorder;
private File mVideoFile;
private int mDisplayOffset;
private int mDeviceOrientation;
private int mSensorOffset;
private Location mLocation;
private Handler mFocusHandler = new Handler();
private final int mPostFocusResetDelay = 3000;
private Runnable mPostFocusResetRunnable = new Runnable() {
@Override
public void run() {
if (!isCameraOpened()) return;
mCamera.cancelAutoFocus();
synchronized (mLock) {
Camera.Parameters params = mCamera.getParameters();
params.setFocusAreas(null);
params.setMeteringAreas(null);
applyDefaultFocus(params); // Revert to internal focus.
mCamera.setParameters(params);
}
}
};
private Mapper mMapper = new Mapper.Mapper1();
private boolean mIsSetup = false;
private boolean mIsCapturingImage = false;
@ -129,9 +137,10 @@ class Camera1 extends CameraController {
}
@WorkerThread
@Override
void start() {
if (isCameraOpened()) stop();
void onStart() {
if (isCameraOpened()) onStop();
if (collectCameraId()) {
mCamera = Camera.open(mCameraId);
@ -155,10 +164,10 @@ class Camera1 extends CameraController {
}
}
@WorkerThread
@Override
void stop() {
mFocusHandler.removeCallbacksAndMessages(null);
void onStop() {
mHandler.get().removeCallbacks(mPostFocusResetRunnable);
if (isCameraOpened()) {
if (mIsCapturingVideo) endVideo();
mCamera.stopPreview();
@ -187,17 +196,6 @@ class Camera1 extends CameraController {
return false;
}
@Override
void onDisplayOffset(int displayOrientation) {
// I doubt this will ever change.
this.mDisplayOffset = displayOrientation;
}
@Override
void onDeviceOrientation(int deviceOrientation) {
this.mDeviceOrientation = deviceOrientation;
}
@Override
void setSessionType(SessionType sessionType) {
@ -260,7 +258,7 @@ class Camera1 extends CameraController {
}
private boolean mergeWhiteBalance(Camera.Parameters params, WhiteBalance oldWhiteBalance) {
if (mOptions.getSupportedWhiteBalance().contains(mWhiteBalance)) {
if (mOptions.supports(mWhiteBalance)) {
params.setWhiteBalance((String) mMapper.map(mWhiteBalance));
return true;
}
@ -281,7 +279,7 @@ class Camera1 extends CameraController {
}
private boolean mergeHdr(Camera.Parameters params, Hdr oldHdr) {
if (mOptions.getSupportedHdr().contains(mHdr)) {
if (mOptions.supports(mHdr)) {
params.setSceneMode((String) mMapper.map(mHdr));
return true;
}
@ -303,7 +301,7 @@ class Camera1 extends CameraController {
private boolean mergeFlash(Camera.Parameters params, Flash oldFlash) {
if (mOptions.getSupportedFlash().contains(mFlash)) {
if (mOptions.supports(mFlash)) {
params.setFlashMode((String) mMapper.map(mFlash));
return true;
}
@ -390,9 +388,15 @@ class Camera1 extends CameraController {
mCamera.takePicture(null, null, null,
new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
public void onPictureTaken(byte[] data, final Camera camera) {
mIsCapturingImage = false;
camera.startPreview(); // This is needed, read somewhere in the docs.
mHandler.post(new Runnable() {
@Override
public void run() {
// This is needed, read somewhere in the docs.
camera.startPreview();
}
});
mCameraCallbacks.processImage(data, consistentWithView, exifFlip);
}
});
@ -443,16 +447,6 @@ class Camera1 extends CameraController {
return true;
}
@Override
Size getCaptureSize() {
return mCaptureSize;
}
@Override
Size getPreviewSize() {
return mPreviewSize;
}
@Override
boolean shouldFlipSizes() {
return mSensorOffset % 180 != 0;
@ -463,17 +457,6 @@ class Camera1 extends CameraController {
return mCamera != null;
}
@Nullable
@Override
ExtraProperties getExtraProperties() {
return mExtraProperties;
}
@Nullable
@Override
CameraOptions getCameraOptions() {
return mOptions;
}
// Internal:
@ -715,7 +698,8 @@ class Camera1 extends CameraController {
public void onAutoFocus(boolean success, Camera camera) {
// TODO lock auto exposure and white balance for a while
mCameraCallbacks.dispatchOnFocusEnd(gesture, success, p);
postResetFocus();
mHandler.get().removeCallbacks(mPostFocusResetRunnable);
mHandler.get().postDelayed(mPostFocusResetRunnable, mPostFocusResetDelay);
}
});
}
@ -723,25 +707,6 @@ class Camera1 extends CameraController {
}
private void postResetFocus() {
mFocusHandler.removeCallbacksAndMessages(null);
mFocusHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!isCameraOpened()) return;
mCamera.cancelAutoFocus();
synchronized (mLock) {
Camera.Parameters params = mCamera.getParameters();
params.setFocusAreas(null);
params.setMeteringAreas(null);
applyDefaultFocus(params); // Revert to internal focus.
mCamera.setParameters(params);
}
}
}, DELAY_MILLIS_BEFORE_RESETTING_FOCUS);
}
private List<Camera.Area> computeMeteringAreas(double viewClickX, double viewClickY) {
// Event came in view coordinates. We must rotate to sensor coordinates.
// First, rescale to the -1000 ... 1000 range.

@ -12,6 +12,7 @@ import android.hardware.camera2.params.StreamConfigurationMap;
import android.location.Location;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.util.Log;
import java.io.File;
@ -29,8 +30,6 @@ class Camera2 extends CameraController {
private String mCameraId;
private Size mCaptureSize;
private Size mPreviewSize;
private Mapper mMapper = new Mapper.Mapper2();
private final HashMap<String, ExtraProperties> mExtraPropertiesMap = new HashMap<>();
@ -79,13 +78,15 @@ class Camera2 extends CameraController {
// CameraImpl:
@WorkerThread
@Override
void start() {
void onStart() {
}
@WorkerThread
@Override
void stop() {
void onStop() {
}
@ -186,57 +187,7 @@ class Camera2 extends CameraController {
return false;
}
@Override
Size getCaptureSize() {
if (mCaptureSize == null && mCameraCharacteristics != null) {
TreeSet<Size> sizes = new TreeSet<>();
sizes.addAll(getAvailableCaptureResolutions());
/* TreeSet<AspectRatio> aspectRatios = new CommonAspectRatioFilter(
getAvailablePreviewResolutions(),
getAvailableCaptureResolutions()
).filter();
AspectRatio targetRatio = aspectRatios.size() > 0 ? aspectRatios.last() : null;
Iterator<Size> descendingSizes = sizes.descendingIterator();
Size size;
while (descendingSizes.hasNext() && mCaptureSize == null) {
size = descendingSizes.next();
if (targetRatio == null || targetRatio.matches(size)) {
mCaptureSize = size;
break;
}
} */
}
return mCaptureSize;
}
@Override
Size getPreviewSize() {
if (mPreviewSize == null && mCameraCharacteristics != null) {
TreeSet<Size> sizes = new TreeSet<>();
sizes.addAll(getAvailablePreviewResolutions());
/* TreeSet<AspectRatio> aspectRatios = new CommonAspectRatioFilter(
getAvailablePreviewResolutions(),
getAvailableCaptureResolutions()
).filter();
AspectRatio targetRatio = aspectRatios.size() > 0 ? aspectRatios.last() : null;
Iterator<Size> descendingSizes = sizes.descendingIterator();
Size size;
while (descendingSizes.hasNext() && mPreviewSize == null) {
size = descendingSizes.next();
if (targetRatio == null || targetRatio.matches(size)) {
mPreviewSize = size;
break;
}
} */
}
return mPreviewSize;
}
@Override
boolean shouldFlipSizes() {
@ -253,22 +204,9 @@ class Camera2 extends CameraController {
return true;
}
@Nullable
@Override
ExtraProperties getExtraProperties() {
if (mCamera == null) {
return null;
}
return mExtraPropertiesMap.get(mCamera.getId());
}
// Internal
// Internal
@Nullable
@Override
CameraOptions getCameraOptions() {
return null;
}
private List<Size> getAvailableCaptureResolutions() {
List<Size> output = new ArrayList<>();

@ -4,6 +4,7 @@ import android.graphics.PointF;
import android.location.Location;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import java.io.File;
@ -19,47 +20,153 @@ abstract class CameraController implements Preview.SurfaceCallback {
protected SessionType mSessionType;
protected Hdr mHdr;
protected Size mCaptureSize;
protected Size mPreviewSize;
protected ExtraProperties mExtraProperties;
protected CameraOptions mOptions;
protected int mDisplayOffset;
protected int mDeviceOrientation;
protected WorkerHandler mHandler;
CameraController(CameraView.CameraCallbacks callback, Preview preview) {
mCameraCallbacks = callback;
mPreview = preview;
mPreview.setSurfaceCallback(this);
mHandler = new WorkerHandler("CameraViewController");
}
//region Start&Stop
// Starts the preview asynchronously.
final void start() {
mHandler.post(new Runnable() {
@Override
public void run() {
onStart();
}
});
}
abstract void start();
abstract void stop();
// Stops the preview asynchronously.
final void stop() {
mHandler.post(new Runnable() {
@Override
public void run() {
onStop();
}
});
}
// Starts the preview.
@WorkerThread
abstract void onStart();
// Stops the preview.
@WorkerThread
abstract void onStop();
//endregion
abstract void onDisplayOffset(int displayOrientation);
abstract void onDeviceOrientation(int deviceOrientation);
//region Rotation callbacks
void onDisplayOffset(int displayOrientation) {
// I doubt this will ever change.
mDisplayOffset = displayOrientation;
}
void onDeviceOrientation(int deviceOrientation) {
mDeviceOrientation = deviceOrientation;
}
//endregion
//region Abstract setParameters
abstract boolean setZoom(float zoom);
abstract boolean setExposureCorrection(float EVvalue);
abstract void setFacing(Facing facing);
abstract void setFlash(Flash flash);
abstract void setWhiteBalance(WhiteBalance whiteBalance);
abstract void setVideoQuality(VideoQuality videoQuality);
abstract void setSessionType(SessionType sessionType);
abstract void setHdr(Hdr hdr);
abstract void setLocation(Location location);
//endregion
//region APIs
abstract boolean capturePicture();
abstract boolean captureSnapshot();
abstract boolean startVideo(@NonNull File file);
abstract boolean endVideo();
abstract Size getCaptureSize();
abstract Size getPreviewSize();
abstract boolean shouldFlipSizes(); // Wheter the Sizes should be flipped to match the view orientation.
abstract boolean isCameraOpened();
abstract boolean startAutoFocus(@Nullable Gesture gesture, PointF point);
@Nullable abstract ExtraProperties getExtraProperties();
@Nullable abstract CameraOptions getCameraOptions();
//endregion
//region final getters
@Nullable
final ExtraProperties getExtraProperties() {
return mExtraProperties;
}
@Nullable
final CameraOptions getCameraOptions() {
return mOptions;
}
final Facing getFacing() {
return mFacing;
}
final Flash getFlash() {
return mFlash;
}
final WhiteBalance getWhiteBalance() {
return mWhiteBalance;
}
final VideoQuality getVideoQuality() {
return mVideoQuality;
}
final SessionType getSessionType() {
return mSessionType;
}
final Hdr getHdr() {
return mHdr;
}
final Size getCaptureSize() {
return mCaptureSize;
}
final Size getPreviewSize() {
return mPreviewSize;
}
final Facing getFacing() { return mFacing; }
final Flash getFlash() { return mFlash; }
final WhiteBalance getWhiteBalance() { return mWhiteBalance; }
final VideoQuality getVideoQuality() { return mVideoQuality; }
final SessionType getSessionType() { return mSessionType; }
final Hdr getHdr() { return mHdr; }
//endregion
}

@ -88,6 +88,74 @@ public class CameraOptions {
CameraOptions(CameraCharacteristics params) {}
/**
* Shorthand for getSupportedFacing().contains(value).
*
* @param facing value
* @return whether it's supported
*/
public boolean supports(Facing facing) {
return getSupportedFacing().contains(facing);
}
/**
* Shorthand for getSupportedFlash().contains(value).
*
* @param flash value
* @return whether it's supported
*/
public boolean supports(Flash flash) {
return getSupportedFlash().contains(flash);
}
/**
* Shorthand for getSupportedWhiteBalance().contains(value).
*
* @param whiteBalance value
* @return whether it's supported
*/
public boolean supports(WhiteBalance whiteBalance) {
return getSupportedWhiteBalance().contains(whiteBalance);
}
/**
* Shorthand for getSupportedHdr().contains(value).
*
* @param hdr value
* @return whether it's supported
*/
public boolean supports(Hdr hdr) {
return getSupportedHdr().contains(hdr);
}
/**
* Shorthand for other methods in this class,
* e.g. supports(GestureAction.ZOOM) == isZoomSupported().
*
* @param action value to be checked
* @return whether it's supported
*/
public boolean supports(GestureAction action) {
switch (action) {
case FOCUS:
case FOCUS_WITH_MARKER:
return isAutoFocusSupported();
case CAPTURE:
case NONE:
return true;
case ZOOM:
return isZoomSupported();
case EXPOSURE_CORRECTION:
return isExposureCorrectionSupported();
}
return false;
}
/**
* Set of supported facing values.
*

@ -1,7 +1,6 @@
package com.otaliastudios.cameraview;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
@ -48,31 +47,20 @@ public class CameraView extends FrameLayout {
private final static int DEFAULT_JPEG_QUALITY = 100;
private final static boolean DEFAULT_CROP_OUTPUT = false;
private Handler mWorkerHandler;
private Handler getWorkerHandler() {
synchronized (this) {
if (mWorkerHandler == null) {
HandlerThread workerThread = new HandlerThread("CameraViewWorker");
workerThread.setDaemon(true);
workerThread.start();
mWorkerHandler = new Handler(workerThread.getLooper());
}
}
return mWorkerHandler;
}
private void run(Runnable runnable) {
getWorkerHandler().post(runnable);
}
// Self managed parameters
private int mJpegQuality;
private boolean mCropOutput;
private float mZoomValue;
private float mExposureCorrectionValue;
private HashMap<Gesture, GestureAction> mGestureMap = new HashMap<>(4);
// Components
private CameraCallbacks mCameraCallbacks;
private OrientationHelper mOrientationHelper;
private CameraController mCameraController;
private Preview mPreviewImpl;
// Views
private GridLinesLayout mGridLinesLayout;
private PinchGestureLayout mPinchGestureLayout;
private TapGestureLayout mTapGestureLayout;
@ -80,10 +68,9 @@ public class CameraView extends FrameLayout {
private boolean mIsStarted;
private boolean mKeepScreenOn;
private float mZoomValue;
private float mExposureCorrectionValue;
private HashMap<Gesture, GestureAction> mGestureMap = new HashMap<>(4);
// Threading
private Handler mUiHandler;
private WorkerHandler mWorkerHandler;
public CameraView(@NonNull Context context) {
super(context, null);
@ -120,9 +107,14 @@ public class CameraView extends FrameLayout {
GestureAction scrollVerticalGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureScrollVertical, GestureAction.DEFAULT_SCROLL_VERTICAL.value()));
a.recycle();
// Components
mCameraCallbacks = new CameraCallbacks();
mPreviewImpl = new TextureViewPreview(context, this);
mCameraController = new Camera1(mCameraCallbacks, mPreviewImpl);
mUiHandler = new Handler(Looper.getMainLooper());
mWorkerHandler = new WorkerHandler("CameraViewWorker");
// Views
mGridLinesLayout = new GridLinesLayout(context);
mPinchGestureLayout = new PinchGestureLayout(context);
mTapGestureLayout = new TapGestureLayout(context);
@ -134,11 +126,11 @@ public class CameraView extends FrameLayout {
mIsStarted = false;
// Self managed
// Apply self managed
setCropOutput(cropOutput);
setJpegQuality(jpegQuality);
// Camera controller params
// Apply camera controller params
setFacing(facing);
setFlash(flash);
setSessionType(sessionType);
@ -146,7 +138,7 @@ public class CameraView extends FrameLayout {
setWhiteBalance(whiteBalance);
setGrid(grid);
// Gestures
// Apply gestures
mapGesture(Gesture.TAP, tapGesture);
// mapGesture(Gesture.DOUBLE_TAP, doubleTapGesture);
mapGesture(Gesture.LONG_TAP, longTapGesture);
@ -155,34 +147,7 @@ public class CameraView extends FrameLayout {
mapGesture(Gesture.SCROLL_VERTICAL, scrollVerticalGesture);
if (!isInEditMode()) {
mOrientationHelper = new OrientationHelper(context) {
private Integer mDisplayOffset;
private Integer mDeviceOrientation;
@Override
public void onDisplayOffsetChanged(int displayOffset) {
mCameraController.onDisplayOffset(displayOffset);
mPreviewImpl.onDisplayOffset(displayOffset);
mDisplayOffset = displayOffset;
send();
}
@Override
protected void onDeviceOrientationChanged(int deviceOrientation) {
mCameraController.onDeviceOrientation(deviceOrientation);
mPreviewImpl.onDeviceOrientation(deviceOrientation);
mDeviceOrientation = deviceOrientation;
send();
}
private void send() {
if (mDeviceOrientation == null) return;
if (mDisplayOffset == null) return;
int value = (mDeviceOrientation + mDisplayOffset) % 360;
mCameraCallbacks.dispatchOnOrientationChanged(value);
}
};
mOrientationHelper = new OrientationHelper(context, mCameraCallbacks);
}
}
@ -497,12 +462,7 @@ public class CameraView extends FrameLayout {
if (checkPermissions(getSessionType())) {
mIsStarted = true;
run(new Runnable() {
@Override
public void run() {
mCameraController.start();
}
});
mCameraController.start();
}
}
@ -584,9 +544,8 @@ public class CameraView extends FrameLayout {
}
public void destroy() {
// TODO: this is not strictly needed
mCameraCallbacks.clearListeners(); // Release inner listener.
// This might be useless, but no time to think about it now.
mWorkerHandler = null;
}
//endregion
@ -788,12 +747,7 @@ public class CameraView extends FrameLayout {
* @param facing a facing value.
*/
public void setFacing(final Facing facing) {
run(new Runnable() {
@Override
public void run() {
mCameraController.setFacing(facing);
}
});
mCameraController.setFacing(facing);
}
@ -1212,20 +1166,21 @@ public class CameraView extends FrameLayout {
}
}
class CameraCallbacks {
private ArrayList<CameraListener> mListeners;
private Handler uiHandler;
class CameraCallbacks implements OrientationHelper.Callbacks {
// Outer listeners
private ArrayList<CameraListener> mListeners = new ArrayList<>(2);
CameraCallbacks() {
mListeners = new ArrayList<>(2);
uiHandler = new Handler(Looper.getMainLooper());
}
// Orientation TODO: move this logic into OrientationHelper
private Integer mDisplayOffset;
private Integer mDeviceOrientation;
CameraCallbacks() {}
public void dispatchOnCameraOpened(final CameraOptions options) {
uiHandler.post(new Runnable() {
mUiHandler.post(new Runnable() {
@Override
public void run() {
for (CameraListener listener : mListeners) {
@ -1237,7 +1192,7 @@ public class CameraView extends FrameLayout {
public void dispatchOnCameraClosed() {
uiHandler.post(new Runnable() {
mUiHandler.post(new Runnable() {
@Override
public void run() {
for (CameraListener listener : mListeners) {
@ -1253,7 +1208,7 @@ public class CameraView extends FrameLayout {
// Request a layout pass for onMeasure() to do its stuff.
// Potentially this will change CameraView size, which changes Surface size,
// which triggers a new Preview size. But hopefully it will converge.
uiHandler.post(new Runnable() {
mUiHandler.post(new Runnable() {
@Override
public void run() {
requestLayout();
@ -1280,7 +1235,7 @@ public class CameraView extends FrameLayout {
* because it was taken with the front camera.
*/
public void processImage(final byte[] jpeg, final boolean consistentWithView, final boolean flipHorizontally) {
getWorkerHandler().post(new Runnable() {
mWorkerHandler.post(new Runnable() {
@Override
public void run() {
byte[] jpeg2 = jpeg;
@ -1300,25 +1255,30 @@ public class CameraView extends FrameLayout {
}
public void processSnapshot(YuvImage yuv, boolean consistentWithView, boolean flipHorizontally) {
byte[] jpeg;
if (mCropOutput && mPreviewImpl.isCropping()) {
int w = consistentWithView ? getWidth() : getHeight();
int h = consistentWithView ? getHeight() : getWidth();
AspectRatio targetRatio = AspectRatio.of(w, h);
jpeg = CropHelper.cropToJpeg(yuv, targetRatio, mJpegQuality);
} else {
ByteArrayOutputStream out = new ByteArrayOutputStream();
yuv.compressToJpeg(new Rect(0, 0, yuv.getWidth(), yuv.getHeight()), mJpegQuality, out);
jpeg = out.toByteArray();
}
dispatchOnPictureTaken(jpeg);
public void processSnapshot(final YuvImage yuv, final boolean consistentWithView, boolean flipHorizontally) {
mWorkerHandler.post(new Runnable() {
@Override
public void run() {
byte[] jpeg;
if (mCropOutput && mPreviewImpl.isCropping()) {
int w = consistentWithView ? getWidth() : getHeight();
int h = consistentWithView ? getHeight() : getWidth();
AspectRatio targetRatio = AspectRatio.of(w, h);
jpeg = CropHelper.cropToJpeg(yuv, targetRatio, mJpegQuality);
} else {
ByteArrayOutputStream out = new ByteArrayOutputStream();
yuv.compressToJpeg(new Rect(0, 0, yuv.getWidth(), yuv.getHeight()), mJpegQuality, out);
jpeg = out.toByteArray();
}
dispatchOnPictureTaken(jpeg);
}
});
}
private void dispatchOnPictureTaken(byte[] jpeg) {
final byte[] data = jpeg;
uiHandler.post(new Runnable() {
mUiHandler.post(new Runnable() {
@Override
public void run() {
for (CameraListener listener : mListeners) {
@ -1330,7 +1290,7 @@ public class CameraView extends FrameLayout {
public void dispatchOnVideoTaken(final File video) {
uiHandler.post(new Runnable() {
mUiHandler.post(new Runnable() {
@Override
public void run() {
for (CameraListener listener : mListeners) {
@ -1342,7 +1302,7 @@ public class CameraView extends FrameLayout {
public void dispatchOnFocusStart(@Nullable final Gesture gesture, final PointF point) {
uiHandler.post(new Runnable() {
mUiHandler.post(new Runnable() {
@Override
public void run() {
if (gesture != null && mGestureMap.get(gesture) == GestureAction.FOCUS_WITH_MARKER) {
@ -1359,7 +1319,7 @@ public class CameraView extends FrameLayout {
public void dispatchOnFocusEnd(@Nullable final Gesture gesture, final boolean success,
final PointF point) {
uiHandler.post(new Runnable() {
mUiHandler.post(new Runnable() {
@Override
public void run() {
if (success && mUseSounds) {
@ -1378,9 +1338,29 @@ public class CameraView extends FrameLayout {
});
}
@Override
public void onDisplayOffsetChanged(int displayOffset) {
mCameraController.onDisplayOffset(displayOffset);
mDisplayOffset = displayOffset;
if (mDeviceOrientation != null) {
int value = (mDeviceOrientation + mDisplayOffset) % 360;
dispatchOnOrientationChanged(value);
}
}
@Override
public void onDeviceOrientationChanged(int deviceOrientation) {
mCameraController.onDeviceOrientation(deviceOrientation);
mDeviceOrientation = deviceOrientation;
if (mDisplayOffset != null) {
int value = (mDeviceOrientation + mDisplayOffset) % 360;
dispatchOnOrientationChanged(value);
}
}
public void dispatchOnOrientationChanged(final int value) {
uiHandler.post(new Runnable() {
private void dispatchOnOrientationChanged(final int value) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
for (CameraListener listener : mListeners) {
@ -1392,7 +1372,7 @@ public class CameraView extends FrameLayout {
public void dispatchOnZoomChanged(final float newValue, final PointF[] fingers) {
uiHandler.post(new Runnable() {
mUiHandler.post(new Runnable() {
@Override
public void run() {
for (CameraListener listener : mListeners) {
@ -1406,7 +1386,7 @@ public class CameraView extends FrameLayout {
public void dispatchOnExposureCorrectionChanged(final float newValue,
final float[] bounds,
final PointF[] fingers) {
uiHandler.post(new Runnable() {
mUiHandler.post(new Runnable() {
@Override
public void run() {
for (CameraListener listener : mListeners) {

@ -2,14 +2,13 @@ package com.otaliastudios.cameraview;
import android.content.Context;
import android.hardware.SensorManager;
import android.support.annotation.NonNull;
import android.util.SparseIntArray;
import android.view.Display;
import android.view.OrientationEventListener;
import android.view.Surface;
abstract class OrientationHelper {
private final OrientationEventListener mOrientationEventListener;
class OrientationHelper {
private static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray();
static {
@ -19,13 +18,20 @@ abstract class OrientationHelper {
DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270);
}
private final OrientationEventListener mListener;
private final Callbacks mCallbacks;
private Display mDisplay;
private int mLastKnownDisplayOffset = -1;
private int mLastOrientation = -1;
OrientationHelper(Context context) {
mOrientationEventListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) {
interface Callbacks {
void onDisplayOffsetChanged(int displayOffset);
void onDeviceOrientationChanged(int deviceOrientation);
}
OrientationHelper(Context context, @NonNull Callbacks callbacks) {
mCallbacks = callbacks;
mListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) {
@Override
public void onOrientationChanged(int orientation) {
@ -44,7 +50,7 @@ abstract class OrientationHelper {
if (or != mLastOrientation) {
mLastOrientation = or;
onDeviceOrientationChanged(mLastOrientation);
mCallbacks.onDeviceOrientationChanged(mLastOrientation);
}
// Let's see if display rotation has changed.. but how could it ever change...??
@ -53,7 +59,7 @@ abstract class OrientationHelper {
final int offset = mDisplay.getRotation();
if (mLastKnownDisplayOffset != offset) {
mLastKnownDisplayOffset = offset;
onDisplayOffsetChanged(DISPLAY_ORIENTATIONS.get(offset));
mCallbacks.onDisplayOffsetChanged(DISPLAY_ORIENTATIONS.get(offset));
}
}
}
@ -63,18 +69,13 @@ abstract class OrientationHelper {
void enable(Display display) {
mDisplay = display;
mOrientationEventListener.enable();
mListener.enable();
mLastKnownDisplayOffset = DISPLAY_ORIENTATIONS.get(display.getRotation());
onDisplayOffsetChanged(mLastKnownDisplayOffset);
mCallbacks.onDisplayOffsetChanged(mLastKnownDisplayOffset);
}
void disable() {
mOrientationEventListener.disable();
mListener.disable();
mDisplay = null;
}
protected abstract void onDisplayOffsetChanged(int displayOffset);
protected abstract void onDeviceOrientationChanged(int deviceOrientation);
}

@ -0,0 +1,30 @@
package com.otaliastudios.cameraview;
import android.os.Handler;
import android.os.HandlerThread;
/**
* Class holding a background handler.
* Using setDaemon right now but a better approach would be to have
* start() and stop() callbacks here. TODO
*/
class WorkerHandler {
private HandlerThread mThread;
private Handler mHandler;
public WorkerHandler(String name) {
mThread = new HandlerThread(name);
mThread.setDaemon(true);
mThread.start();
mHandler = new Handler(mThread.getLooper());
}
public Handler get() {
return mHandler;
}
public void post(Runnable runnable) {
mHandler.post(runnable);
}
}

@ -33,8 +33,6 @@ abstract class Preview {
abstract View getView();
abstract Class getOutputClass();
abstract boolean isReady();
protected void onDisplayOffset(int displayOrientation) {}
protected void onDeviceOrientation(int deviceOrientation) {}
SurfaceHolder getSurfaceHolder() {
return null;
}

Loading…
Cancel
Save