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
17package com.android.messaging.ui.mediapicker.camerafocus;
18
19import android.graphics.Matrix;
20import android.graphics.Rect;
21import android.graphics.RectF;
22import android.hardware.Camera.Area;
23import android.hardware.Camera.Parameters;
24import android.os.Handler;
25import android.os.Looper;
26import android.os.Message;
27
28import com.android.messaging.util.Assert;
29import com.android.messaging.util.LogUtil;
30
31import java.util.ArrayList;
32import 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 */
56public 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 {
96        public void autoFocus();
97        public void cancelAutoFocus();
98        public boolean capture();
99        public void setFocusParameters();
100    }
101
102    private class MainHandler extends Handler {
103        public MainHandler(Looper looper) {
104            super(looper);
105        }
106
107        @Override
108        public void handleMessage(Message msg) {
109            switch (msg.what) {
110                case RESET_TOUCH_FOCUS: {
111                    cancelAutoFocus();
112                    break;
113                }
114            }
115        }
116    }
117
118    public FocusOverlayManager(Listener listener, Looper looper) {
119        mHandler = new MainHandler(looper);
120        mMatrix = new Matrix();
121        mListener = listener;
122    }
123
124    public void setFocusRenderer(PieRenderer renderer) {
125        mPieRenderer = renderer;
126        mInitialized = (mMatrix != null);
127    }
128
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
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
152    public void setMirror(boolean mirror) {
153        mMirror = mirror;
154        setMatrix();
155    }
156
157    public void setDisplayOrientation(int displayOrientation) {
158        mDisplayOrientation = displayOrientation;
159        setMatrix();
160    }
161
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
175    private void lockAeAwbIfNeeded() {
176        if (mLockAeAwbNeeded && !mAeAwbLock) {
177            mAeAwbLock = true;
178            mListener.setFocusParameters();
179        }
180    }
181
182    private void unlockAeAwbIfNeeded() {
183        if (mLockAeAwbNeeded && mAeAwbLock && (mState != STATE_FOCUSING_SNAP_ON_FINISH)) {
184            mAeAwbLock = false;
185            mListener.setFocusParameters();
186        }
187    }
188
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
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
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
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
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
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
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
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
373    public void onPreviewStarted() {
374        mState = STATE_IDLE;
375    }
376
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
384    public void onCameraReleased() {
385        onPreviewStopped();
386    }
387
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
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
409    private void capture() {
410        if (mListener.capture()) {
411            mState = STATE_IDLE;
412            mHandler.removeMessages(RESET_TOUCH_FOCUS);
413        }
414    }
415
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
442    public List getFocusAreas() {
443        return mFocusArea;
444    }
445
446    public List getMeteringAreas() {
447        return mMeteringArea;
448    }
449
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
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
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
503    /* package */ int getFocusState() {
504        return mState;
505    }
506
507    public boolean isFocusCompleted() {
508        return mState == STATE_SUCCESS || mState == STATE_FAIL;
509    }
510
511    public boolean isFocusingSnapOnFinish() {
512        return mState == STATE_FOCUSING_SNAP_ON_FINISH;
513    }
514
515    public void removeMessages() {
516        mHandler.removeMessages(RESET_TOUCH_FOCUS);
517    }
518
519    public void overrideFocusMode(String focusMode) {
520        mOverrideFocusMode = focusMode;
521    }
522
523    public void setAeAwbLock(boolean lock) {
524        mAeAwbLock = lock;
525    }
526
527    public boolean getAeAwbLock() {
528        return mAeAwbLock;
529    }
530
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
538    public static boolean isAutoExposureLockSupported(Parameters params) {
539        return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
540    }
541
542    public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
543        return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
544    }
545
546    public static boolean isSupported(String value, List<String> supported) {
547        return supported != null && supported.indexOf(value) >= 0;
548    }
549
550    public static boolean isMeteringAreaSupported(Parameters params) {
551        return params.getMaxNumMeteringAreas() > 0;
552    }
553
554    public static boolean isFocusAreaSupported(Parameters params) {
555        return (params.getMaxNumFocusAreas() > 0
556                && isSupported(Parameters.FOCUS_MODE_AUTO,
557                params.getSupportedFocusModes()));
558    }
559
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
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
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