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