diff options
author | Michael Kolb <kolby@google.com> | 2013-01-29 20:51:56 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2013-01-29 20:51:57 +0000 |
commit | 9a19022b495447029c81fdcd83dc1a1c86d98b25 (patch) | |
tree | de3268b090958a305529d1f34051bea03bacde46 /src/com/android/camera/FocusOverlayManager.java | |
parent | 72d510612f505d4d8439dab144d5c253346aebc2 (diff) | |
parent | 8872c23e739de38d74f04a8c852ebb5199c905f6 (diff) | |
download | android_packages_apps_Snap-9a19022b495447029c81fdcd83dc1a1c86d98b25.tar.gz android_packages_apps_Snap-9a19022b495447029c81fdcd83dc1a1c86d98b25.tar.bz2 android_packages_apps_Snap-9a19022b495447029c81fdcd83dc1a1c86d98b25.zip |
Merge "Move Camera Java/Native source into Gallery2" into gb-ub-photos-bryce
Diffstat (limited to 'src/com/android/camera/FocusOverlayManager.java')
-rw-r--r-- | src/com/android/camera/FocusOverlayManager.java | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/src/com/android/camera/FocusOverlayManager.java b/src/com/android/camera/FocusOverlayManager.java new file mode 100644 index 000000000..2bec18760 --- /dev/null +++ b/src/com/android/camera/FocusOverlayManager.java @@ -0,0 +1,560 @@ +/* + * Copyright (C) 2012 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.android.camera; + +import android.annotation.TargetApi; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.hardware.Camera.Area; +import android.hardware.Camera.Parameters; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import com.android.camera.ui.FaceView; +import com.android.camera.ui.FocusIndicator; +import com.android.camera.ui.PieRenderer; +import com.android.gallery3d.common.ApiHelper; + +import java.util.ArrayList; +import java.util.List; + +/* A class that handles everything about focus in still picture mode. + * This also handles the metering area because it is the same as focus area. + * + * The test cases: + * (1) The camera has continuous autofocus. Move the camera. Take a picture when + * CAF is not in progress. + * (2) The camera has continuous autofocus. Move the camera. Take a picture when + * CAF is in progress. + * (3) The camera has face detection. Point the camera at some faces. Hold the + * shutter. Release to take a picture. + * (4) The camera has face detection. Point the camera at some faces. Single tap + * the shutter to take a picture. + * (5) The camera has autofocus. Single tap the shutter to take a picture. + * (6) The camera has autofocus. Hold the shutter. Release to take a picture. + * (7) The camera has no autofocus. Single tap the shutter and take a picture. + * (8) The camera has autofocus and supports focus area. Touch the screen to + * trigger autofocus. Take a picture. + * (9) The camera has autofocus and supports focus area. Touch the screen to + * trigger autofocus. Wait until it times out. + * (10) The camera has no autofocus and supports metering area. Touch the screen + * to change metering area. + */ +public class FocusOverlayManager { + private static final String TAG = "CAM_FocusManager"; + + private static final int RESET_TOUCH_FOCUS = 0; + private static final int RESET_TOUCH_FOCUS_DELAY = 3000; + + private int mState = STATE_IDLE; + private static final int STATE_IDLE = 0; // Focus is not active. + private static final int STATE_FOCUSING = 1; // Focus is in progress. + // Focus is in progress and the camera should take a picture after focus finishes. + private static final int STATE_FOCUSING_SNAP_ON_FINISH = 2; + private static final int STATE_SUCCESS = 3; // Focus finishes and succeeds. + private static final int STATE_FAIL = 4; // Focus finishes and fails. + + private boolean mInitialized; + private boolean mFocusAreaSupported; + private boolean mMeteringAreaSupported; + private boolean mLockAeAwbNeeded; + private boolean mAeAwbLock; + private Matrix mMatrix; + + private PieRenderer mPieRenderer; + + private int mPreviewWidth; // The width of the preview frame layout. + private int mPreviewHeight; // The height of the preview frame layout. + private boolean mMirror; // true if the camera is front-facing. + private int mDisplayOrientation; + private FaceView mFaceView; + private List<Object> mFocusArea; // focus area in driver format + private List<Object> mMeteringArea; // metering area in driver format + private String mFocusMode; + private String[] mDefaultFocusModes; + private String mOverrideFocusMode; + private Parameters mParameters; + private ComboPreferences mPreferences; + private Handler mHandler; + Listener mListener; + + public interface Listener { + public void autoFocus(); + public void cancelAutoFocus(); + public boolean capture(); + public void startFaceDetection(); + public void stopFaceDetection(); + public void setFocusParameters(); + } + + private class MainHandler extends Handler { + public MainHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case RESET_TOUCH_FOCUS: { + cancelAutoFocus(); + mListener.startFaceDetection(); + break; + } + } + } + } + + public FocusOverlayManager(ComboPreferences preferences, String[] defaultFocusModes, + Parameters parameters, Listener listener, + boolean mirror, Looper looper) { + mHandler = new MainHandler(looper); + mMatrix = new Matrix(); + mPreferences = preferences; + mDefaultFocusModes = defaultFocusModes; + setParameters(parameters); + mListener = listener; + setMirror(mirror); + } + + public void setFocusRenderer(PieRenderer renderer) { + mPieRenderer = renderer; + mInitialized = (mMatrix != null); + } + + public void setParameters(Parameters parameters) { + // parameters can only be null when onConfigurationChanged is called + // before camera is open. We will just return in this case, because + // parameters will be set again later with the right parameters after + // camera is open. + if (parameters == null) return; + mParameters = parameters; + mFocusAreaSupported = Util.isFocusAreaSupported(parameters); + mMeteringAreaSupported = Util.isMeteringAreaSupported(parameters); + mLockAeAwbNeeded = (Util.isAutoExposureLockSupported(mParameters) || + Util.isAutoWhiteBalanceLockSupported(mParameters)); + } + + public void setPreviewSize(int previewWidth, int previewHeight) { + if (mPreviewWidth != previewWidth || mPreviewHeight != previewHeight) { + mPreviewWidth = previewWidth; + mPreviewHeight = previewHeight; + setMatrix(); + } + } + + public void setMirror(boolean mirror) { + mMirror = mirror; + setMatrix(); + } + + public void setDisplayOrientation(int displayOrientation) { + mDisplayOrientation = displayOrientation; + setMatrix(); + } + + public void setFaceView(FaceView faceView) { + mFaceView = faceView; + } + + private void setMatrix() { + if (mPreviewWidth != 0 && mPreviewHeight != 0) { + Matrix matrix = new Matrix(); + Util.prepareMatrix(matrix, mMirror, mDisplayOrientation, + mPreviewWidth, mPreviewHeight); + // In face detection, the matrix converts the driver coordinates to UI + // coordinates. In tap focus, the inverted matrix converts the UI + // coordinates to driver coordinates. + matrix.invert(mMatrix); + mInitialized = (mPieRenderer != null); + } + } + + private void lockAeAwbIfNeeded() { + if (mLockAeAwbNeeded && !mAeAwbLock) { + mAeAwbLock = true; + mListener.setFocusParameters(); + } + } + + private void unlockAeAwbIfNeeded() { + if (mLockAeAwbNeeded && mAeAwbLock && (mState != STATE_FOCUSING_SNAP_ON_FINISH)) { + mAeAwbLock = false; + mListener.setFocusParameters(); + } + } + + public void onShutterDown() { + if (!mInitialized) return; + + boolean autoFocusCalled = false; + if (needAutoFocusCall()) { + // Do not focus if touch focus has been triggered. + if (mState != STATE_SUCCESS && mState != STATE_FAIL) { + autoFocus(); + autoFocusCalled = true; + } + } + + if (!autoFocusCalled) lockAeAwbIfNeeded(); + } + + public void onShutterUp() { + if (!mInitialized) return; + + if (needAutoFocusCall()) { + // User releases half-pressed focus key. + if (mState == STATE_FOCUSING || mState == STATE_SUCCESS + || mState == STATE_FAIL) { + cancelAutoFocus(); + } + } + + // Unlock AE and AWB after cancelAutoFocus. Camera API does not + // guarantee setParameters can be called during autofocus. + unlockAeAwbIfNeeded(); + } + + public void doSnap() { + if (!mInitialized) return; + + // If the user has half-pressed the shutter and focus is completed, we + // can take the photo right away. If the focus mode is infinity, we can + // also take the photo. + if (!needAutoFocusCall() || (mState == STATE_SUCCESS || mState == STATE_FAIL)) { + capture(); + } else if (mState == STATE_FOCUSING) { + // Half pressing the shutter (i.e. the focus button event) will + // already have requested AF for us, so just request capture on + // focus here. + mState = STATE_FOCUSING_SNAP_ON_FINISH; + } else if (mState == STATE_IDLE) { + // We didn't do focus. This can happen if the user press focus key + // while the snapshot is still in progress. The user probably wants + // the next snapshot as soon as possible, so we just do a snapshot + // without focusing again. + capture(); + } + } + + public void onAutoFocus(boolean focused, boolean shutterButtonPressed) { + if (mState == STATE_FOCUSING_SNAP_ON_FINISH) { + // Take the picture no matter focus succeeds or fails. No need + // to play the AF sound if we're about to play the shutter + // sound. + if (focused) { + mState = STATE_SUCCESS; + } else { + mState = STATE_FAIL; + } + updateFocusUI(); + capture(); + } else if (mState == STATE_FOCUSING) { + // This happens when (1) user is half-pressing the focus key or + // (2) touch focus is triggered. Play the focus tone. Do not + // take the picture now. + if (focused) { + mState = STATE_SUCCESS; + } else { + mState = STATE_FAIL; + } + updateFocusUI(); + // If this is triggered by touch focus, cancel focus after a + // while. + if (mFocusArea != null) { + mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY); + } + if (shutterButtonPressed) { + // Lock AE & AWB so users can half-press shutter and recompose. + lockAeAwbIfNeeded(); + } + } else if (mState == STATE_IDLE) { + // User has released the focus key before focus completes. + // Do nothing. + } + } + + public void onAutoFocusMoving(boolean moving) { + if (!mInitialized) return; + // Ignore if the camera has detected some faces. + if (mFaceView != null && mFaceView.faceExists()) { + mPieRenderer.clear(); + return; + } + + // Ignore if we have requested autofocus. This method only handles + // continuous autofocus. + if (mState != STATE_IDLE) return; + + if (moving) { + mPieRenderer.showStart(); + } else { + mPieRenderer.showSuccess(true); + } + } + + @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) + private void initializeFocusAreas(int focusWidth, int focusHeight, + int x, int y, int previewWidth, int previewHeight) { + if (mFocusArea == null) { + mFocusArea = new ArrayList<Object>(); + mFocusArea.add(new Area(new Rect(), 1)); + } + + // Convert the coordinates to driver format. + calculateTapArea(focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight, + ((Area) mFocusArea.get(0)).rect); + } + + @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) + private void initializeMeteringAreas(int focusWidth, int focusHeight, + int x, int y, int previewWidth, int previewHeight) { + if (mMeteringArea == null) { + mMeteringArea = new ArrayList<Object>(); + mMeteringArea.add(new Area(new Rect(), 1)); + } + + // Convert the coordinates to driver format. + // AE area is bigger because exposure is sensitive and + // easy to over- or underexposure if area is too small. + calculateTapArea(focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight, + ((Area) mMeteringArea.get(0)).rect); + } + + public void onSingleTapUp(int x, int y) { + if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) return; + + // Let users be able to cancel previous touch focus. + if ((mFocusArea != null) && (mState == STATE_FOCUSING || + mState == STATE_SUCCESS || mState == STATE_FAIL)) { + cancelAutoFocus(); + } + // Initialize variables. + int focusWidth = mPieRenderer.getSize(); + int focusHeight = mPieRenderer.getSize(); + if (focusWidth == 0 || mPieRenderer.getWidth() == 0 + || mPieRenderer.getHeight() == 0) return; + int previewWidth = mPreviewWidth; + int previewHeight = mPreviewHeight; + // Initialize mFocusArea. + if (mFocusAreaSupported) { + initializeFocusAreas( + focusWidth, focusHeight, x, y, previewWidth, previewHeight); + } + // Initialize mMeteringArea. + if (mMeteringAreaSupported) { + initializeMeteringAreas( + focusWidth, focusHeight, x, y, previewWidth, previewHeight); + } + + // Use margin to set the focus indicator to the touched area. + mPieRenderer.setFocus(x, y); + + // Stop face detection because we want to specify focus and metering area. + mListener.stopFaceDetection(); + + // Set the focus area and metering area. + mListener.setFocusParameters(); + if (mFocusAreaSupported) { + autoFocus(); + } else { // Just show the indicator in all other cases. + updateFocusUI(); + // Reset the metering area in 3 seconds. + mHandler.removeMessages(RESET_TOUCH_FOCUS); + mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY); + } + } + + public void onPreviewStarted() { + mState = STATE_IDLE; + } + + public void onPreviewStopped() { + // If auto focus was in progress, it would have been stopped. + mState = STATE_IDLE; + resetTouchFocus(); + updateFocusUI(); + } + + public void onCameraReleased() { + onPreviewStopped(); + } + + private void autoFocus() { + Log.v(TAG, "Start autofocus."); + mListener.autoFocus(); + mState = STATE_FOCUSING; + // Pause the face view because the driver will keep sending face + // callbacks after the focus completes. + if (mFaceView != null) mFaceView.pause(); + updateFocusUI(); + mHandler.removeMessages(RESET_TOUCH_FOCUS); + } + + private void cancelAutoFocus() { + Log.v(TAG, "Cancel autofocus."); + + // Reset the tap area before calling mListener.cancelAutofocus. + // Otherwise, focus mode stays at auto and the tap area passed to the + // driver is not reset. + resetTouchFocus(); + mListener.cancelAutoFocus(); + if (mFaceView != null) mFaceView.resume(); + mState = STATE_IDLE; + updateFocusUI(); + mHandler.removeMessages(RESET_TOUCH_FOCUS); + } + + private void capture() { + if (mListener.capture()) { + mState = STATE_IDLE; + mHandler.removeMessages(RESET_TOUCH_FOCUS); + } + } + + public String getFocusMode() { + if (mOverrideFocusMode != null) return mOverrideFocusMode; + List<String> supportedFocusModes = mParameters.getSupportedFocusModes(); + + if (mFocusAreaSupported && mFocusArea != null) { + // Always use autofocus in tap-to-focus. + mFocusMode = Parameters.FOCUS_MODE_AUTO; + } else { + // The default is continuous autofocus. + mFocusMode = mPreferences.getString( + CameraSettings.KEY_FOCUS_MODE, null); + + // Try to find a supported focus mode from the default list. + if (mFocusMode == null) { + for (int i = 0; i < mDefaultFocusModes.length; i++) { + String mode = mDefaultFocusModes[i]; + if (Util.isSupported(mode, supportedFocusModes)) { + mFocusMode = mode; + break; + } + } + } + } + if (!Util.isSupported(mFocusMode, supportedFocusModes)) { + // For some reasons, the driver does not support the current + // focus mode. Fall back to auto. + if (Util.isSupported(Parameters.FOCUS_MODE_AUTO, + mParameters.getSupportedFocusModes())) { + mFocusMode = Parameters.FOCUS_MODE_AUTO; + } else { + mFocusMode = mParameters.getFocusMode(); + } + } + return mFocusMode; + } + + public List getFocusAreas() { + return mFocusArea; + } + + public List getMeteringAreas() { + return mMeteringArea; + } + + public void updateFocusUI() { + if (!mInitialized) return; + // Show only focus indicator or face indicator. + boolean faceExists = (mFaceView != null && mFaceView.faceExists()); + FocusIndicator focusIndicator = (faceExists) ? mFaceView : mPieRenderer; + + if (mState == STATE_IDLE) { + if (mFocusArea == null) { + focusIndicator.clear(); + } else { + // Users touch on the preview and the indicator represents the + // metering area. Either focus area is not supported or + // autoFocus call is not required. + focusIndicator.showStart(); + } + } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) { + focusIndicator.showStart(); + } else { + if (Util.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusMode)) { + // TODO: check HAL behavior and decide if this can be removed. + focusIndicator.showSuccess(false); + } else if (mState == STATE_SUCCESS) { + focusIndicator.showSuccess(false); + } else if (mState == STATE_FAIL) { + focusIndicator.showFail(false); + } + } + } + + public void resetTouchFocus() { + if (!mInitialized) return; + + // Put focus indicator to the center. clear reset position + mPieRenderer.clear(); + + mFocusArea = null; + mMeteringArea = null; + } + + private void calculateTapArea(int focusWidth, int focusHeight, float areaMultiple, + int x, int y, int previewWidth, int previewHeight, Rect rect) { + int areaWidth = (int) (focusWidth * areaMultiple); + int areaHeight = (int) (focusHeight * areaMultiple); + int left = Util.clamp(x - areaWidth / 2, 0, previewWidth - areaWidth); + int top = Util.clamp(y - areaHeight / 2, 0, previewHeight - areaHeight); + + RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight); + mMatrix.mapRect(rectF); + Util.rectFToRect(rectF, rect); + } + + /* package */ int getFocusState() { + return mState; + } + + public boolean isFocusCompleted() { + return mState == STATE_SUCCESS || mState == STATE_FAIL; + } + + public boolean isFocusingSnapOnFinish() { + return mState == STATE_FOCUSING_SNAP_ON_FINISH; + } + + public void removeMessages() { + mHandler.removeMessages(RESET_TOUCH_FOCUS); + } + + public void overrideFocusMode(String focusMode) { + mOverrideFocusMode = focusMode; + } + + public void setAeAwbLock(boolean lock) { + mAeAwbLock = lock; + } + + public boolean getAeAwbLock() { + return mAeAwbLock; + } + + private boolean needAutoFocusCall() { + String focusMode = getFocusMode(); + return !(focusMode.equals(Parameters.FOCUS_MODE_INFINITY) + || focusMode.equals(Parameters.FOCUS_MODE_FIXED) + || focusMode.equals(Parameters.FOCUS_MODE_EDOF)); + } +} |