blob: 23959e47a194be89fc7dc9bf1b8ba582c536008a [file] [log] [blame]
/*
* Copyright 2013 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.ex.camera2.pos;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraMetadata.Key;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.util.Log;
import com.android.ex.camera2.utils.SysTrace;
/**
* Manage the auto focus state machine for CameraDevice.
*
* <p>Requests are created only when the AF needs to be manipulated from the user,
* but automatic camera-caused AF state changes are broadcasted from any new result.</p>
*/
public class AutoFocusStateMachine {
/**
* Observe state AF state transitions triggered by
* {@link AutoFocusStateMachine#onCaptureCompleted onCaptureCompleted}.
*/
public interface AutoFocusStateListener {
/**
* The camera is currently focused (either active or passive).
*
* @param locked True if the lens has been locked from moving, false otherwise.
*/
void onAutoFocusSuccess(CaptureResult result, boolean locked);
/**
* The camera is currently not focused (either active or passive).
*
* @param locked False if the AF is still scanning, true if needs a restart.
*/
void onAutoFocusFail(CaptureResult result, boolean locked);
/**
* The camera is currently scanning (either active or passive)
* and has not yet converged.
*
* <p>This is not called for results where the AF either succeeds or fails.</p>
*/
void onAutoFocusScan(CaptureResult result);
/**
* The camera is currently not doing anything with the autofocus.
*
* <p>Autofocus could be off, or this could be an intermediate state transition as
* scanning restarts.</p>
*/
void onAutoFocusInactive(CaptureResult result);
}
private static final String TAG = "AutoFocusStateMachine";
private static final boolean DEBUG_LOGGING = Log.isLoggable(TAG, Log.DEBUG);
private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
private static final int AF_UNINITIALIZED = -1;
private final AutoFocusStateListener mListener;
private int mLastAfState = AF_UNINITIALIZED;
private int mLastAfMode = AF_UNINITIALIZED;
private int mCurrentAfMode = AF_UNINITIALIZED;
private int mCurrentAfTrigger = AF_UNINITIALIZED;
private int mCurrentAfCookie = AF_UNINITIALIZED;
private String mCurrentAfTrace = "";
private int mLastAfCookie = 0;
public AutoFocusStateMachine(AutoFocusStateListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener should not be null");
}
mListener = listener;
}
/**
* Invoke every time we get a new CaptureResult via
* {@link CameraDevice.CaptureListener#onCaptureCompleted}.
*
* <p>This function is responsible for dispatching updates via the
* {@link AutoFocusStateListener} so without calling this on a regular basis, no
* AF changes will be observed.</p>
*
* @param result CaptureResult
*/
public synchronized void onCaptureCompleted(CaptureResult result) {
/**
* Work-around for b/11269834
* Although these should never-ever happen, harden for ship
*/
if (result == null) {
Log.w(TAG, "onCaptureCompleted - missing result, skipping AF update");
return;
}
Key<Integer> keyAfState = CaptureResult.CONTROL_AF_STATE;
if (keyAfState == null) {
Log.e(TAG, "onCaptureCompleted - missing android.control.afState key, " +
"skipping AF update");
return;
}
Key<Integer> keyAfMode = CaptureResult.CONTROL_AF_MODE;
if (keyAfMode == null) {
Log.e(TAG, "onCaptureCompleted - missing android.control.afMode key, " +
"skipping AF update");
return;
}
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
Integer afMode = result.get(CaptureResult.CONTROL_AF_MODE);
/**
* Work-around for b/11238865
* This is a HAL bug as these fields should be there always.
*/
if (afState == null) {
Log.w(TAG, "onCaptureCompleted - missing android.control.afState !");
return;
} else if (afMode == null) {
Log.w(TAG, "onCaptureCompleted - missing android.control.afMode !");
return;
}
if (DEBUG_LOGGING) Log.d(TAG, "onCaptureCompleted - new AF mode = " + afMode +
" new AF state = " + afState);
if (mLastAfState == afState && afMode == mLastAfMode) {
// Same AF state as last time, nothing else needs to be done.
return;
}
if (VERBOSE_LOGGING) Log.v(TAG, "onCaptureCompleted - new AF mode = " + afMode +
" new AF state = " + afState);
mLastAfState = afState;
mLastAfMode = afMode;
switch (afState) {
case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
mListener.onAutoFocusSuccess(result, /*locked*/true);
endTraceAsync();
break;
case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
mListener.onAutoFocusFail(result, /*locked*/true);
endTraceAsync();
break;
case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED:
mListener.onAutoFocusSuccess(result, /*locked*/false);
break;
case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED:
mListener.onAutoFocusFail(result, /*locked*/false);
break;
case CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN:
mListener.onAutoFocusScan(result);
break;
case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN:
mListener.onAutoFocusScan(result);
break;
case CaptureResult.CONTROL_AF_STATE_INACTIVE:
mListener.onAutoFocusInactive(result);
break;
}
}
/**
* Reset the current AF state.
*
* <p>
* When dropping capture results (by not invoking {@link #onCaptureCompleted} when a new
* {@link CaptureResult} is available), call this function to reset the state. Otherwise
* the next time a new state is observed this class may incorrectly consider it as the same
* state as before, and not issue any callbacks by {@link AutoFocusStateListener}.
* </p>
*/
public synchronized void resetState() {
if (VERBOSE_LOGGING) Log.v(TAG, "resetState - last state was " + mLastAfState);
mLastAfState = AF_UNINITIALIZED;
}
/**
* Lock the lens from moving. Typically used before taking a picture.
*
* <p>After calling this function, submit the new requestBuilder as a separate capture.
* Do not submit it as a repeating request or the AF lock will be repeated every time.</p>
*
* <p>Create a new repeating request from repeatingBuilder and set that as the updated
* repeating request.</p>
*
* <p>If the lock succeeds, {@link AutoFocusStateListener#onAutoFocusSuccess} with
* {@code locked == true} will be invoked. If the lock fails,
* {@link AutoFocusStateListener#onAutoFocusFail} with {@code scanning == false} will be
* invoked.</p>
*
* @param repeatingBuilder Builder for a repeating request.
* @param requestBuilder Builder for a non-repeating request.
*
*/
public synchronized void lockAutoFocus(CaptureRequest.Builder repeatingBuilder,
CaptureRequest.Builder requestBuilder) {
if (VERBOSE_LOGGING) Log.v(TAG, "lockAutoFocus");
if (mCurrentAfMode == AF_UNINITIALIZED) {
throw new IllegalStateException("AF mode was not enabled");
}
beginTraceAsync("AFSM_lockAutoFocus");
mCurrentAfTrigger = CaptureRequest.CONTROL_AF_TRIGGER_START;
repeatingBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
repeatingBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
requestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_START);
}
/**
* Unlock the lens, allowing it to move again. Typically used after taking a picture.
*
* <p>After calling this function, submit the new requestBuilder as a separate capture.
* Do not submit it as a repeating request or the AF lock will be repeated every time.</p>
*
* <p>Create a new repeating request from repeatingBuilder and set that as the updated
* repeating request.</p>
*
* <p>Once the unlock takes effect, {@link AutoFocusStateListener#onAutoFocusInactive} is
* invoked, and after that the effects depend on which mode you were in:
* <ul>
* <li>Passive - Scanning restarts with {@link AutoFocusStateListener#onAutoFocusScan}</li>
* <li>Active - The lens goes back to a default position (no callbacks)</li>
* </ul>
* </p>
*
* @param repeatingBuilder Builder for a repeating request.
* @param requestBuilder Builder for a non-repeating request.
*
*/
public synchronized void unlockAutoFocus(CaptureRequest.Builder repeatingBuilder,
CaptureRequest.Builder requestBuilder) {
if (VERBOSE_LOGGING) Log.v(TAG, "unlockAutoFocus");
if (mCurrentAfMode == AF_UNINITIALIZED) {
throw new IllegalStateException("AF mode was not enabled");
}
mCurrentAfTrigger = CaptureRequest.CONTROL_AF_TRIGGER_CANCEL;
repeatingBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
repeatingBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
requestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
}
/**
* Enable active auto focus, immediately triggering a converging scan.
*
* <p>This is typically only used when locking the passive AF has failed.</p>
*
* <p>Once active AF scanning starts, {@link AutoFocusStateListener#onAutoFocusScan} will be
* invoked.</p>
*
* <p>If the active scan succeeds, {@link AutoFocusStateListener#onAutoFocusSuccess} with
* {@code locked == true} will be invoked. If the active scan fails,
* {@link AutoFocusStateListener#onAutoFocusFail} with {@code scanning == false} will be
* invoked.</p>
*
* <p>After calling this function, submit the new requestBuilder as a separate capture.
* Do not submit it as a repeating request or the AF trigger will be repeated every time.</p>
*
* <p>Create a new repeating request from repeatingBuilder and set that as the updated
* repeating request.</p>
*
* @param repeatingBuilder Builder for a repeating request.
* @param requestBuilder Builder for a non-repeating request.
*
* @param repeatingBuilder Builder for a repeating request.
*/
public synchronized void setActiveAutoFocus(CaptureRequest.Builder repeatingBuilder,
CaptureRequest.Builder requestBuilder) {
if (VERBOSE_LOGGING) Log.v(TAG, "setActiveAutoFocus");
beginTraceAsync("AFSM_setActiveAutoFocus");
mCurrentAfMode = CaptureRequest.CONTROL_AF_MODE_AUTO;
repeatingBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
repeatingBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
requestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_START);
}
/**
* Enable passive autofocus, immediately triggering a non-converging scan.
*
* <p>While passive autofocus is enabled, use {@link #lockAutoFocus} to lock
* the lens before taking a picture. Once a picture is taken, use {@link #unlockAutoFocus}
* to let the lens go back into passive scanning.</p>
*
* <p>Once passive AF scanning starts, {@link AutoFocusStateListener#onAutoFocusScan} will be
* invoked.</p>
*
* @param repeatingBuilder Builder for a repeating request.
* @param picture True for still capture AF, false for video AF.
*/
public synchronized void setPassiveAutoFocus(boolean picture,
CaptureRequest.Builder repeatingBuilder) {
if (VERBOSE_LOGGING) Log.v(TAG, "setPassiveAutoFocus - picture " + picture);
if (picture) {
mCurrentAfMode = CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
} else {
mCurrentAfMode = CaptureResult.CONTROL_AF_MODE_CONTINUOUS_VIDEO;
}
repeatingBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode);
}
private synchronized void beginTraceAsync(String sectionName) {
if (mCurrentAfCookie != AF_UNINITIALIZED) {
// Terminate any currently active async sections before beginning another section
SysTrace.endSectionAsync(mCurrentAfTrace, mCurrentAfCookie);
}
mLastAfCookie++;
mCurrentAfCookie = mLastAfCookie;
mCurrentAfTrace = sectionName;
SysTrace.beginSectionAsync(sectionName, mCurrentAfCookie);
}
private synchronized void endTraceAsync() {
if (mCurrentAfCookie == AF_UNINITIALIZED) {
Log.w(TAG, "endTraceAsync - no current trace active");
return;
}
SysTrace.endSectionAsync(mCurrentAfTrace, mCurrentAfCookie);
mCurrentAfCookie = AF_UNINITIALIZED;
}
}