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