• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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