From 7faceeb5f648ec571eaae3ff225a2c308786e2a1 Mon Sep 17 00:00:00 2001 From: Yuichi Araki Date: Fri, 3 Jun 2016 18:03:29 +0900 Subject: [PATCH] Better auto-focus This simplifies the API by removing focusMode and introducing autoFocus. Pictures are now taken right after auto-focus. Also remove external camera support as there is no easy way to support it for API Level <21. The demo app now has a button to switch camera. Change-Id: If978959d2095632c332d17ab7f99e6ed18d82057 --- .../android/cameraview/demo/MainActivity.java | 30 ++++++++ .../main/res/drawable/ic_switch_camera.xml | 22 ++++++ demo/src/main/res/layout/include_camera.xml | 22 ++++-- demo/src/main/res/menu/main.xml | 24 ++++++ demo/src/main/res/values/strings.xml | 1 + .../android/cameraview/CameraViewTest.java | 28 +++++-- .../android/cameraview/CameraViewImpl.java | 8 +- .../google/android/cameraview/Constants.java | 8 -- .../google/android/cameraview/Camera1.java | 77 +++++++++++++++---- .../android/cameraview/Camera1Constants.java | 43 ----------- .../google/android/cameraview/CameraView.java | 60 +++++++-------- library/src/main/res/values/attrs.xml | 9 +-- library/src/main/res/values/styles.xml | 2 +- 13 files changed, 211 insertions(+), 123 deletions(-) create mode 100644 demo/src/main/res/drawable/ic_switch_camera.xml create mode 100644 demo/src/main/res/menu/main.xml delete mode 100644 library/src/main/camera1/com/google/android/cameraview/Camera1Constants.java diff --git a/demo/src/main/java/com/google/android/cameraview/demo/MainActivity.java b/demo/src/main/java/com/google/android/cameraview/demo/MainActivity.java index 0e6f33f6..56e1192e 100644 --- a/demo/src/main/java/com/google/android/cameraview/demo/MainActivity.java +++ b/demo/src/main/java/com/google/android/cameraview/demo/MainActivity.java @@ -33,9 +33,13 @@ import android.support.v4.app.ActivityCompat; import android.support.v4.app.DialogFragment; import android.support.v4.content.ContextCompat; +import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.widget.Toast; @@ -100,6 +104,12 @@ protected void onCreate(Bundle savedInstanceState) { if (takePicture != null) { takePicture.setOnClickListener(mOnClickListener); } + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayShowTitleEnabled(false); + } } @Override @@ -167,6 +177,26 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis } } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.switch_camera: + if (mCameraView != null) { + int facing = mCameraView.getFacing(); + mCameraView.setFacing(facing == CameraView.FACING_FRONT ? + CameraView.FACING_BACK : CameraView.FACING_FRONT); + } + break; + } + return false; + } + private Handler getBackgroundHandler() { if (mBackgroundHandler == null) { HandlerThread thread = new HandlerThread("background"); diff --git a/demo/src/main/res/drawable/ic_switch_camera.xml b/demo/src/main/res/drawable/ic_switch_camera.xml new file mode 100644 index 00000000..bade6e71 --- /dev/null +++ b/demo/src/main/res/drawable/ic_switch_camera.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/demo/src/main/res/layout/include_camera.xml b/demo/src/main/res/layout/include_camera.xml index 3ea2090c..7c3fd09a 100644 --- a/demo/src/main/res/layout/include_camera.xml +++ b/demo/src/main/res/layout/include_camera.xml @@ -11,12 +11,20 @@ See the License for the specific language governing permissions and limitations under the License. --> - + android:layout_height="match_parent"> + + + + + + \ No newline at end of file diff --git a/demo/src/main/res/menu/main.xml b/demo/src/main/res/menu/main.xml new file mode 100644 index 00000000..24d1dba2 --- /dev/null +++ b/demo/src/main/res/menu/main.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/demo/src/main/res/values/strings.xml b/demo/src/main/res/values/strings.xml index c799a82f..936dd78f 100644 --- a/demo/src/main/res/values/strings.xml +++ b/demo/src/main/res/values/strings.xml @@ -18,4 +18,5 @@ This app saves taken photos to external storage. The app does not have permission to save a photo. Picture taken + Switch camera diff --git a/library/src/androidTest/java/com/google/android/cameraview/CameraViewTest.java b/library/src/androidTest/java/com/google/android/cameraview/CameraViewTest.java index 32da3396..b48affdc 100644 --- a/library/src/androidTest/java/com/google/android/cameraview/CameraViewTest.java +++ b/library/src/androidTest/java/com/google/android/cameraview/CameraViewTest.java @@ -109,6 +109,22 @@ public void check(View view, NoMatchingViewException noViewFoundException) { CameraView cameraView = (CameraView) view; AspectRatio ratio = cameraView.getAspectRatio(); assertThat(ratio, is(notNullValue())); + Set ratios = cameraView.getSupportedAspectRatios(); + assertThat(ratios.size(), is(greaterThanOrEqualTo(1))); + assertThat(ratios, hasItem(ratio)); + if (ratios.size() == 1) { + return; + } + // Pick one ratio to change to + for (AspectRatio r : ratios) { + if (!r.equals(ratio)) { + ratio = r; + break; + } + } + assert ratio != null; + cameraView.setAspectRatio(ratio); + assertThat(cameraView.getAspectRatio(), is(equalTo(ratio))); } }); } @@ -163,16 +179,18 @@ public void check(View view, NoMatchingViewException noViewFoundException) { } @Test - public void testFocusMode() { + public void testAutoFocus() { onView(withId(R.id.camera)) .check(new ViewAssertion() { @Override public void check(View view, NoMatchingViewException noViewFoundException) { CameraView cameraView = (CameraView) view; - assertThat(cameraView.getFocusMode(), is(CameraView.FOCUS_MODE_OFF)); - cameraView.setFocusMode(CameraView.FOCUS_MODE_CONTINUOUS_PICTURE); - assertThat(cameraView.getFocusMode(), - is(CameraView.FOCUS_MODE_CONTINUOUS_PICTURE)); + // This can fail on devices without auto-focus support + assertThat(cameraView.getAutoFocus(), is(true)); + cameraView.setAutoFocus(false); + assertThat(cameraView.getAutoFocus(), is(false)); + cameraView.setAutoFocus(true); + assertThat(cameraView.getAutoFocus(), is(true)); } }); } diff --git a/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java b/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java index 001ae2c4..d388ba29 100644 --- a/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java +++ b/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java @@ -18,6 +18,8 @@ import android.view.TextureView; +import java.util.Set; + abstract class CameraViewImpl { protected final Callback mCallback; @@ -34,13 +36,15 @@ public CameraViewImpl(Callback callback) { abstract boolean isCameraOpened(); + abstract Set getSupportedAspectRatios(); + abstract void setAspectRatio(AspectRatio ratio); abstract AspectRatio getAspectRatio(); - abstract void setFocusMode(int focusMode); + abstract void setAutoFocus(boolean autoFocus); - abstract int getFocusMode(); + abstract boolean getAutoFocus(); abstract void setFacing(int facing); diff --git a/library/src/main/base/com/google/android/cameraview/Constants.java b/library/src/main/base/com/google/android/cameraview/Constants.java index 9d22a0ff..ba27bb05 100644 --- a/library/src/main/base/com/google/android/cameraview/Constants.java +++ b/library/src/main/base/com/google/android/cameraview/Constants.java @@ -19,15 +19,7 @@ interface Constants { - int FOCUS_MODE_OFF = 0; - int FOCUS_MODE_AUTO = 1; - int FOCUS_MODE_MACRO = 2; - int FOCUS_MODE_CONTINUOUS_PICTURE = 3; - int FOCUS_MODE_CONTINUOUS_VIDEO = 4; - int FOCUS_MODE_EDOF = 5; - int FACING_BACK = 0; int FACING_FRONT = 1; - int FACING_EXTERNAL = 2; } diff --git a/library/src/main/camera1/com/google/android/cameraview/Camera1.java b/library/src/main/camera1/com/google/android/cameraview/Camera1.java index f48eb504..30233dd7 100644 --- a/library/src/main/camera1/com/google/android/cameraview/Camera1.java +++ b/library/src/main/camera1/com/google/android/cameraview/Camera1.java @@ -21,6 +21,7 @@ import android.view.TextureView; import java.io.IOException; +import java.util.List; import java.util.Set; import java.util.SortedSet; @@ -49,7 +50,7 @@ class Camera1 extends CameraViewImpl { private boolean mShowingPreview; - private int mFocusMode; + private boolean mAutoFocus; private int mFacing; @@ -141,6 +142,11 @@ boolean isCameraOpened() { return mCamera != null; } + @Override + Set getSupportedAspectRatios() { + return mPreviewSizes.ratios(); + } + @Override void setAspectRatio(AspectRatio ratio) { if (mAspectRatio == null || !isCameraOpened()) { @@ -163,29 +169,30 @@ AspectRatio getAspectRatio() { } @Override - void setFocusMode(int focusMode) { - if (mFocusMode != focusMode) { - mFocusMode = focusMode; - if (mCamera != null) { - mCameraParameters.setFocusMode(Camera1Constants.convertFocusMode(focusMode)); - mCamera.setParameters(mCameraParameters); - } + void setAutoFocus(boolean autoFocus) { + if (mAutoFocus == autoFocus) { + return; + } + if (setAutoFocusInternal(autoFocus)) { + mCamera.setParameters(mCameraParameters); } } @Override - int getFocusMode() { - return mFocusMode; + boolean getAutoFocus() { + String focusMode = mCameraParameters.getFocusMode(); + return focusMode != null && focusMode.contains("continuous"); } @Override void setFacing(int facing) { - if (mFacing != facing) { - mFacing = facing; - if (mCamera != null) { - stop(); - start(); - } + if (mFacing == facing) { + return; + } + mFacing = facing; + if (isCameraOpened()) { + stop(); + start(); } } @@ -199,6 +206,20 @@ void takePicture() { if (!isCameraOpened()) { throw new IllegalStateException("Camera is not ready. Call start() before takePicture()."); } + if (getAutoFocus()) { + mCamera.cancelAutoFocus(); + mCamera.autoFocus(new Camera.AutoFocusCallback() { + @Override + public void onAutoFocus(boolean success, Camera camera) { + takePictureInternal(); + } + }); + } else { + takePictureInternal(); + } + } + + private void takePictureInternal() { mCamera.takePicture(null, null, null, new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { @@ -285,7 +306,7 @@ private void adjustCameraParameters() { mCameraParameters.setPreviewSize(size.getWidth(), size.getHeight()); mCameraParameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); mCameraParameters.setRotation(calcCameraRotation(mDisplayOrientation)); - mCameraParameters.setFocusMode(Camera1Constants.convertFocusMode(mFocusMode)); + setAutoFocusInternal(mAutoFocus); mCamera.setParameters(mCameraParameters); if (mShowingPreview) { mCamera.startPreview(); @@ -334,4 +355,26 @@ private int calcCameraRotation(int rotation) { } } + /** + * @return {@code true} if {@link #mCameraParameters} was modified. + */ + private boolean setAutoFocusInternal(boolean autoFocus) { + mAutoFocus = autoFocus; + if (isCameraOpened()) { + final List modes = mCameraParameters.getSupportedFocusModes(); + if (autoFocus && modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { + mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); + } else if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) { + mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED); + } else if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) { + mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY); + } else { + mCameraParameters.setFocusMode(modes.get(0)); + } + return true; + } else { + return false; + } + } + } diff --git a/library/src/main/camera1/com/google/android/cameraview/Camera1Constants.java b/library/src/main/camera1/com/google/android/cameraview/Camera1Constants.java deleted file mode 100644 index ed18e820..00000000 --- a/library/src/main/camera1/com/google/android/cameraview/Camera1Constants.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.cameraview; - - -import android.hardware.Camera; -import android.support.v4.util.SparseArrayCompat; - -@SuppressWarnings("deprecation") -class Camera1Constants { - - private static SparseArrayCompat sFocusModesMap; - - static String convertFocusMode(int focusMode) { - if (sFocusModesMap == null) { - sFocusModesMap = new SparseArrayCompat<>(); - sFocusModesMap.put(Constants.FOCUS_MODE_OFF, Camera.Parameters.FOCUS_MODE_INFINITY); - sFocusModesMap.put(Constants.FOCUS_MODE_AUTO, Camera.Parameters.FOCUS_MODE_AUTO); - sFocusModesMap.put(Constants.FOCUS_MODE_MACRO, Camera.Parameters.FOCUS_MODE_MACRO); - sFocusModesMap.put(Constants.FOCUS_MODE_CONTINUOUS_PICTURE, - Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); - sFocusModesMap.put(Constants.FOCUS_MODE_CONTINUOUS_VIDEO, - Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); - sFocusModesMap.put(Constants.FOCUS_MODE_EDOF, Camera.Parameters.FOCUS_MODE_EDOF); - } - return sFocusModesMap.get(focusMode); - } - -} diff --git a/library/src/main/java/com/google/android/cameraview/CameraView.java b/library/src/main/java/com/google/android/cameraview/CameraView.java index 619e83d3..e17ca80d 100644 --- a/library/src/main/java/com/google/android/cameraview/CameraView.java +++ b/library/src/main/java/com/google/android/cameraview/CameraView.java @@ -30,27 +30,18 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Set; public class CameraView extends FrameLayout { - public static final int FOCUS_MODE_OFF = Constants.FOCUS_MODE_OFF; - public static final int FOCUS_MODE_AUTO = Constants.FOCUS_MODE_AUTO; - public static final int FOCUS_MODE_MACRO = Constants.FOCUS_MODE_MACRO; - public static final int FOCUS_MODE_CONTINUOUS_PICTURE = Constants.FOCUS_MODE_CONTINUOUS_PICTURE; - public static final int FOCUS_MODE_CONTINUOUS_VIDEO = Constants.FOCUS_MODE_CONTINUOUS_VIDEO; - public static final int FOCUS_MODE_EDOF = Constants.FOCUS_MODE_EDOF; - - @IntDef({FOCUS_MODE_OFF, FOCUS_MODE_AUTO, FOCUS_MODE_MACRO, FOCUS_MODE_CONTINUOUS_PICTURE, - FOCUS_MODE_CONTINUOUS_VIDEO, FOCUS_MODE_EDOF}) - @Retention(RetentionPolicy.SOURCE) - public @interface FocusMode { - } - + /** The camera device faces the opposite direction as the device's screen. */ public static final int FACING_BACK = Constants.FACING_BACK; + + /** The camera device faces the same direction as the device's screen. */ public static final int FACING_FRONT = Constants.FACING_FRONT; - public static final int FACING_EXTERNAL = Constants.FACING_EXTERNAL; - @IntDef({FACING_BACK, FACING_FRONT, FACING_EXTERNAL}) + /** Direction the camera faces relative to device screen. */ + @IntDef({FACING_BACK, FACING_FRONT}) @Retention(RetentionPolicy.SOURCE) public @interface Facing { } @@ -73,7 +64,6 @@ public CameraView(Context context, AttributeSet attrs) { this(context, attrs, 0); } - @SuppressWarnings("WrongConstant") public CameraView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // Internal setup @@ -91,7 +81,8 @@ public CameraView(Context context, AttributeSet attrs, int defStyleAttr) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView, defStyleAttr, R.style.Widget_CameraView); mAdjustViewBounds = a.getBoolean(R.styleable.CameraView_android_adjustViewBounds, false); - setFocusMode(a.getInt(R.styleable.CameraView_focusMode, FOCUS_MODE_OFF)); + setAutoFocus(a.getBoolean(R.styleable.CameraView_autoFocus, true)); + //noinspection WrongConstant setFacing(a.getInt(R.styleable.CameraView_facing, FACING_BACK)); a.recycle(); // Display orientation detector @@ -235,6 +226,13 @@ public boolean getAdjustViewBounds() { return mAdjustViewBounds; } + /** + * Gets all the aspect ratios supported by the current camera. + */ + public Set getSupportedAspectRatios() { + return mImpl.getSupportedAspectRatios(); + } + /** * Sets the aspect ratio of camera. * @@ -255,33 +253,31 @@ public AspectRatio getAspectRatio() { } /** - * Sets the focus mode. + * Enables or disables the continuous auto-focus mode. When the current camera doesn't support + * auto-focus, calling this method will be ignored. * - * @param focusMode The focus mode. Must be one of {@link #FOCUS_MODE_OFF}, - * {@link #FOCUS_MODE_AUTO}, {@link #FOCUS_MODE_MACRO}, - * {@link #FOCUS_MODE_CONTINUOUS_PICTURE}, - * {@link #FOCUS_MODE_CONTINUOUS_VIDEO}, and {@link #FOCUS_MODE_EDOF}. + * @param autoFocus {@code true} to enable continuous auto-focus mode. {@code false} to + * disable it. */ - public void setFocusMode(@FocusMode int focusMode) { - mImpl.setFocusMode(focusMode); + public void setAutoFocus(boolean autoFocus) { + mImpl.setAutoFocus(autoFocus); } /** - * Gets the current focus mode. + * Returns whether the continuous auto-focus mode is enabled. * - * @return The current focus mode. + * @return {@code true} if the continuous auto-focus mode is enabled. {@code false} if it is + * disabled, or if it is not supported by the current camera. */ - @FocusMode - public int getFocusMode() { - //noinspection WrongConstant - return mImpl.getFocusMode(); + public boolean getAutoFocus() { + return mImpl.getAutoFocus(); } /** * Chooses camera by the direction it faces. * - * @param facing The camera facing. Must be one of {@link #FACING_BACK}, - * {@link #FACING_FRONT}, and {@link #FACING_EXTERNAL} + * @param facing The camera facing. Must be either {@link #FACING_BACK} or + * {@link #FACING_FRONT}. */ public void setFacing(@Facing int facing) { mImpl.setFacing(facing); diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index 80fd2513..815762b6 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -14,14 +14,7 @@ - - - - - - - - + diff --git a/library/src/main/res/values/styles.xml b/library/src/main/res/values/styles.xml index 1c120e87..88524a32 100644 --- a/library/src/main/res/values/styles.xml +++ b/library/src/main/res/values/styles.xml @@ -15,7 +15,7 @@