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