blob: c16d01b39f89b3c375a0c0678f1ac49ca204c284 [file] [log] [blame]
/*
* 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<Area> mFocusArea; // focus area in driver format
private List<Area> mMeteringArea; // metering area in driver format
private CameraCapabilities.FocusMode mFocusMode;
private final List<CameraCapabilities.FocusMode> 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<FocusOverlayManager> mManager;
public MainHandler(FocusOverlayManager manager, Looper looper) {
super(looper);
mManager = new WeakReference<FocusOverlayManager>(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<CameraCapabilities.FocusMode> defaultFocusModes, CameraCapabilities capabilities,
Listener listener, boolean mirror, Looper looper, FocusRing focusRing) {
mAppController = appController;
mSettingsManager = appController.getSettingsManager();
mHandler = new MainHandler(this, looper);
mDefaultFocusModes = new ArrayList<CameraCapabilities.FocusMode>(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<Area>();
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<Area>();
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<Area> getFocusAreas() {
return mFocusArea;
}
public List<Area> 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.INFINITY
|| focusMode == CameraCapabilities.FocusMode.FIXED
|| focusMode == CameraCapabilities.FocusMode.EXTENDED_DOF);
}
}