From 5eff7e0eb4654f33de95dbfd9fe6ef1f102e1348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Nicol=C3=A1s=20Pina?= Date: Tue, 2 May 2017 12:47:44 +0200 Subject: [PATCH 1/7] Fix Android Studio preview error using isInEditMode. --- .../com/flurgle/camerakit/CameraView.java | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index e71b8055..cce818db 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -110,43 +110,49 @@ public class CameraView extends FrameLayout { setPermissions(mPermissions); setVideoQuality(mVideoQuality); - mDisplayOrientationDetector = new DisplayOrientationDetector(context) { - @Override - public void onDisplayOrientationChanged(int displayOrientation) { - mCameraImpl.setDisplayOrientation(displayOrientation); - mPreviewImpl.setDisplayOrientation(displayOrientation); - } - }; - - final FocusMarkerLayout focusMarkerLayout = new FocusMarkerLayout(getContext()); - addView(focusMarkerLayout); - focusMarkerLayout.setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent motionEvent) { - int action = motionEvent.getAction(); - if (motionEvent.getAction() == MotionEvent.ACTION_UP && mFocus == CameraKit.Constants.FOCUS_TAP_WITH_MARKER) { - focusMarkerLayout.focus(motionEvent.getX(), motionEvent.getY()); + if (!isInEditMode()) { + mDisplayOrientationDetector = new DisplayOrientationDetector(context) { + @Override + public void onDisplayOrientationChanged(int displayOrientation) { + mCameraImpl.setDisplayOrientation(displayOrientation); + mPreviewImpl.setDisplayOrientation(displayOrientation); } + }; - mPreviewImpl.getView().dispatchTouchEvent(motionEvent); - return true; - } - }); + final FocusMarkerLayout focusMarkerLayout = new FocusMarkerLayout(getContext()); + addView(focusMarkerLayout); + focusMarkerLayout.setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent motionEvent) { + int action = motionEvent.getAction(); + if (motionEvent.getAction() == MotionEvent.ACTION_UP && mFocus == CameraKit.Constants.FOCUS_TAP_WITH_MARKER) { + focusMarkerLayout.focus(motionEvent.getX(), motionEvent.getY()); + } + + mPreviewImpl.getView().dispatchTouchEvent(motionEvent); + return true; + } + }); + } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - mDisplayOrientationDetector.enable( - ViewCompat.isAttachedToWindow(this) - ? DisplayManagerCompat.getInstance(getContext()).getDisplay(Display.DEFAULT_DISPLAY) - : null - ); + if (!isInEditMode()) { + mDisplayOrientationDetector.enable( + ViewCompat.isAttachedToWindow(this) + ? DisplayManagerCompat.getInstance(getContext()).getDisplay(Display.DEFAULT_DISPLAY) + : null + ); + } } @Override protected void onDetachedFromWindow() { - mDisplayOrientationDetector.disable(); + if (!isInEditMode()) { + mDisplayOrientationDetector.disable(); + } super.onDetachedFromWindow(); } From 2ab90438519fae18a6f145139007c2aaf11b8b82 Mon Sep 17 00:00:00 2001 From: Yuval Peress Date: Sun, 11 Jun 2017 08:07:04 -0400 Subject: [PATCH 2/7] Migrate constructor code to init and use a single worker thread. - Moving the initialization code to an init function allows the creation of the CameraView in code using the single context constructor. - Creating a single HandlerThread will avoid having to create new threads for every operation. --- .../com/flurgle/camerakit/CameraView.java | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index e71b8055..44635311 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -8,6 +8,8 @@ import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.YuvImage; +import android.os.Handler; +import android.os.HandlerThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; @@ -37,6 +39,17 @@ import static com.flurgle.camerakit.CameraKit.Constants.PERMISSIONS_STRICT; public class CameraView extends FrameLayout { + private static Handler sWorkerHandler; + + static { + // Initialize a single worker thread. This can be static since only a single camera + // reference can exist at a time. + HandlerThread workerThread = new HandlerThread("CameraViewWorker"); + workerThread.setDaemon(true); + workerThread.start(); + sWorkerHandler = new Handler(workerThread.getLooper()); + } + @Facing private int mFacing; @@ -57,24 +70,30 @@ public class CameraView extends FrameLayout { @VideoQuality private int mVideoQuality; - private int mJpegQuality; private boolean mCropOutput; - private boolean mAdjustViewBounds; + private boolean mAdjustViewBounds; private CameraListenerMiddleWare mCameraListener; - private DisplayOrientationDetector mDisplayOrientationDetector; + private DisplayOrientationDetector mDisplayOrientationDetector; private CameraImpl mCameraImpl; + private PreviewImpl mPreviewImpl; public CameraView(@NonNull Context context) { super(context, null); + init(context, null); } @SuppressWarnings("all") public CameraView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); + init(context, attrs); + } + + @SuppressWarnings("WrongConstant") + private void init(@NonNull Context context, @Nullable AttributeSet attrs) { if (attrs != null) { TypedArray a = context.getTheme().obtainStyledAttributes( attrs, @@ -210,28 +229,27 @@ public class CameraView extends FrameLayout { break; } - new Thread(new Runnable() { + sWorkerHandler.post(new Runnable() { @Override public void run() { mCameraImpl.start(); } - }).start(); + }); } public void stop() { mCameraImpl.stop(); } - public void setFacing(@Facing - final int facing) { + public void setFacing(@Facing final int facing) { this.mFacing = facing; - new Thread(new Runnable() { + sWorkerHandler.post(new Runnable() { @Override public void run() { mCameraImpl.setFacing(facing); } - }).start(); + }); } public void setFlash(@Flash int flash) { From ee1eb88dfc06c610889461ef249c0a7d78b65cb9 Mon Sep 17 00:00:00 2001 From: Yuval Peress Date: Sun, 11 Jun 2017 08:07:53 -0400 Subject: [PATCH 3/7] Add getter for the facing field --- .../src/main/java/com/flurgle/camerakit/CameraView.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index 44635311..d60ea73f 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -241,6 +241,11 @@ public class CameraView extends FrameLayout { mCameraImpl.stop(); } + @Facing + public int getFacing() { + return mFacing; + } + public void setFacing(@Facing final int facing) { this.mFacing = facing; From ae77fb31ddea9098f8f4d565b3a418207169d209 Mon Sep 17 00:00:00 2001 From: Yuval Peress Date: Sun, 11 Jun 2017 08:10:53 -0400 Subject: [PATCH 4/7] Add check for isStarted to avoid multiple start/stop calls. --- .../java/com/flurgle/camerakit/CameraView.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index d60ea73f..cc3b530e 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -81,6 +81,8 @@ public class CameraView extends FrameLayout { private PreviewImpl mPreviewImpl; + private boolean mIsStarted; + public CameraView(@NonNull Context context) { super(context, null); init(context, null); @@ -121,6 +123,7 @@ public class CameraView extends FrameLayout { mPreviewImpl = new TextureViewPreview(context, this); mCameraImpl = new Camera1(mCameraListener, mPreviewImpl); + mIsStarted = false; setFacing(mFacing); setFlash(mFlash); setFocus(mFocus); @@ -202,7 +205,16 @@ public class CameraView extends FrameLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + public boolean isStarted() { + return mIsStarted; + } + public void start() { + if (mIsStarted) { + // Already started, do nothing. + return; + } + mIsStarted = true; int cameraCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA); int audioCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.RECORD_AUDIO); @@ -238,6 +250,11 @@ public class CameraView extends FrameLayout { } public void stop() { + if (!mIsStarted) { + // Already stopped, do nothing. + return; + } + mIsStarted = false; mCameraImpl.stop(); } From 3282a95c61a1b73389c4ed9e3dd2589012be0827 Mon Sep 17 00:00:00 2001 From: Yuval Peress Date: Sun, 11 Jun 2017 08:25:13 -0400 Subject: [PATCH 5/7] Add access to camera properties POJO Currently the only properties in interest (to me at least) are the vertical and horizontal view angles to allow for better AR mode. --- .../api16/com/flurgle/camerakit/Camera1.java | 14 ++++++ .../api21/com/flurgle/camerakit/Camera2.java | 44 +++++++++++++++++++ .../com/flurgle/camerakit/CameraImpl.java | 5 +++ .../flurgle/camerakit/CameraProperties.java | 14 ++++++ .../com/flurgle/camerakit/CameraView.java | 5 +++ 5 files changed, 82 insertions(+) create mode 100644 camerakit/src/main/base/com/flurgle/camerakit/CameraProperties.java diff --git a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java index ca9e1f48..84b0cc5b 100644 --- a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java +++ b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java @@ -6,6 +6,7 @@ import android.hardware.Camera; import android.media.CamcorderProfile; import android.media.MediaRecorder; import android.os.Handler; +import android.support.annotation.Nullable; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; @@ -36,6 +37,7 @@ public class Camera1 extends CameraImpl { private int mCameraId; private Camera mCamera; private Camera.Parameters mCameraParameters; + private CameraProperties mCameraProperties; private Camera.CameraInfo mCameraInfo; private Size mPreviewSize; private Size mCaptureSize; @@ -309,6 +311,12 @@ public class Camera1 extends CameraImpl { return mCamera != null; } + @Nullable + @Override + CameraProperties getCameraProperties() { + return mCameraProperties; + } + // Internal: private void openCamera() { @@ -319,6 +327,7 @@ public class Camera1 extends CameraImpl { mCamera = Camera.open(mCameraId); mCameraParameters = mCamera.getParameters(); + collectCameraProperties(); adjustCameraParameters(); mCamera.setDisplayOrientation(calculatePreviewRotation()); @@ -391,6 +400,11 @@ public class Camera1 extends CameraImpl { mCamera.setParameters(mCameraParameters); } + private void collectCameraProperties() { + mCameraProperties = new CameraProperties(mCameraParameters.getVerticalViewAngle(), + mCameraParameters.getHorizontalViewAngle()); + } + private TreeSet findCommonAspectRatios(List previewSizes, List captureSizes) { Set previewAspectRatios = new HashSet<>(); for (Camera.Size size : previewSizes) { diff --git a/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java b/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java index 6936a411..828c01bb 100644 --- a/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java +++ b/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java @@ -3,14 +3,18 @@ package com.flurgle.camerakit; import android.annotation.TargetApi; import android.content.Context; import android.graphics.ImageFormat; +import android.graphics.PointF; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.params.StreamConfigurationMap; +import android.support.annotation.Nullable; import android.util.Log; +import android.util.SizeF; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.TreeSet; @@ -28,6 +32,8 @@ class Camera2 extends CameraImpl { private Size mCaptureSize; private Size mPreviewSize; + private final HashMap mCameraPropertyMap = new HashMap<>(); + Camera2(CameraListener callback, PreviewImpl preview, Context context) { super(callback, preview); preview.setCallback(new PreviewImpl.Callback() { @@ -38,6 +44,36 @@ class Camera2 extends CameraImpl { }); mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); + + // Get all view angles + try { + for (final String cameraId : mCameraManager.getCameraIdList()) { + CameraCharacteristics characteristics = + mCameraManager.getCameraCharacteristics(cameraId); + @SuppressWarnings("ConstantConditions") + int orientation = characteristics.get(CameraCharacteristics.LENS_FACING); + if (orientation == CameraCharacteristics.LENS_FACING_BACK) { + float[] maxFocus = characteristics.get( + CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS); + if (maxFocus == null) { + continue; + } + SizeF size = characteristics.get( + CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE); + if (size == null) { + continue; + } + float w = size.getWidth(); + float h = size.getHeight(); + mCameraPropertyMap.put(cameraId, new CameraProperties( + (float) Math.toDegrees(2*Math.atan(size.getWidth()/(maxFocus[0]*2))), + (float) Math.toDegrees(2*Math.atan(size.getHeight()/(maxFocus[0]*2))) + )); + } + } + } catch (CameraAccessException e) { + throw new RuntimeException("Failed to get camera view angles", e); + } } // CameraImpl: @@ -196,6 +232,14 @@ class Camera2 extends CameraImpl { return mCamera != null; } + @Nullable + @Override + CameraProperties getCameraProperties() { + if (mCamera == null) { + return null; + } + return mCameraPropertyMap.get(mCamera.getId()); + } // Internal private List getAvailableCaptureResolutions() { diff --git a/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java b/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java index 90b5ed8b..5a5aecc8 100644 --- a/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java +++ b/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java @@ -1,5 +1,7 @@ package com.flurgle.camerakit; +import android.support.annotation.Nullable; + abstract class CameraImpl { protected final CameraListener mCameraListener; @@ -30,4 +32,7 @@ abstract class CameraImpl { abstract Size getPreviewResolution(); abstract boolean isCameraOpened(); + @Nullable + abstract CameraProperties getCameraProperties(); + } diff --git a/camerakit/src/main/base/com/flurgle/camerakit/CameraProperties.java b/camerakit/src/main/base/com/flurgle/camerakit/CameraProperties.java new file mode 100644 index 00000000..6af4d501 --- /dev/null +++ b/camerakit/src/main/base/com/flurgle/camerakit/CameraProperties.java @@ -0,0 +1,14 @@ +package com.flurgle.camerakit; + +/** + * Simple pojo containing various camera properties. + */ +public class CameraProperties { + public final float verticalViewingAngle; + public final float horizontalViewingAngle; + + public CameraProperties(float verticalViewingAngle, float horizontalViewingAngle) { + this.verticalViewingAngle = verticalViewingAngle; + this.horizontalViewingAngle = horizontalViewingAngle; + } +} diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index cc3b530e..a20adc03 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -258,6 +258,11 @@ public class CameraView extends FrameLayout { mCameraImpl.stop(); } + @Nullable + public CameraProperties getCameraProperties() { + return mCameraImpl.getCameraProperties(); + } + @Facing public int getFacing() { return mFacing; From 4a6ef7659b5c4cf88879254c612d5f43e662d519 Mon Sep 17 00:00:00 2001 From: Yuval Peress Date: Sun, 11 Jun 2017 12:09:20 -0400 Subject: [PATCH 6/7] Integrate Lifecycle events with CameraView for easier use. --- build.gradle | 1 + camerakit/build.gradle | 5 ++ .../com/flurgle/camerakit/CameraView.java | 50 ++++++++++++++++++- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 1ea4bd05..395f30b3 100644 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,7 @@ buildscript { allprojects { repositories { jcenter() + maven { url 'https://maven.google.com' } } } diff --git a/camerakit/build.gradle b/camerakit/build.gradle index 82ec2fca..6503553e 100644 --- a/camerakit/build.gradle +++ b/camerakit/build.gradle @@ -33,6 +33,11 @@ android { dependencies { compile 'com.android.support:appcompat-v7:25.2.0' + + + compile "android.arch.lifecycle:runtime:1.0.0-alpha1" + compile "android.arch.lifecycle:extensions:1.0.0-alpha1" + annotationProcessor "android.arch.lifecycle:compiler:1.0.0-alpha1" } apply from: 'https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle' diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index a20adc03..5c73df1a 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -2,6 +2,10 @@ package com.flurgle.camerakit; import android.Manifest; import android.app.Activity; +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.LifecycleObserver; +import android.arch.lifecycle.LifecycleOwner; +import android.arch.lifecycle.OnLifecycleEvent; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManager; @@ -37,7 +41,21 @@ import static com.flurgle.camerakit.CameraKit.Constants.PERMISSIONS_LAZY; import static com.flurgle.camerakit.CameraKit.Constants.PERMISSIONS_PICTURE; import static com.flurgle.camerakit.CameraKit.Constants.PERMISSIONS_STRICT; -public class CameraView extends FrameLayout { +/** + * The CameraView implements the LifecycleObserver interface for ease of use. To take advantage of + * this, simply call the following from any LifecycleOwner: + *
+ * {@code
+ * protected void onCreate(@Nullable Bundle savedInstanceState) {
+ *     super.onCreate(savedInstanceState);
+ *     setContentView(R.layout.my_view);
+ *     ...
+ *     getLifecycle().addObserver(mCameraView);
+ * }
+ * }
+ * 
+ */ +public class CameraView extends FrameLayout implements LifecycleObserver { private static Handler sWorkerHandler; @@ -81,6 +99,7 @@ public class CameraView extends FrameLayout { private PreviewImpl mPreviewImpl; + private Lifecycle mLifecycle; private boolean mIsStarted; public CameraView(@NonNull Context context) { @@ -154,6 +173,7 @@ public class CameraView extends FrameLayout { return true; } }); + mLifecycle = null; } @Override @@ -209,8 +229,34 @@ public class CameraView extends FrameLayout { return mIsStarted; } + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + if (mLifecycle != null && mLifecycle.getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { + // Potentially update the UI + if (enabled) { + start(); + } else { + stop(); + } + } + } + + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + public void onResume(LifecycleOwner owner) { + mLifecycle = owner.getLifecycle(); + start(); + } + + + @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) + public void onPause(LifecycleOwner owner) { + mLifecycle = owner.getLifecycle(); + stop(); + } + public void start() { - if (mIsStarted) { + if (mIsStarted || !isEnabled()) { // Already started, do nothing. return; } From eec74133eddbfe1481b8dd231dcec8bd116af998 Mon Sep 17 00:00:00 2001 From: Yuval Peress Date: Mon, 12 Jun 2017 03:17:35 -0400 Subject: [PATCH 7/7] Fix crash for picture callback after stop() The callback is using the captured camera object instead of checking the mCamera object to verify that we haven't released yet. Also, added a getter for the flash property. --- camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java | 4 +++- .../src/main/java/com/flurgle/camerakit/CameraView.java | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java index 84b0cc5b..956804f3 100644 --- a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java +++ b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java @@ -214,7 +214,9 @@ public class Camera1 extends CameraImpl { @Override public void onPictureTaken(byte[] data, Camera camera) { mCameraListener.onPictureTaken(data); - camera.startPreview(); + if (mCamera != null) { + mCamera.startPreview(); + } } }); break; diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index 5c73df1a..0e343171 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -330,6 +330,11 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mCameraImpl.setFlash(flash); } + @Flash + public int getFlash() { + return mFlash; + } + public void setFocus(@Focus int focus) { this.mFocus = focus; if (this.mFocus == CameraKit.Constants.FOCUS_TAP_WITH_MARKER) {