OSDN Git Service

[OC] Add autofocus (tap-to-focus, UI and pre-shot focus) to OneCamera.
authorAndy Huibers <andyhuibers@google.com>
Thu, 31 Jul 2014 23:31:43 +0000 (16:31 -0700)
committerAndy Huibers <andyhuibers@google.com>
Fri, 8 Aug 2014 20:48:24 +0000 (13:48 -0700)
Change-Id: Ic88359ad27b8e9ee0e74c8d1bd6ecb71fe3c24f0

src/com/android/camera/CaptureModule.java
src/com/android/camera/CaptureModuleUI.java
src/com/android/camera/one/OneCamera.java
src/com/android/camera/one/v2/OneCameraImpl.java

index 0ade6b1..71eba68 100644 (file)
@@ -47,6 +47,8 @@ import com.android.camera.debug.Log.Tag;
 import com.android.camera.hardware.HardwareSpec;
 import com.android.camera.module.ModuleController;
 import com.android.camera.one.OneCamera;
+import com.android.camera.one.OneCamera.AutoFocusMode;
+import com.android.camera.one.OneCamera.AutoFocusState;
 import com.android.camera.one.OneCamera.CaptureReadyCallback;
 import com.android.camera.one.OneCamera.Facing;
 import com.android.camera.one.OneCamera.OpenCallback;
@@ -62,6 +64,8 @@ import com.android.camera.ui.PreviewStatusListener;
 import com.android.camera.ui.TouchCoordinate;
 import com.android.camera.util.CameraUtil;
 import com.android.camera.util.Size;
+import com.android.camera.util.SystemProperties;
+import com.android.camera.util.UsageStatistics;
 import com.android.camera2.R;
 import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
 
@@ -87,6 +91,7 @@ public class CaptureModule extends CameraModule
         implements MediaSaver.QueueListener,
         ModuleController,
         OneCamera.PictureCallback,
+        OneCamera.FocusStateListener,
         PreviewStatusListener.PreviewAreaChangedListener,
         RemoteCameraModule,
         SensorEventListener,
@@ -140,6 +145,29 @@ public class CaptureModule extends CameraModule
                 }
             };
 
+    /**
+     * Show AF target in center of preview and start animation.
+     */
+    Runnable mShowAutoFocusTargetInCenterRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mUI.setAutoFocusTarget(((int) (mPreviewArea.left + mPreviewArea.right)) / 2,
+                    ((int) (mPreviewArea.top + mPreviewArea.bottom)) / 2);
+            mUI.showAutoFocusInProgress();
+        }
+    };
+
+    /**
+     * Hide AF target UI element.
+     */
+    Runnable mHideAutoFocusTargetRunnable = new Runnable() {
+        @Override
+        public void run() {
+            // showAutoFocusSuccess() just hides the AF UI.
+            mUI.showAutoFocusSuccess();
+        }
+    };
+
     private static final Tag TAG = new Tag("CaptureModule");
     private static final String PHOTO_MODULE_STRING_ID = "PhotoModule";
     /** Enable additional debug output. */
@@ -152,6 +180,12 @@ public class CaptureModule extends CameraModule
      */
     private static final int ON_RESUME_TASKS_DELAY_MSEC = 20;
 
+    /** System Properties switch to enable debugging focus UI. */
+    private static final String PROP_FOCUS_DEBUG_UI_KEY = "persist.camera.focus_debug_ui";
+    private static final String PROP_FOCUS_DEBUG_UI_OFF = "0";
+    private static final boolean FOCUS_DEBUG_UI = !PROP_FOCUS_DEBUG_UI_OFF
+            .equals(SystemProperties.get(PROP_FOCUS_DEBUG_UI_KEY, PROP_FOCUS_DEBUG_UI_OFF));
+
     private final Object mDimensionLock = new Object();
     /**
      * Lock for race conditions in the SurfaceTextureListener callbacks.
@@ -186,6 +220,16 @@ public class CaptureModule extends CameraModule
     private ModuleState mState = ModuleState.IDLE;
     /** Current orientation of the device. */
     private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+    /** Current zoom value. */
+    private float mZoomValue = 1f;
+
+    /** True if in AF tap-to-focus sequence. */
+    private boolean mTapToFocusInProgress = false;
+
+    /** Persistence of Tap to Focus target UI after scan complete. */
+    private static final int FOCUS_HOLD_UI_MILLIS = 500;
+    /** Persistence of Tap to Focus target UI timeout. */
+    private static final int FOCUS_HOLD_UI_TIMEOUT_MILLIS = 1500;
 
     /** Accelerometer data. */
     private final float[] mGData = new float[3];
