/* * 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.graphics.Rect; import android.graphics.RectF; import android.hardware.Camera.Area; import android.os.Handler; import android.os.Looper; import android.os.Message; import com.android.camera.app.AppController; import com.android.camera.app.MotionManager; import com.android.camera.debug.Log; import com.android.camera.one.Settings3A; import com.android.camera.settings.Keys; import com.android.camera.settings.SettingsManager; import com.android.camera.ui.PreviewStatusListener; import com.android.camera.ui.TouchCoordinate; import com.android.camera.util.ApiHelper; import com.android.camera.ui.focus.CameraCoordinateTransformer; import com.android.camera.ui.focus.FocusRing; import com.android.camera.util.CameraUtil; import com.android.camera.stats.UsageStatistics; import com.android.ex.camera2.portability.CameraCapabilities; import java.lang.ref.WeakReference; 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 implements PreviewStatusListener.PreviewAreaChangedListener, MotionManager.MotionListener { private static final Log.Tag TAG = new Log.Tag("FocusOverlayMgr"); private static final int RESET_TOUCH_FOCUS = 0; private static final int RESET_TOUCH_FOCUS_DELAY_MILLIS = Settings3A.getFocusHoldMillis(); public static final float AF_REGION_BOX = Settings3A.getAutoFocusRegionWidth(); public static final float AE_REGION_BOX = Settings3A.getMeteringRegionWidth(); 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 CameraCoordinateTransformer mCoordinateTransformer; private boolean mMirror; // true if the camera is front-facing. private int mDisplayOrientation; private List mFocusArea; // focus area in driver format private List mMeteringArea; // metering area in driver format private CameraCapabilities.FocusMode mFocusMode; private final List mDefaultFocusModes; private CameraCapabilities.FocusMode mOverrideFocusMode; private CameraCapabilities mCapabilities; private final AppController mAppController; private final SettingsManager mSettingsManager; private final Handler mHandler; Listener mListener; private boolean mPreviousMoving; private final FocusRing mFocusRing; private final Rect mPreviewRect = new Rect(0, 0, 0, 0); private boolean mFocusLocked; /** Manual tap to focus parameters */ private TouchCoordinate mTouchCoordinate; private long mTouchTime; public interface Listener { public void autoFocus(); public void cancelAutoFocus(); public boolean capture(); public void startFaceDetection(); public void stopFaceDetection(); public void setFocusParameters(); } /** * TODO: Refactor this so that we either don't need a handler or make * mListener not be the activity. */ private static class MainHandler extends Handler { /** * The outer mListener at the moment is actually the CameraActivity, * which we would leak if we didn't break the GC path here using a * WeakReference. */ final WeakReference mManager; public MainHandler(FocusOverlayManager manager, Looper looper) { super(looper); mManager = new WeakReference(manager); } @Override public void handleMessage(Message msg) { FocusOverlayManager manager = mManager.get(); if (manager == null) { return; } switch (msg.what) { case RESET_TOUCH_FOCUS: { manager.cancelAutoFocus(); manager.mListener.startFaceDetection(); break; } } } } public FocusOverlayManager(AppController appController, List defaultFocusModes, CameraCapabilities capabilities, Listener listener, boolean mirror, Looper looper, FocusRing focusRing) { mAppController = appController; mSettingsManager = appController.getSettingsManager(); mHandler = new MainHandler(this, looper); mDefaultFocusModes = new ArrayList(defaultFocusModes); updateCapabilities(capabilities); mListener = listener; setMirror(mirror); mFocusRing = focusRing; mFocusLocked = false; } public void updateCapabilities(CameraCapabilities capabilities) { // capabilities can only be null when onConfigurationChanged is called // before camera is open. We will just return in this case, because // capabilities will be set again later with the right capabilities after // camera is open. if (capabilities == null) { return; } mCapabilities = capabilities; mFocusAreaSupported = mCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA); mMeteringAreaSupported = mCapabilities.supports(CameraCapabilities.Feature.METERING_AREA); mLockAeAwbNeeded = (mCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK) || mCapabilities.supports(CameraCapabilities.Feature.AUTO_WHITE_BALANCE_LOCK)); } /** This setter should be the only way to mutate mPreviewRect. */ public void setPreviewRect(Rect previewRect) { if (!mPreviewRect.equals(previewRect)) { mPreviewRect.set(previewRect); mFocusRing.configurePreviewDimensions(CameraUtil.rectToRectF(mPreviewRect)); resetCoordinateTransformer(); mInitialized = true; } } @Override public void onPreviewAreaChanged(RectF previewArea) { setPreviewRect(CameraUtil.rectFToRect(previewArea)); } public void setMirror(boolean mirror) { mMirror = mirror; resetCoordinateTransformer(); } public void setDisplayOrientation(int displayOrientation) { mDisplayOrientation = displayOrientation; resetCoordinateTransformer(); } private void resetCoordinateTransformer() { if (mPreviewRect.width() > 0 && mPreviewRect.height() > 0) { mCoordinateTransformer = new CameraCoordinateTransformer(mMirror, mDisplayOrientation, CameraUtil.rectToRectF(mPreviewRect)); } else { Log.w(TAG, "The coordinate transformer could not be built because the preview rect" + "did not have a width and height"); } } 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 onShutterUp(CameraCapabilities.FocusMode currentFocusMode) { if (!mInitialized) { return; } if (needAutoFocusCall(currentFocusMode)) { // 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 focusAndCapture(CameraCapabilities.FocusMode currentFocusMode) { if (!mInitialized) { return; } if (!needAutoFocusCall(currentFocusMode)) { // Focus is not needed. capture(); } else if (mState == STATE_SUCCESS || mState == STATE_FAIL) { // Focus is done already. capture(); } else if (mState == STATE_FOCUSING) { // Still focusing and will not trigger snap upon finish. mState = STATE_FOCUSING_SNAP_ON_FINISH; } else if (mState == STATE_IDLE) { autoFocusAndCapture(); } } 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; } 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; } // If this is triggered by touch focus, cancel focus after a // while. if (mFocusArea != null) { mFocusLocked = true; mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY_MILLIS); } 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 we have requested autofocus. This method only handles // continuous autofocus. if (mState != STATE_IDLE) { return; } // animate on false->true trasition only b/8219520 if (moving && !mPreviousMoving) { // Auto focus at the center of the preview. mFocusRing.startPassiveFocus(); mFocusRing.centerFocusLocation(); } else if (!moving && mFocusRing.isPassiveFocusRunning()) { mFocusRing.stopFocusAnimations(); } mPreviousMoving = moving; } /** Returns width of auto focus region in pixels. */ private int getAFRegionSizePx() { return (int) (Math.min(mPreviewRect.width(), mPreviewRect.height()) * AF_REGION_BOX); } /** Returns width of metering region in pixels. */ private int getAERegionSizePx() { return (int) (Math.min(mPreviewRect.width(), mPreviewRect.height()) * AE_REGION_BOX); } private void initializeFocusAreas(int x, int y) { if (mFocusArea == null) { mFocusArea = new ArrayList(); mFocusArea.add(new Area(new Rect(), 1)); } // Convert the coordinates to driver format. mFocusArea.get(0).rect = computeCameraRectFromPreviewCoordinates(x, y, getAFRegionSizePx()); } private void initializeMeteringAreas(int x, int y) { if (mMeteringArea == null) { mMeteringArea = new ArrayList(); mMeteringArea.add(new Area(new Rect(), 1)); } // Convert the coordinates to driver format. mMeteringArea.get(0).rect = computeCameraRectFromPreviewCoordinates(x, y, getAERegionSizePx()); } 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(); } if (mPreviewRect.width() == 0 || mPreviewRect.height() == 0) { return; } // Initialize variables. // Initialize mFocusArea. if (mFocusAreaSupported) { initializeFocusAreas(x, y); } // Initialize mMeteringArea. if (mMeteringAreaSupported) { initializeMeteringAreas(x, y); } mFocusRing.startActiveFocus(); mFocusRing.setFocusLocation(x, y); // Log manual tap to focus. mTouchCoordinate = new TouchCoordinate(x, y, mPreviewRect.width(), mPreviewRect.height()); mTouchTime = System.currentTimeMillis(); // 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. // Reset the metering area in 4 seconds. mHandler.removeMessages(RESET_TOUCH_FOCUS); mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY_MILLIS); } } public void onPreviewStarted() { mState = STATE_IDLE; // Avoid resetting touch focus if N4, b/18681082. if (!ApiHelper.IS_NEXUS_4) { resetTouchFocus(); } } public void onPreviewStopped() { // If auto focus was in progress, it would have been stopped. mState = STATE_IDLE; } public void onCameraReleased() { onPreviewStopped(); } @Override public void onMoving() { if (mFocusLocked) { Log.d(TAG, "onMoving: Early focus unlock."); cancelAutoFocus(); } } /** * Triggers the autofocus and sets the specified state. * * @param focusingState The state to use when focus is in progress. */ private void autoFocus(int focusingState) { mListener.autoFocus(); mState = focusingState; mHandler.removeMessages(RESET_TOUCH_FOCUS); } /** * Triggers the autofocus and set the state to indicate the focus is in * progress. */ private void autoFocus() { autoFocus(STATE_FOCUSING); } /** * Triggers the autofocus and set the state to which a capture will happen * in the following autofocus callback. */ private void autoFocusAndCapture() { autoFocus(STATE_FOCUSING_SNAP_ON_FINISH); } 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(); mState = STATE_IDLE; mFocusLocked = false; mHandler.removeMessages(RESET_TOUCH_FOCUS); } private void capture() { if (mListener.capture()) { mState = STATE_IDLE; mHandler.removeMessages(RESET_TOUCH_FOCUS); } } public CameraCapabilities.FocusMode getFocusMode( final CameraCapabilities.FocusMode currentFocusMode) { if (mOverrideFocusMode != null) { Log.v(TAG, "returning override focus: " + mOverrideFocusMode); return mOverrideFocusMode; } if (mCapabilities == null) { Log.v(TAG, "no capabilities, returning default AUTO focus mode"); return CameraCapabilities.FocusMode.AUTO; } if (mFocusAreaSupported && mFocusArea != null) { Log.v(TAG, "in tap to focus, returning AUTO focus mode"); // Always use autofocus in tap-to-focus. mFocusMode = CameraCapabilities.FocusMode.AUTO; } else { String focusSetting = mSettingsManager.getString(mAppController.getCameraScope(), Keys.KEY_FOCUS_MODE); Log.v(TAG, "stored focus setting for camera: " + focusSetting); // The default is continuous autofocus. mFocusMode = mCapabilities.getStringifier().focusModeFromString(focusSetting); Log.v(TAG, "focus mode resolved from setting: " + mFocusMode); // Try to find a supported focus mode from the default list. if (mFocusMode == null) { for (CameraCapabilities.FocusMode mode : mDefaultFocusModes) { if (mCapabilities.supports(mode)) { mFocusMode = mode; Log.v(TAG, "selected supported focus mode from default list" + mode); break; } } } } if (!mCapabilities.supports(mFocusMode)) { // For some reasons, the driver does not support the current // focus mode. Fall back to auto. if (mCapabilities.supports(CameraCapabilities.FocusMode.AUTO)) { Log.v(TAG, "no supported focus mode, falling back to AUTO"); mFocusMode = CameraCapabilities.FocusMode.AUTO; } else { Log.v(TAG, "no supported focus mode, falling back to current: " + currentFocusMode); mFocusMode = currentFocusMode; } } return mFocusMode; } public List getFocusAreas() { return mFocusArea; } public List getMeteringAreas() { return mMeteringArea; } public void resetTouchFocus() { if (!mInitialized) { return; } mFocusArea = null; mMeteringArea = null; // This will cause current module to call getFocusAreas() and // getMeteringAreas() and send updated regions to camera. mListener.setFocusParameters(); if (mTouchCoordinate != null) { UsageStatistics.instance().tapToFocus(mTouchCoordinate, 0.001f * (System.currentTimeMillis() - mTouchTime)); mTouchCoordinate = null; } } private Rect computeCameraRectFromPreviewCoordinates(int x, int y, int size) { int left = CameraUtil.clamp(x - size / 2, mPreviewRect.left, mPreviewRect.right - size); int top = CameraUtil.clamp(y - size / 2, mPreviewRect.top, mPreviewRect.bottom - size); RectF rectF = new RectF(left, top, left + size, top + size); return CameraUtil.rectFToRect(mCoordinateTransformer.toCameraSpace(rectF)); } /* 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(CameraCapabilities.FocusMode focusMode) { mOverrideFocusMode = focusMode; } public void setAeAwbLock(boolean lock) { mAeAwbLock = lock; } public boolean getAeAwbLock() { return mAeAwbLock; } private boolean needAutoFocusCall(CameraCapabilities.FocusMode focusMode) { return focusMode == CameraCapabilities.FocusMode.AUTO; } }