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