| /* |
| * 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.CaptureResult.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.CaptureCallback#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; |
| } |
| |
| /** |
| * Update the repeating request with current focus mode. |
| * |
| * <p>This is typically used when a new repeating request is created to update preview with |
| * new metadata (i.e. crop region). The current auto focus mode needs to be carried over for |
| * correct auto focus behavior.<p> |
| * |
| * @param repeatingBuilder Builder for a repeating request. |
| */ |
| public synchronized void updateCaptureRequest(CaptureRequest.Builder repeatingBuilder) { |
| if (repeatingBuilder == null) { |
| throw new IllegalArgumentException("repeatingBuilder shouldn't be null"); |
| } |
| |
| if (mCurrentAfMode == AF_UNINITIALIZED) { |
| throw new IllegalStateException("AF mode was not enabled"); |
| } |
| |
| repeatingBuilder.set(CaptureRequest.CONTROL_AF_MODE, mCurrentAfMode); |
| } |
| } |