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.systemui.biometrics.ui; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.graphics.Insets; 23 import android.util.AttributeSet; 24 import android.util.Log; 25 import android.view.View; 26 import android.view.WindowInsets; 27 import android.view.WindowManager; 28 import android.widget.FrameLayout; 29 import android.widget.LinearLayout; 30 import android.widget.TextView; 31 32 import com.android.systemui.R; 33 import com.android.systemui.biometrics.AuthBiometricFingerprintIconController; 34 import com.android.systemui.biometrics.AuthController; 35 import com.android.systemui.biometrics.AuthDialog; 36 import com.android.systemui.biometrics.UdfpsDialogMeasureAdapter; 37 38 import kotlin.Pair; 39 40 /** 41 * Contains the Biometric views (title, subtitle, icon, buttons, etc.). 42 * 43 * TODO(b/251476085): get the udfps junk out of here, at a minimum. Likely can be replaced with a 44 * normal LinearLayout. 45 */ 46 public class BiometricPromptLayout extends LinearLayout { 47 48 private static final String TAG = "BiometricPromptLayout"; 49 50 @NonNull 51 private final WindowManager mWindowManager; 52 @Nullable 53 private AuthController.ScaleFactorProvider mScaleFactorProvider; 54 @Nullable 55 private UdfpsDialogMeasureAdapter mUdfpsAdapter; 56 57 private final boolean mUseCustomBpSize; 58 private final int mCustomBpWidth; 59 private final int mCustomBpHeight; 60 BiometricPromptLayout(Context context)61 public BiometricPromptLayout(Context context) { 62 this(context, null); 63 } 64 BiometricPromptLayout(Context context, AttributeSet attrs)65 public BiometricPromptLayout(Context context, AttributeSet attrs) { 66 super(context, attrs); 67 68 mWindowManager = context.getSystemService(WindowManager.class); 69 70 mUseCustomBpSize = getResources().getBoolean(R.bool.use_custom_bp_size); 71 mCustomBpWidth = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_width); 72 mCustomBpHeight = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_height); 73 } 74 75 @Deprecated setUdfpsAdapter(@onNull UdfpsDialogMeasureAdapter adapter, @NonNull AuthController.ScaleFactorProvider scaleProvider)76 public void setUdfpsAdapter(@NonNull UdfpsDialogMeasureAdapter adapter, 77 @NonNull AuthController.ScaleFactorProvider scaleProvider) { 78 mUdfpsAdapter = adapter; 79 mScaleFactorProvider = scaleProvider != null ? scaleProvider : () -> 1.0f; 80 } 81 82 @Deprecated isUdfps()83 public boolean isUdfps() { 84 return mUdfpsAdapter != null; 85 } 86 87 @Deprecated updateFingerprintAffordanceSize( @onNull AuthBiometricFingerprintIconController iconController)88 public void updateFingerprintAffordanceSize( 89 @NonNull AuthBiometricFingerprintIconController iconController) { 90 if (mUdfpsAdapter != null) { 91 final int sensorDiameter = mUdfpsAdapter.getSensorDiameter( 92 mScaleFactorProvider.provide()); 93 iconController.setIconLayoutParamSize(new Pair(sensorDiameter, sensorDiameter)); 94 } 95 } 96 97 @NonNull onMeasureInternal(int width, int height)98 private AuthDialog.LayoutParams onMeasureInternal(int width, int height) { 99 int totalHeight = 0; 100 final int numChildren = getChildCount(); 101 for (int i = 0; i < numChildren; i++) { 102 final View child = getChildAt(i); 103 104 if (child.getId() == R.id.space_above_icon 105 || child.getId() == R.id.space_below_icon 106 || child.getId() == R.id.button_bar) { 107 child.measure( 108 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 109 MeasureSpec.makeMeasureSpec(child.getLayoutParams().height, 110 MeasureSpec.EXACTLY)); 111 } else if (child.getId() == R.id.biometric_icon_frame) { 112 final View iconView = findViewById(R.id.biometric_icon); 113 child.measure( 114 MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().width, 115 MeasureSpec.EXACTLY), 116 MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height, 117 MeasureSpec.EXACTLY)); 118 } else if (child.getId() == R.id.biometric_icon) { 119 child.measure( 120 MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), 121 MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); 122 } else { 123 child.measure( 124 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 125 MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); 126 } 127 128 if (child.getVisibility() != View.GONE) { 129 totalHeight += child.getMeasuredHeight(); 130 } 131 } 132 133 final AuthDialog.LayoutParams params = new AuthDialog.LayoutParams(width, totalHeight); 134 if (mUdfpsAdapter != null) { 135 return mUdfpsAdapter.onMeasureInternal(width, height, params, 136 (mScaleFactorProvider != null) ? mScaleFactorProvider.provide() : 1.0f); 137 } else { 138 return params; 139 } 140 } 141 142 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)143 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 144 int width = MeasureSpec.getSize(widthMeasureSpec); 145 int height = MeasureSpec.getSize(heightMeasureSpec); 146 147 if (mUseCustomBpSize) { 148 width = mCustomBpWidth; 149 height = mCustomBpHeight; 150 } else { 151 width = Math.min(width, height); 152 } 153 154 // add nav bar insets since the parent AuthContainerView 155 // uses LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS 156 final Insets insets = mWindowManager.getMaximumWindowMetrics().getWindowInsets() 157 .getInsets(WindowInsets.Type.navigationBars()); 158 final AuthDialog.LayoutParams params = onMeasureInternal(width, height); 159 setMeasuredDimension(params.mMediumWidth + insets.left + insets.right, 160 params.mMediumHeight + insets.bottom); 161 } 162 163 @Override onLayout(boolean changed, int left, int top, int right, int bottom)164 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 165 super.onLayout(changed, left, top, right, bottom); 166 167 if (mUdfpsAdapter != null) { 168 // Move the UDFPS icon and indicator text if necessary. This probably only needs to 169 // happen for devices where the UDFPS sensor is too low. 170 // TODO(b/201510778): Update this logic to support cases where the sensor or text 171 // overlap the button bar area. 172 final float bottomSpacerHeight = mUdfpsAdapter.getBottomSpacerHeight(); 173 Log.w(TAG, "bottomSpacerHeight: " + bottomSpacerHeight); 174 if (bottomSpacerHeight < 0) { 175 final FrameLayout iconFrame = findViewById(R.id.biometric_icon_frame); 176 iconFrame.setTranslationY(-bottomSpacerHeight); 177 final TextView indicator = findViewById(R.id.indicator); 178 indicator.setTranslationY(-bottomSpacerHeight); 179 } 180 } 181 } 182 } 183