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