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 { autoFocus()86 void autoFocus(); 87 cancelAutoFocus()88 void cancelAutoFocus(); 89 capture()90 boolean capture(); 91 setFocusParameters()92 void setFocusParameters(); 93 } 94 95 private class MainHandler extends Handler { MainHandler(Looper looper)96 public MainHandler(Looper looper) { 97 super(looper); 98 } 99 100 @Override handleMessage(Message msg)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 FocusOverlayManager(Listener listener, Looper looper)112 public FocusOverlayManager(Listener listener, Looper looper) { 113 mHandler = new MainHandler(looper); 114 mMatrix = new Matrix(); 115 mListener = listener; 116 } 117 setFocusRenderer(PieRenderer renderer)118 public void setFocusRenderer(PieRenderer renderer) { 119 mPieRenderer = renderer; 120 mInitialized = (mMatrix != null); 121 } 122 setParameters(Parameters parameters)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 setPreviewSize(int previewWidth, int previewHeight)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 setMirror(boolean mirror)146 public void setMirror(boolean mirror) { 147 mMirror = mirror; 148 setMatrix(); 149 } 150 setMatrix()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 lockAeAwbIfNeeded()163 private void lockAeAwbIfNeeded() { 164 if (mLockAeAwbNeeded && !mAeAwbLock) { 165 mAeAwbLock = true; 166 mListener.setFocusParameters(); 167 } 168 } 169 onAutoFocus(boolean focused, boolean shutterButtonPressed)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 onAutoFocusMoving(boolean moving)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 initializeFocusAreas( int focusWidth, int focusHeight, int x, int y, int previewWidth, int previewHeight)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 initializeMeteringAreas( int focusWidth, int focusHeight, int x, int y, int previewWidth, int previewHeight)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 onSingleTapUp(int x, int y)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 onPreviewStarted()300 public void onPreviewStarted() { 301 mState = STATE_IDLE; 302 } 303 onPreviewStopped()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 onCameraReleased()311 public void onCameraReleased() { 312 onPreviewStopped(); 313 } 314 autoFocus()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 cancelAutoFocus()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 capture()336 private void capture() { 337 if (mListener.capture()) { 338 mState = STATE_IDLE; 339 mHandler.removeMessages(RESET_TOUCH_FOCUS); 340 } 341 } 342 getFocusMode()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 getFocusAreas()365 public List<Area> getFocusAreas() { 366 return mFocusArea; 367 } 368 getMeteringAreas()369 public List<Area> getMeteringAreas() { 370 return mMeteringArea; 371 } 372 updateFocusUI()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 resetTouchFocus()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 calculateTapArea( int focusWidth, int focusHeight, float areaMultiple, int x, int y, int previewWidth, int previewHeight, Rect rect)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 clamp(int x, int min, int max)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 isAutoExposureLockSupported(Parameters params)446 private boolean isAutoExposureLockSupported(Parameters params) { 447 return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED)); 448 } 449 isAutoWhiteBalanceLockSupported(Parameters params)450 private boolean isAutoWhiteBalanceLockSupported(Parameters params) { 451 return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED)); 452 } 453 isSupported(String value, List<String> supported)454 private boolean isSupported(String value, List<String> supported) { 455 return supported != null && supported.indexOf(value) >= 0; 456 } 457 isMeteringAreaSupported(Parameters params)458 private boolean isMeteringAreaSupported(Parameters params) { 459 return params.getMaxNumMeteringAreas() > 0; 460 } 461 isFocusAreaSupported(Parameters params)462 private boolean isFocusAreaSupported(Parameters params) { 463 return (params.getMaxNumFocusAreas() > 0 464 && isSupported(Parameters.FOCUS_MODE_AUTO, params.getSupportedFocusModes())); 465 } 466 prepareMatrix(Matrix matrix, boolean mirror, int viewWidth, int viewHeight)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 rectFToRect(RectF rectF, Rect rect)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