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 17 package com.android.camera.ui; 18 19 import android.annotation.TargetApi; 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.graphics.Canvas; 23 import android.graphics.Matrix; 24 import android.graphics.Paint; 25 import android.graphics.Paint.Style; 26 import android.graphics.RectF; 27 import android.hardware.Camera.Face; 28 import android.os.Handler; 29 import android.os.Message; 30 import android.util.AttributeSet; 31 import android.util.Log; 32 import android.view.View; 33 34 import com.android.camera.CameraActivity; 35 import com.android.camera.CameraScreenNail; 36 import com.android.camera.R; 37 import com.android.camera.Util; 38 import com.android.gallery3d.common.ApiHelper; 39 40 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) 41 public 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