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