Refactoring. Bug fixing. New logic for sizing. Some docs. Correct orientation in EXIF headers

pull/1/head
Mattia Iavarone 7 years ago
parent b99cc8f746
commit bd36fd73ea
  1. 12
      camerakit/build.gradle
  2. 17
      camerakit/src/main/AndroidManifest.xml
  3. 305
      camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java
  4. 14
      camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java
  5. 7
      camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java
  6. 69
      camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java
  7. 29
      camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java
  8. 29
      camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java
  9. 242
      camerakit/src/main/java/com/flurgle/camerakit/CameraView.java
  10. 5
      camerakit/src/main/types/com/flurgle/camerakit/Permissions.java
  11. 4
      camerakit/src/main/utils/com/flurgle/camerakit/CommonAspectRatioFilter.java
  12. 68
      camerakit/src/main/utils/com/flurgle/camerakit/DisplayOrientationDetector.java
  13. 81
      camerakit/src/main/utils/com/flurgle/camerakit/OrientationHelper.java

@ -7,14 +7,14 @@ ext {
} }
android { android {
compileSdkVersion 25 compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion "25.0.2" buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig { defaultConfig {
minSdkVersion 15 minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion 25 targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1 versionCode 1
versionName "1.0" versionName "0.1"
} }
buildTypes { buildTypes {
release { release {
@ -40,5 +40,5 @@ dependencies {
annotationProcessor 'android.arch.lifecycle:compiler: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' // apply from: 'https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle'

@ -2,19 +2,24 @@
package="com.flurgle.camerakit"> package="com.flurgle.camerakit">
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature android:name="android.hardware.camera" /> <!-- Have developers add this. We don't want AUDIO permission to be auto-added to
apps that just want to take pictures. -->
<!-- uses-permission android:name="android.permission.RECORD_AUDIO" /-->
<uses-feature
android:name="android.hardware.camera"
android:required="true"/>
<uses-feature <uses-feature
android:name="android.hardware.camera.autofocus" android:name="android.hardware.camera.autofocus"
android:required="false" /> android:required="false"/>
<uses-feature <uses-feature
android:name="android.hardware.camera.front" android:name="android.hardware.camera.front"
android:required="false" /> android:required="false"/>
<uses-feature <uses-feature
android:name="android.hardware.microphone" android:name="android.hardware.microphone"
android:required="false" /> android:required="false"/>
<application /> <application/>
</manifest> </manifest>

@ -5,7 +5,6 @@ import android.graphics.YuvImage;
import android.hardware.Camera; import android.hardware.Camera;
import android.media.CamcorderProfile; import android.media.CamcorderProfile;
import android.media.MediaRecorder; import android.media.MediaRecorder;
import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -16,11 +15,8 @@ import android.view.View;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.Collections;
import java.util.Iterator;
import java.util.List; 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.FLASH_OFF;
import static com.flurgle.camerakit.CameraKit.Constants.FOCUS_CONTINUOUS; 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; import static com.flurgle.camerakit.CameraKit.Constants.METHOD_STILL;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class Camera1 extends CameraImpl { class Camera1 extends CameraImpl {
private static final String TAG = Camera1.class.getSimpleName(); private static final String TAG = Camera1.class.getSimpleName();
@ -50,35 +46,26 @@ public class Camera1 extends CameraImpl {
private Camera.AutoFocusCallback mAutofocusCallback; private Camera.AutoFocusCallback mAutofocusCallback;
private boolean capturingImage = false; private boolean capturingImage = false;
private int mDisplayOrientation; private int mDisplayOffset;
private int mDeviceOrientation;
@Facing @Facing private int mFacing;
private int mFacing; @Flash private int mFlash;
@Focus private int mFocus;
@Flash @Method private int mMethod;
private int mFlash; @Zoom private int mZoom;
@VideoQuality private int mVideoQuality;
@Focus
private int mFocus;
@Method
private int mMethod;
@Zoom
private int mZoom;
@VideoQuality
private int mVideoQuality;
private Handler mHandler = new Handler(); private Handler mHandler = new Handler();
Camera1(CameraListener callback, PreviewImpl preview) { Camera1(CameraListener callback, PreviewImpl preview) {
super(callback, preview); super(callback, preview);
preview.setCallback(new PreviewImpl.Callback() { preview.setCallback(new PreviewImpl.OnSurfaceChangedCallback() {
@Override @Override
public void onSurfaceChanged() { public void onSurfaceChanged() {
if (mCamera != null) { if (mCamera != null) {
setupPreview(); setupPreview();
collectCameraSizes();
adjustCameraParameters(); adjustCameraParameters();
} }
} }
@ -105,9 +92,32 @@ public class Camera1 extends CameraImpl {
releaseCamera(); 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 @Override
void setDisplayOrientation(int displayOrientation) { void onDeviceOrientation(int deviceOrientation) {
this.mDisplayOrientation = displayOrientation; this.mDeviceOrientation = deviceOrientation;
} }
@Override @Override
@ -217,19 +227,18 @@ public class Camera1 extends CameraImpl {
case METHOD_STANDARD: case METHOD_STANDARD:
// Null check required for camera here as is briefly null when View is detached // Null check required for camera here as is briefly null when View is detached
if (!capturingImage && mCamera != null) { if (!capturingImage && mCamera != null) {
// Set boolean to wait for image callback // Set boolean to wait for image callback
capturingImage = true; capturingImage = true;
Camera.Parameters parameters = mCamera.getParameters();
parameters.setRotation(computeExifOrientation());
mCamera.setParameters(parameters);
mCamera.takePicture(null, null, null, mCamera.takePicture(null, null, null,
new Camera.PictureCallback() { new Camera.PictureCallback() {
@Override @Override
public void onPictureTaken(byte[] data, Camera camera) { public void onPictureTaken(byte[] data, Camera camera) {
mCameraListener.onPictureTaken(data); mCameraListener.onPictureTaken(data);
// Reset capturing state to allow photos to be taken // Reset capturing state to allow photos to be taken
capturingImage = false; capturingImage = false;
camera.startPreview(); camera.startPreview();
} }
}); });
@ -240,6 +249,7 @@ public class Camera1 extends CameraImpl {
break; break;
case METHOD_STILL: case METHOD_STILL:
// TODO: will calculateCaptureRotation work here? It's the only usage. Test...
mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() { mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
@Override @Override
public void onPreviewFrame(byte[] data, Camera camera) { public void onPreviewFrame(byte[] data, Camera camera) {
@ -270,79 +280,14 @@ public class Camera1 extends CameraImpl {
mCameraListener.onVideoTaken(mVideoFile); 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<Size> 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<Size> sizesFromList(List<Camera.Size> sizes) {
if (sizes == null) return null;
List<Size> 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<Size> previewSizes = sizesFromList(mCameraParameters.getSupportedPreviewSizes());
List<Size> 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 @Override
Size getCaptureResolution() { Size getCaptureSize() {
return mCaptureSize; return mCaptureSize;
} }
@Override @Override
Size getPreviewResolution() { Size getPreviewSize() {
return mPreviewSize; return mPreviewSize;
} }
@ -368,24 +313,12 @@ public class Camera1 extends CameraImpl {
mCameraParameters = mCamera.getParameters(); mCameraParameters = mCamera.getParameters();
collectCameraProperties(); collectCameraProperties();
collectCameraSizes();
adjustCameraParameters(); adjustCameraParameters();
mCamera.setDisplayOrientation(calculatePreviewRotation()); mCamera.setDisplayOrientation(computeCameraToDisplayOffset());
mCameraListener.onCameraOpened(); 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() { private void releaseCamera() {
if (mCamera != null) { if (mCamera != null) {
mCamera.release(); 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) { 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 { } 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() { private int calculateCaptureRotation() {
int previewRotation = calculatePreviewRotation(); int previewRotation = computeCameraToDisplayOffset();
if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
//Front is flipped //Front is flipped
return (previewRotation + 180 + 2*mDisplayOrientation + 720) %360; return (previewRotation + 180 + 2* mDisplayOffset + 720) % 360;
} else { } else {
return previewRotation; return previewRotation;
} }
} }
private void adjustCameraParameters() {
initResolutions();
boolean invertPreviewSizes = mDisplayOrientation%180 != 0; /**
mPreview.setTruePreviewSize( * This is called either on cameraView.start(), or when the underlying surface changes.
invertPreviewSizes? getPreviewResolution().getHeight() : getPreviewResolution().getWidth(), * It is possible that in the first call the preview surface has not already computed its
invertPreviewSizes? getPreviewResolution().getWidth() : getPreviewResolution().getHeight() * 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<Size> previewSizes = sizesFromList(mCameraParameters.getSupportedPreviewSizes());
List<Size> captureSizes = sizesFromList(mCameraParameters.getSupportedPictureSizes());
mCameraParameters.setPreviewSize( /* video stuff: CamcorderProfile camcorderProfile = getCamcorderProfile(mVideoQuality);
getPreviewResolution().getWidth(), mCaptureSize = getSizeWithClosestRatio(
getPreviewResolution().getHeight() (videoSizes == null || videoSizes.isEmpty()) ? previewSizes : videoSizes,
); camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight); */
mCameraParameters.setPictureSize( Size surfaceSize = mPreview.getSurfaceSize();
getCaptureResolution().getWidth(), mCaptureSize = Collections.max(captureSizes);
getCaptureResolution().getHeight() mPreviewSize = computePreviewSize(previewSizes, mCaptureSize, surfaceSize);
); }
int rotation = calculateCaptureRotation();
mCameraParameters.setRotation(rotation);
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); setFocus(mFocus);
setFlash(mFlash); setFlash(mFlash);
mCamera.setParameters(mCameraParameters); mCamera.setParameters(mCameraParameters);
} }
@ -447,28 +407,6 @@ public class Camera1 extends CameraImpl {
mCameraParameters.getHorizontalViewAngle()); mCameraParameters.getHorizontalViewAngle());
} }
private TreeSet<AspectRatio> findCommonAspectRatios(List<Camera.Size> previewSizes, List<Camera.Size> captureSizes) {
Set<AspectRatio> 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<AspectRatio> captureAspectRatios = new HashSet<>();
for (Camera.Size size : captureSizes) {
captureAspectRatios.add(AspectRatio.of(size.width, size.height));
}
TreeSet<AspectRatio> output = new TreeSet<>();
for (AspectRatio aspectRatio : previewAspectRatios) {
if (captureAspectRatios.contains(aspectRatio)) {
output.add(aspectRatio);
}
}
return output;
}
private void initMediaRecorder() { private void initMediaRecorder() {
mMediaRecorder = new MediaRecorder(); mMediaRecorder = new MediaRecorder();
@ -483,7 +421,7 @@ public class Camera1 extends CameraImpl {
mVideoFile = new File(mPreview.getView().getContext().getExternalFilesDir(null), "video.mp4"); mVideoFile = new File(mPreview.getView().getContext().getExternalFilesDir(null), "video.mp4");
mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath()); mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath());
mMediaRecorder.setOrientationHint(calculatePreviewRotation()); mMediaRecorder.setOrientationHint(computeCameraToDisplayOffset());
mMediaRecorder.setVideoSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); mMediaRecorder.setVideoSize(mCaptureSize.getWidth(), mCaptureSize.getHeight());
} }
@ -644,7 +582,7 @@ public class Camera1 extends CameraImpl {
if (camera != null) { if (camera != null) {
camera.cancelAutoFocus(); camera.cancelAutoFocus();
Camera.Parameters params = camera.getParameters(); 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.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
params.setFocusAreas(null); params.setFocusAreas(null);
params.setMeteringAreas(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<Size> sizesFromList(List<Camera.Size> sizes) {
if (sizes == null) return null;
List<Size> 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<Size> sizes, Size captureSize, Size surfaceSize) {
if (sizes == null) return null;
List<Size> consistent = new ArrayList<>(5);
List<Size> 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);
}
} }

@ -3,7 +3,6 @@ package com.flurgle.camerakit;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.graphics.ImageFormat; import android.graphics.ImageFormat;
import android.graphics.PointF;
import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraDevice;
@ -36,7 +35,7 @@ class Camera2 extends CameraImpl {
Camera2(CameraListener callback, PreviewImpl preview, Context context) { Camera2(CameraListener callback, PreviewImpl preview, Context context) {
super(callback, preview); super(callback, preview);
preview.setCallback(new PreviewImpl.Callback() { preview.setCallback(new PreviewImpl.OnSurfaceChangedCallback() {
@Override @Override
public void onSurfaceChanged() { public void onSurfaceChanged() {
@ -89,7 +88,12 @@ class Camera2 extends CameraImpl {
} }
@Override @Override
void setDisplayOrientation(int displayOrientation) { void onDisplayOffset(int displayOrientation) {
}
@Override
void onDeviceOrientation(int deviceOrientation) {
} }
@ -176,7 +180,7 @@ class Camera2 extends CameraImpl {
} }
@Override @Override
Size getCaptureResolution() { Size getCaptureSize() {
if (mCaptureSize == null && mCameraCharacteristics != null) { if (mCaptureSize == null && mCameraCharacteristics != null) {
TreeSet<Size> sizes = new TreeSet<>(); TreeSet<Size> sizes = new TreeSet<>();
sizes.addAll(getAvailableCaptureResolutions()); sizes.addAll(getAvailableCaptureResolutions());
@ -202,7 +206,7 @@ class Camera2 extends CameraImpl {
} }
@Override @Override
Size getPreviewResolution() { Size getPreviewSize() {
if (mPreviewSize == null && mCameraCharacteristics != null) { if (mPreviewSize == null && mCameraCharacteristics != null) {
TreeSet<Size> sizes = new TreeSet<>(); TreeSet<Size> sizes = new TreeSet<>();
sizes.addAll(getAvailablePreviewResolutions()); sizes.addAll(getAvailablePreviewResolutions());

@ -15,7 +15,8 @@ abstract class CameraImpl {
abstract void start(); abstract void start();
abstract void stop(); 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 setFacing(@Facing int facing);
abstract void setFlash(@Flash int flash); abstract void setFlash(@Flash int flash);
@ -28,8 +29,8 @@ abstract class CameraImpl {
abstract void startVideo(); abstract void startVideo();
abstract void endVideo(); abstract void endVideo();
abstract Size getCaptureResolution(); abstract Size getCaptureSize();
abstract Size getPreviewResolution(); abstract Size getPreviewSize();
abstract boolean isCameraOpened(); abstract boolean isCameraOpened();
@Nullable @Nullable

@ -7,20 +7,22 @@ import android.view.View;
abstract class PreviewImpl { abstract class PreviewImpl {
interface Callback { interface OnSurfaceChangedCallback {
void onSurfaceChanged(); void onSurfaceChanged();
} }
private Callback mCallback; private OnSurfaceChangedCallback mOnSurfaceChangedCallback;
private int mWidth; // As far as I can see, these are the view/surface dimensions.
private int mHeight; private int mSurfaceWidth;
private int mSurfaceHeight;
protected int mTrueWidth; // As far as I can see, these are the actual preview dimensions, as set in CameraParameters.
protected int mTrueHeight; private int mDesiredWidth;
private int mDesiredHeight;
void setCallback(Callback callback) { void setCallback(OnSurfaceChangedCallback callback) {
mCallback = callback; mOnSurfaceChangedCallback = callback;
} }
abstract Surface getSurface(); abstract Surface getSurface();
@ -29,12 +31,13 @@ abstract class PreviewImpl {
abstract Class getOutputClass(); abstract Class getOutputClass();
abstract void setDisplayOrientation(int displayOrientation); protected void onDisplayOffset(int displayOrientation) {}
protected void onDeviceOrientation(int deviceOrientation) {}
abstract boolean isReady(); abstract boolean isReady();
protected void dispatchSurfaceChanged() { protected void dispatchSurfaceChanged() {
mCallback.onSurfaceChanged(); mOnSurfaceChangedCallback.onSurfaceChanged();
} }
SurfaceHolder getSurfaceHolder() { SurfaceHolder getSurfaceHolder() {
@ -45,30 +48,37 @@ abstract class PreviewImpl {
return null; return null;
} }
void setSize(int width, int height) { // As far as I can see, these are the view/surface dimensions.
mWidth = width; // This is called by subclasses.
mHeight = height; protected void setSurfaceSize(int width, int height) {
this.mSurfaceWidth = width;
// Refresh true preview size to adjust scaling this.mSurfaceHeight = height;
setTruePreviewSize(mTrueWidth, mTrueHeight); refreshScale(); // Refresh true preview size to adjust scaling
} }
int getWidth() { // As far as I can see, these are the actual preview dimensions, as set in CameraParameters.
return mWidth; // This is called by the CameraImpl.
void setDesiredSize(int width, int height) {
this.mDesiredWidth = width;
this.mDesiredHeight = height;
refreshScale();
} }
int getHeight() { Size getSurfaceSize() {
return mHeight; return new Size(mSurfaceWidth, mSurfaceHeight);
} }
void setTruePreviewSize(final int width, final int height) { /**
this.mTrueWidth = width; * As far as I can see, this extends either width or height of the surface,
this.mTrueHeight = height; * 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() { getView().post(new Runnable() {
@Override @Override
public void run() { public void run() {
if (width != 0 && height != 0) { if (mDesiredWidth != 0 && mDesiredHeight != 0) {
AspectRatio aspectRatio = AspectRatio.of(width, height); AspectRatio aspectRatio = AspectRatio.of(mDesiredWidth, mDesiredHeight);
int targetHeight = (int) (getView().getWidth() * aspectRatio.toFloat()); int targetHeight = (int) (getView().getWidth() * aspectRatio.toFloat());
float scaleY; float scaleY;
if (getView().getHeight() > 0) { if (getView().getHeight() > 0) {
@ -88,13 +98,4 @@ abstract class PreviewImpl {
} }
}); });
} }
int getTrueWidth() {
return mTrueWidth;
}
int getTrueHeight() {
return mTrueHeight;
}
} }

@ -13,8 +13,6 @@ class TextureViewPreview extends PreviewImpl {
private final TextureView mTextureView; private final TextureView mTextureView;
private int mDisplayOrientation;
TextureViewPreview(Context context, ViewGroup parent) { TextureViewPreview(Context context, ViewGroup parent) {
final View view = View.inflate(context, R.layout.texture_view, parent); final View view = View.inflate(context, R.layout.texture_view, parent);
mTextureView = (TextureView) view.findViewById(R.id.texture_view); mTextureView = (TextureView) view.findViewById(R.id.texture_view);
@ -22,20 +20,20 @@ class TextureViewPreview extends PreviewImpl {
@Override @Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
setSize(width, height); setSurfaceSize(width, height);
dispatchSurfaceChanged(); dispatchSurfaceChanged();
} }
@Override @Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
setSize(width, height); setSurfaceSize(width, height);
dispatchSurfaceChanged(); dispatchSurfaceChanged();
setTruePreviewSize(mTrueWidth, mTrueHeight); refreshScale();
} }
@Override @Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
setSize(0, 0); setSurfaceSize(0, 0);
return true; return true;
} }
@ -45,11 +43,6 @@ class TextureViewPreview extends PreviewImpl {
}); });
} }
@Override
void setCallback(Callback callback) {
super.setCallback(callback);
}
@Override @Override
Surface getSurface() { Surface getSurface() {
return new Surface(mTextureView.getSurfaceTexture()); return new Surface(mTextureView.getSurfaceTexture());
@ -65,21 +58,11 @@ class TextureViewPreview extends PreviewImpl {
return SurfaceTexture.class; return SurfaceTexture.class;
} }
@Override
void setDisplayOrientation(int displayOrientation) {
mDisplayOrientation = displayOrientation;
}
@Override @Override
boolean isReady() { boolean isReady() {
return mTextureView.getSurfaceTexture() != null; return mTextureView.getSurfaceTexture() != null;
} }
@Override
protected void dispatchSurfaceChanged() {
super.dispatchSurfaceChanged();
}
@Override @Override
SurfaceTexture getSurfaceTexture() { SurfaceTexture getSurfaceTexture() {
return mTextureView.getSurfaceTexture(); return mTextureView.getSurfaceTexture();
@ -87,8 +70,8 @@ class TextureViewPreview extends PreviewImpl {
@TargetApi(15) @TargetApi(15)
@Override @Override
void setTruePreviewSize(int width, int height) { void setDesiredSize(int width, int height) {
super.setTruePreviewSize(width, height); super.setDesiredSize(width, height);
if (mTextureView.getSurfaceTexture() != null) { if (mTextureView.getSurfaceTexture() != null) {
mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height); mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);
} }

@ -4,16 +4,9 @@ import android.content.res.Resources;
public class CameraKit { 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 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_BACK = 0;
public static final int FACING_FRONT = 1; 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_STILL = 1;
public static final int METHOD_SPEED = 2; 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
* <uses-permission android:name="android.permission.RECORD_AUDIO" />
* 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_480P = 0;
public static final int VIDEO_QUALITY_720P = 1; 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_FOCUS = Constants.FOCUS_CONTINUOUS;
static final int DEFAULT_ZOOM = Constants.ZOOM_OFF; static final int DEFAULT_ZOOM = Constants.ZOOM_OFF;
static final int DEFAULT_METHOD = Constants.METHOD_STANDARD; 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_VIDEO_QUALITY = Constants.VIDEO_QUALITY_480P;
static final int DEFAULT_JPEG_QUALITY = 100; static final int DEFAULT_JPEG_QUALITY = 100;

@ -8,6 +8,7 @@ import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.OnLifecycleEvent; import android.arch.lifecycle.OnLifecycleEvent;
import android.content.Context; import android.content.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Rect; import android.graphics.Rect;
@ -18,12 +19,12 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat; 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.AttributeSet;
import android.util.Log;
import android.view.Display; import android.view.Display;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import java.io.ByteArrayOutputStream; 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_ON;
import static com.flurgle.camerakit.CameraKit.Constants.FLASH_TORCH; 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.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_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 * 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 { public class CameraView extends FrameLayout implements LifecycleObserver {
private final static String TAG = CameraView.class.getSimpleName();
private static Handler sWorkerHandler; private static Handler sWorkerHandler;
static { private static Handler getWorkerHandler() {
// Initialize a single worker thread. This can be static since only a single camera if (sWorkerHandler == null) {
// reference can exist at a time. HandlerThread workerThread = new HandlerThread("CameraViewWorker");
HandlerThread workerThread = new HandlerThread("CameraViewWorker"); workerThread.setDaemon(true);
workerThread.setDaemon(true); workerThread.start();
workerThread.start(); sWorkerHandler = new Handler(workerThread.getLooper());
sWorkerHandler = new Handler(workerThread.getLooper()); }
return sWorkerHandler;
} }
@Facing private void run(Runnable runnable) {
private int mFacing; getWorkerHandler().post(runnable);
}
@Flash
private int mFlash;
@Focus
private int mFocus;
@Method
private int mMethod;
@Zoom
private int mZoom;
@Permissions
private int mPermissions;
@VideoQuality @Facing private int mFacing;
private int mVideoQuality; @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 int mJpegQuality;
private boolean mCropOutput; private boolean mCropOutput;
private boolean mAdjustViewBounds; private boolean mAdjustViewBounds;
private CameraListenerMiddleWare mCameraListener; private CameraListenerMiddleWare mCameraListener;
private OrientationHelper mOrientationHelper;
private DisplayOrientationDetector mDisplayOrientationDetector;
private CameraImpl mCameraImpl; private CameraImpl mCameraImpl;
private PreviewImpl mPreviewImpl; private PreviewImpl mPreviewImpl;
private Lifecycle mLifecycle; private Lifecycle mLifecycle;
@ -108,7 +99,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
init(context, null); init(context, null);
} }
@SuppressWarnings("all")
public CameraView(@NonNull Context context, @Nullable AttributeSet attrs) { public CameraView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs); super(context, attrs);
init(context, attrs); init(context, attrs);
@ -117,11 +107,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
@SuppressWarnings("WrongConstant") @SuppressWarnings("WrongConstant")
private void init(@NonNull Context context, @Nullable AttributeSet attrs) { private void init(@NonNull Context context, @Nullable AttributeSet attrs) {
if (attrs != null) { if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes( TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CameraView, 0, 0);
attrs,
R.styleable.CameraView,
0, 0);
try { try {
mFacing = a.getInteger(R.styleable.CameraView_ckFacing, CameraKit.Defaults.DEFAULT_FACING); mFacing = a.getInteger(R.styleable.CameraView_ckFacing, CameraKit.Defaults.DEFAULT_FACING);
mFlash = a.getInteger(R.styleable.CameraView_ckFlash, CameraKit.Defaults.DEFAULT_FLASH); mFlash = a.getInteger(R.styleable.CameraView_ckFlash, CameraKit.Defaults.DEFAULT_FLASH);
@ -149,15 +135,21 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
setFocus(mFocus); setFocus(mFocus);
setMethod(mMethod); setMethod(mMethod);
setZoom(mZoom); setZoom(mZoom);
setPermissions(mPermissions); setPermissionPolicy(mPermissions);
setVideoQuality(mVideoQuality); setVideoQuality(mVideoQuality);
if (!isInEditMode()) { if (!isInEditMode()) {
mDisplayOrientationDetector = new DisplayOrientationDetector(context) { mOrientationHelper = new OrientationHelper(context) {
@Override @Override
public void onDisplayOrientationChanged(int displayOrientation) { public void onDisplayOffsetChanged(int displayOffset) {
mCameraImpl.setDisplayOrientation(displayOrientation); mCameraImpl.onDisplayOffset(displayOffset);
mPreviewImpl.setDisplayOrientation(displayOrientation); 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 @Override
public boolean onTouch(View v, MotionEvent motionEvent) { public boolean onTouch(View v, MotionEvent motionEvent) {
int action = motionEvent.getAction(); 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()); focusMarkerLayout.focus(motionEvent.getX(), motionEvent.getY());
} }
@ -182,55 +174,69 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
@Override @Override
protected void onAttachedToWindow() { protected void onAttachedToWindow() {
super.onAttachedToWindow(); super.onAttachedToWindow();
Log.e(TAG, "onAttachedToWindow");
if (!isInEditMode()) { if (!isInEditMode()) {
mDisplayOrientationDetector.enable( Log.e(TAG, "onAttachedToWindow: enabling orientation detector.");
ViewCompat.isAttachedToWindow(this) WindowManager manager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
? DisplayManagerCompat.getInstance(getContext()) Display display = manager.getDefaultDisplay();
.getDisplay(Display.DEFAULT_DISPLAY) mOrientationHelper.enable(display);
: null
);
} }
} }
@Override @Override
protected void onDetachedFromWindow() { protected void onDetachedFromWindow() {
Log.e(TAG, "onDetachedFromWindow");
if (!isInEditMode()) { if (!isInEditMode()) {
mDisplayOrientationDetector.disable(); Log.e(TAG, "onDetachedFromWindow: disabling orientation detector.");
mOrientationHelper.disable();
} }
super.onDetachedFromWindow(); 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 @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mAdjustViewBounds) { if (!mAdjustViewBounds) {
Size previewSize = getPreviewSize(); super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (previewSize != null) { return;
if (getLayoutParams().width == LayoutParams.WRAP_CONTENT) { }
int height = MeasureSpec.getSize(heightMeasureSpec);
float ratio = (float) height / (float) previewSize.getWidth(); Size previewSize = getPreviewSize();
int width = (int) (previewSize.getHeight() * ratio); if (previewSize == null) {
super.onMeasure( super.onMeasure(widthMeasureSpec, heightMeasureSpec);
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), return;
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;
}
} }
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() { public boolean isStarted() {
@ -263,30 +269,29 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
stop(); 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() { public void start() {
if (mIsStarted || !isEnabled()) { if (mIsStarted || !isEnabled()) {
// Already started, do nothing. // Already started, do nothing.
return; return;
} }
mIsStarted = true; checkPermissionPolicyOrThrow();
int cameraCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA); int cameraCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA);
int audioCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.RECORD_AUDIO); int audioCheck = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.RECORD_AUDIO);
switch (mPermissions) { switch (mPermissions) {
case PERMISSIONS_STRICT: case PERMISSIONS_VIDEO:
if (cameraCheck != PackageManager.PERMISSION_GRANTED || audioCheck != PackageManager.PERMISSION_GRANTED) { if (cameraCheck != PackageManager.PERMISSION_GRANTED || audioCheck != PackageManager.PERMISSION_GRANTED) {
requestPermissions(true, true); requestPermissions(true, true);
return; return;
} }
break; break;
case PERMISSIONS_LAZY:
if (cameraCheck != PackageManager.PERMISSION_GRANTED) {
requestPermissions(true, true);
return;
}
break;
case PERMISSIONS_PICTURE: case PERMISSIONS_PICTURE:
if (cameraCheck != PackageManager.PERMISSION_GRANTED) { if (cameraCheck != PackageManager.PERMISSION_GRANTED) {
requestPermissions(true, false); requestPermissions(true, false);
@ -295,7 +300,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
break; break;
} }
sWorkerHandler.post(new Runnable() { mIsStarted = true;
run(new Runnable() {
@Override @Override
public void run() { public void run() {
mCameraImpl.start(); mCameraImpl.start();
@ -325,7 +331,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
public void setFacing(@Facing final int facing) { public void setFacing(@Facing final int facing) {
this.mFacing = facing; this.mFacing = facing;
sWorkerHandler.post(new Runnable() { run(new Runnable() {
@Override @Override
public void run() { public void run() {
mCameraImpl.setFacing(facing); mCameraImpl.setFacing(facing);
@ -363,8 +369,13 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
mCameraImpl.setZoom(mZoom); 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; this.mPermissions = permissions;
checkPermissionPolicyOrThrow();
} }
public void setVideoQuality(@VideoQuality int videoQuality) { public void setVideoQuality(@VideoQuality int videoQuality) {
@ -431,12 +442,18 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
mCameraImpl.endVideo(); 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() { public Size getPreviewSize() {
return mCameraImpl != null ? mCameraImpl.getPreviewResolution() : null; return mCameraImpl != null ? mCameraImpl.getPreviewSize() : null;
} }
@Nullable
public Size getCaptureSize() { public Size getCaptureSize() {
return mCameraImpl != null ? mCameraImpl.getCaptureResolution() : null; return mCameraImpl != null ? mCameraImpl.getCaptureSize() : null;
} }
private void requestPermissions(boolean requestCamera, boolean requestAudio) { private void requestPermissions(boolean requestCamera, boolean requestAudio) {
@ -452,12 +469,10 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
List<String> permissions = new ArrayList<>(); List<String> permissions = new ArrayList<>();
if (requestCamera) permissions.add(Manifest.permission.CAMERA); if (requestCamera) permissions.add(Manifest.permission.CAMERA);
if (requestAudio) permissions.add(Manifest.permission.RECORD_AUDIO); if (requestAudio) permissions.add(Manifest.permission.RECORD_AUDIO);
if (activity != null) { if (activity != null) {
ActivityCompat.requestPermissions( ActivityCompat.requestPermissions(activity,
activity,
permissions.toArray(new String[permissions.size()]), 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) { public void onPictureTaken(byte[] jpeg) {
super.onPictureTaken(jpeg); super.onPictureTaken(jpeg);
if (mCropOutput) { if (mCropOutput) {
int width = mMethod == METHOD_STANDARD ? mCameraImpl.getCaptureResolution().getWidth() : mCameraImpl.getPreviewResolution().getWidth(); int width = mMethod == METHOD_STANDARD ? mCameraImpl.getCaptureSize().getWidth() : mCameraImpl.getPreviewSize().getWidth();
int height = mMethod == METHOD_STANDARD ? mCameraImpl.getCaptureResolution().getHeight() : mCameraImpl.getPreviewResolution().getHeight(); int height = mMethod == METHOD_STANDARD ? mCameraImpl.getCaptureSize().getHeight() : mCameraImpl.getPreviewSize().getHeight();
AspectRatio outputRatio = AspectRatio.of(getWidth(), getHeight()); AspectRatio outputRatio = AspectRatio.of(getWidth(), getHeight());
getCameraListener().onPictureTaken(new CenterCrop(jpeg, outputRatio, mJpegQuality).getJpeg()); getCameraListener().onPictureTaken(new CenterCrop(jpeg, outputRatio, mJpegQuality).getJpeg());
} else { } 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.
}
}
}
} }

@ -5,11 +5,10 @@ import android.support.annotation.IntDef;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; 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_PICTURE;
import static com.flurgle.camerakit.CameraKit.Constants.PERMISSIONS_STRICT; import static com.flurgle.camerakit.CameraKit.Constants.PERMISSIONS_VIDEO;
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({PERMISSIONS_STRICT, PERMISSIONS_LAZY, PERMISSIONS_PICTURE}) @IntDef({PERMISSIONS_VIDEO, PERMISSIONS_PICTURE})
public @interface Permissions { public @interface Permissions {
} }

@ -18,9 +18,7 @@ public class CommonAspectRatioFilter {
public TreeSet<AspectRatio> filter() { public TreeSet<AspectRatio> filter() {
Set<AspectRatio> previewAspectRatios = new HashSet<>(); Set<AspectRatio> previewAspectRatios = new HashSet<>();
for (Size size : mPreviewSizes) { 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<AspectRatio> captureAspectRatios = new HashSet<>(); Set<AspectRatio> captureAspectRatios = new HashSet<>();

@ -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);
}

@ -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);
}
Loading…
Cancel
Save