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