1 /*
2  * Copyright (C) 2023 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.settings.biometrics2.ui.widget;
18 
19 import android.content.Context;
20 import android.graphics.Rect;
21 import android.graphics.RectF;
22 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.util.RotationUtils;
28 import android.view.DisplayInfo;
29 import android.view.Surface;
30 import android.view.ViewGroup;
31 import android.view.ViewTreeObserver;
32 import android.widget.FrameLayout;
33 import android.widget.ImageView;
34 import android.widget.RelativeLayout;
35 
36 import androidx.annotation.NonNull;
37 import androidx.annotation.Nullable;
38 
39 import com.android.settings.R;
40 import com.android.systemui.biometrics.UdfpsUtils;
41 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
42 
43 /**
44  * View corresponding with udfps_enroll_view.xml
45  */
46 public class UdfpsEnrollView extends FrameLayout {
47     private static final String TAG = "UdfpsEnrollView";
48     @NonNull
49     private final UdfpsEnrollDrawable mFingerprintDrawable;
50     @NonNull
51     private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable;
52     @NonNull
53     private final Handler mHandler;
54 
55     @NonNull
56     private ImageView mFingerprintProgressView;
57     private UdfpsUtils mUdfpsUtils;
58 
59     private int mProgressBarRadius;
60 
61     private Rect mSensorRect;
62     private UdfpsOverlayParams mOverlayParams;
63     private FingerprintSensorPropertiesInternal mSensorProperties;
64 
65     private int mTotalSteps = -1;
66     private int mRemainingSteps = -1;
67     private int mLocationsEnrolled = 0;
68     private int mCenterTouchCount = 0;
69 
UdfpsEnrollView(Context context, @Nullable AttributeSet attrs)70     public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
71         super(context, attrs);
72         mFingerprintDrawable = new UdfpsEnrollDrawable(mContext, attrs);
73         mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, attrs);
74         mHandler = new Handler(Looper.getMainLooper());
75         mUdfpsUtils = new UdfpsUtils();
76     }
77 
78     @Override
onFinishInflate()79     protected void onFinishInflate() {
80         ImageView fingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view);
81         fingerprintView.setImageDrawable(mFingerprintDrawable);
82         mFingerprintProgressView = findViewById(R.id.udfps_enroll_animation_fp_progress_view);
83         mFingerprintProgressView.setImageDrawable(mFingerprintProgressDrawable);
84     }
85 
86     /**
87      * Receive enroll progress information from FingerprintEnrollEnrollingUdfpsFragment
88      */
onEnrollmentProgress(int remaining, int totalSteps)89     public void onEnrollmentProgress(int remaining, int totalSteps) {
90         if (mTotalSteps == -1) {
91             mTotalSteps = totalSteps;
92         }
93         mRemainingSteps = remaining;
94         mHandler.post(() -> {
95             mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps);
96             mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
97         });
98     }
99 
100     /**
101      * Receive enroll help information from FingerprintEnrollEnrollingUdfpsFragment
102      */
onEnrollmentHelp()103     public void onEnrollmentHelp() {
104         mHandler.post(
105                 () -> mFingerprintProgressDrawable.onEnrollmentHelp(mRemainingSteps, mTotalSteps));
106     }
107 
108     /**
109      * Receive onAcquired from FingerprintEnrollEnrollingUdfpsFragment
110      */
onAcquired(boolean isAcquiredGood)111     public void onAcquired(boolean isAcquiredGood) {
112         final boolean animateIfLastStepGood =
113                 isAcquiredGood && (mRemainingSteps <= 2 && mRemainingSteps >= 0);
114         mHandler.post(() -> {
115             onFingerUp();
116             if (animateIfLastStepGood) mFingerprintProgressDrawable.onLastStepAcquired();
117         });
118     }
119 
120     /**
121      * Receive onPointerDown from FingerprintEnrollEnrollingUdfpsFragment
122      */
onPointerDown(int sensorId)123     public void onPointerDown(int sensorId) {
124         onFingerDown();
125     }
126 
127     /**
128      * Receive onPointerUp from FingerprintEnrollEnrollingUdfpsFragment
129      */
onPointerUp(int sensorId)130     public void onPointerUp(int sensorId) {
131         onFingerUp();
132     }
133 
134     private final ViewTreeObserver.OnDrawListener mOnDrawListener = this::updateOverlayParams;
135 
136     /**
137      * setup SensorProperties
138      */
setSensorProperties(FingerprintSensorPropertiesInternal properties)139     public void setSensorProperties(FingerprintSensorPropertiesInternal properties) {
140         mSensorProperties = properties;
141         ((ViewGroup) getParent()).getViewTreeObserver().addOnDrawListener(mOnDrawListener);
142     }
143 
144     @Override
onDetachedFromWindow()145     protected void onDetachedFromWindow() {
146         final ViewGroup parent = (ViewGroup) getParent();
147         if (parent != null) {
148             final ViewTreeObserver observer = parent.getViewTreeObserver();
149             if (observer != null) {
150                 observer.removeOnDrawListener(mOnDrawListener);
151             }
152         }
153         super.onDetachedFromWindow();
154     }
155 
onSensorRectUpdated()156     private void onSensorRectUpdated() {
157         updateDimensions();
158 
159         // Updates sensor rect in relation to the overlay view
160         mSensorRect.set(getPaddingX(), getPaddingY(),
161                 (mOverlayParams.getSensorBounds().width() + getPaddingX()),
162                 (mOverlayParams.getSensorBounds().height() + getPaddingY()));
163         mFingerprintDrawable.onSensorRectUpdated(new RectF(mSensorRect));
164     }
165 
updateDimensions()166     private void updateDimensions() {
167         // Original sensorBounds assume portrait mode.
168         final Rect rotatedBounds = new Rect(mOverlayParams.getSensorBounds());
169         int rotation = mOverlayParams.getRotation();
170         if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
171             RotationUtils.rotateBounds(
172                     rotatedBounds,
173                     mOverlayParams.getNaturalDisplayWidth(),
174                     mOverlayParams.getNaturalDisplayHeight(),
175                     rotation
176             );
177         }
178 
179         RelativeLayout parent = ((RelativeLayout) getParent());
180         if (parent == null) {
181             Log.e(TAG, "Fail to updateDimensions for " + this + ", parent null");
182             return;
183         }
184         final int[] coords = parent.getLocationOnScreen();
185         final int parentLeft = coords[0];
186         final int parentTop = coords[1];
187         final int parentRight = parentLeft + parent.getWidth();
188         final int parentBottom = parentTop + parent.getHeight();
189 
190 
191         RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(getWidth(),
192                 getHeight());
193         if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
194             params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
195             params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
196             params.rightMargin = parentRight - rotatedBounds.right - getPaddingX();
197             params.topMargin = rotatedBounds.top - parentTop - getPaddingY();
198         } else {
199             if (rotation == Surface.ROTATION_90) {
200                 params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
201                 params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
202                 params.rightMargin = parentRight - rotatedBounds.right - getPaddingX();
203                 params.bottomMargin = parentBottom - rotatedBounds.bottom - getPaddingY();
204             } else {
205                 params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
206                 params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
207                 params.bottomMargin = parentBottom - rotatedBounds.bottom - getPaddingY();
208                 params.leftMargin = rotatedBounds.left - parentLeft - getPaddingX();
209             }
210         }
211         params.height = rotatedBounds.height() + 2 * getPaddingX();
212         params.width = rotatedBounds.width() + 2 * getPaddingY();
213         setLayoutParams(params);
214     }
215 
onFingerDown()216     private void onFingerDown() {
217         mFingerprintDrawable.setShouldSkipDraw(true);
218         mFingerprintDrawable.invalidateSelf();
219     }
220 
onFingerUp()221     private void onFingerUp() {
222         mFingerprintDrawable.setShouldSkipDraw(false);
223         mFingerprintDrawable.invalidateSelf();
224     }
225 
getPaddingX()226     private int getPaddingX() {
227         return mProgressBarRadius;
228     }
229 
getPaddingY()230     private int getPaddingY() {
231         return mProgressBarRadius;
232     }
233 
updateOverlayParams()234     private void updateOverlayParams() {
235 
236         if (mSensorProperties == null) {
237             android.util.Log.e(TAG, "There is no sensor info!");
238             return;
239         }
240 
241         DisplayInfo displayInfo = new DisplayInfo();
242         if (getDisplay() == null) {
243             android.util.Log.e(TAG, "Can not get display");
244             return;
245         }
246         getDisplay().getDisplayInfo(displayInfo);
247         Rect udfpsBounds = mSensorProperties.getLocation().getRect();
248         float scaleFactor = mUdfpsUtils.getScaleFactor(displayInfo);
249         udfpsBounds.scale(scaleFactor);
250 
251         final Rect overlayBounds = new Rect(
252                 0, /* left */
253                 displayInfo.getNaturalHeight() / 2, /* top */
254                 displayInfo.getNaturalWidth(), /* right */
255                 displayInfo.getNaturalHeight() /* botom */);
256 
257         mOverlayParams = new UdfpsOverlayParams(
258                 udfpsBounds,
259                 overlayBounds,
260                 displayInfo.getNaturalWidth(),
261                 displayInfo.getNaturalHeight(),
262                 scaleFactor,
263                 displayInfo.rotation,
264                 mSensorProperties.sensorType);
265 
266         post(() -> {
267             mProgressBarRadius =
268                     (int) (mOverlayParams.getScaleFactor() * getContext().getResources().getInteger(
269                             R.integer.config_udfpsEnrollProgressBar));
270             mSensorRect = new Rect(mOverlayParams.getSensorBounds());
271 
272             onSensorRectUpdated();
273         });
274 
275     }
276 }
277 
278