@@ -214,19 +258,16 @@ public class CaptureModule extends CameraModule
 
     /** Current display rotation in degrees. */
     private int mDisplayRotation;
-    /** Current width of the screen, in pixels. */
+    /** Current screen width in pixels. */
     private int mScreenWidth;
-    /** Current height of the screen, in pixels. */
+    /** Current screen height in pixels. */
     private int mScreenHeight;
-    /** Current preview width, in pixels. */
+    /** Current width of preview frames from camera. */
     private int mPreviewBufferWidth;
-    /** Current preview height, in pixels. */
+    /** Current height of preview frames from camera.. */
     private int mPreviewBufferHeight;
-
-    // /** Current preview area width. */
-    // private float mFullPreviewWidth;
-    // /** Current preview area height. */
-    // private float mFullPreviewHeight;
+    /** Area used by preview. */
+    RectF mPreviewArea;
 
     /** The current preview transformation matrix. */
     private Matrix mPreviewTranformationMatrix = new Matrix();
@@ -325,6 +366,7 @@ public class CaptureModule extends CameraModule
 
     @Override
     public void onPreviewAreaChanged(RectF previewArea) {
+        mPreviewArea = previewArea;
         // mUI.updatePreviewAreaRect(previewArea);
         // mUI.positionProgressOverlay(previewArea);
     }
@@ -421,6 +463,7 @@ public class CaptureModule extends CameraModule
                     public void onReadyForCapture() {
                         Log.d(TAG, "Ready for capture.");
                         onPreviewStarted();
+                        mCamera.setFocusStateListener(CaptureModule.this);
                     }
                 });
             }
@@ -578,8 +621,97 @@ public class CaptureModule extends CameraModule
         return false;
     }
 
+    /**
+     * Focus sequence starts for zone around tap location for single tap.
+     */
     @Override
     public void onSingleTapUp(View view, int x, int y) {
+        Log.v(TAG, "onSingleTapUp x=" + x + " y=" + y);
+        // TODO: This should query actual capability.
+        if (mCameraFacing == Facing.FRONT) {
+            return;
+        }
+        triggerFocusAtScreenCoord(x, y);
+    }
+
+    // TODO: Consider refactoring FocusOverlayManager.
+    // Currently AF state transitions are controlled in OneCameraImpl.
+    // PhotoModule uses FocusOverlayManager which uses API1/portability
+    // logic and coordinates.
+
+    private void triggerFocusAtScreenCoord(int x, int y) {
+        mTapToFocusInProgress = true;
+        // Show UI immediately even though scan has not started yet.
+        mUI.setAutoFocusTarget(x, y);
+        mUI.showAutoFocusInProgress();
+        mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
+        mMainHandler.postDelayed(mHideAutoFocusTargetRunnable, FOCUS_HOLD_UI_TIMEOUT_MILLIS);
+
+        // Normalize coordinates to [0,1] per CameraOne API.
+        float points[] = new float[2];
+        points[0] = (x - mPreviewArea.left) / mPreviewArea.width();
+        points[1] = (y - mPreviewArea.top) / mPreviewArea.height();
+
+        // Rotate coordinates to portrait orientation per CameraOne API.
+        Matrix rotationMatrix = new Matrix();
+        rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f);
+        rotationMatrix.mapPoints(points);
+        mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]);
+
+        // Log touch (screen coordinates).
+        if (mZoomValue == 1f) {
+            TouchCoordinate touchCoordinate = new TouchCoordinate(x - mPreviewArea.left,
+                    y - mPreviewArea.top, mPreviewArea.width(), mPreviewArea.height());
+            // TODO: Add to logging: duration, rotation.
+            UsageStatistics.instance().tapToFocus(touchCoordinate, null);
+        }
+    }
+
+    /**
+     * This AF status listener does two things:
+     * <ol>
+     * <li>Ends tap-to-focus period when mode goes from AUTO to CONTINUOUS_PICTURE.</li>
+     * <li>Updates AF UI if tap-to-focus is not in progress.</li>
+     * </ol>
+     */
+    public void onFocusStatusUpdate(final AutoFocusMode mode, final AutoFocusState state) {
+        Log.v(TAG, "AF status is mode:" + mode + " state:" + state);
+
+        if (FOCUS_DEBUG_UI) {
+            // TODO: Add debug circle radius+color UI to FocusOverlay.
+            // mMainHandler.post(...)
+        }
+
+        // After tap to focus SCAN completes, clear UI after FOCUS_HOLD_UI_MILLIS.
+        if (mTapToFocusInProgress && mode == AutoFocusMode.AUTO &&
+                (state == AutoFocusState.STOPPED_FOCUSED ||
+                        state == AutoFocusState.STOPPED_UNFOCUSED)) {
+            mMainHandler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    mTapToFocusInProgress = false;
+                    mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
+                    mMainHandler.post(mHideAutoFocusTargetRunnable);
+                }
+            }, FOCUS_HOLD_UI_MILLIS);
+        }
+
+        // Use the OneCamera auto focus callbacks to show the UI, except for
+        // tap to focus where we show UI right away at touch, and then turn
+        // it off early at 0.5 sec, before the focus lock expires at 3 sec.
+        if (!mTapToFocusInProgress) {
+            switch (state) {
+                case SCANNING:
+                    mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
+                    mMainHandler.post(mShowAutoFocusTargetInCenterRunnable);
+                    break;
+                case STOPPED_FOCUSED:
+                case STOPPED_UNFOCUSED:
+                    mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
+                    mMainHandler.post(mHideAutoFocusTargetRunnable);
+                    break;
+            }
+        }
     }
 
     @Override
