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 state = 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 initialized; 66 private boolean focusAreaSupported; 67 private boolean meteringAreaSupported; 68 private boolean lockAeAwbNeeded; 69 private boolean aeAwbLock; 70 private Matrix matrix; 71 72 private PieRenderer pieRenderer; 73 74 private int previewWidth; // The width of the preview frame layout. 75 private int previewHeight; // The height of the preview frame layout. 76 private boolean mirror; // true if the camera is front-facing. 77 private List<Area> focusArea; // focus area in driver format 78 private List<Area> meteringArea; // metering area in driver format 79 private String focusMode; 80 private Parameters parameters; 81 private Handler handler; 82 private Listener listener; 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 handler = new MainHandler(looper); 114 matrix = new Matrix(); 115 this.listener = listener; 116 } 117 setFocusRenderer(PieRenderer renderer)118 public void setFocusRenderer(PieRenderer renderer) { 119 pieRenderer = renderer; 120 initialized = (matrix != 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 this.parameters = parameters; 132 focusAreaSupported = isFocusAreaSupported(parameters); 133 meteringAreaSupported = isMeteringAreaSupported(parameters); 134 lockAeAwbNeeded = 135 (isAutoExposureLockSupported(this.parameters) 136 || isAutoWhiteBalanceLockSupported(this.parameters)); 137 } 138 setPreviewSize(int previewWidth, int previewHeight)139 public void setPreviewSize(int previewWidth, int previewHeight) { 140 if (this.previewWidth != previewWidth || this.previewHeight != previewHeight) { 141 this.previewWidth = previewWidth; 142 this.previewHeight = previewHeight; 143 setMatrix(); 144 } 145 } 146 setMirror(boolean mirror)147 public void setMirror(boolean mirror) { 148 this.mirror = mirror; 149 setMatrix(); 150 } 151 setMatrix()152 private void setMatrix() { 153 if (previewWidth != 0 && previewHeight != 0) { 154 Matrix matrix = new Matrix(); 155 prepareMatrix(matrix, mirror, previewWidth, previewHeight); 156 // In face detection, the matrix converts the driver coordinates to UI 157 // coordinates. In tap focus, the inverted matrix converts the UI 158 // coordinates to driver coordinates. 159 matrix.invert(this.matrix); 160 initialized = (pieRenderer != null); 161 } 162 } 163 lockAeAwbIfNeeded()164 private void lockAeAwbIfNeeded() { 165 if (lockAeAwbNeeded && !aeAwbLock) { 166 aeAwbLock = true; 167 listener.setFocusParameters(); 168 } 169 } 170 onAutoFocus(boolean focused, boolean shutterButtonPressed)171 public void onAutoFocus(boolean focused, boolean shutterButtonPressed) { 172 if (state == STATE_FOCUSING_SNAP_ON_FINISH) { 173 // Take the picture no matter focus succeeds or fails. No need 174 // to play the AF sound if we're about to play the shutter 175 // sound. 176 if (focused) { 177 state = STATE_SUCCESS; 178 } else { 179 state = STATE_FAIL; 180 } 181 updateFocusUI(); 182 capture(); 183 } else if (state == STATE_FOCUSING) { 184 // This happens when (1) user is half-pressing the focus key or 185 // (2) touch focus is triggered. Play the focus tone. Do not 186 // take the picture now. 187 if (focused) { 188 state = STATE_SUCCESS; 189 } else { 190 state = STATE_FAIL; 191 } 192 updateFocusUI(); 193 // If this is triggered by touch focus, cancel focus after a 194 // while. 195 if (focusArea != null) { 196 handler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY); 197 } 198 if (shutterButtonPressed) { 199 // Lock AE & AWB so users can half-press shutter and recompose. 200 lockAeAwbIfNeeded(); 201 } 202 } else if (state == STATE_IDLE) { 203 // User has released the focus key before focus completes. 204 // Do nothing. 205 } 206 } 207 onAutoFocusMoving(boolean moving)208 public void onAutoFocusMoving(boolean moving) { 209 if (!initialized) { 210 return; 211 } 212 213 // Ignore if we have requested autofocus. This method only handles 214 // continuous autofocus. 215 if (state != STATE_IDLE) { 216 return; 217 } 218 219 if (moving) { 220 pieRenderer.showStart(); 221 } else { 222 pieRenderer.showSuccess(true); 223 } 224 } 225 initializeFocusAreas( int focusWidth, int focusHeight, int x, int y, int previewWidth, int previewHeight)226 private void initializeFocusAreas( 227 int focusWidth, int focusHeight, int x, int y, int previewWidth, int previewHeight) { 228 if (focusArea == null) { 229 focusArea = new ArrayList<>(); 230 focusArea.add(new Area(new Rect(), 1)); 231 } 232 233 // Convert the coordinates to driver format. 234 calculateTapArea( 235 focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight, focusArea.get(0).rect); 236 } 237 initializeMeteringAreas( int focusWidth, int focusHeight, int x, int y, int previewWidth, int previewHeight)238 private void initializeMeteringAreas( 239 int focusWidth, int focusHeight, int x, int y, int previewWidth, int previewHeight) { 240 if (meteringArea == null) { 241 meteringArea = new ArrayList<>(); 242 meteringArea.add(new Area(new Rect(), 1)); 243 } 244 245 // Convert the coordinates to driver format. 246 // AE area is bigger because exposure is sensitive and 247 // easy to over- or underexposure if area is too small. 248 calculateTapArea( 249 focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight, meteringArea.get(0).rect); 250 } 251 onSingleTapUp(int x, int y)252 public void onSingleTapUp(int x, int y) { 253 if (!initialized || state == STATE_FOCUSING_SNAP_ON_FINISH) { 254 return; 255 } 256 257 // Let users be able to cancel previous touch focus. 258 if ((focusArea != null) 259 && (state == STATE_FOCUSING || state == STATE_SUCCESS || state == STATE_FAIL)) { 260 cancelAutoFocus(); 261 } 262 // Initialize variables. 263 int focusWidth = pieRenderer.getSize(); 264 int focusHeight = pieRenderer.getSize(); 265 if (focusWidth == 0 || pieRenderer.getWidth() == 0 || pieRenderer.getHeight() == 0) { 266 return; 267 } 268 int previewWidth = this.previewWidth; 269 int previewHeight = this.previewHeight; 270 // Initialize mFocusArea. 271 if (focusAreaSupported) { 272 initializeFocusAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight); 273 } 274 // Initialize mMeteringArea. 275 if (meteringAreaSupported) { 276 initializeMeteringAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight); 277 } 278 279 // Use margin to set the focus indicator to the touched area. 280 pieRenderer.setFocus(x, y); 281 282 // Set the focus area and metering area. 283 listener.setFocusParameters(); 284 if (focusAreaSupported) { 285 autoFocus(); 286 } else { // Just show the indicator in all other cases. 287 updateFocusUI(); 288 // Reset the metering area in 3 seconds. 289 handler.removeMessages(RESET_TOUCH_FOCUS); 290 handler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY); 291 } 292 } 293 onPreviewStarted()294 public void onPreviewStarted() { 295 state = STATE_IDLE; 296 } 297 onPreviewStopped()298 public void onPreviewStopped() { 299 // If auto focus was in progress, it would have been stopped. 300 state = STATE_IDLE; 301 resetTouchFocus(); 302 updateFocusUI(); 303 } 304 onCameraReleased()305 public void onCameraReleased() { 306 onPreviewStopped(); 307 } 308 autoFocus()309 private void autoFocus() { 310 LogUtil.v("FocusOverlayManager.autoFocus", "Start autofocus."); 311 listener.autoFocus(); 312 state = STATE_FOCUSING; 313 updateFocusUI(); 314 handler.removeMessages(RESET_TOUCH_FOCUS); 315 } 316 cancelAutoFocus()317 public void cancelAutoFocus() { 318 LogUtil.v("FocusOverlayManager.cancelAutoFocus", "Cancel autofocus."); 319 320 // Reset the tap area before calling mListener.cancelAutofocus. 321 // Otherwise, focus mode stays at auto and the tap area passed to the 322 // driver is not reset. 323 resetTouchFocus(); 324 listener.cancelAutoFocus(); 325 state = STATE_IDLE; 326 updateFocusUI(); 327 handler.removeMessages(RESET_TOUCH_FOCUS); 328 } 329 capture()330 private void capture() { 331 if (listener.capture()) { 332 state = STATE_IDLE; 333 handler.removeMessages(RESET_TOUCH_FOCUS); 334 } 335 } 336 getFocusMode()337 public String getFocusMode() { 338 List<String> supportedFocusModes = parameters.getSupportedFocusModes(); 339 340 if (focusAreaSupported && focusArea != null) { 341 // Always use autofocus in tap-to-focus. 342 focusMode = Parameters.FOCUS_MODE_AUTO; 343 } else { 344 focusMode = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE; 345 } 346 347 if (!isSupported(focusMode, supportedFocusModes)) { 348 // For some reasons, the driver does not support the current 349 // focus mode. Fall back to auto. 350 if (isSupported(Parameters.FOCUS_MODE_AUTO, parameters.getSupportedFocusModes())) { 351 focusMode = Parameters.FOCUS_MODE_AUTO; 352 } else { 353 focusMode = parameters.getFocusMode(); 354 } 355 } 356 return focusMode; 357 } 358 getFocusAreas()359 public List<Area> getFocusAreas() { 360 return focusArea; 361 } 362 getMeteringAreas()363 public List<Area> getMeteringAreas() { 364 return meteringArea; 365 } 366 updateFocusUI()367 private void updateFocusUI() { 368 if (!initialized) { 369 return; 370 } 371 FocusIndicator focusIndicator = pieRenderer; 372 373 if (state == STATE_IDLE) { 374 if (focusArea == null) { 375 focusIndicator.clear(); 376 } else { 377 // Users touch on the preview and the indicator represents the 378 // metering area. Either focus area is not supported or 379 // autoFocus call is not required. 380 focusIndicator.showStart(); 381 } 382 } else if (state == STATE_FOCUSING || state == STATE_FOCUSING_SNAP_ON_FINISH) { 383 focusIndicator.showStart(); 384 } else { 385 if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(focusMode)) { 386 // TODO(blemmon): check HAL behavior and decide if this can be removed. 387 focusIndicator.showSuccess(false); 388 } else if (state == STATE_SUCCESS) { 389 focusIndicator.showSuccess(false); 390 } else if (state == STATE_FAIL) { 391 focusIndicator.showFail(false); 392 } 393 } 394 } 395 resetTouchFocus()396 private void resetTouchFocus() { 397 if (!initialized) { 398 return; 399 } 400 401 // Put focus indicator to the center. clear reset position 402 pieRenderer.clear(); 403 404 focusArea = null; 405 meteringArea = null; 406 } 407 calculateTapArea( int focusWidth, int focusHeight, float areaMultiple, int x, int y, int previewWidth, int previewHeight, Rect rect)408 private void calculateTapArea( 409 int focusWidth, 410 int focusHeight, 411 float areaMultiple, 412 int x, 413 int y, 414 int previewWidth, 415 int previewHeight, 416 Rect rect) { 417 int areaWidth = (int) (focusWidth * areaMultiple); 418 int areaHeight = (int) (focusHeight * areaMultiple); 419 final int maxW = previewWidth - areaWidth; 420 int left = maxW > 0 ? clamp(x - areaWidth / 2, 0, maxW) : 0; 421 final int maxH = previewHeight - areaHeight; 422 int top = maxH > 0 ? clamp(y - areaHeight / 2, 0, maxH) : 0; 423 424 RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight); 425 matrix.mapRect(rectF); 426 rectFToRect(rectF, rect); 427 } 428 clamp(int x, int min, int max)429 private int clamp(int x, int min, int max) { 430 Assert.checkArgument(max >= min); 431 if (x > max) { 432 return max; 433 } 434 if (x < min) { 435 return min; 436 } 437 return x; 438 } 439 isAutoExposureLockSupported(Parameters params)440 private boolean isAutoExposureLockSupported(Parameters params) { 441 return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED)); 442 } 443 isAutoWhiteBalanceLockSupported(Parameters params)444 private boolean isAutoWhiteBalanceLockSupported(Parameters params) { 445 return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED)); 446 } 447 isSupported(String value, List<String> supported)448 private boolean isSupported(String value, List<String> supported) { 449 return supported != null && supported.indexOf(value) >= 0; 450 } 451 isMeteringAreaSupported(Parameters params)452 private boolean isMeteringAreaSupported(Parameters params) { 453 return params.getMaxNumMeteringAreas() > 0; 454 } 455 isFocusAreaSupported(Parameters params)456 private boolean isFocusAreaSupported(Parameters params) { 457 return (params.getMaxNumFocusAreas() > 0 458 && isSupported(Parameters.FOCUS_MODE_AUTO, params.getSupportedFocusModes())); 459 } 460 prepareMatrix(Matrix matrix, boolean mirror, int viewWidth, int viewHeight)461 private void prepareMatrix(Matrix matrix, boolean mirror, int viewWidth, int viewHeight) { 462 // Need mirror for front camera. 463 matrix.setScale(mirror ? -1 : 1, 1); 464 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000). 465 // UI coordinates range from (0, 0) to (width, height). 466 matrix.postScale(viewWidth / 2000f, viewHeight / 2000f); 467 matrix.postTranslate(viewWidth / 2f, viewHeight / 2f); 468 } 469 rectFToRect(RectF rectF, Rect rect)470 private void rectFToRect(RectF rectF, Rect rect) { 471 rect.left = Math.round(rectF.left); 472 rect.top = Math.round(rectF.top); 473 rect.right = Math.round(rectF.right); 474 rect.bottom = Math.round(rectF.bottom); 475 } 476 } 477