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