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