1 /* 2 * Copyright (C) 2020 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.systemui.biometrics; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.graphics.Canvas; 24 import android.graphics.Color; 25 import android.graphics.Paint; 26 import android.graphics.PointF; 27 import android.graphics.RectF; 28 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; 29 import android.os.Build; 30 import android.os.UserHandle; 31 import android.provider.Settings; 32 import android.text.TextUtils; 33 import android.util.AttributeSet; 34 import android.util.Log; 35 import android.view.MotionEvent; 36 import android.view.Surface; 37 import android.view.View; 38 import android.widget.FrameLayout; 39 40 import com.android.systemui.R; 41 import com.android.systemui.biometrics.UdfpsHbmTypes.HbmType; 42 import com.android.systemui.doze.DozeReceiver; 43 44 /** 45 * A view containing 1) A SurfaceView for HBM, and 2) A normal drawable view for all other 46 * animations. 47 */ 48 public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIlluminator { 49 private static final String TAG = "UdfpsView"; 50 51 private static final String SETTING_HBM_TYPE = 52 "com.android.systemui.biometrics.UdfpsSurfaceView.hbmType"; 53 private static final @HbmType int DEFAULT_HBM_TYPE = UdfpsHbmTypes.LOCAL_HBM; 54 55 private static final int DEBUG_TEXT_SIZE_PX = 32; 56 57 @NonNull private final RectF mSensorRect; 58 @NonNull private final Paint mDebugTextPaint; 59 private final float mSensorTouchAreaCoefficient; 60 private final int mOnIlluminatedDelayMs; 61 private final @HbmType int mHbmType; 62 63 // Only used for UdfpsHbmTypes.GLOBAL_HBM. 64 @Nullable private UdfpsSurfaceView mGhbmView; 65 // Can be different for enrollment, BiometricPrompt, Keyguard, etc. 66 @Nullable private UdfpsAnimationViewController mAnimationViewController; 67 // Used to obtain the sensor location. 68 @NonNull private FingerprintSensorPropertiesInternal mSensorProps; 69 @Nullable private UdfpsHbmProvider mHbmProvider; 70 @Nullable private String mDebugMessage; 71 private boolean mIlluminationRequested; 72 UdfpsView(Context context, AttributeSet attrs)73 public UdfpsView(Context context, AttributeSet attrs) { 74 super(context, attrs); 75 76 TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.UdfpsView, 0, 77 0); 78 try { 79 if (!a.hasValue(R.styleable.UdfpsView_sensorTouchAreaCoefficient)) { 80 throw new IllegalArgumentException( 81 "UdfpsView must contain sensorTouchAreaCoefficient"); 82 } 83 mSensorTouchAreaCoefficient = a.getFloat( 84 R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f); 85 } finally { 86 a.recycle(); 87 } 88 89 mSensorRect = new RectF(); 90 91 mDebugTextPaint = new Paint(); 92 mDebugTextPaint.setAntiAlias(true); 93 mDebugTextPaint.setColor(Color.BLUE); 94 mDebugTextPaint.setTextSize(DEBUG_TEXT_SIZE_PX); 95 96 mOnIlluminatedDelayMs = mContext.getResources().getInteger( 97 com.android.internal.R.integer.config_udfps_illumination_transition_ms); 98 99 if (Build.IS_ENG || Build.IS_USERDEBUG) { 100 mHbmType = Settings.Secure.getIntForUser(mContext.getContentResolver(), 101 SETTING_HBM_TYPE, DEFAULT_HBM_TYPE, UserHandle.USER_CURRENT); 102 } else { 103 mHbmType = DEFAULT_HBM_TYPE; 104 } 105 } 106 107 // Don't propagate any touch events to the child views. 108 @Override onInterceptTouchEvent(MotionEvent ev)109 public boolean onInterceptTouchEvent(MotionEvent ev) { 110 return mAnimationViewController == null 111 || !mAnimationViewController.shouldPauseAuth(); 112 } 113 114 @Override onFinishInflate()115 protected void onFinishInflate() { 116 if (mHbmType == UdfpsHbmTypes.GLOBAL_HBM) { 117 mGhbmView = findViewById(R.id.hbm_view); 118 } 119 } 120 setSensorProperties(@onNull FingerprintSensorPropertiesInternal properties)121 void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) { 122 mSensorProps = properties; 123 } 124 125 @Override setHbmProvider(@ullable UdfpsHbmProvider hbmProvider)126 public void setHbmProvider(@Nullable UdfpsHbmProvider hbmProvider) { 127 mHbmProvider = hbmProvider; 128 } 129 130 @Override dozeTimeTick()131 public void dozeTimeTick() { 132 if (mAnimationViewController != null) { 133 mAnimationViewController.dozeTimeTick(); 134 } 135 } 136 137 @Override onLayout(boolean changed, int left, int top, int right, int bottom)138 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 139 super.onLayout(changed, left, top, right, bottom); 140 int paddingX = mAnimationViewController == null ? 0 141 : mAnimationViewController.getPaddingX(); 142 int paddingY = mAnimationViewController == null ? 0 143 : mAnimationViewController.getPaddingY(); 144 mSensorRect.set( 145 paddingX, 146 paddingY, 147 2 * mSensorProps.sensorRadius + paddingX, 148 2 * mSensorProps.sensorRadius + paddingY); 149 150 if (mAnimationViewController != null) { 151 mAnimationViewController.onSensorRectUpdated(new RectF(mSensorRect)); 152 } 153 } 154 onTouchOutsideView()155 void onTouchOutsideView() { 156 if (mAnimationViewController != null) { 157 mAnimationViewController.onTouchOutsideView(); 158 } 159 } 160 setAnimationViewController( @ullable UdfpsAnimationViewController animationViewController)161 void setAnimationViewController( 162 @Nullable UdfpsAnimationViewController animationViewController) { 163 mAnimationViewController = animationViewController; 164 } 165 getAnimationViewController()166 @Nullable UdfpsAnimationViewController getAnimationViewController() { 167 return mAnimationViewController; 168 } 169 170 @Override onAttachedToWindow()171 protected void onAttachedToWindow() { 172 super.onAttachedToWindow(); 173 Log.v(TAG, "onAttachedToWindow"); 174 } 175 176 @Override onDetachedFromWindow()177 protected void onDetachedFromWindow() { 178 super.onDetachedFromWindow(); 179 Log.v(TAG, "onDetachedFromWindow"); 180 } 181 182 @Override onDraw(Canvas canvas)183 protected void onDraw(Canvas canvas) { 184 super.onDraw(canvas); 185 if (!mIlluminationRequested) { 186 if (!TextUtils.isEmpty(mDebugMessage)) { 187 canvas.drawText(mDebugMessage, 0, 160, mDebugTextPaint); 188 } 189 } 190 } 191 setDebugMessage(String message)192 void setDebugMessage(String message) { 193 mDebugMessage = message; 194 postInvalidate(); 195 } 196 isWithinSensorArea(float x, float y)197 boolean isWithinSensorArea(float x, float y) { 198 // The X and Y coordinates of the sensor's center. 199 final PointF translation = mAnimationViewController == null 200 ? new PointF(0, 0) 201 : mAnimationViewController.getTouchTranslation(); 202 final float cx = mSensorRect.centerX() + translation.x; 203 final float cy = mSensorRect.centerY() + translation.y; 204 // Radii along the X and Y axes. 205 final float rx = (mSensorRect.right - mSensorRect.left) / 2.0f; 206 final float ry = (mSensorRect.bottom - mSensorRect.top) / 2.0f; 207 208 return x > (cx - rx * mSensorTouchAreaCoefficient) 209 && x < (cx + rx * mSensorTouchAreaCoefficient) 210 && y > (cy - ry * mSensorTouchAreaCoefficient) 211 && y < (cy + ry * mSensorTouchAreaCoefficient) 212 && !mAnimationViewController.shouldPauseAuth(); 213 } 214 isIlluminationRequested()215 boolean isIlluminationRequested() { 216 return mIlluminationRequested; 217 } 218 219 /** 220 * @param onIlluminatedRunnable Runs when the first illumination frame reaches the panel. 221 */ 222 @Override startIllumination(@ullable Runnable onIlluminatedRunnable)223 public void startIllumination(@Nullable Runnable onIlluminatedRunnable) { 224 mIlluminationRequested = true; 225 if (mAnimationViewController != null) { 226 mAnimationViewController.onIlluminationStarting(); 227 } 228 229 if (mGhbmView != null) { 230 mGhbmView.setGhbmIlluminationListener(this::doIlluminate); 231 mGhbmView.setVisibility(View.VISIBLE); 232 mGhbmView.startGhbmIllumination(onIlluminatedRunnable); 233 } else { 234 doIlluminate(null /* surface */, onIlluminatedRunnable); 235 } 236 } 237 doIlluminate(@ullable Surface surface, @Nullable Runnable onIlluminatedRunnable)238 private void doIlluminate(@Nullable Surface surface, @Nullable Runnable onIlluminatedRunnable) { 239 if (mGhbmView != null && surface == null) { 240 Log.e(TAG, "doIlluminate | surface must be non-null for GHBM"); 241 } 242 mHbmProvider.enableHbm(mHbmType, surface, () -> { 243 if (mGhbmView != null) { 244 mGhbmView.drawIlluminationDot(mSensorRect); 245 } 246 if (onIlluminatedRunnable != null) { 247 // No framework API can reliably tell when a frame reaches the panel. A timeout 248 // is the safest solution. 249 postDelayed(onIlluminatedRunnable, mOnIlluminatedDelayMs); 250 } else { 251 Log.w(TAG, "doIlluminate | onIlluminatedRunnable is null"); 252 } 253 }); 254 } 255 256 @Override stopIllumination()257 public void stopIllumination() { 258 mIlluminationRequested = false; 259 if (mAnimationViewController != null) { 260 mAnimationViewController.onIlluminationStopped(); 261 } 262 if (mGhbmView != null) { 263 mGhbmView.setGhbmIlluminationListener(null); 264 mGhbmView.setVisibility(View.INVISIBLE); 265 } 266 mHbmProvider.disableHbm(null /* onHbmDisabled */); 267 } 268 } 269