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