diff --git a/camerakit/build.gradle b/camerakit/build.gradle index 793d9410..e67385f7 100644 --- a/camerakit/build.gradle +++ b/camerakit/build.gradle @@ -7,14 +7,14 @@ ext { } android { - compileSdkVersion 25 - buildToolsVersion "25.0.2" + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - minSdkVersion 15 - targetSdkVersion 25 + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "1.0" + versionName "0.1" } buildTypes { release { @@ -40,5 +40,5 @@ dependencies { annotationProcessor 'android.arch.lifecycle:compiler:1.0.0-alpha1' } -apply from: 'https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle' +// apply from: 'https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle' diff --git a/camerakit/src/main/AndroidManifest.xml b/camerakit/src/main/AndroidManifest.xml index 31b72ed2..0b5e8472 100644 --- a/camerakit/src/main/AndroidManifest.xml +++ b/camerakit/src/main/AndroidManifest.xml @@ -2,19 +2,24 @@ package="com.flurgle.camerakit"> - - + + + + + android:required="false"/> + android:required="false"/> + android:required="false"/> - + \ No newline at end of file diff --git a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java index f964d7a5..6d3e9af4 100644 --- a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java +++ b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java @@ -5,7 +5,6 @@ import android.graphics.YuvImage; import android.hardware.Camera; import android.media.CamcorderProfile; import android.media.MediaRecorder; -import android.os.Build; import android.os.Handler; import android.util.Log; import android.support.annotation.Nullable; @@ -16,11 +15,8 @@ import android.view.View; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; +import java.util.Collections; import java.util.List; -import java.util.Set; -import java.util.TreeSet; import static com.flurgle.camerakit.CameraKit.Constants.FLASH_OFF; import static com.flurgle.camerakit.CameraKit.Constants.FOCUS_CONTINUOUS; @@ -30,7 +26,7 @@ import static com.flurgle.camerakit.CameraKit.Constants.METHOD_STANDARD; import static com.flurgle.camerakit.CameraKit.Constants.METHOD_STILL; @SuppressWarnings("deprecation") -public class Camera1 extends CameraImpl { +class Camera1 extends CameraImpl { private static final String TAG = Camera1.class.getSimpleName(); @@ -50,35 +46,26 @@ public class Camera1 extends CameraImpl { private Camera.AutoFocusCallback mAutofocusCallback; private boolean capturingImage = false; - private int mDisplayOrientation; + private int mDisplayOffset; + private int mDeviceOrientation; - @Facing - private int mFacing; - - @Flash - private int mFlash; - - @Focus - private int mFocus; - - @Method - private int mMethod; - - @Zoom - private int mZoom; - - @VideoQuality - private int mVideoQuality; + @Facing private int mFacing; + @Flash private int mFlash; + @Focus private int mFocus; + @Method private int mMethod; + @Zoom private int mZoom; + @VideoQuality private int mVideoQuality; private Handler mHandler = new Handler(); Camera1(CameraListener callback, PreviewImpl preview) { super(callback, preview); - preview.setCallback(new PreviewImpl.Callback() { + preview.setCallback(new PreviewImpl.OnSurfaceChangedCallback() { @Override public void onSurfaceChanged() { if (mCamera != null) { setupPreview(); + collectCameraSizes(); adjustCameraParameters(); } } @@ -105,9 +92,32 @@ public class Camera1 extends CameraImpl { releaseCamera(); } + /** + * Sets the output stream for our preview. + * To be called when preview is ready. + */ + private void setupPreview() { + try { + if (mPreview.getOutputClass() == SurfaceHolder.class) { + mCamera.setPreviewDisplay(mPreview.getSurfaceHolder()); + } else { + mCamera.setPreviewTexture(mPreview.getSurfaceTexture()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + @Override + void onDisplayOffset(int displayOrientation) { + // I doubt this will ever change. + this.mDisplayOffset = displayOrientation; + } + @Override - void setDisplayOrientation(int displayOrientation) { - this.mDisplayOrientation = displayOrientation; + void onDeviceOrientation(int deviceOrientation) { + this.mDeviceOrientation = deviceOrientation; } @Override @@ -217,19 +227,18 @@ public class Camera1 extends CameraImpl { case METHOD_STANDARD: // Null check required for camera here as is briefly null when View is detached if (!capturingImage && mCamera != null) { - // Set boolean to wait for image callback capturingImage = true; - + Camera.Parameters parameters = mCamera.getParameters(); + parameters.setRotation(computeExifOrientation()); + mCamera.setParameters(parameters); 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 capturingImage = false; - camera.startPreview(); } }); @@ -240,6 +249,7 @@ public class Camera1 extends CameraImpl { break; case METHOD_STILL: + // TODO: will calculateCaptureRotation work here? It's the only usage. Test... mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { @@ -270,79 +280,14 @@ public class Camera1 extends CameraImpl { mCameraListener.onVideoTaken(mVideoFile); } - // Code from SandriosCamera library - // https://github.com/sandrios/sandriosCamera/blob/master/sandriosCamera/src/main/java/com/sandrios/sandriosCamera/internal/utils/CameraHelper.java#L218 - public static Size getSizeWithClosestRatio(List sizes, int width, int height) - { - if (sizes == null) return null; - - double MIN_TOLERANCE = 100; - double targetRatio = (double) height / width; - Size optimalSize = null; - double minDiff = Double.MAX_VALUE; - - int targetHeight = height; - - for (Size size : sizes) { - if (size.getWidth() == width && size.getHeight() == height) - return size; - - double ratio = (double) size.getHeight() / size.getWidth(); - - if (Math.abs(ratio - targetRatio) < MIN_TOLERANCE) MIN_TOLERANCE = ratio; - else continue; - - if (Math.abs(size.getHeight() - targetHeight) < minDiff) { - optimalSize = size; - minDiff = Math.abs(size.getHeight() - targetHeight); - } - } - - if (optimalSize == null) { - minDiff = Double.MAX_VALUE; - for (Size size : sizes) { - if (Math.abs(size.getHeight() - targetHeight) < minDiff) { - optimalSize = size; - minDiff = Math.abs(size.getHeight() - targetHeight); - } - } - } - return optimalSize; - } - - List sizesFromList(List sizes) { - if (sizes == null) return null; - List result = new ArrayList<>(sizes.size()); - - for (Camera.Size size : sizes) { - result.add(new Size(size.width, size.height)); - } - - return result; - } - - // Code from SandriosCamera library - // https://github.com/sandrios/sandriosCamera/blob/master/sandriosCamera/src/main/java/com/sandrios/sandriosCamera/internal/manager/impl/Camera1Manager.java#L212 - void initResolutions() { - List previewSizes = sizesFromList(mCameraParameters.getSupportedPreviewSizes()); - List videoSizes = (Build.VERSION.SDK_INT > 10) ? sizesFromList(mCameraParameters.getSupportedVideoSizes()) : previewSizes; - - CamcorderProfile camcorderProfile = getCamcorderProfile(mVideoQuality); - - mCaptureSize = getSizeWithClosestRatio( - (videoSizes == null || videoSizes.isEmpty()) ? previewSizes : videoSizes, - camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight); - - mPreviewSize = getSizeWithClosestRatio(previewSizes, mCaptureSize.getWidth(), mCaptureSize.getHeight()); - } @Override - Size getCaptureResolution() { + Size getCaptureSize() { return mCaptureSize; } @Override - Size getPreviewResolution() { + Size getPreviewSize() { return mPreviewSize; } @@ -368,24 +313,12 @@ public class Camera1 extends CameraImpl { mCameraParameters = mCamera.getParameters(); collectCameraProperties(); + collectCameraSizes(); adjustCameraParameters(); - mCamera.setDisplayOrientation(calculatePreviewRotation()); - + mCamera.setDisplayOrientation(computeCameraToDisplayOffset()); mCameraListener.onCameraOpened(); } - private void setupPreview() { - try { - if (mPreview.getOutputClass() == SurfaceHolder.class) { - mCamera.setPreviewDisplay(mPreview.getSurfaceHolder()); - } else { - mCamera.setPreviewTexture(mPreview.getSurfaceTexture()); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - private void releaseCamera() { if (mCamera != null) { mCamera.release(); @@ -397,48 +330,75 @@ public class Camera1 extends CameraImpl { } } - private int calculatePreviewRotation() { + /** + * Returns how much should the sensor image be rotated before being shown. + * It is meant to be fed to Camera.setDisplayOrientation(). + */ + private int computeCameraToDisplayOffset() { if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - return ((mCameraInfo.orientation - mDisplayOrientation) + 360 + 180) % 360; + // or: (360 - ((info.orientation + displayOrientation) % 360)) % 360; + return ((mCameraInfo.orientation - mDisplayOffset) + 360 + 180) % 360; } else { - return (mCameraInfo.orientation - mDisplayOrientation + 360) % 360; + return (mCameraInfo.orientation - mDisplayOffset + 360) % 360; } } + /** + * Returns the orientation to be set as a exif tag. This is already managed by + * the camera APIs as long as you call {@link Camera.Parameters#setRotation(int)}. + */ + private int computeExifOrientation() { + return (mDeviceOrientation + mCameraInfo.orientation) % 360; + } + private int calculateCaptureRotation() { - int previewRotation = calculatePreviewRotation(); + int previewRotation = computeCameraToDisplayOffset(); if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { //Front is flipped - return (previewRotation + 180 + 2*mDisplayOrientation + 720) %360; + return (previewRotation + 180 + 2* mDisplayOffset + 720) % 360; } else { return previewRotation; } } - private void adjustCameraParameters() { - initResolutions(); - boolean invertPreviewSizes = mDisplayOrientation%180 != 0; - mPreview.setTruePreviewSize( - invertPreviewSizes? getPreviewResolution().getHeight() : getPreviewResolution().getWidth(), - invertPreviewSizes? getPreviewResolution().getWidth() : getPreviewResolution().getHeight() - ); + /** + * This is called either on cameraView.start(), or when the underlying surface changes. + * It is possible that in the first call the preview surface has not already computed its + * dimensions. + * But when it does, the {@link PreviewImpl.OnSurfaceChangedCallback} should be called, + * and this should be refreshed. + * + * + * TODO: either camera or video. + */ + private void collectCameraSizes() { + List previewSizes = sizesFromList(mCameraParameters.getSupportedPreviewSizes()); + List captureSizes = sizesFromList(mCameraParameters.getSupportedPictureSizes()); - mCameraParameters.setPreviewSize( - getPreviewResolution().getWidth(), - getPreviewResolution().getHeight() - ); + /* video stuff: CamcorderProfile camcorderProfile = getCamcorderProfile(mVideoQuality); + mCaptureSize = getSizeWithClosestRatio( + (videoSizes == null || videoSizes.isEmpty()) ? previewSizes : videoSizes, + camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight); */ - mCameraParameters.setPictureSize( - getCaptureResolution().getWidth(), - getCaptureResolution().getHeight() - ); - int rotation = calculateCaptureRotation(); - mCameraParameters.setRotation(rotation); + Size surfaceSize = mPreview.getSurfaceSize(); + mCaptureSize = Collections.max(captureSizes); + mPreviewSize = computePreviewSize(previewSizes, mCaptureSize, surfaceSize); + } + + private void adjustCameraParameters() { + boolean invertPreviewSizes = mDisplayOffset % 180 != 0; + mPreview.setDesiredSize( + invertPreviewSizes? getPreviewSize().getHeight() : getPreviewSize().getWidth(), + invertPreviewSizes? getPreviewSize().getWidth() : getPreviewSize().getHeight() + ); + mCameraParameters.setPreviewSize(getPreviewSize().getWidth(), getPreviewSize().getHeight()); + mCameraParameters.setPictureSize(getCaptureSize().getWidth(), getCaptureSize().getHeight()); + // int rotation = calculateCaptureRotation(); + // mCameraParameters.setRotation(rotation); setFocus(mFocus); setFlash(mFlash); - mCamera.setParameters(mCameraParameters); } @@ -447,28 +407,6 @@ public class Camera1 extends CameraImpl { mCameraParameters.getHorizontalViewAngle()); } - private TreeSet findCommonAspectRatios(List previewSizes, List captureSizes) { - Set previewAspectRatios = new HashSet<>(); - for (Camera.Size size : previewSizes) { - if (size.width >= CameraKit.Internal.screenHeight && size.height >= CameraKit.Internal.screenWidth) { - previewAspectRatios.add(AspectRatio.of(size.width, size.height)); - } - } - - Set captureAspectRatios = new HashSet<>(); - for (Camera.Size size : captureSizes) { - captureAspectRatios.add(AspectRatio.of(size.width, size.height)); - } - - TreeSet output = new TreeSet<>(); - for (AspectRatio aspectRatio : previewAspectRatios) { - if (captureAspectRatios.contains(aspectRatio)) { - output.add(aspectRatio); - } - } - - return output; - } private void initMediaRecorder() { mMediaRecorder = new MediaRecorder(); @@ -483,7 +421,7 @@ public class Camera1 extends CameraImpl { mVideoFile = new File(mPreview.getView().getContext().getExternalFilesDir(null), "video.mp4"); mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath()); - mMediaRecorder.setOrientationHint(calculatePreviewRotation()); + mMediaRecorder.setOrientationHint(computeCameraToDisplayOffset()); mMediaRecorder.setVideoSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); } @@ -644,7 +582,7 @@ public class Camera1 extends CameraImpl { if (camera != null) { camera.cancelAutoFocus(); Camera.Parameters params = camera.getParameters(); - if (params.getFocusMode() != Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) { + if (!params.getFocusMode().equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); params.setFocusAreas(null); params.setMeteringAreas(null); @@ -684,4 +622,53 @@ public class Camera1 extends CameraImpl { } } + + + // Size static stuff. + + /** + * Returns a list of {@link Size} out of Camera.Sizes. + */ + private static List sizesFromList(List sizes) { + if (sizes == null) return null; + List result = new ArrayList<>(sizes.size()); + for (Camera.Size size : sizes) { + result.add(new Size(size.width, size.height)); + } + return result; + } + + + /** + * Policy here is to return a size that is big enough to fit the surface size, + * and possibly consistent with the capture size. + * @param sizes list of possible sizes + * @param captureSize size representing desired aspect ratio + * @param surfaceSize size representing the current surface size. We'd like the returned value to be bigger. + * @return chosen size + */ + private static Size computePreviewSize(List sizes, Size captureSize, Size surfaceSize) { + if (sizes == null) return null; + + List consistent = new ArrayList<>(5); + List bigEnoughAndConsistent = new ArrayList<>(5); + + final AspectRatio targetRatio = AspectRatio.of(captureSize.getWidth(), captureSize.getHeight()); + final Size targetSize = captureSize; + for (Size size : sizes) { + AspectRatio ratio = AspectRatio.of(size.getWidth(), size.getHeight()); + if (ratio.equals(targetRatio)) { + consistent.add(size); + if (size.getHeight() >= targetSize.getHeight() && size.getWidth() >= targetSize.getWidth()) { + bigEnoughAndConsistent.add(size); + } + } + } + + if (bigEnoughAndConsistent.size() > 0) return Collections.min(bigEnoughAndConsistent); + if (consistent.size() > 0) return Collections.max(consistent); + return Collections.max(sizes); + } + + } diff --git a/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java b/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java index 828c01bb..c4a37257 100644 --- a/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java +++ b/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java @@ -3,7 +3,6 @@ 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; @@ -36,7 +35,7 @@ class Camera2 extends CameraImpl { Camera2(CameraListener callback, PreviewImpl preview, Context context) { super(callback, preview); - preview.setCallback(new PreviewImpl.Callback() { + preview.setCallback(new PreviewImpl.OnSurfaceChangedCallback() { @Override public void onSurfaceChanged() { @@ -89,7 +88,12 @@ class Camera2 extends CameraImpl { } @Override - void setDisplayOrientation(int displayOrientation) { + void onDisplayOffset(int displayOrientation) { + + } + + @Override + void onDeviceOrientation(int deviceOrientation) { } @@ -176,7 +180,7 @@ class Camera2 extends CameraImpl { } @Override - Size getCaptureResolution() { + Size getCaptureSize() { if (mCaptureSize == null && mCameraCharacteristics != null) { TreeSet sizes = new TreeSet<>(); sizes.addAll(getAvailableCaptureResolutions()); @@ -202,7 +206,7 @@ class Camera2 extends CameraImpl { } @Override - Size getPreviewResolution() { + Size getPreviewSize() { if (mPreviewSize == null && mCameraCharacteristics != null) { TreeSet sizes = new TreeSet<>(); sizes.addAll(getAvailablePreviewResolutions()); diff --git a/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java b/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java index 5a5aecc8..72c4e2f7 100644 --- a/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java +++ b/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java @@ -15,7 +15,8 @@ abstract class CameraImpl { abstract void start(); abstract void stop(); - abstract void setDisplayOrientation(int displayOrientation); + abstract void onDisplayOffset(int displayOrientation); + abstract void onDeviceOrientation(int deviceOrientation); abstract void setFacing(@Facing int facing); abstract void setFlash(@Flash int flash); @@ -28,8 +29,8 @@ abstract class CameraImpl { abstract void startVideo(); abstract void endVideo(); - abstract Size getCaptureResolution(); - abstract Size getPreviewResolution(); + abstract Size getCaptureSize(); + abstract Size getPreviewSize(); abstract boolean isCameraOpened(); @Nullable diff --git a/camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java b/camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java index 6afcb182..81e50edd 100644 --- a/camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java +++ b/camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java @@ -7,20 +7,22 @@ import android.view.View; abstract class PreviewImpl { - interface Callback { + interface OnSurfaceChangedCallback { void onSurfaceChanged(); } - private Callback mCallback; + private OnSurfaceChangedCallback mOnSurfaceChangedCallback; - private int mWidth; - private int mHeight; + // As far as I can see, these are the view/surface dimensions. + private int mSurfaceWidth; + private int mSurfaceHeight; - protected int mTrueWidth; - protected int mTrueHeight; + // As far as I can see, these are the actual preview dimensions, as set in CameraParameters. + private int mDesiredWidth; + private int mDesiredHeight; - void setCallback(Callback callback) { - mCallback = callback; + void setCallback(OnSurfaceChangedCallback callback) { + mOnSurfaceChangedCallback = callback; } abstract Surface getSurface(); @@ -29,12 +31,13 @@ abstract class PreviewImpl { abstract Class getOutputClass(); - abstract void setDisplayOrientation(int displayOrientation); + protected void onDisplayOffset(int displayOrientation) {} + protected void onDeviceOrientation(int deviceOrientation) {} abstract boolean isReady(); protected void dispatchSurfaceChanged() { - mCallback.onSurfaceChanged(); + mOnSurfaceChangedCallback.onSurfaceChanged(); } SurfaceHolder getSurfaceHolder() { @@ -45,30 +48,37 @@ abstract class PreviewImpl { return null; } - void setSize(int width, int height) { - mWidth = width; - mHeight = height; - - // Refresh true preview size to adjust scaling - setTruePreviewSize(mTrueWidth, mTrueHeight); + // As far as I can see, these are the view/surface dimensions. + // This is called by subclasses. + protected void setSurfaceSize(int width, int height) { + this.mSurfaceWidth = width; + this.mSurfaceHeight = height; + refreshScale(); // Refresh true preview size to adjust scaling } - int getWidth() { - return mWidth; + // As far as I can see, these are the actual preview dimensions, as set in CameraParameters. + // This is called by the CameraImpl. + void setDesiredSize(int width, int height) { + this.mDesiredWidth = width; + this.mDesiredHeight = height; + refreshScale(); } - int getHeight() { - return mHeight; + Size getSurfaceSize() { + return new Size(mSurfaceWidth, mSurfaceHeight); } - void setTruePreviewSize(final int width, final int height) { - this.mTrueWidth = width; - this.mTrueHeight = height; + /** + * As far as I can see, this extends either width or height of the surface, + * to match the desired aspect ratio. + * This means that the external part of the surface will be cropped by the outer view. + */ + protected void refreshScale() { getView().post(new Runnable() { @Override public void run() { - if (width != 0 && height != 0) { - AspectRatio aspectRatio = AspectRatio.of(width, height); + if (mDesiredWidth != 0 && mDesiredHeight != 0) { + AspectRatio aspectRatio = AspectRatio.of(mDesiredWidth, mDesiredHeight); int targetHeight = (int) (getView().getWidth() * aspectRatio.toFloat()); float scaleY; if (getView().getHeight() > 0) { @@ -88,13 +98,4 @@ abstract class PreviewImpl { } }); } - - int getTrueWidth() { - return mTrueWidth; - } - - int getTrueHeight() { - return mTrueHeight; - } - } diff --git a/camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java b/camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java index 4f2e226d..b717c1d1 100644 --- a/camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java +++ b/camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java @@ -13,8 +13,6 @@ class TextureViewPreview extends PreviewImpl { private final TextureView mTextureView; - private int mDisplayOrientation; - TextureViewPreview(Context context, ViewGroup parent) { final View view = View.inflate(context, R.layout.texture_view, parent); mTextureView = (TextureView) view.findViewById(R.id.texture_view); @@ -22,20 +20,20 @@ class TextureViewPreview extends PreviewImpl { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - setSize(width, height); + setSurfaceSize(width, height); dispatchSurfaceChanged(); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - setSize(width, height); + setSurfaceSize(width, height); dispatchSurfaceChanged(); - setTruePreviewSize(mTrueWidth, mTrueHeight); + refreshScale(); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - setSize(0, 0); + setSurfaceSize(0, 0); return true; } @@ -45,11 +43,6 @@ class TextureViewPreview extends PreviewImpl { }); } - @Override - void setCallback(Callback callback) { - super.setCallback(callback); - } - @Override Surface getSurface() { return new Surface(mTextureView.getSurfaceTexture()); @@ -65,21 +58,11 @@ class TextureViewPreview extends PreviewImpl { return SurfaceTexture.class; } - @Override - void setDisplayOrientation(int displayOrientation) { - mDisplayOrientation = displayOrientation; - } - @Override boolean isReady() { return mTextureView.getSurfaceTexture() != null; } - @Override - protected void dispatchSurfaceChanged() { - super.dispatchSurfaceChanged(); - } - @Override SurfaceTexture getSurfaceTexture() { return mTextureView.getSurfaceTexture(); @@ -87,8 +70,8 @@ class TextureViewPreview extends PreviewImpl { @TargetApi(15) @Override - void setTruePreviewSize(int width, int height) { - super.setTruePreviewSize(width, height); + void setDesiredSize(int width, int height) { + super.setDesiredSize(width, height); if (mTextureView.getSurfaceTexture() != null) { mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height); } diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java index bce758db..033ae806 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java @@ -4,16 +4,9 @@ import android.content.res.Resources; public class CameraKit { - static class Internal { - - static final int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels; - static final int screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels; - - } - public static class Constants { - public static final int PERMISSION_REQUEST_CAMERA = 16; + public static final int PERMISSION_REQUEST_CODE = 16; public static final int FACING_BACK = 0; public static final int FACING_FRONT = 1; @@ -35,9 +28,21 @@ public class CameraKit { public static final int METHOD_STILL = 1; public static final int METHOD_SPEED = 2; - public static final int PERMISSIONS_STRICT = 0; - public static final int PERMISSIONS_LAZY = 1; - public static final int PERMISSIONS_PICTURE = 2; + + /** + * This means the app will be requesting Camera and Audio permissions on Marshmallow+. + * With this configuration, it is mandatory that the developer adds + * + * to its manifest, or the app will crash. + */ + public static final int PERMISSIONS_VIDEO = 0; + + /** + * This means the app will be requesting the Camera permissions on Marshmallow+. + * On older APIs, this is not important, since the Camera permission is already + * declared in the manifest by the library. + */ + public static final int PERMISSIONS_PICTURE = 1; public static final int VIDEO_QUALITY_480P = 0; public static final int VIDEO_QUALITY_720P = 1; @@ -56,7 +61,7 @@ public class CameraKit { static final int DEFAULT_FOCUS = Constants.FOCUS_CONTINUOUS; static final int DEFAULT_ZOOM = Constants.ZOOM_OFF; static final int DEFAULT_METHOD = Constants.METHOD_STANDARD; - static final int DEFAULT_PERMISSIONS = Constants.PERMISSIONS_STRICT; + static final int DEFAULT_PERMISSIONS = Constants.PERMISSIONS_PICTURE; static final int DEFAULT_VIDEO_QUALITY = Constants.VIDEO_QUALITY_480P; static final int DEFAULT_JPEG_QUALITY = 100; diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index bba800e6..6bcea31c 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -8,6 +8,7 @@ import android.arch.lifecycle.LifecycleOwner; import android.arch.lifecycle.OnLifecycleEvent; import android.content.Context; import android.content.ContextWrapper; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.graphics.Rect; @@ -18,12 +19,12 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; -import android.support.v4.hardware.display.DisplayManagerCompat; -import android.support.v4.view.ViewCompat; import android.util.AttributeSet; +import android.util.Log; import android.view.Display; import android.view.MotionEvent; import android.view.View; +import android.view.WindowManager; import android.widget.FrameLayout; import java.io.ByteArrayOutputStream; @@ -38,9 +39,8 @@ import static com.flurgle.camerakit.CameraKit.Constants.FLASH_OFF; import static com.flurgle.camerakit.CameraKit.Constants.FLASH_ON; import static com.flurgle.camerakit.CameraKit.Constants.FLASH_TORCH; import static com.flurgle.camerakit.CameraKit.Constants.METHOD_STANDARD; -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; +import static com.flurgle.camerakit.CameraKit.Constants.PERMISSIONS_VIDEO; /** * The CameraView implements the LifecycleObserver interface for ease of use. To take advantage of @@ -58,46 +58,37 @@ import static com.flurgle.camerakit.CameraKit.Constants.PERMISSIONS_STRICT; */ public class CameraView extends FrameLayout implements LifecycleObserver { + private final static String TAG = CameraView.class.getSimpleName(); + 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()); + private static Handler getWorkerHandler() { + if (sWorkerHandler == null) { + HandlerThread workerThread = new HandlerThread("CameraViewWorker"); + workerThread.setDaemon(true); + workerThread.start(); + sWorkerHandler = new Handler(workerThread.getLooper()); + } + return sWorkerHandler; } - @Facing - private int mFacing; - - @Flash - private int mFlash; - - @Focus - private int mFocus; - - @Method - private int mMethod; - - @Zoom - private int mZoom; - - @Permissions - private int mPermissions; + private void run(Runnable runnable) { + getWorkerHandler().post(runnable); + } - @VideoQuality - private int mVideoQuality; + @Facing private int mFacing; + @Flash private int mFlash; + @Focus private int mFocus; + @Method private int mMethod; + @Zoom private int mZoom; + @Permissions private int mPermissions; + @VideoQuality private int mVideoQuality; private int mJpegQuality; private boolean mCropOutput; - private boolean mAdjustViewBounds; private CameraListenerMiddleWare mCameraListener; - - private DisplayOrientationDetector mDisplayOrientationDetector; + private OrientationHelper mOrientationHelper; private CameraImpl mCameraImpl; - private PreviewImpl mPreviewImpl; private Lifecycle mLifecycle; @@ -108,7 +99,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver { init(context, null); } - @SuppressWarnings("all") public CameraView(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context, attrs); @@ -117,11 +107,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { @SuppressWarnings("WrongConstant") private void init(@NonNull Context context, @Nullable AttributeSet attrs) { if (attrs != null) { - TypedArray a = context.getTheme().obtainStyledAttributes( - attrs, - R.styleable.CameraView, - 0, 0); - + TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CameraView, 0, 0); try { mFacing = a.getInteger(R.styleable.CameraView_ckFacing, CameraKit.Defaults.DEFAULT_FACING); mFlash = a.getInteger(R.styleable.CameraView_ckFlash, CameraKit.Defaults.DEFAULT_FLASH); @@ -149,15 +135,21 @@ public class CameraView extends FrameLayout implements LifecycleObserver { setFocus(mFocus); setMethod(mMethod); setZoom(mZoom); - setPermissions(mPermissions); + setPermissionPolicy(mPermissions); setVideoQuality(mVideoQuality); if (!isInEditMode()) { - mDisplayOrientationDetector = new DisplayOrientationDetector(context) { + mOrientationHelper = new OrientationHelper(context) { @Override - public void onDisplayOrientationChanged(int displayOrientation) { - mCameraImpl.setDisplayOrientation(displayOrientation); - mPreviewImpl.setDisplayOrientation(displayOrientation); + public void onDisplayOffsetChanged(int displayOffset) { + mCameraImpl.onDisplayOffset(displayOffset); + mPreviewImpl.onDisplayOffset(displayOffset); + } + + @Override + protected void onDeviceOrientationChanged(int deviceOrientation) { + mCameraImpl.onDeviceOrientation(deviceOrientation); + mPreviewImpl.onDeviceOrientation(deviceOrientation); } }; @@ -167,7 +159,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { @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) { + if (action == MotionEvent.ACTION_UP && mFocus == CameraKit.Constants.FOCUS_TAP_WITH_MARKER) { focusMarkerLayout.focus(motionEvent.getX(), motionEvent.getY()); } @@ -182,55 +174,69 @@ public class CameraView extends FrameLayout implements LifecycleObserver { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); + Log.e(TAG, "onAttachedToWindow"); if (!isInEditMode()) { - mDisplayOrientationDetector.enable( - ViewCompat.isAttachedToWindow(this) - ? DisplayManagerCompat.getInstance(getContext()) - .getDisplay(Display.DEFAULT_DISPLAY) - : null - ); + Log.e(TAG, "onAttachedToWindow: enabling orientation detector."); + WindowManager manager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); + Display display = manager.getDefaultDisplay(); + mOrientationHelper.enable(display); } } @Override protected void onDetachedFromWindow() { + Log.e(TAG, "onDetachedFromWindow"); if (!isInEditMode()) { - mDisplayOrientationDetector.disable(); + Log.e(TAG, "onDetachedFromWindow: disabling orientation detector."); + mOrientationHelper.disable(); } super.onDetachedFromWindow(); } + + /** + * If {@link CameraView#mAdjustViewBounds} was set AND one of the dimensions is set to WRAP_CONTENT, + * CameraView will adjust that dimensions to fit the preview aspect ratio as returned by + * {@link #getPreviewSize()}. + * + * If this is not true, the surface will adapt to the dimension specified in the layout file. + * Having fixed dimensions means that, very likely, what the user sees is different from what + * the final picture will be. This is also due to what happens in {@link PreviewImpl#refreshScale()}. + */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (mAdjustViewBounds) { - Size previewSize = getPreviewSize(); - if (previewSize != null) { - if (getLayoutParams().width == LayoutParams.WRAP_CONTENT) { - int height = MeasureSpec.getSize(heightMeasureSpec); - float ratio = (float) height / (float) previewSize.getWidth(); - int width = (int) (previewSize.getHeight() * ratio); - super.onMeasure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - heightMeasureSpec - ); - return; - } else if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) { - int width = MeasureSpec.getSize(widthMeasureSpec); - float ratio = (float) width / (float) previewSize.getHeight(); - int height = (int) (previewSize.getWidth() * ratio); - super.onMeasure( - widthMeasureSpec, - MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) - ); - return; - } - } else { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - return; - } + if (!mAdjustViewBounds) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + + Size previewSize = getPreviewSize(); + if (previewSize == null) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (getLayoutParams().width == LayoutParams.WRAP_CONTENT) { + int height = MeasureSpec.getSize(heightMeasureSpec); + float ratio = (float) height / (float) previewSize.getWidth(); + int width = (int) (previewSize.getHeight() * ratio); + super.onMeasure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + heightMeasureSpec + ); + + } else if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) { + int width = MeasureSpec.getSize(widthMeasureSpec); + float ratio = (float) width / (float) previewSize.getHeight(); + int height = (int) (previewSize.getWidth() * ratio); + super.onMeasure( + widthMeasureSpec, + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) + ); + + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } } public boolean isStarted() { @@ -263,30 +269,29 @@ public class CameraView extends FrameLayout implements LifecycleObserver { stop(); } + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + public void onDestroy(LifecycleOwner owner) { + mLifecycle = owner.getLifecycle(); + // This might be useless, but no time to think about it now. + sWorkerHandler = null; + } + public void start() { if (mIsStarted || !isEnabled()) { // Already started, do nothing. return; } - mIsStarted = true; + checkPermissionPolicyOrThrow(); int cameraCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA); int audioCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.RECORD_AUDIO); - switch (mPermissions) { - case PERMISSIONS_STRICT: + case PERMISSIONS_VIDEO: if (cameraCheck != PackageManager.PERMISSION_GRANTED || audioCheck != PackageManager.PERMISSION_GRANTED) { requestPermissions(true, true); return; } break; - case PERMISSIONS_LAZY: - if (cameraCheck != PackageManager.PERMISSION_GRANTED) { - requestPermissions(true, true); - return; - } - break; - case PERMISSIONS_PICTURE: if (cameraCheck != PackageManager.PERMISSION_GRANTED) { requestPermissions(true, false); @@ -295,7 +300,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver { break; } - sWorkerHandler.post(new Runnable() { + mIsStarted = true; + run(new Runnable() { @Override public void run() { mCameraImpl.start(); @@ -325,7 +331,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { public void setFacing(@Facing final int facing) { this.mFacing = facing; - sWorkerHandler.post(new Runnable() { + run(new Runnable() { @Override public void run() { mCameraImpl.setFacing(facing); @@ -363,8 +369,13 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mCameraImpl.setZoom(mZoom); } - public void setPermissions(@Permissions int permissions) { + /** + * Sets permission policy. + * @param permissions desired policy, either picture or video. + */ + public void setPermissionPolicy(@Permissions int permissions) { this.mPermissions = permissions; + checkPermissionPolicyOrThrow(); } public void setVideoQuality(@VideoQuality int videoQuality) { @@ -431,12 +442,18 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mCameraImpl.endVideo(); } + /** + * Returns the size used for the preview, + * or null if it hasn't been computed (for example if the surface is not ready). + */ + @Nullable public Size getPreviewSize() { - return mCameraImpl != null ? mCameraImpl.getPreviewResolution() : null; + return mCameraImpl != null ? mCameraImpl.getPreviewSize() : null; } + @Nullable public Size getCaptureSize() { - return mCameraImpl != null ? mCameraImpl.getCaptureResolution() : null; + return mCameraImpl != null ? mCameraImpl.getCaptureSize() : null; } private void requestPermissions(boolean requestCamera, boolean requestAudio) { @@ -452,12 +469,10 @@ public class CameraView extends FrameLayout implements LifecycleObserver { List permissions = new ArrayList<>(); if (requestCamera) permissions.add(Manifest.permission.CAMERA); if (requestAudio) permissions.add(Manifest.permission.RECORD_AUDIO); - if (activity != null) { - ActivityCompat.requestPermissions( - activity, + ActivityCompat.requestPermissions(activity, permissions.toArray(new String[permissions.size()]), - CameraKit.Constants.PERMISSION_REQUEST_CAMERA); + CameraKit.Constants.PERMISSION_REQUEST_CODE); } } @@ -481,8 +496,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver { public void onPictureTaken(byte[] jpeg) { super.onPictureTaken(jpeg); if (mCropOutput) { - int width = mMethod == METHOD_STANDARD ? mCameraImpl.getCaptureResolution().getWidth() : mCameraImpl.getPreviewResolution().getWidth(); - int height = mMethod == METHOD_STANDARD ? mCameraImpl.getCaptureResolution().getHeight() : mCameraImpl.getPreviewResolution().getHeight(); + int width = mMethod == METHOD_STANDARD ? mCameraImpl.getCaptureSize().getWidth() : mCameraImpl.getPreviewSize().getWidth(); + int height = mMethod == METHOD_STANDARD ? mCameraImpl.getCaptureSize().getHeight() : mCameraImpl.getPreviewSize().getHeight(); AspectRatio outputRatio = AspectRatio.of(getWidth(), getHeight()); getCameraListener().onPictureTaken(new CenterCrop(jpeg, outputRatio, mJpegQuality).getJpeg()); } else { @@ -521,4 +536,29 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } + /** + * If mPermissions == PERMISSIONS_VIDEO we will ask for RECORD_AUDIO permission. + * If the developer did not add this to its manifest, throw and fire warnings. + * (Hoping this is not cought elsewhere... we should test). + */ + private void checkPermissionPolicyOrThrow() { + if (mPermissions == PERMISSIONS_VIDEO) { + try { + PackageManager manager = getContext().getPackageManager(); + PackageInfo info = manager.getPackageInfo(getContext().getPackageName(), PackageManager.GET_PERMISSIONS); + for (String requestedPermission : info.requestedPermissions) { + if (requestedPermission.equals(Manifest.permission.RECORD_AUDIO)) { + return; + } + } + String message = "When the permission policy is PERMISSION_VIDEO, the RECORD_AUDIO permission " + + "should be added to the application manifest file."; + Log.w(TAG, message); + throw new IllegalStateException(message); + } catch (PackageManager.NameNotFoundException e) { + // Not possible. + } + } + } + } diff --git a/camerakit/src/main/types/com/flurgle/camerakit/Permissions.java b/camerakit/src/main/types/com/flurgle/camerakit/Permissions.java index f3588f1d..6060c3b3 100644 --- a/camerakit/src/main/types/com/flurgle/camerakit/Permissions.java +++ b/camerakit/src/main/types/com/flurgle/camerakit/Permissions.java @@ -5,11 +5,10 @@ import android.support.annotation.IntDef; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -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; +import static com.flurgle.camerakit.CameraKit.Constants.PERMISSIONS_VIDEO; @Retention(RetentionPolicy.SOURCE) -@IntDef({PERMISSIONS_STRICT, PERMISSIONS_LAZY, PERMISSIONS_PICTURE}) +@IntDef({PERMISSIONS_VIDEO, PERMISSIONS_PICTURE}) public @interface Permissions { } diff --git a/camerakit/src/main/utils/com/flurgle/camerakit/CommonAspectRatioFilter.java b/camerakit/src/main/utils/com/flurgle/camerakit/CommonAspectRatioFilter.java index b0cc9d21..4f46c5e8 100644 --- a/camerakit/src/main/utils/com/flurgle/camerakit/CommonAspectRatioFilter.java +++ b/camerakit/src/main/utils/com/flurgle/camerakit/CommonAspectRatioFilter.java @@ -18,9 +18,7 @@ public class CommonAspectRatioFilter { public TreeSet filter() { Set previewAspectRatios = new HashSet<>(); for (Size size : mPreviewSizes) { - if (size.getWidth() >= CameraKit.Internal.screenHeight && size.getHeight() >= CameraKit.Internal.screenWidth) { - previewAspectRatios.add(AspectRatio.of(size.getWidth(), size.getHeight())); - } + previewAspectRatios.add(AspectRatio.of(size.getWidth(), size.getHeight())); } Set captureAspectRatios = new HashSet<>(); diff --git a/camerakit/src/main/utils/com/flurgle/camerakit/DisplayOrientationDetector.java b/camerakit/src/main/utils/com/flurgle/camerakit/DisplayOrientationDetector.java deleted file mode 100644 index 28caef8a..00000000 --- a/camerakit/src/main/utils/com/flurgle/camerakit/DisplayOrientationDetector.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.flurgle.camerakit; - -import android.content.Context; -import android.util.SparseIntArray; -import android.view.Display; -import android.view.OrientationEventListener; -import android.view.Surface; - -public abstract class DisplayOrientationDetector { - - private final OrientationEventListener mOrientationEventListener; - - static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray(); - static { - DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0); - DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90); - DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180); - DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270); - } - - private Display mDisplay; - - private int mLastKnownDisplayOrientation = 0; - - public DisplayOrientationDetector(Context context) { - mOrientationEventListener = new OrientationEventListener(context) { - - private int mLastKnownRotation = -1; - - @Override - public void onOrientationChanged(int orientation) { - if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN || mDisplay == null) { - return; - } - - final int rotation = mDisplay.getRotation(); - if (mLastKnownRotation != rotation) { - mLastKnownRotation = rotation; - dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(rotation)); - } - } - - }; - } - - public void enable(Display display) { - mDisplay = display; - mOrientationEventListener.enable(); - dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(display.getRotation())); - } - - public void disable() { - mOrientationEventListener.disable(); - mDisplay = null; - } - - public int getLastKnownDisplayOrientation() { - return mLastKnownDisplayOrientation; - } - - void dispatchOnDisplayOrientationChanged(int displayOrientation) { - mLastKnownDisplayOrientation = displayOrientation; - onDisplayOrientationChanged(displayOrientation); - } - - public abstract void onDisplayOrientationChanged(int displayOrientation); - -} \ No newline at end of file diff --git a/camerakit/src/main/utils/com/flurgle/camerakit/OrientationHelper.java b/camerakit/src/main/utils/com/flurgle/camerakit/OrientationHelper.java new file mode 100644 index 00000000..b0eb969f --- /dev/null +++ b/camerakit/src/main/utils/com/flurgle/camerakit/OrientationHelper.java @@ -0,0 +1,81 @@ +package com.flurgle.camerakit; + +import android.content.Context; +import android.hardware.SensorManager; +import android.media.ExifInterface; +import android.util.SparseIntArray; +import android.view.Display; +import android.view.OrientationEventListener; +import android.view.Surface; + +abstract class OrientationHelper { + + private final OrientationEventListener mOrientationEventListener; + + private static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray(); + static { + DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0); + DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90); + DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180); + DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270); + } + + private Display mDisplay; + + private int mLastKnownDisplayOffset = -1; + private int mLastOrientation = -1; + + OrientationHelper(Context context) { + mOrientationEventListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) { + + @Override + public void onOrientationChanged(int orientation) { + int or = 0; + if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) { + or = 0; + } else if (orientation >= 315 || orientation < 45) { + or = 0; + } else if (orientation >= 45 && orientation < 135) { + or = 90; + } else if (orientation >= 135 && orientation < 225) { + or = 180; + } else if (orientation >= 225 && orientation < 315) { + or = 270; + } + + if (or != mLastOrientation) { + mLastOrientation = or; + onDeviceOrientationChanged(mLastOrientation); + } + + // Let's see if display rotation has changed.. but how could it ever change...?? + // This makes no sense apparently. I'll leave it for now. + if (mDisplay != null) { + final int offset = mDisplay.getRotation(); + if (mLastKnownDisplayOffset != offset) { + mLastKnownDisplayOffset = offset; + onDisplayOffsetChanged(DISPLAY_ORIENTATIONS.get(offset)); + } + } + } + + }; + } + + void enable(Display display) { + mDisplay = display; + mOrientationEventListener.enable(); + mLastKnownDisplayOffset = DISPLAY_ORIENTATIONS.get(display.getRotation()); + onDisplayOffsetChanged(mLastKnownDisplayOffset); + } + + void disable() { + mOrientationEventListener.disable(); + mDisplay = null; + } + + protected abstract void onDisplayOffsetChanged(int displayOffset); + + protected abstract void onDeviceOrientationChanged(int deviceOrientation); + +} \ No newline at end of file