Home | History | Annotate | Download | only in camerafocus
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.dialer.callcomposer.camera.camerafocus;
     18 
     19 import android.graphics.Matrix;
     20 import android.graphics.Rect;
     21 import android.graphics.RectF;
     22 import android.hardware.Camera.Area;
     23 import android.hardware.Camera.Parameters;
     24 import android.os.Handler;
     25 import android.os.Looper;
     26 import android.os.Message;
     27 import com.android.dialer.common.Assert;
     28 import com.android.dialer.common.LogUtil;
     29 import java.util.ArrayList;
     30 import java.util.List;
     31 
     32 /**
     33  * A class that handles everything about focus in still picture mode. This also handles the metering
     34  * area because it is the same as focus area.
     35  *
     36  * <p>The test cases: (1) The camera has continuous autofocus. Move the camera. Take a picture when
     37  * CAF is not in progress. (2) The camera has continuous autofocus. Move the camera. Take a picture
     38  * when CAF is in progress. (3) The camera has face detection. Point the camera at some faces. Hold
     39  * the shutter. Release to take a picture. (4) The camera has face detection. Point the camera at
     40  * some faces. Single tap the shutter to take a picture. (5) The camera has autofocus. Single tap
     41  * the shutter to take a picture. (6) The camera has autofocus. Hold the shutter. Release to take a
     42  * picture. (7) The camera has no autofocus. Single tap the shutter and take a picture. (8) The
     43  * camera has autofocus and supports focus area. Touch the screen to trigger autofocus. Take a
     44  * picture. (9) The camera has autofocus and supports focus area. Touch the screen to trigger
     45  * autofocus. Wait until it times out. (10) The camera has no autofocus and supports metering area.
     46  * Touch the screen to change metering area.
     47  */
     48 public class FocusOverlayManager {
     49   private static final String TRUE = "true";
     50   private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
     51   private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED =
     52       "auto-whitebalance-lock-supported";
     53 
     54   private static final int RESET_TOUCH_FOCUS = 0;
     55   private static final int RESET_TOUCH_FOCUS_DELAY = 3000;
     56 
     57   private int mState = STATE_IDLE;
     58   private static final int STATE_IDLE = 0; // Focus is not active.
     59   private static final int STATE_FOCUSING = 1; // Focus is in progress.
     60   // Focus is in progress and the camera should take a picture after focus finishes.
     61   private static final int STATE_FOCUSING_SNAP_ON_FINISH = 2;
     62   private static final int STATE_SUCCESS = 3; // Focus finishes and succeeds.
     63   private static final int STATE_FAIL = 4; // Focus finishes and fails.
     64 
     65   private boolean mInitialized;
     66   private boolean mFocusAreaSupported;
     67   private boolean mMeteringAreaSupported;
     68   private boolean mLockAeAwbNeeded;
     69   private boolean mAeAwbLock;
     70   private Matrix mMatrix;
     71 
     72   private PieRenderer mPieRenderer;
     73 
     74   private int mPreviewWidth; // The width of the preview frame layout.
     75   private int mPreviewHeight; // The height of the preview frame layout.
     76   private boolean mMirror; // true if the camera is front-facing.
     77   private List<Area> mFocusArea; // focus area in driver format
     78   private List<Area> mMeteringArea; // metering area in driver format
     79   private String mFocusMode;
     80   private Parameters mParameters;
     81   private Handler mHandler;
     82   private Listener mListener;
     83 
     84   /** Listener used for the focus indicator to communicate back to the camera. */
     85   public interface Listener {
     86     void autoFocus();
     87 
     88     void cancelAutoFocus();
     89 
     90     boolean capture();
     91 
     92     void setFocusParameters();
     93   }
     94 
     95   private class MainHandler extends Handler {
     96     public MainHandler(Looper looper) {
     97       super(looper);
     98     }
     99 
    100     @Override
    101     public void handleMessage(Message msg) {
    102       switch (msg.what) {
    103         case RESET_TOUCH_FOCUS:
    104           {
    105             cancelAutoFocus();
    106             break;
    107           }
    108       }
    109     }
    110   }
    111 
    112   public FocusOverlayManager(Listener listener, Looper looper) {
    113     mHandler = new MainHandler(looper);
    114     mMatrix = new Matrix();
    115     mListener = listener;
    116   }
    117 
    118   public void setFocusRenderer(PieRenderer renderer) {
    119     mPieRenderer = renderer;
    120     mInitialized = (mMatrix != null);
    121   }
    122 
    123   public void setParameters(Parameters parameters) {
    124     // parameters can only be null when onConfigurationChanged is called
    125     // before camera is open. We will just return in this case, because
    126     // parameters will be set again later with the right parameters after
    127     // camera is open.
    128     if (parameters == null) {
    129       return;
    130     }
    131     mParameters = parameters;
    132     mFocusAreaSupported = isFocusAreaSupported(parameters);
    133     mMeteringAreaSupported = isMeteringAreaSupported(parameters);
    134     mLockAeAwbNeeded =
    135         (isAutoExposureLockSupported(mParameters) || isAutoWhiteBalanceLockSupported(mParameters));
    136   }
    137 
    138   public void setPreviewSize(int previewWidth, int previewHeight) {
    139     if (mPreviewWidth != previewWidth || mPreviewHeight != previewHeight) {
    140       mPreviewWidth = previewWidth;
    141       mPreviewHeight = previewHeight;
    142       setMatrix();
    143     }
    144   }
    145 
    146   public void setMirror(boolean mirror) {
    147     mMirror = mirror;
    148     setMatrix();
    149   }
    150 
    151   private void setMatrix() {
    152     if (mPreviewWidth != 0 && mPreviewHeight != 0) {
    153       Matrix matrix = new Matrix();
    154       prepareMatrix(matrix, mMirror, mPreviewWidth, mPreviewHeight);
    155       // In face detection, the matrix converts the driver coordinates to UI
    156       // coordinates. In tap focus, the inverted matrix converts the UI
    157       // coordinates to driver coordinates.
    158       matrix.invert(mMatrix);
    159       mInitialized = (mPieRenderer != null);
    160     }
    161   }
    162 
    163   private void lockAeAwbIfNeeded() {
    164     if (mLockAeAwbNeeded && !mAeAwbLock) {
    165       mAeAwbLock = true;
    166       mListener.setFocusParameters();
    167     }
    168   }
    169 
    170   public void onAutoFocus(boolean focused, boolean shutterButtonPressed) {
    171     if (mState == STATE_FOCUSING_SNAP_ON_FINISH) {
    172       // Take the picture no matter focus succeeds or fails. No need
    173       // to play the AF sound if we're about to play the shutter
    174       // sound.
    175       if (focused) {
    176         mState = STATE_SUCCESS;
    177       } else {
    178         mState = STATE_FAIL;
    179       }
    180       updateFocusUI();
    181       capture();
    182     } else if (mState == STATE_FOCUSING) {
    183       // This happens when (1) user is half-pressing the focus key or
    184       // (2) touch focus is triggered. Play the focus tone. Do not
    185       // take the picture now.
    186       if (focused) {
    187         mState = STATE_SUCCESS;
    188       } else {
    189         mState = STATE_FAIL;
    190       }
    191       updateFocusUI();
    192       // If this is triggered by touch focus, cancel focus after a
    193       // while.
    194       if (mFocusArea != null) {
    195         mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
    196       }
    197       if (shutterButtonPressed) {
    198         // Lock AE & AWB so users can half-press shutter and recompose.
    199         lockAeAwbIfNeeded();
    200       }
    201     } else if (mState == STATE_IDLE) {
    202       // User has released the focus key before focus completes.
    203       // Do nothing.
    204     }
    205   }
    206 
    207   public void onAutoFocusMoving(boolean moving) {
    208     if (!mInitialized) {
    209       return;
    210     }
    211 
    212     // Ignore if we have requested autofocus. This method only handles
    213     // continuous autofocus.
    214     if (mState != STATE_IDLE) {
    215       return;
    216     }
    217 
    218     if (moving) {
    219       mPieRenderer.showStart();
    220     } else {
    221       mPieRenderer.showSuccess(true);
    222     }
    223   }
    224 
    225   private void initializeFocusAreas(
    226       int focusWidth, int focusHeight, int x, int y, int previewWidth, int previewHeight) {
    227     if (mFocusArea == null) {
    228       mFocusArea = new ArrayList<>();
    229       mFocusArea.add(new Area(new Rect(), 1));
    230     }
    231 
    232     // Convert the coordinates to driver format.
    233     calculateTapArea(
    234         focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight, mFocusArea.get(0).rect);
    235   }
    236 
    237   private void initializeMeteringAreas(
    238       int focusWidth, int focusHeight, int x, int y, int previewWidth, int previewHeight) {
    239     if (mMeteringArea == null) {
    240       mMeteringArea = new ArrayList<>();
    241       mMeteringArea.add(new Area(new Rect(), 1));
    242     }
    243 
    244     // Convert the coordinates to driver format.
    245     // AE area is bigger because exposure is sensitive and
    246     // easy to over- or underexposure if area is too small.
    247     calculateTapArea(
    248         focusWidth,
    249         focusHeight,
    250         1.5f,
    251         x,
    252         y,
    253         previewWidth,
    254         previewHeight,
    255         mMeteringArea.get(0).rect);
    256   }
    257 
    258   public void onSingleTapUp(int x, int y) {
    259     if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
    260       return;
    261     }
    262 
    263     // Let users be able to cancel previous touch focus.
    264     if ((mFocusArea != null)
    265         && (mState == STATE_FOCUSING || mState == STATE_SUCCESS || mState == STATE_FAIL)) {
    266       cancelAutoFocus();
    267     }
    268     // Initialize variables.
    269     int focusWidth = mPieRenderer.getSize();
    270     int focusHeight = mPieRenderer.getSize();
    271     if (focusWidth == 0 || mPieRenderer.getWidth() == 0 || mPieRenderer.getHeight() == 0) {
    272       return;
    273     }
    274     int previewWidth = mPreviewWidth;
    275     int previewHeight = mPreviewHeight;
    276     // Initialize mFocusArea.
    277     if (mFocusAreaSupported) {
    278       initializeFocusAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight);
    279     }
    280     // Initialize mMeteringArea.
    281     if (mMeteringAreaSupported) {
    282       initializeMeteringAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight);
    283     }
    284 
    285     // Use margin to set the focus indicator to the touched area.
    286     mPieRenderer.setFocus(x, y);
    287 
    288     // Set the focus area and metering area.
    289     mListener.setFocusParameters();
    290     if (mFocusAreaSupported) {
    291       autoFocus();
    292     } else { // Just show the indicator in all other cases.
    293       updateFocusUI();
    294       // Reset the metering area in 3 seconds.
    295       mHandler.removeMessages(RESET_TOUCH_FOCUS);
    296       mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
    297     }
    298   }
    299 
    300   public void onPreviewStarted() {
    301     mState = STATE_IDLE;
    302   }
    303 
    304   public void onPreviewStopped() {
    305     // If auto focus was in progress, it would have been stopped.
    306     mState = STATE_IDLE;
    307     resetTouchFocus();
    308     updateFocusUI();
    309   }
    310 
    311   public void onCameraReleased() {
    312     onPreviewStopped();
    313   }
    314 
    315   private void autoFocus() {
    316     LogUtil.v("FocusOverlayManager.autoFocus", "Start autofocus.");
    317     mListener.autoFocus();
    318     mState = STATE_FOCUSING;
    319     updateFocusUI();
    320     mHandler.removeMessages(RESET_TOUCH_FOCUS);
    321   }
    322 
    323   public void cancelAutoFocus() {
    324     LogUtil.v("FocusOverlayManager.cancelAutoFocus", "Cancel autofocus.");
    325 
    326     // Reset the tap area before calling mListener.cancelAutofocus.
    327     // Otherwise, focus mode stays at auto and the tap area passed to the
    328     // driver is not reset.
    329     resetTouchFocus();
    330     mListener.cancelAutoFocus();
    331     mState = STATE_IDLE;
    332     updateFocusUI();
    333     mHandler.removeMessages(RESET_TOUCH_FOCUS);
    334   }
    335 
    336   private void capture() {
    337     if (mListener.capture()) {
    338       mState = STATE_IDLE;
    339       mHandler.removeMessages(RESET_TOUCH_FOCUS);
    340     }
    341   }
    342 
    343   public String getFocusMode() {
    344     List<String> supportedFocusModes = mParameters.getSupportedFocusModes();
    345 
    346     if (mFocusAreaSupported && mFocusArea != null) {
    347       // Always use autofocus in tap-to-focus.
    348       mFocusMode = Parameters.FOCUS_MODE_AUTO;
    349     } else {
    350       mFocusMode = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
    351     }
    352 
    353     if (!isSupported(mFocusMode, supportedFocusModes)) {
    354       // For some reasons, the driver does not support the current
    355       // focus mode. Fall back to auto.
    356       if (isSupported(Parameters.FOCUS_MODE_AUTO, mParameters.getSupportedFocusModes())) {
    357         mFocusMode = Parameters.FOCUS_MODE_AUTO;
    358       } else {
    359         mFocusMode = mParameters.getFocusMode();
    360       }
    361     }
    362     return mFocusMode;
    363   }
    364 
    365   public List<Area> getFocusAreas() {
    366     return mFocusArea;
    367   }
    368 
    369   public List<Area> getMeteringAreas() {
    370     return mMeteringArea;
    371   }
    372 
    373   private void updateFocusUI() {
    374     if (!mInitialized) {
    375       return;
    376     }
    377     FocusIndicator focusIndicator = mPieRenderer;
    378 
    379     if (mState == STATE_IDLE) {
    380       if (mFocusArea == null) {
    381         focusIndicator.clear();
    382       } else {
    383         // Users touch on the preview and the indicator represents the
    384         // metering area. Either focus area is not supported or
    385         // autoFocus call is not required.
    386         focusIndicator.showStart();
    387       }
    388     } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
    389       focusIndicator.showStart();
    390     } else {
    391       if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusMode)) {
    392         // TODO: check HAL behavior and decide if this can be removed.
    393         focusIndicator.showSuccess(false);
    394       } else if (mState == STATE_SUCCESS) {
    395         focusIndicator.showSuccess(false);
    396       } else if (mState == STATE_FAIL) {
    397         focusIndicator.showFail(false);
    398       }
    399     }
    400   }
    401 
    402   private void resetTouchFocus() {
    403     if (!mInitialized) {
    404       return;
    405     }
    406 
    407     // Put focus indicator to the center. clear reset position
    408     mPieRenderer.clear();
    409 
    410     mFocusArea = null;
    411     mMeteringArea = null;
    412   }
    413 
    414   private void calculateTapArea(
    415       int focusWidth,
    416       int focusHeight,
    417       float areaMultiple,
    418       int x,
    419       int y,
    420       int previewWidth,
    421       int previewHeight,
    422       Rect rect) {
    423     int areaWidth = (int) (focusWidth * areaMultiple);
    424     int areaHeight = (int) (focusHeight * areaMultiple);
    425     final int maxW = previewWidth - areaWidth;
    426     int left = maxW > 0 ? clamp(x - areaWidth / 2, 0, maxW) : 0;
    427     final int maxH = previewHeight - areaHeight;
    428     int top = maxH > 0 ? clamp(y - areaHeight / 2, 0, maxH) : 0;
    429 
    430     RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight);
    431     mMatrix.mapRect(rectF);
    432     rectFToRect(rectF, rect);
    433   }
    434 
    435   private int clamp(int x, int min, int max) {
    436     Assert.checkArgument(max >= min);
    437     if (x > max) {
    438       return max;
    439     }
    440     if (x < min) {
    441       return min;
    442     }
    443     return x;
    444   }
    445 
    446   private boolean isAutoExposureLockSupported(Parameters params) {
    447     return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
    448   }
    449 
    450   private boolean isAutoWhiteBalanceLockSupported(Parameters params) {
    451     return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
    452   }
    453 
    454   private boolean isSupported(String value, List<String> supported) {
    455     return supported != null && supported.indexOf(value) >= 0;
    456   }
    457 
    458   private boolean isMeteringAreaSupported(Parameters params) {
    459     return params.getMaxNumMeteringAreas() > 0;
    460   }
    461 
    462   private boolean isFocusAreaSupported(Parameters params) {
    463     return (params.getMaxNumFocusAreas() > 0
    464         && isSupported(Parameters.FOCUS_MODE_AUTO, params.getSupportedFocusModes()));
    465   }
    466 
    467   private void prepareMatrix(Matrix matrix, boolean mirror, int viewWidth, int viewHeight) {
    468     // Need mirror for front camera.
    469     matrix.setScale(mirror ? -1 : 1, 1);
    470     // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
    471     // UI coordinates range from (0, 0) to (width, height).
    472     matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
    473     matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
    474   }
    475 
    476   private void rectFToRect(RectF rectF, Rect rect) {
    477     rect.left = Math.round(rectF.left);
    478     rect.top = Math.round(rectF.top);
    479     rect.right = Math.round(rectF.right);
    480     rect.bottom = Math.round(rectF.bottom);
    481   }
    482 }
    483