FaceView.java revision 8dbb2a65ef1b0404c169e8fada6c0d0f154dd08f
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.ui;
18
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.content.res.Resources;
22import android.graphics.Canvas;
23import android.graphics.Color;
24import android.graphics.Matrix;
25import android.graphics.Paint;
26import android.graphics.Paint.Style;
27import android.graphics.RectF;
28import android.hardware.Camera.Face;
29import android.os.Handler;
30import android.os.Message;
31import android.util.AttributeSet;
32import android.util.Log;
33import android.view.View;
34
35import com.android.camera.R;
36import com.android.camera.Util;
37import com.android.gallery3d.common.ApiHelper;
38
39@TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
40public class FaceView extends View implements FocusIndicator, Rotatable {
41    private static final String TAG = "FaceView";
42    private final boolean LOGV = false;
43    // The value for android.hardware.Camera.setDisplayOrientation.
44    private int mDisplayOrientation;
45    // The orientation compensation for the face indicator to make it look
46    // correctly in all device orientations. Ex: if the value is 90, the
47    // indicator should be rotated 90 degrees counter-clockwise.
48    private int mOrientation;
49    private boolean mMirror;
50    private boolean mPause;
51    private Matrix mMatrix = new Matrix();
52    private RectF mRect = new RectF();
53    // As face detection can be flaky, we add a layer of filtering on top of it
54    // to avoid rapid changes in state (eg, flickering between has faces and
55    // not having faces)
56    private Face[] mFaces;
57    private Face[] mPendingFaces;
58    private int mColor;
59    private final int mFocusingColor;
60    private final int mFocusedColor;
61    private final int mFailColor;
62    private Paint mPaint;
63    private volatile boolean mBlocked;
64
65    private static final int MSG_SWITCH_FACES = 1;
66    private static final int SWITCH_DELAY = 70;
67    private boolean mStateSwitchPending = false;
68    private Handler mHandler = new Handler() {
69        @Override
70        public void handleMessage(Message msg) {
71            switch (msg.what) {
72            case MSG_SWITCH_FACES:
73                mStateSwitchPending = false;
74                mFaces = mPendingFaces;
75                invalidate();
76                break;
77            }
78        }
79    };
80
81    public FaceView(Context context, AttributeSet attrs) {
82        super(context, attrs);
83        Resources res = getResources();
84        mFocusingColor = res.getColor(R.color.face_detect_start);
85        mFocusedColor = res.getColor(R.color.face_detect_success);
86        mFailColor = res.getColor(R.color.face_detect_fail);
87        mColor = mFocusingColor;
88        mPaint = new Paint();
89        mPaint.setAntiAlias(true);
90        mPaint.setStyle(Style.STROKE);
91        mPaint.setStrokeWidth(res.getDimension(R.dimen.focus_outer_stroke));
92    }
93
94    public void setFaces(Face[] faces) {
95        if (LOGV) Log.v(TAG, "Num of faces=" + faces.length);
96        if (mPause) return;
97        if (mFaces != null) {
98            if ((faces.length > 0 && mFaces.length == 0)
99                    || (faces.length == 0 && mFaces.length > 0)) {
100                mPendingFaces = faces;
101                if (!mStateSwitchPending) {
102                    mStateSwitchPending = true;
103                    mHandler.sendEmptyMessageDelayed(MSG_SWITCH_FACES, SWITCH_DELAY);
104                }
105                return;
106            }
107        }
108        if (mStateSwitchPending) {
109            mStateSwitchPending = false;
110            mHandler.removeMessages(MSG_SWITCH_FACES);
111        }
112        mFaces = faces;
113        invalidate();
114    }
115
116    public void setDisplayOrientation(int orientation) {
117        mDisplayOrientation = orientation;
118        if (LOGV) Log.v(TAG, "mDisplayOrientation=" + orientation);
119    }
120
121    @Override
122    public void setOrientation(int orientation, boolean animation) {
123        mOrientation = orientation;
124        invalidate();
125    }
126
127    public void setMirror(boolean mirror) {
128        mMirror = mirror;
129        if (LOGV) Log.v(TAG, "mMirror=" + mirror);
130    }
131
132    public boolean faceExists() {
133        return (mFaces != null && mFaces.length > 0);
134    }
135
136    @Override
137    public void showStart() {
138        mColor = mFocusingColor;
139        invalidate();
140    }
141
142    // Ignore the parameter. No autofocus animation for face detection.
143    @Override
144    public void showSuccess(boolean timeout) {
145        mColor = mFocusedColor;
146        invalidate();
147    }
148
149    // Ignore the parameter. No autofocus animation for face detection.
150    @Override
151    public void showFail(boolean timeout) {
152        mColor = mFailColor;
153        invalidate();
154    }
155
156    @Override
157    public void clear() {
158        // Face indicator is displayed during preview. Do not clear the
159        // drawable.
160        mColor = mFocusingColor;
161        mFaces = null;
162        invalidate();
163    }
164
165    public void pause() {
166        mPause = true;
167    }
168
169    public void resume() {
170        mPause = false;
171    }
172
173    public void setBlockDraw(boolean block) {
174        mBlocked = block;
175    }
176
177    @Override
178    protected void onDraw(Canvas canvas) {
179        if (!mBlocked && (mFaces != null) && (mFaces.length > 0)) {
180            // Prepare the matrix.
181            Util.prepareMatrix(mMatrix, mMirror, mDisplayOrientation, getWidth(), getHeight());
182
183            // Focus indicator is directional. Rotate the matrix and the canvas
184            // so it looks correctly in all orientations.
185            canvas.save();
186            mMatrix.postRotate(mOrientation); // postRotate is clockwise
187            canvas.rotate(-mOrientation); // rotate is counter-clockwise (for canvas)
188            for (int i = 0; i < mFaces.length; i++) {
189                // Filter out false positives.
190                if (mFaces[i].score < 50) continue;
191
192                // Transform the coordinates.
193                mRect.set(mFaces[i].rect);
194                if (LOGV) Util.dumpRect(mRect, "Original rect");
195                mMatrix.mapRect(mRect);
196                if (LOGV) Util.dumpRect(mRect, "Transformed rect");
197                mPaint.setColor(mColor);
198                canvas.drawOval(mRect, mPaint);
199            }
200            canvas.restore();
201        }
202        super.onDraw(canvas);
203    }
204}
205