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