1 /*
2  * Copyright (C) 2022 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.biometrics.fingerprint;
18 
19 import android.content.Context;
20 import android.graphics.Rect;
21 import android.graphics.RectF;
22 import android.hardware.fingerprint.FingerprintSensorProperties;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.util.AttributeSet;
26 import android.util.RotationUtils;
27 import android.view.Gravity;
28 import android.view.Surface;
29 import android.view.ViewGroup;
30 import android.widget.FrameLayout;
31 import android.widget.ImageView;
32 
33 import androidx.annotation.NonNull;
34 import androidx.annotation.Nullable;
35 
36 import com.android.settings.R;
37 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
38 
39 /**
40  * View corresponding with udfps_enroll_view.xml
41  */
42 public class UdfpsEnrollView extends FrameLayout implements UdfpsEnrollHelper.Listener {
43     private static final String TAG = "UdfpsEnrollView";
44     @NonNull
45     private final UdfpsEnrollDrawable mFingerprintDrawable;
46     @NonNull
47     private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable;
48     @NonNull
49     private final Handler mHandler;
50 
51     @NonNull
52     private ImageView mFingerprintProgressView;
53 
54     private int mProgressBarRadius;
55 
56     // sensorRect may be bigger than the sensor. True sensor dimensions are defined in
57     // overlayParams.sensorBounds
58     private Rect mSensorRect;
59     private UdfpsOverlayParams mOverlayParams;
60 
UdfpsEnrollView(Context context, @Nullable AttributeSet attrs)61     public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
62         super(context, attrs);
63         mFingerprintDrawable = new UdfpsEnrollDrawable(mContext, attrs);
64         mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, attrs);
65         mHandler = new Handler(Looper.getMainLooper());
66     }
67 
68     @Override
onFinishInflate()69     protected void onFinishInflate() {
70         ImageView fingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view);
71         fingerprintView.setImageDrawable(mFingerprintDrawable);
72         mFingerprintProgressView = findViewById(R.id.udfps_enroll_animation_fp_progress_view);
73         mFingerprintProgressView.setImageDrawable(mFingerprintProgressDrawable);
74     }
75 
76     // Implements UdfpsEnrollHelper.Listener
77     @Override
onEnrollmentProgress(int remaining, int totalSteps)78     public void onEnrollmentProgress(int remaining, int totalSteps) {
79         mHandler.post(() -> {
80             mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps);
81             mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
82         });
83     }
84 
85     @Override
onEnrollmentHelp(int remaining, int totalSteps)86     public void onEnrollmentHelp(int remaining, int totalSteps) {
87         mHandler.post(() -> mFingerprintProgressDrawable.onEnrollmentHelp(remaining, totalSteps));
88     }
89 
90     @Override
onAcquired(boolean animateIfLastStepGood)91     public void onAcquired(boolean animateIfLastStepGood) {
92         mHandler.post(() -> {
93             onFingerUp();
94             if (animateIfLastStepGood) mFingerprintProgressDrawable.onLastStepAcquired();
95         });
96     }
97 
98     @Override
onPointerDown(int sensorId)99     public void onPointerDown(int sensorId) {
100         onFingerDown();
101     }
102 
103     @Override
onPointerUp(int sensorId)104     public void onPointerUp(int sensorId) {
105         onFingerUp();
106     }
107 
getOverlayParams()108     public UdfpsOverlayParams getOverlayParams() {
109         return mOverlayParams;
110     }
111 
112     /**
113      * Set UdfpsOverlayParams
114      */
setOverlayParams(UdfpsOverlayParams params)115     public void setOverlayParams(UdfpsOverlayParams params) {
116         mOverlayParams = params;
117 
118         post(() -> {
119             mProgressBarRadius =
120                     (int) (mOverlayParams.getScaleFactor() * getContext().getResources().getInteger(
121                             R.integer.config_udfpsEnrollProgressBar));
122             mSensorRect = new Rect(mOverlayParams.getSensorBounds());
123 
124             onSensorRectUpdated();
125         });
126     }
127 
128     /**
129      * Set UdfpsEnrollHelper
130      */
setEnrollHelper(UdfpsEnrollHelper enrollHelper)131     public void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
132         mFingerprintDrawable.setEnrollHelper(enrollHelper);
133         enrollHelper.setListener(this);
134     }
135 
onSensorRectUpdated()136     private void onSensorRectUpdated() {
137         updateDimensions();
138 
139         // Updates sensor rect in relation to the overlay view
140         mSensorRect.set(getPaddingX(), getPaddingY(),
141                 (mOverlayParams.getSensorBounds().width() + getPaddingX()),
142                 (mOverlayParams.getSensorBounds().height() + getPaddingY()));
143         mFingerprintDrawable.onSensorRectUpdated(new RectF(mSensorRect));
144     }
145 
updateDimensions()146     private void updateDimensions() {
147         // Original sensorBounds assume portrait mode.
148         final Rect rotatedBounds = new Rect(mOverlayParams.getSensorBounds());
149         int rotation = mOverlayParams.getRotation();
150         if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
151             RotationUtils.rotateBounds(
152                     rotatedBounds,
153                     mOverlayParams.getNaturalDisplayWidth(),
154                     mOverlayParams.getNaturalDisplayHeight(),
155                     rotation
156             );
157         }
158 
159         // Use parent view's and rotatedBound's absolute coordinates to decide the margins of
160         // UdfpsEnrollView, so that its center keeps consistent with sensor rect's.
161         ViewGroup parentView = (ViewGroup) getParent();
162         MarginLayoutParams marginLayoutParams = (MarginLayoutParams) getLayoutParams();
163         FrameLayout.LayoutParams params = (LayoutParams) getLayoutParams();
164         if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
165             final int[] coords = parentView.getLocationOnScreen();
166             final int parentLeft = coords[0];
167             final int parentTop = coords[1];
168             final int parentRight = parentLeft + parentView.getWidth();
169             params.gravity = Gravity.RIGHT | Gravity.TOP;
170             final int rightMargin = parentRight - rotatedBounds.right - getPaddingX();
171             final int topMargin = rotatedBounds.top - parentTop - getPaddingY();
172             if (marginLayoutParams.rightMargin == rightMargin
173                     && marginLayoutParams.topMargin == topMargin) {
174                 return;
175             }
176             marginLayoutParams.rightMargin = rightMargin;
177             marginLayoutParams.topMargin = topMargin;
178             setLayoutParams(params);
179         } else {
180             final int[] coords = parentView.getLocationOnScreen();
181             final int parentLeft = coords[0];
182             final int parentTop = coords[1];
183             final int parentRight = parentLeft + parentView.getWidth();
184             final int parentBottom = parentTop + parentView.getHeight();
185             if (rotation == Surface.ROTATION_90) {
186                 params.gravity = Gravity.RIGHT | Gravity.BOTTOM;
187                 marginLayoutParams.rightMargin = parentRight - rotatedBounds.right - getPaddingX();
188                 marginLayoutParams.bottomMargin =
189                         parentBottom - rotatedBounds.bottom - getPaddingY();
190             } else if (rotation == Surface.ROTATION_270) {
191                 params.gravity = Gravity.LEFT | Gravity.BOTTOM;
192                 marginLayoutParams.leftMargin = rotatedBounds.left - parentLeft - getPaddingX();
193                 marginLayoutParams.bottomMargin =
194                         parentBottom - rotatedBounds.bottom - getPaddingY();
195             }
196         }
197 
198         params.height = rotatedBounds.height() + 2 * getPaddingX();
199         params.width = rotatedBounds.width() + 2 * getPaddingY();
200         setLayoutParams(params);
201 
202 
203     }
204 
onFingerDown()205     private void onFingerDown() {
206         if (mOverlayParams.getSensorType() == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
207             mFingerprintDrawable.setShouldSkipDraw(true);
208         }
209         mFingerprintDrawable.invalidateSelf();
210     }
211 
onFingerUp()212     private void onFingerUp() {
213         mFingerprintDrawable.setShouldSkipDraw(false);
214         mFingerprintDrawable.invalidateSelf();
215     }
216 
getPaddingX()217     private int getPaddingX() {
218         return mProgressBarRadius;
219     }
220 
getPaddingY()221     private int getPaddingY() {
222         return mProgressBarRadius;
223     }
224 }
225