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