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