@@ -654,6 +786,7 @@ public class CaptureModule extends CameraModule
 
     /***
      * Update the preview transform based on the new dimensions.
+     * TODO: Make work with all: aspect ratios/resolutions x screens/cameras.
      */
     private void updatePreviewTransform(int incomingWidth, int incomingHeight,
             boolean forceUpdate) {
@@ -759,11 +892,9 @@ public class CaptureModule extends CameraModule
             }
             mPreviewTranformationMatrix.postScale(scale, scale, centerX, centerY);
 
+            // TODO: Take these quantities from mPreviewArea.
             float previewWidth = effectiveWidth * scale;
             float previewHeight = effectiveHeight * scale;
-            // mFullPreviewWidth = previewWidth;
-            // mFullPreviewHeight = previewHeight;
-
             float previewCenterX = previewWidth / 2;
             float previewCenterY = previewHeight / 2;
             mPreviewTranformationMatrix.postTranslate(previewCenterX - centerX, previewCenterY
@@ -833,6 +964,7 @@ public class CaptureModule extends CameraModule
 
     private void closeCamera() {
         if (mCamera != null) {
+            mCamera.setFocusStateListener(null);
             mCamera.close(null);
             mCamera = null;
         }
index e926a4a..b3ea99c 100644 (file)
@@ -174,13 +174,10 @@ public class CaptureModuleUI implements
         mFocusUI.onFocusFailed();
     }
 
-    public void setAutoFocusTarget(int x, int y, boolean isAutoFocus) {
+    public void setAutoFocusTarget(int x, int y) {
+        // TODO: refactor.
+        boolean isAutoFocus = false;
         mFocusUI.setFocusPosition(x, y, isAutoFocus);
-        // Log manual tap to focus.
-        // TODO: Log coordinates. Note: 16x9 mode will be tricky.
-        if (isAutoFocus == false) {
-            UsageStatistics.instance().tapToFocus(null, null);
-        }
     }
 
     public void clearAutoFocusIndicator() {
index a1d4c11..d179c72 100644 (file)
@@ -36,6 +36,40 @@ public interface OneCamera {
     }
 
     /**
+     * Auto focus system status.
+     * <ul>
+     * <li>{@link #INACTIVE}</li>
+     * <li>{@link #SCANNING}</li>
+     * <li>{@link #STOPPED_FOCUSED}</li>
+     * <li>{@link #STOPPED_UNFOCUSED}</li>
+     * </ul>
+     */
+    public static enum AutoFocusState {
+        /** Indicates AF system is inactive for some reason (could be an error). */
+        INACTIVE,
+        /** Indicates scan in progress. */
+        SCANNING,
+        /** Indicates scan success (camera in focus). */
+        STOPPED_FOCUSED,
+        /** Indicates scan or other failure.  */
+        STOPPED_UNFOCUSED
+    }
+
+    /**
+     * Auto focus system mode.
+     * <ul>
+     * <li>{@link #CONTINUOUS_PICTURE}</li>
+     * <li>{@link #AUTO}</li>
+     * </ul>
+     */
+    public static enum AutoFocusMode {
+        /** System is continuously focusing. */
+        CONTINUOUS_PICTURE,
+        /** System is running a triggered scan. */
+        AUTO
+    }
+
+    /**
      * Classes implementing this interface will be called when the camera was
      * opened or failed to open.
      */
@@ -129,16 +163,17 @@ public interface OneCamera {
 
     /**
      * Classes implementing this interface will be called when the state of the
-     * focus changes.
+     * focus changes.  Guaranteed not to stay stuck in scanning state past some
+     * reasonable timeout even if Camera API is stuck.
      */
     public static interface FocusStateListener {
         /**
-         * Called when an auto-focus run ended
+         * Called when mode or state of auto focus system changes.
          *
-         * @param success whether auto-focus succeeded. If true, it means that
-         *            the image should not be sharp.
+         * @param mode Is manual AF trigger cycle active.
+         * @param state Current state: scanning, focused, not focused, inactive.
          */
-        public void onAutoFocusDone(boolean success);
+        public void onFocusStatusUpdate(AutoFocusMode mode, AutoFocusState state);
     }
 
     /**
@@ -191,6 +226,22 @@ public interface OneCamera {
     }
 
     /**
+     * Triggers auto focus scan for default ROI.
+     */
+    public void triggerAutoFocus();
+
+    /**
+     * Meters and triggers auto focus scan with ROI around tap point.
+     * <p/>
+     * Normalized coordinates are referenced to portrait preview window
+     * with 0,0 top left and 1,1 bottom right.  Rotation has no effect.
+     *
+     * @param nx normalized x coordinate.
+     * @param nx normalized y coordinate.
+     */
+    public void triggerFocusAndMeterAtPoint(float nx, float ny);
+
+    /**
      * Call this to take a picture.
      *
      * @param params parameters for taking pictures.
index 56e2a0d..fb14c9f 100644 (file)
@@ -17,6 +17,7 @@
 package com.android.camera.one.v2;
 
 import android.graphics.ImageFormat;
+import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
@@ -24,12 +25,16 @@ import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.MeteringRectangle;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.Image;
 import android.media.ImageReader;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.SystemClock;
 import android.view.OrientationEventListener;
 import android.view.Surface;
 
@@ -45,6 +50,8 @@ import com.android.camera.one.OneCamera;
 import com.android.camera.one.OneCamera.PhotoCaptureParameters.Flash;
 import com.android.camera.session.CaptureSession;
 import com.android.camera.util.Size;
+import com.android.camera.util.CameraUtil;
+import com.android.camera.util.SystemProperties;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -70,9 +77,96 @@ public class OneCameraImpl extends AbstractOneCamera {
     }
 
     private static final Tag TAG = new Tag("OneCameraImpl2");
+
+    /** System Properties switch to enable additional focus logging. */
+    private static final String PROP_FOCUS_DEBUG_KEY = "persist.camera.focus_debug_log";
+    private static final String PROP_FOCUS_DEBUG_OFF = "0";
+    private static final boolean FOCUS_DEBUG = !PROP_FOCUS_DEBUG_OFF
+            .equals(SystemProperties.get(PROP_FOCUS_DEBUG_KEY, PROP_FOCUS_DEBUG_OFF));
+
     /** Default JPEG encoding quality. */
     private static final Byte JPEG_QUALITY = 90;
 
+    /** Width and height of touch metering region as fraction of longest edge. */
+    private static final float METERING_REGION_EDGE = 0.1f;
+    /** Metering region weight between 0 and 1. */
+    private static final float METERING_REGION_WEIGHT = 0.25f;
+    /** Duration to hold after manual focus tap. */
+    private static final int FOCUS_HOLD_MILLIS = 3000;
+
+    /**
+     * CaptureRequest tags.
+     * <ul>
+     * <li>{@link #PRESHOT_TRIGGERED_AF}</li>
+     * <li>{@link #CAPTURE}</li>
+     * </ul>
+     */
+    public static enum RequestTag {
+        /** Request that is part of a pre shot trigger. */
+        PRESHOT_TRIGGERED_AF,
+        /** Capture request (purely for logging). */
+        CAPTURE
+    }
+
+    /** Current CONTROL_AF_MODE request to Camera2 API. */
+    private int mLastRequestedControlAFMode = CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
+    /** Last OneCamera.AutoFocusState reported. */
+    private AutoFocusState mLastResultAFState = AutoFocusState.INACTIVE;
+    /** Last OneCamera.AutoFocusMode reported. */
+    private AutoFocusMode mLastResultAFMode = AutoFocusMode.CONTINUOUS_PICTURE;
+    /** Flag to take a picture when in AUTO mode and the lens is stopped. */
+    private boolean mTakePictureWhenLensStoppedAndAuto = false;
+    /** Flag to take a picture when the lens is stopped. */
+    private boolean mTakePictureWhenLensIsStopped = false;
+    /** Takes a (delayed) picture with appropriate parameters. */
+    private Runnable mTakePictureRunnable;
+    /** Last time takePicture() was called in uptimeMillis. */
+    private long mTakePictureStartMillis;
+    /** Runnable that returns to CONTROL_AF_MODE = AF_CONTINUOUS_PICTURE. */
+    private Runnable mReturnToContinuousAFRunnable = new Runnable() {
+        @Override
+        public void run() {
+            repeatingPreviewWithReadyListener(null);
+        }
+    };
+
+    /** Current zoom value.  1.0 is no zoom. */
+    private float mZoomValue = 1f;
+    /** If partial results was OK, don't need to process total result. */
+    private boolean mAutoFocusStateListenerPartialOK = false;
+
+    /**
+     * Common listener for preview frame metadata.
+     */
+    private CameraCaptureSession.CaptureListener mAutoFocusStateListener = new
+            CameraCaptureSession.CaptureListener() {
+                // AF state information is sometimes available 1 frame before
+                // onCaptureCompleted(), so we take advantage of that.
+                @Override
+                public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
+                                                CaptureResult partialResult) {
+
+                    if (partialResult.get(CaptureResult.CONTROL_AF_STATE) != null) {
+                        mAutoFocusStateListenerPartialOK = true;
+                        autofocusStateChangeDispatcher(partialResult);
+                        if (FOCUS_DEBUG) {
+                            logExtraFocusInfo(partialResult);
+                        }
+                    } else {
+                        mAutoFocusStateListenerPartialOK = false;
+                    }
+                    super.onCaptureProgressed(session, request, partialResult);
+                }
+                @Override
+                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
+                                               TotalCaptureResult result) {
+                    if (!mAutoFocusStateListenerPartialOK) {
+                        autofocusStateChangeDispatcher(result);
+                    }
+                    super.onCaptureCompleted(session, request, result);
+                }
+            };
+
     /** Thread on which the camera operations are running. */
     private final HandlerThread mCameraThread;
     /** Handler of the {@link #mCameraThread}. */
@@ -113,6 +207,7 @@ public class OneCameraImpl extends AbstractOneCamera {
                     // Since this is not an HDR+ session, we will just save the result.
                     capture.session.startEmpty();
                     byte[] imageBytes = acquireJpegBytesAndClose(reader);
+                    // TODO: The savePicture call here seems to block UI thread.
                     savePicture(imageBytes, capture.parameters, capture.session);
                     capture.parameters.callback.onPictureTaken(capture.session);
                 }
@@ -140,21 +235,70 @@ public class OneCameraImpl extends AbstractOneCamera {
         Log.d(TAG, "New Camera2 based OneCameraImpl created.");
     }
 
+    /**
+     * Take picture, initiating an auto focus scan if needed.
+     */
     @Override
-    public void takePicture(PhotoCaptureParameters params, CaptureSession session) {
+    public void takePicture(final PhotoCaptureParameters params, final CaptureSession session) {
+        if (mTakePictureWhenLensStoppedAndAuto || mTakePictureWhenLensIsStopped) {
+            // Do not do anything when a picture is already in progress.
+            return;
+        }
+
+        mTakePictureRunnable = new Runnable() {
+            @Override
+            public void run() {
+                takePictureNow(params, session);
+            }
+        };
+        mTakePictureStartMillis = SystemClock.uptimeMillis();
+
+        if (mLastResultAFMode == AutoFocusMode.CONTINUOUS_PICTURE
+                && mLastResultAFState == AutoFocusState.STOPPED_UNFOCUSED) {
+            Log.v(TAG, "Unfocused: Triggering auto focus scan.");
+            // Trigger auto focus scan if in CONTINUOUS_PICTURE + unfocused.
+            mTakePictureWhenLensStoppedAndAuto = true;
+            repeatingPreviewWithAFTrigger(null, null, RequestTag.PRESHOT_TRIGGERED_AF);
+        } else if (mLastResultAFState == AutoFocusState.SCANNING) {
+            // Delay shot if scanning.
+            Log.v(TAG, "Waiting until scan is done before taking shot.");
+            mTakePictureWhenLensIsStopped = true;
+        } else {
+            takePictureNow(params, session);
+        }
+    }
+
+    /**
+     * Take picture immediately.  Parameters passed through from takePicture().
+     */
+    public void takePictureNow(PhotoCaptureParameters params, CaptureSession session) {
+        long dt = SystemClock.uptimeMillis() - mTakePictureStartMillis;
+        Log.v(TAG, "Taking shot with extra AF delay of " + dt + " ms.");
         // This will throw a RuntimeException, if parameters are not sane.
         params.checkSanity();
         try {
-            // JPEG capture
+            // JPEG capture.
             CaptureRequest.Builder builder = mDevice
                     .createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+            // TODO: Check that these control modes are correct for AWB, AE.
+            if (mLastRequestedControlAFMode == CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
+                builder.set(CaptureRequest.CONTROL_AF_MODE,
+                        CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+                Log.v(TAG, "CaptureRequest with CONTROL_AF_MODE_CONTINUOUS_PICTURE.");
+            } else if (mLastRequestedControlAFMode == CameraMetadata.CONTROL_AF_MODE_AUTO) {
+                builder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO);
+                builder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                        CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
+                Log.v(TAG, "CaptureRequest with AUTO.");
+            }
+            builder.setTag(RequestTag.CAPTURE);
             builder.set(CaptureRequest.JPEG_QUALITY, JPEG_QUALITY);
             builder.set(CaptureRequest.JPEG_ORIENTATION, getJpegRotation(params.orientation));
             builder.addTarget(mPreviewSurface);
             builder.addTarget(mJpegImageReader.getSurface());
             applyFlashMode(params.flashMode, builder);
             CaptureRequest request = builder.build();
-            mCaptureSession.capture(request, null, mCameraHandler);
+            mCaptureSession.capture(request, mAutoFocusStateListener, mCameraHandler);
         } catch (CameraAccessException e) {
             Log.e(TAG, "Could not access camera for JPEG capture.");
             params.callback.onPictureTakenFailed();
@@ -190,6 +334,11 @@ public class OneCameraImpl extends AbstractOneCamera {
             Log.w(TAG, "Camera is already closed.");
             return;
         }
+        try {
+            mCaptureSession.abortCaptures();
+        } catch (CameraAccessException e) {
+            Log.e(TAG, "Could not abort captures in progress.");
+        }
         mIsClosed = true;
         mCloseCallback = closeCallback;
         mCameraThread.quitSafely();
@@ -305,7 +454,7 @@ public class OneCameraImpl extends AbstractOneCamera {
                 @Override
                 public void onConfigured(CameraCaptureSession session) {
                     mCaptureSession = session;
-                    startPreviewInternal(listener);
+                    repeatingPreviewWithReadyListener(listener);
                 }
 
                 @Override
@@ -323,29 +472,203 @@ public class OneCameraImpl extends AbstractOneCamera {
     }
 
     /**
-     * Configures and creates the request for starting the preview.
+     * Request preview capture stream with AF_MODE_CONTINUOUS_PICTURE.
      *
-     * @param listener called when request was build and sent, or if setting up
-     *            the request failed.
+     * @param readyListener called when request was build and sent, or if
+     *                      setting up the request failed.
      */
-    private void startPreviewInternal(CaptureReadyCallback listener) {
-        Log.v(TAG, "issuePreviewCaptureRequest.");
+    private void repeatingPreviewWithReadyListener(CaptureReadyCallback readyListener) {
         try {
-            CaptureRequest.Builder builder = mDevice
-                    .createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+            CaptureRequest.Builder builder = mDevice.
+                    createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+            builder.addTarget(mPreviewSurface);
             builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
+            mLastRequestedControlAFMode = CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
             builder.set(CaptureRequest.CONTROL_AF_MODE,
                     CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+            builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
+            mCaptureSession.setRepeatingRequest(builder.build(), mAutoFocusStateListener,
+                    mCameraHandler);
+            Log.v(TAG, "Sent preview request with AF_MODE_CONTINUOUS_PICTURE.");
+            if (readyListener != null) {
+                readyListener.onReadyForCapture();
+            }
+        } catch (CameraAccessException ex) {
+            Log.e(TAG, "Could not access camera setting up preview.", ex);
+            if (readyListener != null) {
+                readyListener.onSetupFailed();
+            }
+        }
+    }
+
+    /**
+     * Request preview capture stream with auto focus cycle.
+     *
+     * @param focusRegions focus regions, for tap to focus/expose.
+     * @param meteringRegions metering regions, for tap to focus/expose.
+     */
+    private void repeatingPreviewWithAFTrigger(MeteringRectangle[] focusRegions,
+                                               MeteringRectangle[] meteringRegions, Object tag) {
+        try {
+            CaptureRequest.Builder builder;
+            builder = mDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
             builder.addTarget(mPreviewSurface);
-            mCaptureSession.setRepeatingRequest(builder.build(), null, mCameraHandler);
-            listener.onReadyForCapture();
-        } catch (CameraAccessException e) {
-            Log.e(TAG, "Could not access camera setting up preview.");
-            listener.onSetupFailed();
+            builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
+            if (focusRegions != null) {
+                builder.set(CaptureRequest.CONTROL_AF_REGIONS, focusRegions);
+            }
+            if (meteringRegions != null) {
+                builder.set(CaptureRequest.CONTROL_AE_REGIONS, meteringRegions);
+            }
+            builder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO);
+            mLastRequestedControlAFMode = CameraMetadata.CONTROL_AF_MODE_AUTO;
+
+            // Step 1: Request single frame CONTROL_AF_TRIGGER_START.
+            builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
+            mCaptureSession.capture(builder.build(), mAutoFocusStateListener, mCameraHandler);
+
+            // Step 2: Request continuous frames CONTROL_AF_TRIGGER_IDLE.
+            builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
+            builder.setTag(tag);
+            mCaptureSession.setRepeatingRequest(builder.build(), mAutoFocusStateListener,
+                    mCameraHandler);
+            resumeContinuousAFAfterDelay(FOCUS_HOLD_MILLIS);
+        } catch (CameraAccessException ex) {
+            Log.e(TAG, "Could not execute preview request.", ex);
+        }
+    }
+
+    /**
+     * Resume AF_MODE_CONTINUOUS_PICTURE after FOCUS_HOLD_MILLIS.
+     */
+    private void resumeContinuousAFAfterDelay(int millis) {
+        mCameraHandler.removeCallbacks(mReturnToContinuousAFRunnable);
+        mCameraHandler.postDelayed(mReturnToContinuousAFRunnable, millis);
+    }
+
+    /**
+     * This method takes appropriate action if camera2 AF state changes.
+     * <ol>
+     * <li>Reports changes in camera2 AF state to OneCamera.FocusStateListener.</li>
+     * <li>Take picture after AF scan.</li>
+     * <li>TODO: Take picture after AE_PRECAPTURE sequence for flash.</li>
+     * </ol>
+     */
+    private void autofocusStateChangeDispatcher(CaptureResult result) {
+        Integer nativeAFControlState = result.get(CaptureResult.CONTROL_AF_STATE);
+        Integer nativeAFControlMode = result.get(CaptureResult.CONTROL_AF_MODE);
+        Object tag = result.getRequest().getTag();
+
+        // Convert to OneCamera mode and state.
+        AutoFocusMode resultAFMode = modeFromCamera2Mode(nativeAFControlMode);
+        AutoFocusState resultAFState = stateFromCamera2State(nativeAFControlState);
+
+        boolean lensIsStopped = (resultAFState == AutoFocusState.STOPPED_FOCUSED ||
+                resultAFState == AutoFocusState.STOPPED_UNFOCUSED);
+        if (tag == RequestTag.PRESHOT_TRIGGERED_AF && lensIsStopped &&
+                mTakePictureWhenLensStoppedAndAuto) {
+            // Take the shot.
+            mCameraHandler.post(mTakePictureRunnable);
+            // Return to passive scanning.
+            mCameraHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    repeatingPreviewWithReadyListener(null);
+                }
+            });
+            mTakePictureWhenLensStoppedAndAuto = false;
+        }
+        if (mTakePictureWhenLensIsStopped && lensIsStopped) {
+            // Take the shot.
+            mCameraHandler.post(mTakePictureRunnable);
+            mTakePictureWhenLensIsStopped = false;
+        }
+
+        // Report state change when mode or state has changed.
+        if (resultAFState != mLastResultAFState || resultAFMode != mLastResultAFMode
+                && mFocusStateListener != null) {
+            mFocusStateListener.onFocusStatusUpdate(resultAFMode, resultAFState);
+        }
+        mLastResultAFState = resultAFState;
+        mLastResultAFMode = resultAFMode;
+    }
+
+    /**
+     * Convert reported camera2 AF state to OneCamera AutoFocusState.
+     */
+    private static AutoFocusState stateFromCamera2State(int state) {
+        switch (state) {
+            case CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN:
+            case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN:
+                return AutoFocusState.SCANNING;
+            case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED:
+            case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
+                return AutoFocusState.STOPPED_FOCUSED;
+            case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED:
+            case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+                return AutoFocusState.STOPPED_UNFOCUSED;
+            default:
+                return AutoFocusState.INACTIVE;
         }
     }
 
     /**
+     * Convert reported camera2 AF state to OneCamera AutoFocusMode.
+     */
+    private static AutoFocusMode modeFromCamera2Mode(int mode) {
+        if (mode == CaptureResult.CONTROL_AF_MODE_AUTO) {
+            return AutoFocusMode.AUTO;
+        } else {
+            // CONTROL_AF_MODE_CONTINUOUS_PICTURE is the other mode used.
+            return AutoFocusMode.CONTINUOUS_PICTURE;
+        }
+    }
+
+    @Override
+    public void triggerAutoFocus() {
+        Log.v(TAG, "triggerAutoFocus()");
+        repeatingPreviewWithAFTrigger(null, null, null);
+    }
+
+    @Override
+    public void triggerFocusAndMeterAtPoint(float nx, float ny) {
+        Log.v(TAG, "triggerFocusAndMeterAtPoint(" + nx + "," + ny + ")");
+        float points[] = new float[]{nx, ny};
+        // Make sure the points are in [0,1] range.
+        points[0] = CameraUtil.clamp(points[0], 0f, 1f);
+        points[1] = CameraUtil.clamp(points[1], 0f, 1f);
+
+        // Shrink points towards center if zoomed.
+        if (mZoomValue > 1f) {
+            Matrix zoomMatrix = new Matrix();
+            zoomMatrix.postScale(1f / mZoomValue, 1f / mZoomValue, 0.5f, 0.5f);
+            zoomMatrix.mapPoints(points);
+        }
+
+        // TODO: Make this work when preview aspect ratio != sensor aspect ratio.
+        Rect sensor = mCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+        int edge = (int) (METERING_REGION_EDGE * Math.max(sensor.width(), sensor.height()));
+        // x0 and y0 in sensor coordinate system, rotated 90 degrees from portrait.
+        int x0 = (int) (sensor.width() * points[1]);
+        int y0 = (int) (sensor.height() * (1f - points[0]));
+        int x1 = x0 + edge;
+        int y1 = y0 + edge;
+
+        // Make sure regions are inside the sensor area.
+        x0 = CameraUtil.clamp(x0, 0, sensor.width() - 1);
+        x1 = CameraUtil.clamp(x1, 0, sensor.width() - 1);
+        y0 = CameraUtil.clamp(y0, 0, sensor.height() - 1);
+        y1 = CameraUtil.clamp(y1, 0, sensor.height() - 1);
+        int wt = (int) ((1 - METERING_REGION_WEIGHT) * (float) MeteringRectangle.METERING_WEIGHT_MIN
+                + METERING_REGION_WEIGHT * (float) MeteringRectangle.METERING_WEIGHT_MAX);
+
+        Log.v(TAG, "sensor 3A @ x0=" + x0 + " y0=" + y0 + " dx=" + (x1 - x0) + " dy=" + (y1 - y0));
+        MeteringRectangle[] regions = new MeteringRectangle[]{
+                new MeteringRectangle(x0, y0, x1 - x0, y1 - y0, wt)};
+        repeatingPreviewWithAFTrigger(regions, regions, null);
+    }
+
+    /**
      * Calculate the aspect ratio of the full size capture on this device.
      *
      * @param characteristics the characteristics of the camera device.
@@ -419,4 +742,44 @@ public class OneCameraImpl extends AbstractOneCamera {
                 break;
         }
     }
+
+    /**
+     * Utility function: converts CaptureResult.CONTROL_AF_STATE* to String.
+     */
+    private static String camera2ControlAFStateDesc(int aFState) {
+        switch (aFState) {
+            case CaptureResult.CONTROL_AF_STATE_INACTIVE:
+                return "inactive";
+            case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN:
+                return "passive_scan";
+            case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED:
+                return "passive_focused";
+            case CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN:
+                return "active_scan";
+            case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
+                return "focus_locked";
+            case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+                return "not_focus_locked";
+            case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED:
+                return "passive_unfocused";
+            default:
+                return "unknown";
+        }
+    }
+
+    private void logExtraFocusInfo(CaptureResult result) {
+        Object tag = result.getRequest().getTag();
+        // Nexus 5 has a bug where CONTROL_AF_STATE is missing sometimes.
+        if (result.get(CaptureResult.CONTROL_AF_STATE) == null) {
+            //throw new IllegalStateException("CaptureResult missing CONTROL_AF_STATE.");
+            Log.e(TAG, "\n!!!! TotalCaptureResult missing CONTROL_AF_STATE. !!!!\n ");
+            return;
+        }
+        Log.v(TAG, "camera2 AF state: " + camera2ControlAFStateDesc(result.
+                get(CaptureResult.CONTROL_AF_STATE)) +
+                (tag == null ? "" : ("  tag: " + tag)));
+        if (result.get(CaptureResult.LENS_FOCUS_DISTANCE) != null) {
+            Log.v(TAG, "  lens @ " + result.get(CaptureResult.LENS_FOCUS_DISTANCE));
+        }
+    }
 }