• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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