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