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/api16/com/flurgle/camerakit/Camera1.java b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java index ea542997..b6457083 100644 --- a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java +++ b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java @@ -7,6 +7,7 @@ import android.media.CamcorderProfile; import android.media.MediaRecorder; import android.os.Handler; import android.util.Log; +import android.support.annotation.Nullable; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; @@ -39,6 +40,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; @@ -212,7 +214,6 @@ public class Camera1 extends CameraImpl { void captureImage() { switch (mMethod) { case METHOD_STANDARD: - // Null check required for camera here as is briefly null when View is detached if (!capturingImage && mCamera != null) { @@ -221,10 +222,8 @@ public class Camera1 extends CameraImpl { mCamera.takePicture(null, null, null, new Camera.PictureCallback() { - @Override public void onPictureTaken(byte[] data, Camera camera) { - mCameraListener.onPictureTaken(data); // Reset capturing state to allow photos to be taken @@ -331,6 +330,12 @@ public class Camera1 extends CameraImpl { return mCamera != null; } + @Nullable + @Override + CameraProperties getCameraProperties() { + return mCameraProperties; + } + // Internal: private void openCamera() { @@ -341,6 +346,7 @@ public class Camera1 extends CameraImpl { mCamera = Camera.open(mCameraId); mCameraParameters = mCamera.getParameters(); + collectCameraProperties(); adjustCameraParameters(); mCamera.setDisplayOrientation(calculatePreviewRotation()); @@ -413,6 +419,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 b3ffafb9..5930e2e3 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -2,12 +2,18 @@ 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; 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; @@ -35,7 +41,32 @@ 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; + + 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 +88,33 @@ 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; + private Lifecycle mLifecycle; + private boolean mIsStarted; + 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, @@ -102,6 +142,7 @@ public class CameraView extends FrameLayout { mPreviewImpl = new TextureViewPreview(context, this); mCameraImpl = new Camera1(mCameraListener, mPreviewImpl); + mIsStarted = false; setFacing(mFacing); setFlash(mFlash); setFocus(mFocus); @@ -110,7 +151,7 @@ public class CameraView extends FrameLayout { setPermissions(mPermissions); setVideoQuality(mVideoQuality); - if(!isInEditMode()){ + if (!isInEditMode()) { mDisplayOrientationDetector = new DisplayOrientationDetector(context) { @Override public void onDisplayOrientationChanged(int displayOrientation) { @@ -118,29 +159,29 @@ public class CameraView extends FrameLayout { 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()); + 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; } - - mPreviewImpl.getView().dispatchTouchEvent(motionEvent); - return true; - } - }); + }); + } + mLifecycle = null; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - - if(!isInEditMode()) { + if (!isInEditMode()) { mDisplayOrientationDetector.enable( ViewCompat.isAttachedToWindow(this) ? DisplayManagerCompat.getInstance(getContext()) @@ -152,7 +193,9 @@ public class CameraView extends FrameLayout { @Override protected void onDetachedFromWindow() { - mDisplayOrientationDetector.disable(); + if (!isInEditMode()) { + mDisplayOrientationDetector.disable(); + } super.onDetachedFromWindow(); } @@ -189,7 +232,42 @@ public class CameraView extends FrameLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + public boolean isStarted() { + 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 || !isEnabled()) { + // Already started, do nothing. + return; + } + mIsStarted = true; int cameraCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA); int audioCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.RECORD_AUDIO); @@ -216,28 +294,42 @@ public class CameraView extends FrameLayout { break; } - new Thread(new Runnable() { + sWorkerHandler.post(new Runnable() { @Override public void run() { mCameraImpl.start(); } - }).start(); + }); } public void stop() { + if (!mIsStarted) { + // Already stopped, do nothing. + return; + } + mIsStarted = false; mCameraImpl.stop(); } - public void setFacing(@Facing - final int facing) { + @Nullable + public CameraProperties getCameraProperties() { + return mCameraImpl.getCameraProperties(); + } + + @Facing + public int getFacing() { + return mFacing; + } + + 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) { @@ -245,6 +337,11 @@ public class CameraView extends FrameLayout { 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) {