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