1/*
2 * Copyright (C) 2011 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 com.android.camera.ui.FaceView;
20import com.android.camera.ui.FocusIndicator;
21import com.android.camera.ui.FocusIndicatorView;
22
23import android.content.res.AssetFileDescriptor;
24import android.graphics.Matrix;
25import android.graphics.Rect;
26import android.graphics.RectF;
27import android.hardware.Camera.Area;
28import android.hardware.Camera.Parameters;
29import android.hardware.CameraSound;
30import android.os.Handler;
31import android.os.Message;
32import android.util.Log;
33import android.view.MotionEvent;
34import android.view.View;
35import android.view.ViewGroup;
36import android.widget.RelativeLayout;
37
38import java.util.ArrayList;
39import java.util.List;
40
41// A class that handles everything about focus in still picture mode.
42// This also handles the metering area because it is the same as focus area.
43public class FocusManager {
44    private static final String TAG = "FocusManager";
45
46    private static final int RESET_TOUCH_FOCUS = 0;
47    private static final int FOCUS_BEEP_VOLUME = 100;
48    private static final int RESET_TOUCH_FOCUS_DELAY = 3000;
49
50    private int mState = STATE_IDLE;
51    private static final int STATE_IDLE = 0; // Focus is not active.
52    private static final int STATE_FOCUSING = 1; // Focus is in progress.
53    // Focus is in progress and the camera should take a picture after focus finishes.
54    private static final int STATE_FOCUSING_SNAP_ON_FINISH = 2;
55    private static final int STATE_SUCCESS = 3; // Focus finishes and succeeds.
56    private static final int STATE_FAIL = 4; // Focus finishes and fails.
57
58    private boolean mInitialized;
59    private boolean mFocusAreaSupported;
60    private boolean mLockAeAwbNeeded;
61    private boolean mAeAwbLock;
62    private Matrix mMatrix;
63    private View mFocusIndicatorRotateLayout;
64    private FocusIndicatorView mFocusIndicator;
65    private View mPreviewFrame;
66    private FaceView mFaceView;
67    private List<Area> mFocusArea; // focus area in driver format
68    private List<Area> mMeteringArea; // metering area in driver format
69    private String mFocusMode;
70    private String[] mDefaultFocusModes;
71    private String mOverrideFocusMode;
72    private Parameters mParameters;
73    private ComboPreferences mPreferences;
74    private Handler mHandler;
75    Listener mListener;
76
77    public interface Listener {
78        public void autoFocus();
79        public void cancelAutoFocus();
80        public boolean capture();
81        public void startFaceDetection();
82        public void stopFaceDetection();
83        public void setFocusParameters();
84        public void playSound(int soundId);
85    }
86
87    private class MainHandler extends Handler {
88        @Override
89        public void handleMessage(Message msg) {
90            switch (msg.what) {
91                case RESET_TOUCH_FOCUS: {
92                    cancelAutoFocus();
93                    mListener.startFaceDetection();
94                    break;
95                }
96            }
97        }
98    }
99
100    public FocusManager(ComboPreferences preferences, String[] defaultFocusModes) {
101        mPreferences = preferences;
102        mDefaultFocusModes = defaultFocusModes;
103        mHandler = new MainHandler();
104        mMatrix = new Matrix();
105    }
106
107    // This has to be initialized before initialize().
108    public void initializeParameters(Parameters parameters) {
109        mParameters = parameters;
110        mFocusAreaSupported = (mParameters.getMaxNumFocusAreas() > 0
111                && isSupported(Parameters.FOCUS_MODE_AUTO,
112                        mParameters.getSupportedFocusModes()));
113        mLockAeAwbNeeded = (mParameters.isAutoExposureLockSupported() ||
114                mParameters.isAutoWhiteBalanceLockSupported());
115    }
116
117    public void initialize(View focusIndicatorRotate, View previewFrame,
118            FaceView faceView, Listener listener, boolean mirror, int displayOrientation) {
119        mFocusIndicatorRotateLayout = focusIndicatorRotate;
120        mFocusIndicator = (FocusIndicatorView) focusIndicatorRotate.findViewById(
121                R.id.focus_indicator);
122        mPreviewFrame = previewFrame;
123        mFaceView = faceView;
124        mListener = listener;
125
126        Matrix matrix = new Matrix();
127        Util.prepareMatrix(matrix, mirror, displayOrientation,
128                previewFrame.getWidth(), previewFrame.getHeight());
129        // In face detection, the matrix converts the driver coordinates to UI
130        // coordinates. In tap focus, the inverted matrix converts the UI
131        // coordinates to driver coordinates.
132        matrix.invert(mMatrix);
133
134        if (mParameters != null) {
135            mInitialized = true;
136        } else {
137            Log.e(TAG, "mParameters is not initialized.");
138        }
139    }
140
141    public void onShutterDown() {
142        if (!mInitialized) return;
143
144        // Lock AE and AWB so users can half-press shutter and recompose.
145        if (mLockAeAwbNeeded && !mAeAwbLock) {
146            mAeAwbLock = true;
147            mListener.setFocusParameters();
148        }
149
150        if (needAutoFocusCall()) {
151            // Do not focus if touch focus has been triggered.
152            if (mState != STATE_SUCCESS && mState != STATE_FAIL) {
153                autoFocus();
154            }
155        }
156    }
157
158    public void onShutterUp() {
159        if (!mInitialized) return;
160
161        if (needAutoFocusCall()) {
162            // User releases half-pressed focus key.
163            if (mState == STATE_FOCUSING || mState == STATE_SUCCESS
164                    || mState == STATE_FAIL) {
165                cancelAutoFocus();
166            }
167        }
168
169        // Unlock AE and AWB after cancelAutoFocus. Camera API does not
170        // guarantee setParameters can be called during autofocus.
171        if (mLockAeAwbNeeded && mAeAwbLock && (mState != STATE_FOCUSING_SNAP_ON_FINISH)) {
172            mAeAwbLock = false;
173            mListener.setFocusParameters();
174        }
175    }
176
177    public void doSnap() {
178        if (!mInitialized) return;
179
180        // If the user has half-pressed the shutter and focus is completed, we
181        // can take the photo right away. If the focus mode is infinity, we can
182        // also take the photo.
183        if (!needAutoFocusCall() || (mState == STATE_SUCCESS || mState == STATE_FAIL)) {
184            capture();
185        } else if (mState == STATE_FOCUSING) {
186            // Half pressing the shutter (i.e. the focus button event) will
187            // already have requested AF for us, so just request capture on
188            // focus here.
189            mState = STATE_FOCUSING_SNAP_ON_FINISH;
190        } else if (mState == STATE_IDLE) {
191            // We didn't do focus. This can happen if the user press focus key
192            // while the snapshot is still in progress. The user probably wants
193            // the next snapshot as soon as possible, so we just do a snapshot
194            // without focusing again.
195            capture();
196        }
197    }
198
199    public void onShutter() {
200        resetTouchFocus();
201        updateFocusUI();
202    }
203
204    public void onAutoFocus(boolean focused) {
205        if (mState == STATE_FOCUSING_SNAP_ON_FINISH) {
206            // Take the picture no matter focus succeeds or fails. No need
207            // to play the AF sound if we're about to play the shutter
208            // sound.
209            if (focused) {
210                mState = STATE_SUCCESS;
211            } else {
212                mState = STATE_FAIL;
213            }
214            updateFocusUI();
215            capture();
216        } else if (mState == STATE_FOCUSING) {
217            // This happens when (1) user is half-pressing the focus key or
218            // (2) touch focus is triggered. Play the focus tone. Do not
219            // take the picture now.
220            if (focused) {
221                mState = STATE_SUCCESS;
222                // Do not play the sound in continuous autofocus mode. It does
223                // not do a full scan. The focus callback arrives before doSnap
224                // so the state is always STATE_FOCUSING.
225                if (!Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.
226                        equals(mFocusMode)) {
227                    mListener.playSound(CameraSound.FOCUS_COMPLETE);
228                }
229            } else {
230                mState = STATE_FAIL;
231            }
232            updateFocusUI();
233            // If this is triggered by touch focus, cancel focus after a
234            // while.
235            if (mFocusArea != null) {
236                mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
237            }
238        } else if (mState == STATE_IDLE) {
239            // User has released the focus key before focus completes.
240            // Do nothing.
241        }
242    }
243
244    public boolean onTouch(MotionEvent e) {
245        if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) return false;
246
247        // Let users be able to cancel previous touch focus.
248        if ((mFocusArea != null) && (mState == STATE_FOCUSING ||
249                    mState == STATE_SUCCESS || mState == STATE_FAIL)) {
250            cancelAutoFocus();
251        }
252
253        // Initialize variables.
254        int x = Math.round(e.getX());
255        int y = Math.round(e.getY());
256        int focusWidth = mFocusIndicatorRotateLayout.getWidth();
257        int focusHeight = mFocusIndicatorRotateLayout.getHeight();
258        int previewWidth = mPreviewFrame.getWidth();
259        int previewHeight = mPreviewFrame.getHeight();
260        if (mFocusArea == null) {
261            mFocusArea = new ArrayList<Area>();
262            mFocusArea.add(new Area(new Rect(), 1));
263            mMeteringArea = new ArrayList<Area>();
264            mMeteringArea.add(new Area(new Rect(), 1));
265        }
266
267        // Convert the coordinates to driver format.
268        // AE area is bigger because exposure is sensitive and
269        // easy to over- or underexposure if area is too small.
270        calculateTapArea(focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight,
271                mFocusArea.get(0).rect);
272        calculateTapArea(focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight,
273                mMeteringArea.get(0).rect);
274
275        // Use margin to set the focus indicator to the touched area.
276        RelativeLayout.LayoutParams p =
277                (RelativeLayout.LayoutParams) mFocusIndicatorRotateLayout.getLayoutParams();
278        int left = Util.clamp(x - focusWidth / 2, 0, previewWidth - focusWidth);
279        int top = Util.clamp(y - focusHeight / 2, 0, previewHeight - focusHeight);
280        p.setMargins(left, top, 0, 0);
281        // Disable "center" rule because we no longer want to put it in the center.
282        int[] rules = p.getRules();
283        rules[RelativeLayout.CENTER_IN_PARENT] = 0;
284        mFocusIndicatorRotateLayout.requestLayout();
285
286        // Stop face detection because we want to specify focus and metering area.
287        mListener.stopFaceDetection();
288
289        // Set the focus area and metering area.
290        mListener.setFocusParameters();
291        if (mFocusAreaSupported && (e.getAction() == MotionEvent.ACTION_UP)) {
292            autoFocus();
293        } else {  // Just show the indicator in all other cases.
294            updateFocusUI();
295            // Reset the metering area in 3 seconds.
296            mHandler.removeMessages(RESET_TOUCH_FOCUS);
297            mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
298        }
299
300        return true;
301    }
302
303    public void onPreviewStarted() {
304        mState = STATE_IDLE;
305    }
306
307    public void onPreviewStopped() {
308        mState = STATE_IDLE;
309        resetTouchFocus();
310        // If auto focus was in progress, it would have been canceled.
311        updateFocusUI();
312    }
313
314    public void onCameraReleased() {
315        onPreviewStopped();
316    }
317
318    private void autoFocus() {
319        Log.v(TAG, "Start autofocus.");
320        mListener.autoFocus();
321        mState = STATE_FOCUSING;
322        // Pause the face view because the driver will keep sending face
323        // callbacks after the focus completes.
324        if (mFaceView != null) mFaceView.pause();
325        updateFocusUI();
326        mHandler.removeMessages(RESET_TOUCH_FOCUS);
327    }
328
329    private void cancelAutoFocus() {
330        Log.v(TAG, "Cancel autofocus.");
331
332        // Reset the tap area before calling mListener.cancelAutofocus.
333        // Otherwise, focus mode stays at auto and the tap area passed to the
334        // driver is not reset.
335        resetTouchFocus();
336        mListener.cancelAutoFocus();
337        if (mFaceView != null) mFaceView.resume();
338        mState = STATE_IDLE;
339        updateFocusUI();
340        mHandler.removeMessages(RESET_TOUCH_FOCUS);
341    }
342
343    private void capture() {
344        if (mListener.capture()) {
345            mState = STATE_IDLE;
346            mHandler.removeMessages(RESET_TOUCH_FOCUS);
347        }
348    }
349
350    // This can only be called after mParameters is initialized.
351    public String getFocusMode() {
352        if (mOverrideFocusMode != null) return mOverrideFocusMode;
353        List<String> supportedFocusModes = mParameters.getSupportedFocusModes();
354
355        if (mFocusAreaSupported && mFocusArea != null) {
356            // Always use autofocus in tap-to-focus.
357            mFocusMode = Parameters.FOCUS_MODE_AUTO;
358        } else {
359            // The default is continuous autofocus.
360            mFocusMode = mPreferences.getString(
361                    CameraSettings.KEY_FOCUS_MODE, null);
362
363            // Try to find a supported focus mode from the default list.
364            if (mFocusMode == null) {
365                for (int i = 0; i < mDefaultFocusModes.length; i++) {
366                    String mode = mDefaultFocusModes[i];
367                    if (isSupported(mode, supportedFocusModes)) {
368                        mFocusMode = mode;
369                        break;
370                    }
371                }
372            }
373        }
374        if (!isSupported(mFocusMode, supportedFocusModes)) {
375            // For some reasons, the driver does not support the current
376            // focus mode. Fall back to auto.
377            if (isSupported(Parameters.FOCUS_MODE_AUTO,
378                    mParameters.getSupportedFocusModes())) {
379                mFocusMode = Parameters.FOCUS_MODE_AUTO;
380            } else {
381                mFocusMode = mParameters.getFocusMode();
382            }
383        }
384        return mFocusMode;
385    }
386
387    public List<Area> getFocusAreas() {
388        return mFocusArea;
389    }
390
391    public List<Area> getMeteringAreas() {
392        return mMeteringArea;
393    }
394
395    public void updateFocusUI() {
396        if (!mInitialized) return;
397
398        // Set the length of focus indicator according to preview frame size.
399        int len = Math.min(mPreviewFrame.getWidth(), mPreviewFrame.getHeight()) / 4;
400        ViewGroup.LayoutParams layout = mFocusIndicator.getLayoutParams();
401        layout.width = len;
402        layout.height = len;
403
404        // Show only focus indicator or face indicator.
405        boolean faceExists = (mFaceView != null && mFaceView.faceExists());
406        FocusIndicator focusIndicator = (faceExists) ? mFaceView : mFocusIndicator;
407
408        if (mState == STATE_IDLE) {
409            if (mFocusArea == null) {
410                focusIndicator.clear();
411            } else {
412                // Users touch on the preview and the indicator represents the
413                // metering area. Either focus area is not supported or
414                // autoFocus call is not required.
415                focusIndicator.showStart();
416            }
417        } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
418            focusIndicator.showStart();
419        } else {
420            // In CAF, do not show success or failure because it only returns
421            // the focus status. It does not do a full scan. So the result is
422            // failure most of the time.
423            if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusMode)) {
424                focusIndicator.showStart();
425            } else if (mState == STATE_SUCCESS) {
426                focusIndicator.showSuccess();
427            } else if (mState == STATE_FAIL) {
428                focusIndicator.showFail();
429            }
430        }
431    }
432
433    public void resetTouchFocus() {
434        if (!mInitialized) return;
435
436        // Put focus indicator to the center.
437        RelativeLayout.LayoutParams p =
438                (RelativeLayout.LayoutParams) mFocusIndicatorRotateLayout.getLayoutParams();
439        int[] rules = p.getRules();
440        rules[RelativeLayout.CENTER_IN_PARENT] = RelativeLayout.TRUE;
441        p.setMargins(0, 0, 0, 0);
442
443        mFocusArea = null;
444        mMeteringArea = null;
445    }
446
447    public void calculateTapArea(int focusWidth, int focusHeight, float areaMultiple,
448            int x, int y, int previewWidth, int previewHeight, Rect rect) {
449        int areaWidth = (int)(focusWidth * areaMultiple);
450        int areaHeight = (int)(focusHeight * areaMultiple);
451        int left = Util.clamp(x - areaWidth / 2, 0, previewWidth - areaWidth);
452        int top = Util.clamp(y - areaHeight / 2, 0, previewHeight - areaHeight);
453
454        RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight);
455        mMatrix.mapRect(rectF);
456        Util.rectFToRect(rectF, rect);
457    }
458
459    public boolean isFocusCompleted() {
460        return mState == STATE_SUCCESS || mState == STATE_FAIL;
461    }
462
463    public boolean isFocusingSnapOnFinish() {
464        return mState == STATE_FOCUSING_SNAP_ON_FINISH;
465    }
466
467    public void removeMessages() {
468        mHandler.removeMessages(RESET_TOUCH_FOCUS);
469    }
470
471    public void overrideFocusMode(String focusMode) {
472        mOverrideFocusMode = focusMode;
473    }
474
475    public void setAeAwbLock(boolean lock) {
476        mAeAwbLock = lock;
477    }
478
479    public boolean getAeAwbLock() {
480        return mAeAwbLock;
481    }
482
483    private static boolean isSupported(String value, List<String> supported) {
484        return supported == null ? false : supported.indexOf(value) >= 0;
485    }
486
487    private boolean needAutoFocusCall() {
488        String focusMode = getFocusMode();
489        return !(focusMode.equals(Parameters.FOCUS_MODE_INFINITY)
490                || focusMode.equals(Parameters.FOCUS_MODE_FIXED)
491                || focusMode.equals(Parameters.FOCUS_MODE_EDOF));
492    }
493}
494