/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.biometrics2.ui.widget; import android.content.Context; import android.graphics.Rect; import android.graphics.RectF; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; import android.util.Log; import android.util.RotationUtils; import android.view.DisplayInfo; import android.view.Surface; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RelativeLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settings.R; import com.android.systemui.biometrics.UdfpsUtils; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; /** * View corresponding with udfps_enroll_view.xml */ public class UdfpsEnrollView extends FrameLayout { private static final String TAG = "UdfpsEnrollView"; @NonNull private final UdfpsEnrollDrawable mFingerprintDrawable; @NonNull private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable; @NonNull private final Handler mHandler; @NonNull private ImageView mFingerprintProgressView; private UdfpsUtils mUdfpsUtils; private int mProgressBarRadius; private Rect mSensorRect; private UdfpsOverlayParams mOverlayParams; private FingerprintSensorPropertiesInternal mSensorProperties; private int mTotalSteps = -1; private int mRemainingSteps = -1; private int mLocationsEnrolled = 0; private int mCenterTouchCount = 0; public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); mFingerprintDrawable = new UdfpsEnrollDrawable(mContext, attrs); mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, attrs); mHandler = new Handler(Looper.getMainLooper()); mUdfpsUtils = new UdfpsUtils(); } @Override protected void onFinishInflate() { ImageView fingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view); fingerprintView.setImageDrawable(mFingerprintDrawable); mFingerprintProgressView = findViewById(R.id.udfps_enroll_animation_fp_progress_view); mFingerprintProgressView.setImageDrawable(mFingerprintProgressDrawable); } /** * Receive enroll progress information from FingerprintEnrollEnrollingUdfpsFragment */ public void onEnrollmentProgress(int remaining, int totalSteps) { if (mTotalSteps == -1) { mTotalSteps = totalSteps; } mRemainingSteps = remaining; mHandler.post(() -> { mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps); mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps); }); } /** * Receive enroll help information from FingerprintEnrollEnrollingUdfpsFragment */ public void onEnrollmentHelp() { mHandler.post( () -> mFingerprintProgressDrawable.onEnrollmentHelp(mRemainingSteps, mTotalSteps)); } /** * Receive onAcquired from FingerprintEnrollEnrollingUdfpsFragment */ public void onAcquired(boolean isAcquiredGood) { final boolean animateIfLastStepGood = isAcquiredGood && (mRemainingSteps <= 2 && mRemainingSteps >= 0); mHandler.post(() -> { onFingerUp(); if (animateIfLastStepGood) mFingerprintProgressDrawable.onLastStepAcquired(); }); } /** * Receive onPointerDown from FingerprintEnrollEnrollingUdfpsFragment */ public void onPointerDown(int sensorId) { onFingerDown(); } /** * Receive onPointerUp from FingerprintEnrollEnrollingUdfpsFragment */ public void onPointerUp(int sensorId) { onFingerUp(); } private final ViewTreeObserver.OnDrawListener mOnDrawListener = this::updateOverlayParams; /** * setup SensorProperties */ public void setSensorProperties(FingerprintSensorPropertiesInternal properties) { mSensorProperties = properties; ((ViewGroup) getParent()).getViewTreeObserver().addOnDrawListener(mOnDrawListener); } @Override protected void onDetachedFromWindow() { final ViewGroup parent = (ViewGroup) getParent(); if (parent != null) { final ViewTreeObserver observer = parent.getViewTreeObserver(); if (observer != null) { observer.removeOnDrawListener(mOnDrawListener); } } super.onDetachedFromWindow(); } private void onSensorRectUpdated() { updateDimensions(); // Updates sensor rect in relation to the overlay view mSensorRect.set(getPaddingX(), getPaddingY(), (mOverlayParams.getSensorBounds().width() + getPaddingX()), (mOverlayParams.getSensorBounds().height() + getPaddingY())); mFingerprintDrawable.onSensorRectUpdated(new RectF(mSensorRect)); } private void updateDimensions() { // Original sensorBounds assume portrait mode. final Rect rotatedBounds = new Rect(mOverlayParams.getSensorBounds()); int rotation = mOverlayParams.getRotation(); if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) { RotationUtils.rotateBounds( rotatedBounds, mOverlayParams.getNaturalDisplayWidth(), mOverlayParams.getNaturalDisplayHeight(), rotation ); } RelativeLayout parent = ((RelativeLayout) getParent()); if (parent == null) { Log.e(TAG, "Fail to updateDimensions for " + this + ", parent null"); return; } final int[] coords = parent.getLocationOnScreen(); final int parentLeft = coords[0]; final int parentTop = coords[1]; final int parentRight = parentLeft + parent.getWidth(); final int parentBottom = parentTop + parent.getHeight(); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(getWidth(), getHeight()); if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { params.addRule(RelativeLayout.ALIGN_PARENT_TOP); params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); params.rightMargin = parentRight - rotatedBounds.right - getPaddingX(); params.topMargin = rotatedBounds.top - parentTop - getPaddingY(); } else { if (rotation == Surface.ROTATION_90) { params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); params.rightMargin = parentRight - rotatedBounds.right - getPaddingX(); params.bottomMargin = parentBottom - rotatedBounds.bottom - getPaddingY(); } else { params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); params.addRule(RelativeLayout.ALIGN_PARENT_LEFT); params.bottomMargin = parentBottom - rotatedBounds.bottom - getPaddingY(); params.leftMargin = rotatedBounds.left - parentLeft - getPaddingX(); } } params.height = rotatedBounds.height() + 2 * getPaddingX(); params.width = rotatedBounds.width() + 2 * getPaddingY(); setLayoutParams(params); } private void onFingerDown() { mFingerprintDrawable.setShouldSkipDraw(true); mFingerprintDrawable.invalidateSelf(); } private void onFingerUp() { mFingerprintDrawable.setShouldSkipDraw(false); mFingerprintDrawable.invalidateSelf(); } private int getPaddingX() { return mProgressBarRadius; } private int getPaddingY() { return mProgressBarRadius; } private void updateOverlayParams() { if (mSensorProperties == null) { android.util.Log.e(TAG, "There is no sensor info!"); return; } DisplayInfo displayInfo = new DisplayInfo(); if (getDisplay() == null) { android.util.Log.e(TAG, "Can not get display"); return; } getDisplay().getDisplayInfo(displayInfo); Rect udfpsBounds = mSensorProperties.getLocation().getRect(); float scaleFactor = mUdfpsUtils.getScaleFactor(displayInfo); udfpsBounds.scale(scaleFactor); final Rect overlayBounds = new Rect( 0, /* left */ displayInfo.getNaturalHeight() / 2, /* top */ displayInfo.getNaturalWidth(), /* right */ displayInfo.getNaturalHeight() /* botom */); mOverlayParams = new UdfpsOverlayParams( udfpsBounds, overlayBounds, displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), scaleFactor, displayInfo.rotation, mSensorProperties.sensorType); post(() -> { mProgressBarRadius = (int) (mOverlayParams.getScaleFactor() * getContext().getResources().getInteger( R.integer.config_udfpsEnrollProgressBar)); mSensorRect = new Rect(mOverlayParams.getSensorBounds()); onSensorRectUpdated(); }); } }