1 /* 2 * Copyright (C) 2015 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.messaging.ui.mediapicker.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 28 import com.android.messaging.util.Assert; 29 import com.android.messaging.util.LogUtil; 30 31 import java.util.ArrayList; 32 import java.util.List; 33 34 /* A class that handles everything about focus in still picture mode. 35 * This also handles the metering area because it is the same as focus area. 36 * 37 * The test cases: 38 * (1) The camera has continuous autofocus. Move the camera. Take a picture when 39 * CAF is not in progress. 40 * (2) The camera has continuous autofocus. Move the camera. Take a picture when 41 * CAF is in progress. 42 * (3) The camera has face detection. Point the camera at some faces. Hold the 43 * shutter. Release to take a picture. 44 * (4) The camera has face detection. Point the camera at some faces. Single tap 45 * the shutter to take a picture. 46 * (5) The camera has autofocus. Single tap the shutter to take a picture. 47 * (6) The camera has autofocus. Hold the shutter. Release to take a picture. 48 * (7) The camera has no autofocus. Single tap the shutter and take a picture. 49 * (8) The camera has autofocus and supports focus area. Touch the screen to 50 * trigger autofocus. Take a picture. 51 * (9) The camera has autofocus and supports focus area. Touch the screen to 52 * trigger autofocus. Wait until it times out. 53 * (10) The camera has no autofocus and supports metering area. Touch the screen 54 * to change metering area. 55 */ 56 public class FocusOverlayManager { 57 private static final String TAG = LogUtil.BUGLE_TAG; 58 private static final String TRUE = "true"; 59 private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported"; 60 private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = 61 "auto-whitebalance-lock-supported"; 62 63 private static final int RESET_TOUCH_FOCUS = 0; 64 private static final int RESET_TOUCH_FOCUS_DELAY = 3000; 65 66 private int mState = STATE_IDLE; 67 private static final int STATE_IDLE = 0; // Focus is not active. 68 private static final int STATE_FOCUSING = 1; // Focus is in progress. 69 // Focus is in progress and the camera should take a picture after focus finishes. 70 private static final int STATE_FOCUSING_SNAP_ON_FINISH = 2; 71 private static final int STATE_SUCCESS = 3; // Focus finishes and succeeds. 72 private static final int STATE_FAIL = 4; // Focus finishes and fails. 73 74 private boolean mInitialized; 75 private boolean mFocusAreaSupported; 76 private boolean mMeteringAreaSupported; 77 private boolean mLockAeAwbNeeded; 78 private boolean mAeAwbLock; 79 private Matrix mMatrix; 80 81 private PieRenderer mPieRenderer; 82 83 private int mPreviewWidth; // The width of the preview frame layout. 84 private int mPreviewHeight; // The height of the preview frame layout. 85 private boolean mMirror; // true if the camera is front-facing. 86 private int mDisplayOrientation; 87 private List<Object> mFocusArea; // focus area in driver format 88 private List<Object> mMeteringArea; // metering area in driver format 89 private String mFocusMode; 90 private String mOverrideFocusMode; 91 private Parameters mParameters; 92 private Handler mHandler; 93 Listener mListener; 94 95 public interface Listener { autoFocus()96 public void autoFocus(); cancelAutoFocus()97 public void cancelAutoFocus(); capture()98 public boolean capture(); setFocusParameters()99 public void setFocusParameters(); 100 } 101 102 private class MainHandler extends Handler { MainHandler(Looper looper)103 public MainHandler(Looper looper) { 104 super(looper); 105 } 106 107 @Override handleMessage(Message msg)108 public void handleMessage(Message msg) { 109 switch (msg.what) { 110 case RESET_TOUCH_FOCUS: { 111 cancelAutoFocus(); 112 break; 113 } 114 } 115 } 116 } 117 FocusOverlayManager(Listener listener, Looper looper)118 public FocusOverlayManager(Listener listener, Looper looper) { 119 mHandler = new MainHandler(looper); 120 mMatrix = new Matrix(); 121 mListener = listener; 122 } 123 setFocusRenderer(PieRenderer renderer)124 public void setFocusRenderer(PieRenderer renderer) { 125 mPieRenderer = renderer; 126 mInitialized = (mMatrix != null); 127 } 128 setParameters(Parameters parameters)129 public void setParameters(Parameters parameters) { 130 // parameters can only be null when onConfigurationChanged is called 131 // before camera is open. We will just return in this case, because 132 // parameters will be set again later with the right parameters after 133 // camera is open. 134 if (parameters == null) { 135 return; 136 } 137 mParameters = parameters; 138 mFocusAreaSupported = isFocusAreaSupported(parameters); 139 mMeteringAreaSupported = isMeteringAreaSupported(parameters); 140 mLockAeAwbNeeded = (isAutoExposureLockSupported(mParameters) || 141 isAutoWhiteBalanceLockSupported(mParameters)); 142 } 143 setPreviewSize(int previewWidth, int previewHeight)144 public void setPreviewSize(int previewWidth, int previewHeight) { 145 if (mPreviewWidth != previewWidth || mPreviewHeight != previewHeight) { 146 mPreviewWidth = previewWidth; 147 mPreviewHeight = previewHeight; 148 setMatrix(); 149 } 150 } 151 setMirror(boolean mirror)152 public void setMirror(boolean mirror) { 153 mMirror = mirror; 154 setMatrix(); 155 } 156 setDisplayOrientation(int displayOrientation)157 public void setDisplayOrientation(int displayOrientation) { 158 mDisplayOrientation = displayOrientation; 159 setMatrix(); 160 } 161 setMatrix()162 private void setMatrix() { 163 if (mPreviewWidth != 0 && mPreviewHeight != 0) { 164 Matrix matrix = new Matrix(); 165 prepareMatrix(matrix, mMirror, mDisplayOrientation, 166 mPreviewWidth, mPreviewHeight); 167 // In face detection, the matrix converts the driver coordinates to UI 168 // coordinates. In tap focus, the inverted matrix converts the UI 169 // coordinates to driver coordinates. 170 matrix.invert(mMatrix); 171 mInitialized = (mPieRenderer != null); 172 } 173 } 174 lockAeAwbIfNeeded()175 private void lockAeAwbIfNeeded() { 176 if (mLockAeAwbNeeded && !mAeAwbLock) { 177 mAeAwbLock = true; 178 mListener.setFocusParameters(); 179 } 180 } 181 unlockAeAwbIfNeeded()182 private void unlockAeAwbIfNeeded() { 183 if (mLockAeAwbNeeded && mAeAwbLock && (mState != STATE_FOCUSING_SNAP_ON_FINISH)) { 184 mAeAwbLock = false; 185 mListener.setFocusParameters(); 186 } 187 } 188 onShutterDown()189 public void onShutterDown() { 190 if (!mInitialized) { 191 return; 192 } 193 194 boolean autoFocusCalled = false; 195 if (needAutoFocusCall()) { 196 // Do not focus if touch focus has been triggered. 197 if (mState != STATE_SUCCESS && mState != STATE_FAIL) { 198 autoFocus(); 199 autoFocusCalled = true; 200 } 201 } 202 203 if (!autoFocusCalled) { 204 lockAeAwbIfNeeded(); 205 } 206 } 207 onShutterUp()208 public void onShutterUp() { 209 if (!mInitialized) { 210 return; 211 } 212 213 if (needAutoFocusCall()) { 214 // User releases half-pressed focus key. 215 if (mState == STATE_FOCUSING || mState == STATE_SUCCESS 216 || mState == STATE_FAIL) { 217 cancelAutoFocus(); 218 } 219 } 220 221 // Unlock AE and AWB after cancelAutoFocus. Camera API does not 222 // guarantee setParameters can be called during autofocus. 223 unlockAeAwbIfNeeded(); 224 } 225 doSnap()226 public void doSnap() { 227 if (!mInitialized) { 228 return; 229 } 230 231 // If the user has half-pressed the shutter and focus is completed, we 232 // can take the photo right away. If the focus mode is infinity, we can 233 // also take the photo. 234 if (!needAutoFocusCall() || (mState == STATE_SUCCESS || mState == STATE_FAIL)) { 235 capture(); 236 } else if (mState == STATE_FOCUSING) { 237 // Half pressing the shutter (i.e. the focus button event) will 238 // already have requested AF for us, so just request capture on 239 // focus here. 240 mState = STATE_FOCUSING_SNAP_ON_FINISH; 241 } else if (mState == STATE_IDLE) { 242 // We didn't do focus. This can happen if the user press focus key 243 // while the snapshot is still in progress. The user probably wants 244 // the next snapshot as soon as possible, so we just do a snapshot 245 // without focusing again. 246 capture(); 247 } 248 } 249 onAutoFocus(boolean focused, boolean shutterButtonPressed)250 public void onAutoFocus(boolean focused, boolean shutterButtonPressed) { 251 if (mState == STATE_FOCUSING_SNAP_ON_FINISH) { 252 // Take the picture no matter focus succeeds or fails. No need 253 // to play the AF sound if we're about to play the shutter 254 // sound. 255 if (focused) { 256 mState = STATE_SUCCESS; 257 } else { 258 mState = STATE_FAIL; 259 } 260 updateFocusUI(); 261 capture(); 262 } else if (mState == STATE_FOCUSING) { 263 // This happens when (1) user is half-pressing the focus key or 264 // (2) touch focus is triggered. Play the focus tone. Do not 265 // take the picture now. 266 if (focused) { 267 mState = STATE_SUCCESS; 268 } else { 269 mState = STATE_FAIL; 270 } 271 updateFocusUI(); 272 // If this is triggered by touch focus, cancel focus after a 273 // while. 274 if (mFocusArea != null) { 275 mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY); 276 } 277 if (shutterButtonPressed) { 278 // Lock AE & AWB so users can half-press shutter and recompose. 279 lockAeAwbIfNeeded(); 280 } 281 } else if (mState == STATE_IDLE) { 282 // User has released the focus key before focus completes. 283 // Do nothing. 284 } 285 } 286 onAutoFocusMoving(boolean moving)287 public void onAutoFocusMoving(boolean moving) { 288 if (!mInitialized) { 289 return; 290 } 291 292 // Ignore if we have requested autofocus. This method only handles 293 // continuous autofocus. 294 if (mState != STATE_IDLE) { 295 return; 296 } 297 298 if (moving) { 299 mPieRenderer.showStart(); 300 } else { 301 mPieRenderer.showSuccess(true); 302 } 303 } 304 initializeFocusAreas(int focusWidth, int focusHeight, int x, int y, int previewWidth, int previewHeight)305 private void initializeFocusAreas(int focusWidth, int focusHeight, 306 int x, int y, int previewWidth, int previewHeight) { 307 if (mFocusArea == null) { 308 mFocusArea = new ArrayList<Object>(); 309 mFocusArea.add(new Area(new Rect(), 1)); 310 } 311 312 // Convert the coordinates to driver format. 313 calculateTapArea(focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight, 314 ((Area) mFocusArea.get(0)).rect); 315 } 316 initializeMeteringAreas(int focusWidth, int focusHeight, int x, int y, int previewWidth, int previewHeight)317 private void initializeMeteringAreas(int focusWidth, int focusHeight, 318 int x, int y, int previewWidth, int previewHeight) { 319 if (mMeteringArea == null) { 320 mMeteringArea = new ArrayList<Object>(); 321 mMeteringArea.add(new Area(new Rect(), 1)); 322 } 323 324 // Convert the coordinates to driver format. 325 // AE area is bigger because exposure is sensitive and 326 // easy to over- or underexposure if area is too small. 327 calculateTapArea(focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight, 328 ((Area) mMeteringArea.get(0)).rect); 329 } 330 onSingleTapUp(int x, int y)331 public void onSingleTapUp(int x, int y) { 332 if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) { 333 return; 334 } 335 336 // Let users be able to cancel previous touch focus. 337 if ((mFocusArea != null) && (mState == STATE_FOCUSING || 338 mState == STATE_SUCCESS || mState == STATE_FAIL)) { 339 cancelAutoFocus(); 340 } 341 // Initialize variables. 342 int focusWidth = mPieRenderer.getSize(); 343 int focusHeight = mPieRenderer.getSize(); 344 if (focusWidth == 0 || mPieRenderer.getWidth() == 0 || mPieRenderer.getHeight() == 0) { 345 return; 346 } 347 int previewWidth = mPreviewWidth; 348 int previewHeight = mPreviewHeight; 349 // Initialize mFocusArea. 350 if (mFocusAreaSupported) { 351 initializeFocusAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight); 352 } 353 // Initialize mMeteringArea. 354 if (mMeteringAreaSupported) { 355 initializeMeteringAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight); 356 } 357 358 // Use margin to set the focus indicator to the touched area. 359 mPieRenderer.setFocus(x, y); 360 361 // Set the focus area and metering area. 362 mListener.setFocusParameters(); 363 if (mFocusAreaSupported) { 364 autoFocus(); 365 } else { // Just show the indicator in all other cases. 366 updateFocusUI(); 367 // Reset the metering area in 3 seconds. 368 mHandler.removeMessages(RESET_TOUCH_FOCUS); 369 mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY); 370 } 371 } 372 onPreviewStarted()373 public void onPreviewStarted() { 374 mState = STATE_IDLE; 375 } 376 onPreviewStopped()377 public void onPreviewStopped() { 378 // If auto focus was in progress, it would have been stopped. 379 mState = STATE_IDLE; 380 resetTouchFocus(); 381 updateFocusUI(); 382 } 383 onCameraReleased()384 public void onCameraReleased() { 385 onPreviewStopped(); 386 } 387 autoFocus()388 private void autoFocus() { 389 LogUtil.v(TAG, "Start autofocus."); 390 mListener.autoFocus(); 391 mState = STATE_FOCUSING; 392 updateFocusUI(); 393 mHandler.removeMessages(RESET_TOUCH_FOCUS); 394 } 395 cancelAutoFocus()396 private void cancelAutoFocus() { 397 LogUtil.v(TAG, "Cancel autofocus."); 398 399 // Reset the tap area before calling mListener.cancelAutofocus. 400 // Otherwise, focus mode stays at auto and the tap area passed to the 401 // driver is not reset. 402 resetTouchFocus(); 403 mListener.cancelAutoFocus(); 404 mState = STATE_IDLE; 405 updateFocusUI(); 406 mHandler.removeMessages(RESET_TOUCH_FOCUS); 407 } 408 capture()409 private void capture() { 410 if (mListener.capture()) { 411 mState = STATE_IDLE; 412 mHandler.removeMessages(RESET_TOUCH_FOCUS); 413 } 414 } 415 getFocusMode()416 public String getFocusMode() { 417 if (mOverrideFocusMode != null) { 418 return mOverrideFocusMode; 419 } 420 List<String> supportedFocusModes = mParameters.getSupportedFocusModes(); 421 422 if (mFocusAreaSupported && mFocusArea != null) { 423 // Always use autofocus in tap-to-focus. 424 mFocusMode = Parameters.FOCUS_MODE_AUTO; 425 } else { 426 mFocusMode = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE; 427 } 428 429 if (!isSupported(mFocusMode, supportedFocusModes)) { 430 // For some reasons, the driver does not support the current 431 // focus mode. Fall back to auto. 432 if (isSupported(Parameters.FOCUS_MODE_AUTO, 433 mParameters.getSupportedFocusModes())) { 434 mFocusMode = Parameters.FOCUS_MODE_AUTO; 435 } else { 436 mFocusMode = mParameters.getFocusMode(); 437 } 438 } 439 return mFocusMode; 440 } 441 getFocusAreas()442 public List getFocusAreas() { 443 return mFocusArea; 444 } 445 getMeteringAreas()446 public List getMeteringAreas() { 447 return mMeteringArea; 448 } 449 updateFocusUI()450 public void updateFocusUI() { 451 if (!mInitialized) { 452 return; 453 } 454 FocusIndicator focusIndicator = mPieRenderer; 455 456 if (mState == STATE_IDLE) { 457 if (mFocusArea == null) { 458 focusIndicator.clear(); 459 } else { 460 // Users touch on the preview and the indicator represents the 461 // metering area. Either focus area is not supported or 462 // autoFocus call is not required. 463 focusIndicator.showStart(); 464 } 465 } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) { 466 focusIndicator.showStart(); 467 } else { 468 if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusMode)) { 469 // TODO: check HAL behavior and decide if this can be removed. 470 focusIndicator.showSuccess(false); 471 } else if (mState == STATE_SUCCESS) { 472 focusIndicator.showSuccess(false); 473 } else if (mState == STATE_FAIL) { 474 focusIndicator.showFail(false); 475 } 476 } 477 } 478 resetTouchFocus()479 public void resetTouchFocus() { 480 if (!mInitialized) { 481 return; 482 } 483 484 // Put focus indicator to the center. clear reset position 485 mPieRenderer.clear(); 486 487 mFocusArea = null; 488 mMeteringArea = null; 489 } 490 calculateTapArea(int focusWidth, int focusHeight, float areaMultiple, int x, int y, int previewWidth, int previewHeight, Rect rect)491 private void calculateTapArea(int focusWidth, int focusHeight, float areaMultiple, 492 int x, int y, int previewWidth, int previewHeight, Rect rect) { 493 int areaWidth = (int) (focusWidth * areaMultiple); 494 int areaHeight = (int) (focusHeight * areaMultiple); 495 int left = clamp(x - areaWidth / 2, 0, previewWidth - areaWidth); 496 int top = clamp(y - areaHeight / 2, 0, previewHeight - areaHeight); 497 498 RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight); 499 mMatrix.mapRect(rectF); 500 rectFToRect(rectF, rect); 501 } 502 getFocusState()503 /* package */ int getFocusState() { 504 return mState; 505 } 506 isFocusCompleted()507 public boolean isFocusCompleted() { 508 return mState == STATE_SUCCESS || mState == STATE_FAIL; 509 } 510 isFocusingSnapOnFinish()511 public boolean isFocusingSnapOnFinish() { 512 return mState == STATE_FOCUSING_SNAP_ON_FINISH; 513 } 514 removeMessages()515 public void removeMessages() { 516 mHandler.removeMessages(RESET_TOUCH_FOCUS); 517 } 518 overrideFocusMode(String focusMode)519 public void overrideFocusMode(String focusMode) { 520 mOverrideFocusMode = focusMode; 521 } 522 setAeAwbLock(boolean lock)523 public void setAeAwbLock(boolean lock) { 524 mAeAwbLock = lock; 525 } 526 getAeAwbLock()527 public boolean getAeAwbLock() { 528 return mAeAwbLock; 529 } 530 needAutoFocusCall()531 private boolean needAutoFocusCall() { 532 String focusMode = getFocusMode(); 533 return !(focusMode.equals(Parameters.FOCUS_MODE_INFINITY) 534 || focusMode.equals(Parameters.FOCUS_MODE_FIXED) 535 || focusMode.equals(Parameters.FOCUS_MODE_EDOF)); 536 } 537 isAutoExposureLockSupported(Parameters params)538 public static boolean isAutoExposureLockSupported(Parameters params) { 539 return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED)); 540 } 541 isAutoWhiteBalanceLockSupported(Parameters params)542 public static boolean isAutoWhiteBalanceLockSupported(Parameters params) { 543 return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED)); 544 } 545 isSupported(String value, List<String> supported)546 public static boolean isSupported(String value, List<String> supported) { 547 return supported != null && supported.indexOf(value) >= 0; 548 } 549 isMeteringAreaSupported(Parameters params)550 public static boolean isMeteringAreaSupported(Parameters params) { 551 return params.getMaxNumMeteringAreas() > 0; 552 } 553 isFocusAreaSupported(Parameters params)554 public static boolean isFocusAreaSupported(Parameters params) { 555 return (params.getMaxNumFocusAreas() > 0 556 && isSupported(Parameters.FOCUS_MODE_AUTO, 557 params.getSupportedFocusModes())); 558 } 559 prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, int viewWidth, int viewHeight)560 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, 561 int viewWidth, int viewHeight) { 562 // Need mirror for front camera. 563 matrix.setScale(mirror ? -1 : 1, 1); 564 // This is the value for android.hardware.Camera.setDisplayOrientation. 565 matrix.postRotate(displayOrientation); 566 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000). 567 // UI coordinates range from (0, 0) to (width, height). 568 matrix.postScale(viewWidth / 2000f, viewHeight / 2000f); 569 matrix.postTranslate(viewWidth / 2f, viewHeight / 2f); 570 } 571 clamp(int x, int min, int max)572 public static int clamp(int x, int min, int max) { 573 Assert.isTrue(max >= min); 574 if (x > max) { 575 return max; 576 } 577 if (x < min) { 578 return min; 579 } 580 return x; 581 } 582 rectFToRect(RectF rectF, Rect rect)583 public static void rectFToRect(RectF rectF, Rect rect) { 584 rect.left = Math.round(rectF.left); 585 rect.top = Math.round(rectF.top); 586 rect.right = Math.round(rectF.right); 587 rect.bottom = Math.round(rectF.bottom); 588 } 589 } 590