From 8a168355600061f615f8cb49519a8193bb21fc76 Mon Sep 17 00:00:00 2001 From: Tim H Date: Thu, 5 Oct 2017 13:47:40 +0200 Subject: [PATCH 01/14] implement basic error handling --- .../cameraview/CameraController.java | 12 ++++-------- .../cameraview/CameraException.java | 11 +++++++++++ .../cameraview/CameraListener.java | 13 +++++++++++++ .../otaliastudios/cameraview/CameraView.java | 19 +++++++++++++++++++ 4 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java index c9c1038c..32e1bf63 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java @@ -98,8 +98,7 @@ abstract class CameraController implements CameraPreview.SurfaceCallback { mCameraCallbacks.dispatchOnCameraOpened(mOptions); } catch (Exception e) { - LOG.e("Error while starting the camera engine.", e); - throw new RuntimeException(e); + mCameraCallbacks.onError("Error while starting the camera engine.", e); } } }); @@ -122,8 +121,7 @@ abstract class CameraController implements CameraPreview.SurfaceCallback { mCameraCallbacks.dispatchOnCameraClosed(); } catch (Exception e) { - LOG.e("Error while stopping the camera engine.", e); - throw new RuntimeException(e); + mCameraCallbacks.onError("Error while stopping the camera engine.", e); } } }); @@ -140,8 +138,8 @@ abstract class CameraController implements CameraPreview.SurfaceCallback { LOG.i("Stop immediately. Stopped. State is:", ss()); } catch (Exception e) { // Do nothing. - LOG.i("Stop immediately. Exception while stopping.", e); mState = STATE_STOPPED; + mCameraCallbacks.onError("Stop immediately. Exception while stopping.", e); } } @@ -170,9 +168,7 @@ abstract class CameraController implements CameraPreview.SurfaceCallback { mCameraCallbacks.dispatchOnCameraOpened(mOptions); } catch (Exception e) { - LOG.e("Error while restarting the camera engine.", e); - throw new RuntimeException(e); - + mCameraCallbacks.onError("Error while restarting the camera engine.", e); } } }); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java new file mode 100644 index 00000000..8e28309b --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java @@ -0,0 +1,11 @@ +package com.otaliastudios.cameraview; + +/** + * An object of this class describes an error that occurred during the normal runtime of the camera. + */ +public class CameraException extends RuntimeException { + + public CameraException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java index 1da71188..295f6165 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java @@ -130,4 +130,17 @@ public abstract class CameraListener { } + /** + * Notifies that an error occurred in any of the previously called methods. + * + * The default implementation will just throw the original exception again to prevent missing + * error handling. Override this method without calling the super method in order to implement + * custom error handling. + * + * @param exception the caught exception + */ + @UiThread + public void onError(CameraException exception) { + throw exception; + } } \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 5c97f25e..2ddfd678 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -1308,6 +1308,7 @@ public class CameraView extends FrameLayout { void dispatchOnFocusEnd(@Nullable Gesture trigger, boolean success, PointF where); void dispatchOnZoomChanged(final float newValue, final PointF[] fingers); void dispatchOnExposureCorrectionChanged(float newValue, float[] bounds, PointF[] fingers); + void onError(String message, Exception cause); } private class Callbacks implements CameraCallbacks { @@ -1555,6 +1556,24 @@ public class CameraView extends FrameLayout { } }); } + + /** + * Log and redirect the given error information to the CameraListeners. + * + * @param message a message describing which camera action failed + * @param cause the original exception that caused the error + */ + @Override + public void onError(String message, Exception cause) { + // log + LOG.e(message, cause); + + // redirect + CameraException cameraException = new CameraException(message, cause); + for (CameraListener listener : mListeners) { + listener.onError(cameraException); + } + } } //endregion From 122a2cb17425d087e371aec3e62ed92a9672bdb1 Mon Sep 17 00:00:00 2001 From: Tim H Date: Thu, 5 Oct 2017 18:32:16 +0200 Subject: [PATCH 02/14] apply some of the changes suggested by natario1 --- .../otaliastudios/cameraview/CameraController.java | 2 +- .../com/otaliastudios/cameraview/CameraView.java | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java index 32e1bf63..3ddcf32f 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java @@ -138,8 +138,8 @@ abstract class CameraController implements CameraPreview.SurfaceCallback { LOG.i("Stop immediately. Stopped. State is:", ss()); } catch (Exception e) { // Do nothing. + LOG.i("Stop immediately. Exception while stopping.", e); mState = STATE_STOPPED; - mCameraCallbacks.onError("Stop immediately. Exception while stopping.", e); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 2ddfd678..7e56dc54 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -1569,10 +1569,15 @@ public class CameraView extends FrameLayout { LOG.e(message, cause); // redirect - CameraException cameraException = new CameraException(message, cause); - for (CameraListener listener : mListeners) { - listener.onError(cameraException); - } + final CameraException cameraException = new CameraException(message, cause); + mUiHandler.post(new Runnable() { + @Override + public void run() { + for (CameraListener listener : mListeners) { + listener.onError(cameraException); + } + } + }); } } From 0778f97730389e6ec224bc2a8aba9a6604e341ce Mon Sep 17 00:00:00 2001 From: Tim H Date: Thu, 5 Oct 2017 18:57:23 +0200 Subject: [PATCH 03/14] apply remaining suggestions --- README.md | 13 ++++++++++++- .../otaliastudios/cameraview/CameraController.java | 12 +++++++++--- .../com/otaliastudios/cameraview/CameraView.java | 12 +++++------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1c3fa1ac..18373dc2 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,17 @@ camera.addCameraListener(new CameraListener() { */ @Override public void onExposureCorrectionChanged(float newValue, float[] bounds, PointF[] fingers) {} + + /** + * Notifies that an error occurred in any of the previously called methods. + * The default implementation will just throw the original exception again to prevent missing + * error handling. Override this method without calling the super method in order to implement + * custom error handling. + */ + @Override + public void onError(CameraException exception) { + throw exception; + } }); ``` @@ -525,7 +536,7 @@ These are still things that need to be done, off the top of my head: - [ ] add a `setPreferredAspectRatio` API to choose the capture size. Preview size will adapt, and then, if let free, the CameraView will adapt as well - [ ] animate grid lines similar to stock camera app - [ ] add onRequestPermissionResults for easy permission callback -- [ ] better error handling, maybe with a onError(e) method in the public listener, or have each public method return a boolean +- [ ] better error handling, maybe extending the current onError(e) method to handle more use cases, or have each public method return a boolean - [ ] decent code coverage ## Device-specific issues diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java index 3ddcf32f..78cc64f1 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java @@ -98,7 +98,9 @@ abstract class CameraController implements CameraPreview.SurfaceCallback { mCameraCallbacks.dispatchOnCameraOpened(mOptions); } catch (Exception e) { - mCameraCallbacks.onError("Error while starting the camera engine.", e); + CameraException cameraException = + new CameraException("Error while starting the camera engine.", e); + mCameraCallbacks.onError(cameraException); } } }); @@ -121,7 +123,9 @@ abstract class CameraController implements CameraPreview.SurfaceCallback { mCameraCallbacks.dispatchOnCameraClosed(); } catch (Exception e) { - mCameraCallbacks.onError("Error while stopping the camera engine.", e); + CameraException cameraException = + new CameraException("Error while stopping the camera engine.", e); + mCameraCallbacks.onError(cameraException); } } }); @@ -168,7 +172,9 @@ abstract class CameraController implements CameraPreview.SurfaceCallback { mCameraCallbacks.dispatchOnCameraOpened(mOptions); } catch (Exception e) { - mCameraCallbacks.onError("Error while restarting the camera engine.", e); + CameraException cameraException = + new CameraException("Error while restarting the camera engine.", e); + mCameraCallbacks.onError(cameraException); } } }); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 7e56dc54..d2f441f5 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -1308,7 +1308,7 @@ public class CameraView extends FrameLayout { void dispatchOnFocusEnd(@Nullable Gesture trigger, boolean success, PointF where); void dispatchOnZoomChanged(final float newValue, final PointF[] fingers); void dispatchOnExposureCorrectionChanged(float newValue, float[] bounds, PointF[] fingers); - void onError(String message, Exception cause); + void onError(CameraException exception); } private class Callbacks implements CameraCallbacks { @@ -1560,21 +1560,19 @@ public class CameraView extends FrameLayout { /** * Log and redirect the given error information to the CameraListeners. * - * @param message a message describing which camera action failed - * @param cause the original exception that caused the error + * @param exception the error cause */ @Override - public void onError(String message, Exception cause) { + public void onError(final CameraException exception) { // log - LOG.e(message, cause); + LOG.e(exception.getMessage(), exception.getCause()); // redirect - final CameraException cameraException = new CameraException(message, cause); mUiHandler.post(new Runnable() { @Override public void run() { for (CameraListener listener : mListeners) { - listener.onError(cameraException); + listener.onError(exception); } } }); From 48fb88ae0a4fa5580ab76c910204722e4ad7c331 Mon Sep 17 00:00:00 2001 From: Tim H Date: Tue, 10 Oct 2017 15:04:58 +0200 Subject: [PATCH 04/14] handle multiple error listeners --- README.md | 5 +++-- .../otaliastudios/cameraview/CameraException.java | 4 ++-- .../com/otaliastudios/cameraview/CameraView.java | 13 ++++++++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 18373dc2..650768cf 100644 --- a/README.md +++ b/README.md @@ -220,8 +220,9 @@ camera.addCameraListener(new CameraListener() { /** * Notifies that an error occurred in any of the previously called methods. * The default implementation will just throw the original exception again to prevent missing - * error handling. Override this method without calling the super method in order to implement - * custom error handling. + * error handling. As soon as this method was overridden at least once (without calling the + * super method or re-throwing the exception), the default behavior will be disabled. So pay + * attention to not swallowing any exceptions. */ @Override public void onError(CameraException exception) { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java index 8e28309b..ec587c78 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java @@ -5,7 +5,7 @@ package com.otaliastudios.cameraview; */ public class CameraException extends RuntimeException { - public CameraException(String message, Throwable cause) { + CameraException(String message, Throwable cause) { super(message, cause); } -} +} \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index d2f441f5..024de527 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -1571,8 +1571,19 @@ public class CameraView extends FrameLayout { mUiHandler.post(new Runnable() { @Override public void run() { + + // all error listeners will be called, but at most one of them should actually + // throw the exception + int count = 0; for (CameraListener listener : mListeners) { - listener.onError(exception); + try { + listener.onError(exception); + } catch (CameraException ce) { + count++; + } + } + if (count == mListeners.size()) { + throw exception; } } }); From 758f815aa1922b1678488dd2e5867319f45f2bf9 Mon Sep 17 00:00:00 2001 From: Tim H Date: Tue, 10 Oct 2017 15:59:01 +0200 Subject: [PATCH 05/14] introduce new camera exceptions --- .../CameraConfigurationFailedException.java | 16 ++++++++++++++++ .../cameraview/CameraUnavailableException.java | 17 +++++++++++++++++ .../cameraview/CapturingFailedException.java | 15 +++++++++++++++ .../CapturingImageFailedException.java | 17 +++++++++++++++++ .../CapturingPictureFailedException.java | 17 +++++++++++++++++ .../CapturingSnapshotFailedException.java | 17 +++++++++++++++++ .../CapturingVideoFailedException.java | 16 ++++++++++++++++ 7 files changed, 115 insertions(+) create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/CameraConfigurationFailedException.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/CameraUnavailableException.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/CapturingFailedException.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/CapturingImageFailedException.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/CapturingPictureFailedException.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/CapturingSnapshotFailedException.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/CapturingVideoFailedException.java diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraConfigurationFailedException.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraConfigurationFailedException.java new file mode 100644 index 00000000..ef035ed8 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraConfigurationFailedException.java @@ -0,0 +1,16 @@ +package com.otaliastudios.cameraview; + +/** + * An object of this class describes an error that occurred during the normal runtime of the camera. + * The previously started setting change failed, but the camera should be still available. + */ +public class CameraConfigurationFailedException extends CameraException { + + CameraConfigurationFailedException(String message) { + super(message); + } + + CameraConfigurationFailedException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraUnavailableException.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraUnavailableException.java new file mode 100644 index 00000000..e8637746 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraUnavailableException.java @@ -0,0 +1,17 @@ +package com.otaliastudios.cameraview; + +/** + * An object of this class describes an error that occurred during the normal runtime of the camera. + * It prevents the camera from being used. The cause may be temporary or permanent. You should + * restart the camera or deactivate any user interaction with the camera. + */ +public class CameraUnavailableException extends CameraException { + + CameraUnavailableException(String message) { + super(message); + } + + CameraUnavailableException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingFailedException.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingFailedException.java new file mode 100644 index 00000000..f5e8044c --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingFailedException.java @@ -0,0 +1,15 @@ +package com.otaliastudios.cameraview; + +/** + * An object of this class describes an error that occurred during the normal runtime of the camera. + * The previously started capturing failed, but the camera should be still available. + */ +public abstract class CapturingFailedException extends CameraException { + CapturingFailedException(String message) { + super(message); + } + + CapturingFailedException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingImageFailedException.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingImageFailedException.java new file mode 100644 index 00000000..58ee4b74 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingImageFailedException.java @@ -0,0 +1,17 @@ +package com.otaliastudios.cameraview; + +/** + * An object of this class describes an error that occurred during the normal runtime of the camera. + * The previously started image capturing failed (snapshot or "real picture"), but the camera should + * be still available. + */ +public abstract class CapturingImageFailedException extends CapturingFailedException { + + CapturingImageFailedException(String message) { + super(message); + } + + CapturingImageFailedException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingPictureFailedException.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingPictureFailedException.java new file mode 100644 index 00000000..ea6aa6fa --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingPictureFailedException.java @@ -0,0 +1,17 @@ +package com.otaliastudios.cameraview; + +/** + * An object of this class describes an error that occurred during the normal runtime of the camera. + * The previously started picture capturing failed, but the camera should be still available. + * This exception does not handle failed snapshots. + */ +public class CapturingPictureFailedException extends CapturingImageFailedException { + + CapturingPictureFailedException(String message) { + super(message); + } + + CapturingPictureFailedException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingSnapshotFailedException.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingSnapshotFailedException.java new file mode 100644 index 00000000..f19de20c --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingSnapshotFailedException.java @@ -0,0 +1,17 @@ +package com.otaliastudios.cameraview; + +/** + * An object of this class describes an error that occurred during the normal runtime of the camera. + * The previously started snapshot capturing failed, but the camera should be still available. + * This exception does not handle failed "real picture" capturing. + */ +public class CapturingSnapshotFailedException extends CapturingImageFailedException { + + CapturingSnapshotFailedException(String message) { + super(message); + } + + CapturingSnapshotFailedException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingVideoFailedException.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingVideoFailedException.java new file mode 100644 index 00000000..d9707b8d --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CapturingVideoFailedException.java @@ -0,0 +1,16 @@ +package com.otaliastudios.cameraview; + +/** + * An object of this class describes an error that occurred during the normal runtime of the camera. + * The previously started video capturing failed, but the camera should be still available. + */ +public class CapturingVideoFailedException extends CapturingFailedException { + + CapturingVideoFailedException(String message) { + super(message); + } + + CapturingVideoFailedException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file From d9141b6cdfa564a244301c9ddf7f287c308c7aa7 Mon Sep 17 00:00:00 2001 From: Tim H Date: Tue, 10 Oct 2017 15:59:39 +0200 Subject: [PATCH 06/14] implement camera exceptions for all existing exceptions in Camera1 --- .../com/otaliastudios/cameraview/Camera1.java | 19 ++++++++++++++----- .../cameraview/CameraController.java | 6 +++--- .../cameraview/CameraException.java | 6 +++++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index a5f603bd..15f095cf 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -75,8 +75,9 @@ class Camera1 extends CameraController { try { setup(); } catch (Exception e) { - LOG.w("onSurfaceAvailable:", "Exception while binding camera to preview.", e); - throw new RuntimeException(e); + CameraException cameraException = new CameraUnavailableException( + "onSurfaceAvailable: Exception while binding camera to preview.", e); + mCameraCallbacks.onError(cameraException); } } }); @@ -394,7 +395,9 @@ class Camera1 extends CameraController { if (mIsCapturingVideo) { // TODO: actually any call to getParameters() could fail while recording a video. // See. https://stackoverflow.com/questions/14941625/correct-handling-of-exception-getparameters-failed-empty-parameters - throw new IllegalStateException("Can't change video quality while recording a video."); + CameraException cameraException = new CameraConfigurationFailedException("Can't change video quality while recording a video."); + mCameraCallbacks.onError(cameraException); + return; } mVideoQuality = videoQuality; @@ -606,14 +609,20 @@ class Camera1 extends CameraController { mMediaRecorder.start(); return true; } catch (Exception e) { - LOG.e("Error while starting MediaRecorder. Swallowing.", e); + CameraException cameraException = + new CapturingVideoFailedException("Error while starting MediaRecorder. " + + "Swallowing.", e); + mCameraCallbacks.onError(cameraException); mVideoFile = null; mCamera.lock(); endVideo(); return false; } } else { - throw new IllegalStateException("Can't record video while session type is picture"); + CameraException cameraException = + new CapturingVideoFailedException("Can't record video while session type is picture"); + mCameraCallbacks.onError(cameraException); + return false; } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java index 78cc64f1..f274abc2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java @@ -99,7 +99,7 @@ abstract class CameraController implements CameraPreview.SurfaceCallback { } catch (Exception e) { CameraException cameraException = - new CameraException("Error while starting the camera engine.", e); + new CameraUnavailableException("Error while starting the camera engine.", e); mCameraCallbacks.onError(cameraException); } } @@ -124,7 +124,7 @@ abstract class CameraController implements CameraPreview.SurfaceCallback { } catch (Exception e) { CameraException cameraException = - new CameraException("Error while stopping the camera engine.", e); + new CameraUnavailableException("Error while stopping the camera engine.", e); mCameraCallbacks.onError(cameraException); } } @@ -173,7 +173,7 @@ abstract class CameraController implements CameraPreview.SurfaceCallback { } catch (Exception e) { CameraException cameraException = - new CameraException("Error while restarting the camera engine.", e); + new CameraUnavailableException("Error while restarting the camera engine.", e); mCameraCallbacks.onError(cameraException); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java index ec587c78..046e2dd8 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraException.java @@ -3,7 +3,11 @@ package com.otaliastudios.cameraview; /** * An object of this class describes an error that occurred during the normal runtime of the camera. */ -public class CameraException extends RuntimeException { +public abstract class CameraException extends RuntimeException { + + CameraException(String message) { + super(message); + } CameraException(String message, Throwable cause) { super(message, cause); From c253c4dc2be8e832d9ee7843055389677762c561 Mon Sep 17 00:00:00 2001 From: Tim H Date: Tue, 10 Oct 2017 16:15:26 +0200 Subject: [PATCH 07/14] extend error handling for the CameraView --- .../otaliastudios/cameraview/CameraView.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 024de527..e59ad523 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -557,9 +557,10 @@ public class CameraView extends FrameLayout { return; } } - LOG.e("Permission error:", "When the session type is set to video,", - "the RECORD_AUDIO permission should be added to the app manifest file."); - throw new IllegalStateException(CameraLogger.lastMessage); + CameraException cameraException = new CameraUnavailableException("Permission " + + "error: When the session type is set to video, the RECORD_AUDIO " + + "permission should be added to the app manifest file."); + mCameraCallbacks.onError(cameraException); } catch (PackageManager.NameNotFoundException e) { // Not possible. } @@ -1002,7 +1003,11 @@ public class CameraView extends FrameLayout { */ public void setJpegQuality(int jpegQuality) { if (jpegQuality <= 0 || jpegQuality > 100) { - throw new IllegalArgumentException("JPEG quality should be > 0 and <= 100"); + IllegalArgumentException illegalArgumentException = new + IllegalArgumentException("JPEG quality should be > 0 and <= 100"); + CameraException cameraException = new CameraConfigurationFailedException("Could not" + + " set JpegQuality", illegalArgumentException); + mCameraCallbacks.onError(cameraException); } mJpegQuality = jpegQuality; } @@ -1165,15 +1170,16 @@ public class CameraView extends FrameLayout { * so callers should ensure they have appropriate permissions to write to the file. * Recording will be automatically stopped after durationMillis, unless * {@link #stopCapturingVideo()} is not called meanwhile. + * Triggers error handler, if durationMillis is less than 500 milliseconds. * * @param file a file where the video will be saved * @param durationMillis video max duration - * - * @throws IllegalArgumentException if durationMillis is less than 500 milliseconds */ public void startCapturingVideo(File file, long durationMillis) { if (durationMillis < 500) { - throw new IllegalArgumentException("Video duration can't be < 500 milliseconds"); + CameraException cameraException = new CapturingVideoFailedException("Video duration" + + " can't be < 500 milliseconds"); + mCameraCallbacks.onError(cameraException); } startCapturingVideo(file); mUiHandler.postDelayed(new Runnable() { From 910f4da42c410f21de3366325c26ba7ec2ee32c5 Mon Sep 17 00:00:00 2001 From: Tim H Date: Tue, 10 Oct 2017 16:31:36 +0200 Subject: [PATCH 08/14] handle currently uncaught exception in startAutoFocus --- .../com/otaliastudios/cameraview/Camera1.java | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index 15f095cf..983e99fb 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -768,31 +768,42 @@ class Camera1 extends CameraController { boolean startAutoFocus(@Nullable final Gesture gesture, PointF point) { if (!isCameraAvailable()) return false; if (!mOptions.isAutoFocusSupported()) return false; - final PointF p = new PointF(point.x, point.y); // copy. - List meteringAreas2 = computeMeteringAreas(p.x, p.y); - List meteringAreas1 = meteringAreas2.subList(0, 1); - synchronized (mLock) { - // At this point we are sure that camera supports auto focus... right? Look at CameraView.onTouchEvent(). - Camera.Parameters params = mCamera.getParameters(); - int maxAF = params.getMaxNumFocusAreas(); - int maxAE = params.getMaxNumMeteringAreas(); - if (maxAF > 0) params.setFocusAreas(maxAF > 1 ? meteringAreas2 : meteringAreas1); - if (maxAE > 0) params.setMeteringAreas(maxAE > 1 ? meteringAreas2 : meteringAreas1); - params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); - mCamera.setParameters(params); - mCameraCallbacks.dispatchOnFocusStart(gesture, p); - // TODO this is not guaranteed to be called... Fix. - mCamera.autoFocus(new Camera.AutoFocusCallback() { - @Override - public void onAutoFocus(boolean success, Camera camera) { - // TODO lock auto exposure and white balance for a while - mCameraCallbacks.dispatchOnFocusEnd(gesture, success, p); - mHandler.get().removeCallbacks(mPostFocusResetRunnable); - mHandler.get().postDelayed(mPostFocusResetRunnable, mPostFocusResetDelay); - } - }); + + try { + final PointF p = new PointF(point.x, point.y); // copy. + List meteringAreas2 = computeMeteringAreas(p.x, p.y); + List meteringAreas1 = meteringAreas2.subList(0, 1); + synchronized (mLock) { + // At this point we are sure that camera supports auto focus... right? Look at CameraView.onTouchEvent(). + Camera.Parameters params = mCamera.getParameters(); + int maxAF = params.getMaxNumFocusAreas(); + int maxAE = params.getMaxNumMeteringAreas(); + if (maxAF > 0) params.setFocusAreas(maxAF > 1 ? meteringAreas2 : meteringAreas1); + if (maxAE > 0) params.setMeteringAreas(maxAE > 1 ? meteringAreas2 : meteringAreas1); + params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); + mCamera.setParameters(params); + mCameraCallbacks.dispatchOnFocusStart(gesture, p); + // TODO this is not guaranteed to be called... Fix. + mCamera.autoFocus(new Camera.AutoFocusCallback() { + @Override + public void onAutoFocus(boolean success, Camera camera) { + // TODO lock auto exposure and white balance for a while + mCameraCallbacks.dispatchOnFocusEnd(gesture, success, p); + mHandler.get().removeCallbacks(mPostFocusResetRunnable); + mHandler.get().postDelayed(mPostFocusResetRunnable, mPostFocusResetDelay); + } + }); + } + return true; + } + catch (Exception e) { + // at least setParameters may fail. + // TODO why does it fail and is it possible to prevent such errors? + CameraException cameraException = new CameraConfigurationFailedException("Failed to " + + "start auto focus.", e); + mCameraCallbacks.onError(cameraException); + return false; } - return true; } From 1f748c36aeaf8d84445348635be560f2173533dc Mon Sep 17 00:00:00 2001 From: Tim H Date: Tue, 10 Oct 2017 19:33:14 +0200 Subject: [PATCH 09/14] check for newer exceptions inside of the error listener handling --- .../java/com/otaliastudios/cameraview/Camera1.java | 4 ++-- .../com/otaliastudios/cameraview/CameraView.java | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index 983e99fb..d7fe074d 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -797,8 +797,8 @@ class Camera1 extends CameraController { return true; } catch (Exception e) { - // at least setParameters may fail. - // TODO why does it fail and is it possible to prevent such errors? + // at least getParameters and setParameters may fail. + // TODO why do they fail and is it possible to prevent such errors? CameraException cameraException = new CameraConfigurationFailedException("Failed to " + "start auto focus.", e); mCameraCallbacks.onError(cameraException); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index e59ad523..d767218e 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -1579,15 +1579,24 @@ public class CameraView extends FrameLayout { public void run() { // all error listeners will be called, but at most one of them should actually - // throw the exception + // throw an exception int count = 0; for (CameraListener listener : mListeners) { try { listener.onError(exception); } catch (CameraException ce) { - count++; + // if a custom error handler caused a new exception, we throw the new + // one instead of the original one + if (ce == exception) { + count++; + } + else { + throw ce; + } } } + + // the original exception is only thrown, if all existing listeners threw it if (count == mListeners.size()) { throw exception; } From 4b72db41d0565b8f9f36a5fbc0d5403b8dffdf08 Mon Sep 17 00:00:00 2001 From: Tim H Date: Tue, 10 Oct 2017 20:02:11 +0200 Subject: [PATCH 10/14] add error handling for capturing images --- .../com/otaliastudios/cameraview/Camera1.java | 133 ++++++++++-------- 1 file changed, 74 insertions(+), 59 deletions(-) diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index d7fe074d..b58a44f5 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -428,36 +428,43 @@ class Camera1 extends CameraController { if (!mOptions.isVideoSnapshotSupported()) return false; } - // Set boolean to wait for image callback - mIsCapturingImage = true; - final int exifRotation = computeExifRotation(); - final boolean exifFlip = computeExifFlip(); - final int sensorToDisplay = computeSensorToDisplayOffset(); - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - params.setRotation(exifRotation); - mCamera.setParameters(params); + try { + // Set boolean to wait for image callback + mIsCapturingImage = true; + final int exifRotation = computeExifRotation(); + final boolean exifFlip = computeExifFlip(); + final int sensorToDisplay = computeSensorToDisplayOffset(); + synchronized (mLock) { + Camera.Parameters params = mCamera.getParameters(); + params.setRotation(exifRotation); + 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(null, null, null, + new Camera.PictureCallback() { + @Override + public void onPictureTaken(byte[] data, final Camera camera) { + mIsCapturingImage = false; + mHandler.post(new Runnable() { + @Override + public void run() { + // This is needed, read somewhere in the docs. + camera.startPreview(); + } + }); + mCameraCallbacks.processImage(data, consistentWithView, exifFlip); + } + }); + return true; + } + catch (Exception e) { + CameraException cameraException = new CapturingPictureFailedException("Capturing a picture failed.", e); + mCameraCallbacks.onError(cameraException); + return false; } - // 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(null, null, null, - new Camera.PictureCallback() { - @Override - public void onPictureTaken(byte[] data, final Camera camera) { - mIsCapturingImage = false; - mHandler.post(new Runnable() { - @Override - public void run() { - // This is needed, read somewhere in the docs. - camera.startPreview(); - } - }); - mCameraCallbacks.processImage(data, consistentWithView, exifFlip); - } - }); - return true; } @@ -471,37 +478,45 @@ class Camera1 extends CameraController { capturePicture(); return false; } - mIsCapturingImage = true; - mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() { - @Override - public void onPreviewFrame(final byte[] data, Camera camera) { - // 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. - // Adding EXIF to a byte array, unfortunately, is hard. - Camera.Parameters params = mCamera.getParameters(); - final int sensorToDevice = computeExifRotation(); - final int sensorToDisplay = computeSensorToDisplayOffset(); - final boolean exifFlip = computeExifFlip(); - final boolean flip = sensorToDevice % 180 != 0; - final int preWidth = mPreviewSize.getWidth(); - final int preHeight = mPreviewSize.getHeight(); - final int postWidth = flip ? preHeight : preWidth; - final int postHeight = flip ? preWidth : preHeight; - final int format = params.getPreviewFormat(); - WorkerHandler.run(new Runnable() { - @Override - public void run() { - final boolean consistentWithView = (sensorToDevice + sensorToDisplay + 180) % 180 == 0; - byte[] rotatedData = RotationHelper.rotate(data, preWidth, preHeight, sensorToDevice); - YuvImage yuv = new YuvImage(rotatedData, format, postWidth, postHeight, null); - mCameraCallbacks.processSnapshot(yuv, consistentWithView, exifFlip); - mIsCapturingImage = false; - } - }); - } - }); - return true; + try { + mIsCapturingImage = true; + mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() { + @Override + public void onPreviewFrame(final byte[] data, Camera camera) { + // 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. + // Adding EXIF to a byte array, unfortunately, is hard. + Camera.Parameters params = mCamera.getParameters(); + final int sensorToDevice = computeExifRotation(); + final int sensorToDisplay = computeSensorToDisplayOffset(); + final boolean exifFlip = computeExifFlip(); + final boolean flip = sensorToDevice % 180 != 0; + final int preWidth = mPreviewSize.getWidth(); + final int preHeight = mPreviewSize.getHeight(); + final int postWidth = flip ? preHeight : preWidth; + final int postHeight = flip ? preWidth : preHeight; + final int format = params.getPreviewFormat(); + WorkerHandler.run(new Runnable() { + @Override + public void run() { + + final boolean consistentWithView = (sensorToDevice + sensorToDisplay + 180) % 180 == 0; + byte[] rotatedData = RotationHelper.rotate(data, preWidth, preHeight, sensorToDevice); + YuvImage yuv = new YuvImage(rotatedData, format, postWidth, postHeight, null); + mCameraCallbacks.processSnapshot(yuv, consistentWithView, exifFlip); + mIsCapturingImage = false; + } + }); + } + }); + return true; + } + catch (Exception e) { + CameraException cameraException = new CapturingSnapshotFailedException("Capturing a snapshot failed.", e); + mCameraCallbacks.onError(cameraException); + return false; + } } @Override From 98458f7cfc59493629b92dbde05da5228fdc862f Mon Sep 17 00:00:00 2001 From: Tim H Date: Tue, 10 Oct 2017 20:26:15 +0200 Subject: [PATCH 11/14] update README.md --- README.md | 86 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 650768cf..d706c4c0 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,74 @@ camera.postDelayed(new Runnable() { ``` +### Error Handling + +#### Default Handler +The default implementation will just throw all exceptions to prevent missing error handling. + +#### Basic Custom Handler +You can implement custom error handling by overriding the ``onError`` listener. +As soon as this method was overridden at least once (without calling the super method or re-throwing the exception), +the default behavior will be disabled. So pay attention to not swallowing any exceptions. + +```java +camera.addCameraListener(new CameraListener() { + @Override + public void onError(CameraException exception) { + throw exception; + } +}); +``` + +#### Advanced Custom Handler +Furthermore, you can distinguish different error types. E.g. some of them imply that the +CameraView should be disabled or restarted while others may be ignored in some use cases. + +```java +camera.addCameraListener(new CameraListener() { + @Override + public void onError(CameraException exception) { + if (exception instanceof CameraUnavailableException) { + // the exception prevents the camera from being used. The cause may be + // temporary or permanent. You should restart the camera or deactivate any + // user interaction with the camera. + } + else if (exception instanceof CameraConfigurationFailedException) { + // The previously started setting change failed, + // but the camera should be still available. + } + else if (exception instanceof CapturingFailedException) { + // The previously started capturing (of any kind) failed, but the camera + // should be still available. + + if (exception instanceof CapturingImageFailedException) { + // the previously started image capturing failed (snapshot or + // "real picture"), but the camera should be still available. + + if (exception instanceof CapturingPictureFailedException) { + // The previously started picture capturing failed, but the camera + // should be still available. This exception does not handle failed + // snapshots. + } + else if (exception instanceof CapturingSnapshotFailedException) { + // The previously started snapshot capturing failed, but the camera + // should be still available. This exception does not handle failed + // "real picture" capturing. + } + } + else if (exception instanceof CapturingVideoFailedException) { + // The previously started video capturing failed, but the camera + // should be still available. + } + } + else { + throw exception; + } + } +}); +``` + + ### Other camera events Make sure you can react to different camera events by setting up one or more `CameraListener` instances. All these are executed on the UI thread. @@ -215,20 +283,7 @@ camera.addCameraListener(new CameraListener() { * to be changed. This can be used, for example, to draw a seek bar. */ @Override - public void onExposureCorrectionChanged(float newValue, float[] bounds, PointF[] fingers) {} - - /** - * Notifies that an error occurred in any of the previously called methods. - * The default implementation will just throw the original exception again to prevent missing - * error handling. As soon as this method was overridden at least once (without calling the - * super method or re-throwing the exception), the default behavior will be disabled. So pay - * attention to not swallowing any exceptions. - */ - @Override - public void onError(CameraException exception) { - throw exception; - } - + public void onExposureCorrectionChanged(float newValue, float[] bounds, PointF[] fingers) {} }); ``` @@ -529,6 +584,7 @@ This is what was done since the library was forked. I have kept the original str - *Tests!* - *`CameraLogger` APIs for logging and bug reports* - *Better threading, start() in worker thread and callbacks in UI* +- *Custom error handling: prevent app crashes (caused by inevitable errors) by simply overriding a listener* These are still things that need to be done, off the top of my head: @@ -537,7 +593,7 @@ These are still things that need to be done, off the top of my head: - [ ] add a `setPreferredAspectRatio` API to choose the capture size. Preview size will adapt, and then, if let free, the CameraView will adapt as well - [ ] animate grid lines similar to stock camera app - [ ] add onRequestPermissionResults for easy permission callback -- [ ] better error handling, maybe extending the current onError(e) method to handle more use cases, or have each public method return a boolean +- [ ] better error handling, maybe extending the current onError(e) method to handle more use cases (e.g. the ones only caught by a boolean return value) - [ ] decent code coverage ## Device-specific issues From f9feb8a4d3555eb1d86f559057e5a2c8b79b2c78 Mon Sep 17 00:00:00 2001 From: Tim H Date: Tue, 10 Oct 2017 20:39:27 +0200 Subject: [PATCH 12/14] attach Android native error listener for the Camera1 API --- .../com/otaliastudios/cameraview/Camera1.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index b58a44f5..f3bf1b12 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -19,6 +19,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static android.hardware.Camera.CAMERA_ERROR_SERVER_DIED; +import static android.hardware.Camera.CAMERA_ERROR_UNKNOWN; + @SuppressWarnings("deprecation") class Camera1 extends CameraController { @@ -165,6 +168,38 @@ class Camera1 extends CameraController { if (collectCameraId()) { mCamera = Camera.open(mCameraId); + /** + * attach Android native error listener for the Camera1 API + * TODO it's not yet sure how the caught errors interact with the exceptions caught + * outside of the following handler. Furthermore, for most errors it's not known whether + * they are crucial or not. Therefore, such errors are handled as low-priority + * CameraConfigurationFailedExceptions for now. + */ + mCamera.setErrorCallback(new Camera.ErrorCallback() { + @Override + public void onError(int errorCode, Camera camera) { + + // extend error information by known error codes + CameraException cameraException; + if (errorCode == CAMERA_ERROR_SERVER_DIED) { + cameraException = new CameraUnavailableException( + "Media server died. In this case, the application must release the" + + " Camera object and instantiate a new one."); + } + else if (errorCode == CAMERA_ERROR_UNKNOWN) { + cameraException = new CameraConfigurationFailedException( + "Unspecified camera error."); + } + else { + cameraException = new CameraConfigurationFailedException( + "Received camera error code: " + errorCode); + } + + // redirect error + mCameraCallbacks.onError(cameraException); + } + }); + // Set parameters that might have been set before the camera was opened. synchronized (mLock) { LOG.i("onStart:", "Applying default parameters."); From d663d9817674641d915756aae4d8de949f2e0605 Mon Sep 17 00:00:00 2001 From: Tim H Date: Tue, 10 Oct 2017 20:56:00 +0200 Subject: [PATCH 13/14] handle setting changes that may fail by using setParameters() --- .../com/otaliastudios/cameraview/Camera1.java | 185 ++++++++++++------ 1 file changed, 120 insertions(+), 65 deletions(-) diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index f3bf1b12..8b85c1d7 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -286,14 +286,21 @@ class Camera1 extends CameraController { @Override void setLocation(Location location) { - Location oldLocation = mLocation; - mLocation = location; - if (isCameraAvailable()) { - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - if (mergeLocation(params, oldLocation)) mCamera.setParameters(params); + try { + Location oldLocation = mLocation; + mLocation = location; + if (isCameraAvailable()) { + synchronized (mLock) { + Camera.Parameters params = mCamera.getParameters(); + if (mergeLocation(params, oldLocation)) mCamera.setParameters(params); + } } } + catch (Exception e) { + CameraException cameraException = + new CameraConfigurationFailedException("Failed to set the location.", e); + mCameraCallbacks.onError(cameraException); + } } private boolean mergeLocation(Camera.Parameters params, Location oldLocation) { @@ -324,14 +331,22 @@ class Camera1 extends CameraController { @Override void setWhiteBalance(WhiteBalance whiteBalance) { - WhiteBalance old = mWhiteBalance; - mWhiteBalance = whiteBalance; - if (isCameraAvailable()) { - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - if (mergeWhiteBalance(params, old)) mCamera.setParameters(params); + try { + WhiteBalance old = mWhiteBalance; + mWhiteBalance = whiteBalance; + if (isCameraAvailable()) { + synchronized (mLock) { + Camera.Parameters params = mCamera.getParameters(); + if (mergeWhiteBalance(params, old)) mCamera.setParameters(params); + } } } + catch (Exception e) { + // TODO handle, !mergeWhiteBalance, too? + CameraException cameraException = + new CameraConfigurationFailedException("Failed to set the white balance.", e); + mCameraCallbacks.onError(cameraException); + } } private boolean mergeWhiteBalance(Camera.Parameters params, WhiteBalance oldWhiteBalance) { @@ -345,14 +360,22 @@ class Camera1 extends CameraController { @Override void setHdr(Hdr hdr) { - Hdr old = mHdr; - mHdr = hdr; - if (isCameraAvailable()) { - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - if (mergeHdr(params, old)) mCamera.setParameters(params); + try { + Hdr old = mHdr; + mHdr = hdr; + if (isCameraAvailable()) { + synchronized (mLock) { + Camera.Parameters params = mCamera.getParameters(); + if (mergeHdr(params, old)) mCamera.setParameters(params); + } } } + catch (Exception e) { + // TODO handle, !mergeHdr, too? + CameraException cameraException = + new CameraConfigurationFailedException("Failed to set hdr.", e); + mCameraCallbacks.onError(cameraException); + } } private boolean mergeHdr(Camera.Parameters params, Hdr oldHdr) { @@ -377,14 +400,22 @@ class Camera1 extends CameraController { @Override void setFlash(Flash flash) { - Flash old = mFlash; - mFlash = flash; - if (isCameraAvailable()) { - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - if (mergeFlash(params, old)) mCamera.setParameters(params); + try { + Flash old = mFlash; + mFlash = flash; + if (isCameraAvailable()) { + synchronized (mLock) { + Camera.Parameters params = mCamera.getParameters(); + if (mergeFlash(params, old)) mCamera.setParameters(params); + } } } + catch (Exception e) { + // TODO handle, !mergeFlash, too? + CameraException cameraException = + new CameraConfigurationFailedException("Failed to set flash.", e); + mCameraCallbacks.onError(cameraException); + } } @@ -427,31 +458,37 @@ class Camera1 extends CameraController { @Override void setVideoQuality(VideoQuality videoQuality) { - if (mIsCapturingVideo) { - // TODO: actually any call to getParameters() could fail while recording a video. - // See. https://stackoverflow.com/questions/14941625/correct-handling-of-exception-getparameters-failed-empty-parameters - CameraException cameraException = new CameraConfigurationFailedException("Can't change video quality while recording a video."); - mCameraCallbacks.onError(cameraException); - return; - } - mVideoQuality = videoQuality; - if (isCameraAvailable() && mSessionType == SessionType.VIDEO) { - // Change capture size to a size that fits the video aspect ratio. - Size oldSize = mCaptureSize; - mCaptureSize = computeCaptureSize(); - if (!mCaptureSize.equals(oldSize)) { - // New video quality triggers a new aspect ratio. - // Go on and see if preview size should change also. - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); - mCamera.setParameters(params); + try { + if (mIsCapturingVideo) { + // TODO: actually any call to getParameters() could fail while recording a video. + // See. https://stackoverflow.com/questions/14941625/correct-handling-of-exception-getparameters-failed-empty-parameters + throw new IllegalStateException("Can't change video quality while recording a video."); + } + + mVideoQuality = videoQuality; + if (isCameraAvailable() && mSessionType == SessionType.VIDEO) { + // Change capture size to a size that fits the video aspect ratio. + Size oldSize = mCaptureSize; + mCaptureSize = computeCaptureSize(); + if (!mCaptureSize.equals(oldSize)) { + // New video quality triggers a new aspect ratio. + // Go on and see if preview size should change also. + synchronized (mLock) { + Camera.Parameters params = mCamera.getParameters(); + params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); + mCamera.setParameters(params); + } + onSurfaceChanged(); } - onSurfaceChanged(); + LOG.i("setVideoQuality:", "captureSize:", mCaptureSize); + LOG.i("setVideoQuality:", "previewSize:", mPreviewSize); } - LOG.i("setVideoQuality:", "captureSize:", mCaptureSize); - LOG.i("setVideoQuality:", "previewSize:", mPreviewSize); + } + catch (Exception e) { + CameraException cameraException = + new CameraConfigurationFailedException("Failed to set video quality.", e); + mCameraCallbacks.onError(cameraException); } } @@ -782,32 +819,50 @@ class Camera1 extends CameraController { @Override boolean setZoom(float zoom) { - if (!isCameraAvailable()) return false; - if (!mOptions.isZoomSupported()) return false; - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - float max = params.getMaxZoom(); - params.setZoom((int) (zoom * max)); - mCamera.setParameters(params); + try { + if (!isCameraAvailable()) return false; + if (!mOptions.isZoomSupported()) return false; + synchronized (mLock) { + Camera.Parameters params = mCamera.getParameters(); + float max = params.getMaxZoom(); + params.setZoom((int) (zoom * max)); + mCamera.setParameters(params); + } + return true; } - return true; + catch (Exception e) { + CameraException cameraException = + new CameraConfigurationFailedException("Failed to set zoom.", e); + mCameraCallbacks.onError(cameraException); + return false; + } + } @Override boolean setExposureCorrection(float EVvalue) { - if (!isCameraAvailable()) return false; - if (!mOptions.isExposureCorrectionSupported()) return false; - float max = mOptions.getExposureCorrectionMaxValue(); - float min = mOptions.getExposureCorrectionMinValue(); - EVvalue = EVvalue < min ? min : EVvalue > max ? max : EVvalue; // cap - synchronized (mLock) { - Camera.Parameters params = mCamera.getParameters(); - int indexValue = (int) (EVvalue / params.getExposureCompensationStep()); - params.setExposureCompensation(indexValue); - mCamera.setParameters(params); + try { + if (!isCameraAvailable()) return false; + if (!mOptions.isExposureCorrectionSupported()) return false; + float max = mOptions.getExposureCorrectionMaxValue(); + float min = mOptions.getExposureCorrectionMinValue(); + EVvalue = EVvalue < min ? min : EVvalue > max ? max : EVvalue; // cap + synchronized (mLock) { + Camera.Parameters params = mCamera.getParameters(); + int indexValue = (int) (EVvalue / params.getExposureCompensationStep()); + params.setExposureCompensation(indexValue); + mCamera.setParameters(params); + } + return true; } - return true; + catch (Exception e) { + CameraException cameraException = + new CameraConfigurationFailedException("Failed to set exposure correction.", e); + mCameraCallbacks.onError(cameraException); + return false; + } + } // ----------------- From 3d82937795c4ef749c6791dcd9116f32ecb53a85 Mon Sep 17 00:00:00 2001 From: Tim H Date: Tue, 17 Oct 2017 18:37:28 +0200 Subject: [PATCH 14/14] prevent app crash caused by failing to reset the auto focus --- .../com/otaliastudios/cameraview/Camera1.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index 8b85c1d7..57a5f7c0 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -40,14 +40,24 @@ class Camera1 extends CameraController { private Runnable mPostFocusResetRunnable = new Runnable() { @Override public void run() { - if (!isCameraAvailable()) 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); + try { + if (!isCameraAvailable()) 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); + } + } + catch (Exception e) { + // at least setParameters may fail. + // problem may be device-specific to the Samsung Galaxy J5 + // TODO why does it fail occasionally and is it possible to prevent such errors? + CameraException cameraException = new CameraConfigurationFailedException("Failed to " + + "reset auto focus.", e); + mCameraCallbacks.onError(cameraException); } } };