Add AuthBiometricUdfpsView for portrait
Adds AuthDialog.LayoutParams which should be used to hold any layout or measurement related information. Adds onMeasureInternal which returns AuthDialog.LayoutParams so that subclasses can return custom layout parameters while keeping common things like setMeasuredDimension in the base class. The AuthBiometricUdfpsView in portrait mode includes a spacer whose height is calculated based on the sizes of everything that's below the physical sensor, including 1) Text indicator area (for error messages) 2) Footer button bar 3) BiometricPrompt distance to navigation bar 4) Height of navigation bar The indicator text is the only non-static element below the icon. Changes it to marquee on a single line for now. AuthBiometricView animates the icon's holder instead of the icon itself. Otherwise, the holder would need to allow drawing outside of its bounds. Test: No effect on existing devices Test: atest com.android.systemui.biometrics Fixes: 171071087 Change-Id: I3b62f02a44f4d3cf6925d7782451596fbf8db0e1
This commit is contained in:
@@ -43,24 +43,36 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
|
||||
* The location of the center of the sensor if applicable. For example, sensors of type
|
||||
* {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
|
||||
* distance in pixels, measured from the left edge of the screen.
|
||||
* TODO: Value should be provided from the HAL
|
||||
*/
|
||||
public final int sensorLocationX = 540;
|
||||
public final int sensorLocationX;
|
||||
|
||||
/**
|
||||
* The location of the center of the sensor if applicable. For example, sensors of type
|
||||
* {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
|
||||
* distance in pixels, measured from the top edge of the screen.
|
||||
* TODO: Value should be provided from the HAL
|
||||
*
|
||||
*/
|
||||
public final int sensorLocationY = 1636;
|
||||
public final int sensorLocationY;
|
||||
|
||||
/**
|
||||
* The radius of the sensor if applicable. For example, sensors of type
|
||||
* {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the radius
|
||||
* of the sensor, in pixels.
|
||||
*/
|
||||
public final int sensorRadius = 130;
|
||||
public final int sensorRadius;
|
||||
|
||||
public FingerprintSensorPropertiesInternal(int sensorId,
|
||||
@SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
|
||||
@FingerprintSensorProperties.SensorType int sensorType,
|
||||
boolean resetLockoutRequiresHardwareAuthToken, int sensorLocationX, int sensorLocationY,
|
||||
int sensorRadius) {
|
||||
super(sensorId, strength, maxEnrollmentsPerUser);
|
||||
this.sensorType = sensorType;
|
||||
this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
|
||||
this.sensorLocationX = sensorLocationX;
|
||||
this.sensorLocationY = sensorLocationY;
|
||||
this.sensorRadius = sensorRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes SensorProperties with specified values
|
||||
@@ -69,15 +81,19 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
|
||||
@SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
|
||||
@FingerprintSensorProperties.SensorType int sensorType,
|
||||
boolean resetLockoutRequiresHardwareAuthToken) {
|
||||
super(sensorId, strength, maxEnrollmentsPerUser);
|
||||
this.sensorType = sensorType;
|
||||
this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
|
||||
// TODO: Value should be provided from the HAL
|
||||
this(sensorId, strength, maxEnrollmentsPerUser, sensorType,
|
||||
resetLockoutRequiresHardwareAuthToken, 540 /* sensorLocationX */,
|
||||
1636 /* sensorLocationY */, 130 /* sensorRadius */);
|
||||
}
|
||||
|
||||
protected FingerprintSensorPropertiesInternal(Parcel in) {
|
||||
super(in);
|
||||
sensorType = in.readInt();
|
||||
resetLockoutRequiresHardwareAuthToken = in.readBoolean();
|
||||
sensorLocationX = in.readInt();
|
||||
sensorLocationY = in.readInt();
|
||||
sensorRadius = in.readInt();
|
||||
}
|
||||
|
||||
public static final Creator<FingerprintSensorPropertiesInternal> CREATOR =
|
||||
@@ -103,6 +119,9 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeInt(sensorType);
|
||||
dest.writeBoolean(resetLockoutRequiresHardwareAuthToken);
|
||||
dest.writeInt(sensorLocationX);
|
||||
dest.writeInt(sensorLocationY);
|
||||
dest.writeInt(sensorRadius);
|
||||
}
|
||||
|
||||
public boolean isAnyUdfpsType() {
|
||||
|
||||
@@ -37,13 +37,32 @@
|
||||
android:gravity="@integer/biometric_dialog_text_gravity"
|
||||
style="@style/TextAppearance.AuthCredential.Description"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/biometric_icon"
|
||||
android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
|
||||
android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
|
||||
android:paddingTop="48dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:scaleType="fitXY" />
|
||||
<Space android:id="@+id/space_above_icon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:visibility="visible" />
|
||||
|
||||
<!-- Use a frame layout since certain biometrics (such as UDFPS) require the icon to be centered
|
||||
within a certain area on the display. This makes it easy to 1) guarantee max size, and
|
||||
2) center the icon within the reserved area. -->
|
||||
<FrameLayout android:id="@+id/biometric_icon_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_horizontal">
|
||||
<ImageView
|
||||
android:id="@+id/biometric_icon"
|
||||
android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
|
||||
android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="fitXY" />
|
||||
</FrameLayout>
|
||||
|
||||
<!-- For sensors such as UDFPS, this view is used during custom measurement/layoutto add extra
|
||||
padding so that the biometric icon is always in the right physical position. -->
|
||||
<Space android:id="@+id/space_below_icon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/indicator"
|
||||
@@ -54,6 +73,8 @@
|
||||
android:textSize="12sp"
|
||||
android:gravity="center_horizontal"
|
||||
android:accessibilityLiveRegion="polite"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
android:textColor="@color/biometric_dialog_gray"/>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
26
packages/SystemUI/res/layout/auth_biometric_udfps_view.xml
Normal file
26
packages/SystemUI/res/layout/auth_biometric_udfps_view.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<!--
|
||||
~ Copyright (C) 2020 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.
|
||||
-->
|
||||
|
||||
<com.android.systemui.biometrics.AuthBiometricUdfpsView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/contents"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/auth_biometric_contents"/>
|
||||
|
||||
</com.android.systemui.biometrics.AuthBiometricUdfpsView>
|
||||
@@ -184,7 +184,7 @@ public class AuthBiometricFaceView extends AuthBiometricView {
|
||||
mIconController.updateState(mState, newState);
|
||||
|
||||
if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
|
||||
(newState == STATE_AUTHENTICATING && mSize == AuthDialog.SIZE_MEDIUM)) {
|
||||
(newState == STATE_AUTHENTICATING && getSize() == AuthDialog.SIZE_MEDIUM)) {
|
||||
resetErrorView(mContext, mIndicatorView);
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ public class AuthBiometricFaceView extends AuthBiometricView {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed(String failureReason) {
|
||||
if (mSize == AuthDialog.SIZE_MEDIUM) {
|
||||
if (getSize() == AuthDialog.SIZE_MEDIUM) {
|
||||
mTryAgainButton.setVisibility(View.VISIBLE);
|
||||
mPositiveButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.systemui.biometrics;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.systemui.R;
|
||||
|
||||
/**
|
||||
* Manages the layout for under-display fingerprint sensors (UDFPS). Ensures that UI elements
|
||||
* do not overlap with
|
||||
*/
|
||||
public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView {
|
||||
|
||||
private static final String TAG = "AuthBiometricUdfpsView";
|
||||
|
||||
@Nullable private FingerprintSensorPropertiesInternal mSensorProps;
|
||||
|
||||
public AuthBiometricUdfpsView(Context context) {
|
||||
this(context, null /* attrs */);
|
||||
}
|
||||
|
||||
public AuthBiometricUdfpsView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
void setSensorProps(@NonNull FingerprintSensorPropertiesInternal prop) {
|
||||
mSensorProps = prop;
|
||||
}
|
||||
|
||||
/**
|
||||
* For devices where the sensor is too high up, calculates the amount of padding necessary to
|
||||
* move/center the biometric icon within the sensor's physical location.
|
||||
*/
|
||||
static int calculateBottomSpacerHeight(int displayHeightPx, int navbarHeightPx,
|
||||
int dialogBottomMarginPx, @NonNull View buttonBar, @NonNull View textIndicator,
|
||||
@NonNull FingerprintSensorPropertiesInternal sensorProperties) {
|
||||
final int sensorDistanceFromBottom = displayHeightPx - sensorProperties.sensorLocationY
|
||||
- sensorProperties.sensorRadius;
|
||||
|
||||
final int spacerHeight = sensorDistanceFromBottom
|
||||
- textIndicator.getMeasuredHeight()
|
||||
- buttonBar.getMeasuredHeight()
|
||||
- dialogBottomMarginPx
|
||||
- navbarHeightPx;
|
||||
|
||||
Log.d(TAG, "Display height: " + displayHeightPx
|
||||
+ ", Distance from bottom: " + sensorDistanceFromBottom
|
||||
+ ", Bottom margin: " + dialogBottomMarginPx
|
||||
+ ", Navbar height: " + navbarHeightPx
|
||||
+ ", Spacer height: " + spacerHeight);
|
||||
|
||||
return spacerHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
|
||||
final View spaceBelowIcon = findViewById(R.id.space_below_icon);
|
||||
spaceBelowIcon.setVisibility(View.VISIBLE);
|
||||
|
||||
// Get the height of the everything below the icon. Currently, that's the indicator and
|
||||
// button bar
|
||||
final View textIndicator = findViewById(R.id.indicator);
|
||||
final View buttonBar = findViewById(R.id.button_bar);
|
||||
|
||||
// Figure out where the bottom of the sensor anim should be.
|
||||
// Navbar + dialogMargin + buttonBar + textIndicator + spacerHeight = sensorDistFromBottom
|
||||
final int dialogBottomMarginPx = getResources()
|
||||
.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding);
|
||||
final WindowManager wm = getContext().getSystemService(WindowManager.class);
|
||||
final Rect bounds = wm.getCurrentWindowMetrics().getBounds();
|
||||
final int navbarHeight = wm.getCurrentWindowMetrics().getWindowInsets()
|
||||
.getInsets(WindowInsets.Type.navigationBars()).toRect().height();
|
||||
final int displayHeight = bounds.height();
|
||||
|
||||
final int spacerHeight = calculateBottomSpacerHeight(displayHeight, navbarHeight,
|
||||
dialogBottomMarginPx, buttonBar, textIndicator, mSensorProps);
|
||||
|
||||
// Go through each of the children and do the custom measurement.
|
||||
int totalHeight = 0;
|
||||
final int numChildren = getChildCount();
|
||||
final int sensorDiameter = mSensorProps.sensorRadius * 2;
|
||||
for (int i = 0; i < numChildren; i++) {
|
||||
final View child = getChildAt(i);
|
||||
|
||||
if (child.getId() == R.id.biometric_icon_frame) {
|
||||
// Create a frame that's exactly the size of the sensor circle
|
||||
child.measure(
|
||||
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY));
|
||||
} else if (child.getId() == R.id.biometric_icon) {
|
||||
// Icon should never be larger than the circle
|
||||
child.measure(
|
||||
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST),
|
||||
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST));
|
||||
} else if (child.getId() == R.id.space_above_icon) {
|
||||
child.measure(
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
|
||||
MeasureSpec.EXACTLY));
|
||||
} else if (child.getId() == R.id.button_bar) {
|
||||
child.measure(
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
|
||||
MeasureSpec.EXACTLY));
|
||||
} else if (child.getId() == R.id.space_below_icon) {
|
||||
// Set the spacer height so the fingerprint icon is on the physical sensor area
|
||||
child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(spacerHeight, MeasureSpec.EXACTLY));
|
||||
} else {
|
||||
child.measure(
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
|
||||
}
|
||||
|
||||
if (child.getVisibility() != View.GONE) {
|
||||
totalHeight += child.getMeasuredHeight();
|
||||
}
|
||||
}
|
||||
|
||||
return new AuthDialog.LayoutParams(width, totalHeight);
|
||||
}
|
||||
}
|
||||
@@ -144,6 +144,10 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
return mBiometricView.findViewById(R.id.biometric_icon);
|
||||
}
|
||||
|
||||
public View getIconHolderView() {
|
||||
return mBiometricView.findViewById(R.id.biometric_icon_frame);
|
||||
}
|
||||
|
||||
public int getDelayAfterError() {
|
||||
return BiometricPrompt.HIDE_DIALOG_DELAY;
|
||||
}
|
||||
@@ -164,11 +168,12 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
private boolean mRequireConfirmation;
|
||||
private int mUserId;
|
||||
private int mEffectiveUserId;
|
||||
@AuthDialog.DialogSize int mSize = AuthDialog.SIZE_UNKNOWN;
|
||||
private @AuthDialog.DialogSize int mSize = AuthDialog.SIZE_UNKNOWN;
|
||||
|
||||
private TextView mTitleView;
|
||||
private TextView mSubtitleView;
|
||||
private TextView mDescriptionView;
|
||||
private View mIconHolderView;
|
||||
protected ImageView mIconView;
|
||||
@VisibleForTesting protected TextView mIndicatorView;
|
||||
@VisibleForTesting Button mNegativeButton;
|
||||
@@ -176,8 +181,7 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
@VisibleForTesting Button mTryAgainButton;
|
||||
|
||||
// Measurements when biometric view is showing text, buttons, etc.
|
||||
private int mMediumHeight;
|
||||
private int mMediumWidth;
|
||||
@Nullable @VisibleForTesting AuthDialog.LayoutParams mLayoutParams;
|
||||
|
||||
private Callback mCallback;
|
||||
protected @BiometricState int mState;
|
||||
@@ -302,13 +306,13 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
|
||||
final float iconPadding = getResources()
|
||||
.getDimension(R.dimen.biometric_dialog_icon_padding);
|
||||
mIconView.setY(getHeight() - mIconView.getHeight() - iconPadding);
|
||||
mIconHolderView.setY(getHeight() - mIconHolderView.getHeight() - iconPadding);
|
||||
|
||||
// Subtract the vertical padding from the new height since it's only used to create
|
||||
// extra space between the other elements, and not part of the actual icon.
|
||||
final int newHeight = mIconView.getHeight() + 2 * (int) iconPadding
|
||||
- mIconView.getPaddingTop() - mIconView.getPaddingBottom();
|
||||
mPanelController.updateForContentDimensions(mMediumWidth, newHeight,
|
||||
final int newHeight = mIconHolderView.getHeight() + 2 * (int) iconPadding
|
||||
- mIconHolderView.getPaddingTop() - mIconHolderView.getPaddingBottom();
|
||||
mPanelController.updateForContentDimensions(mLayoutParams.mMediumWidth, newHeight,
|
||||
0 /* animateDurationMs */);
|
||||
|
||||
mSize = newSize;
|
||||
@@ -320,9 +324,9 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
|
||||
// Animate the icon back to original position
|
||||
final ValueAnimator iconAnimator =
|
||||
ValueAnimator.ofFloat(mIconView.getY(), mIconOriginalY);
|
||||
ValueAnimator.ofFloat(mIconHolderView.getY(), mIconOriginalY);
|
||||
iconAnimator.addUpdateListener((animation) -> {
|
||||
mIconView.setY((float) animation.getAnimatedValue());
|
||||
mIconHolderView.setY((float) animation.getAnimatedValue());
|
||||
});
|
||||
|
||||
// Animate the text
|
||||
@@ -374,10 +378,12 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
as.play(iconAnimator).with(opacityAnimator);
|
||||
as.start();
|
||||
// Animate the panel
|
||||
mPanelController.updateForContentDimensions(mMediumWidth, mMediumHeight,
|
||||
mPanelController.updateForContentDimensions(mLayoutParams.mMediumWidth,
|
||||
mLayoutParams.mMediumHeight,
|
||||
AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS);
|
||||
} else if (newSize == AuthDialog.SIZE_MEDIUM) {
|
||||
mPanelController.updateForContentDimensions(mMediumWidth, mMediumHeight,
|
||||
mPanelController.updateForContentDimensions(mLayoutParams.mMediumWidth,
|
||||
mLayoutParams.mMediumHeight,
|
||||
0 /* animateDurationMs */);
|
||||
mSize = newSize;
|
||||
} else if (newSize == AuthDialog.SIZE_LARGE) {
|
||||
@@ -566,6 +572,7 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
mIndicatorView.setText(message);
|
||||
mIndicatorView.setTextColor(mTextColorError);
|
||||
mIndicatorView.setVisibility(View.VISIBLE);
|
||||
mIndicatorView.setSelected(true);
|
||||
mHandler.postDelayed(resetMessageRunnable, BiometricPrompt.HIDE_DIALOG_DELAY);
|
||||
|
||||
Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
|
||||
@@ -586,6 +593,7 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
mSubtitleView = mInjector.getSubtitleView();
|
||||
mDescriptionView = mInjector.getDescriptionView();
|
||||
mIconView = mInjector.getIconView();
|
||||
mIconHolderView = mInjector.getIconHolderView();
|
||||
mIndicatorView = mInjector.getIndicatorView();
|
||||
mNegativeButton = mInjector.getNegativeButton();
|
||||
mPositiveButton = mInjector.getPositiveButton();
|
||||
@@ -688,29 +696,45 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
mHandler.removeCallbacksAndMessages(null /* all */);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
final int newWidth = Math.min(width, height);
|
||||
|
||||
/**
|
||||
* Contains all of the testable logic that should be invoked when {@link #onMeasure(int, int)}
|
||||
* is invoked. In addition, this allows subclasses to implement custom measuring logic while
|
||||
* allowing the base class to have common code to apply the custom measurements.
|
||||
*
|
||||
* @param width Width to constrain the measurements to.
|
||||
* @param height Height to constrain the measurements to.
|
||||
* @return See {@link AuthDialog.LayoutParams}
|
||||
*/
|
||||
AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
|
||||
int totalHeight = 0;
|
||||
final int numChildren = getChildCount();
|
||||
for (int i = 0; i < numChildren; i++) {
|
||||
final View child = getChildAt(i);
|
||||
|
||||
if (child.getId() == R.id.biometric_icon) {
|
||||
if (child.getId() == R.id.space_above_icon) {
|
||||
child.measure(
|
||||
MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.AT_MOST),
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
|
||||
MeasureSpec.EXACTLY));
|
||||
} else if (child.getId() == R.id.biometric_icon_frame) {
|
||||
final View iconView = findViewById(R.id.biometric_icon);
|
||||
child.measure(
|
||||
MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().width,
|
||||
MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height,
|
||||
MeasureSpec.EXACTLY));
|
||||
} else if (child.getId() == R.id.biometric_icon) {
|
||||
child.measure(
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
|
||||
} else if (child.getId() == R.id.button_bar) {
|
||||
} else if (child.getId() == R.id.button_bar) {
|
||||
child.measure(
|
||||
MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
|
||||
MeasureSpec.EXACTLY));
|
||||
} else {
|
||||
child.measure(
|
||||
MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
|
||||
}
|
||||
|
||||
@@ -719,11 +743,19 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
}
|
||||
}
|
||||
|
||||
// Use the new width so it's centered horizontally
|
||||
setMeasuredDimension(newWidth, totalHeight);
|
||||
return new AuthDialog.LayoutParams(width, totalHeight);
|
||||
}
|
||||
|
||||
mMediumHeight = totalHeight;
|
||||
mMediumWidth = getMeasuredWidth();
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
final int newWidth = Math.min(width, height);
|
||||
|
||||
// Use "newWidth" instead, so the landscape dialog width is the same as the portrait
|
||||
// width.
|
||||
mLayoutParams = onMeasureInternal(newWidth, height);
|
||||
setMeasuredDimension(mLayoutParams.mMediumWidth, mLayoutParams.mMediumHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -741,7 +773,7 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
// Start with initial size only once. Subsequent layout changes don't matter since we
|
||||
// only care about the initial icon position.
|
||||
if (mIconOriginalY == 0) {
|
||||
mIconOriginalY = mIconView.getY();
|
||||
mIconOriginalY = mIconHolderView.getY();
|
||||
if (mSavedState == null) {
|
||||
updateSize(!mRequireConfirmation && supportsSmallDialog() ? AuthDialog.SIZE_SMALL
|
||||
: AuthDialog.SIZE_MEDIUM);
|
||||
@@ -764,4 +796,8 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
private boolean isDeviceCredentialAllowed() {
|
||||
return Utils.isDeviceCredentialAllowed(mPromptInfo);
|
||||
}
|
||||
|
||||
@AuthDialog.DialogSize int getSize() {
|
||||
return mSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,9 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.hardware.biometrics.BiometricAuthenticator;
|
||||
import android.hardware.biometrics.BiometricConstants;
|
||||
import android.hardware.biometrics.PromptInfo;
|
||||
import android.hardware.face.FaceSensorPropertiesInternal;
|
||||
import android.hardware.fingerprint.FingerprintSensorProperties;
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
@@ -286,8 +284,23 @@ public class AuthContainerView extends LinearLayout
|
||||
if (config.mSensorIds.length == 1) {
|
||||
final int singleSensorAuthId = config.mSensorIds[0];
|
||||
if (Utils.containsSensorId(mFpProps, singleSensorAuthId)) {
|
||||
mBiometricView = (AuthBiometricFingerprintView)
|
||||
factory.inflate(R.layout.auth_biometric_fingerprint_view, null, false);
|
||||
FingerprintSensorPropertiesInternal sensorProps = null;
|
||||
for (FingerprintSensorPropertiesInternal prop : mFpProps) {
|
||||
if (prop.sensorId == singleSensorAuthId) {
|
||||
sensorProps = prop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sensorProps.isAnyUdfpsType()) {
|
||||
AuthBiometricUdfpsView udfpsView = (AuthBiometricUdfpsView) factory
|
||||
.inflate(R.layout.auth_biometric_udfps_view, null, false);
|
||||
udfpsView.setSensorProps(sensorProps);
|
||||
mBiometricView = udfpsView;
|
||||
} else {
|
||||
mBiometricView = (AuthBiometricFingerprintView) factory
|
||||
.inflate(R.layout.auth_biometric_fingerprint_view, null, false);
|
||||
}
|
||||
} else if (Utils.containsSensorId(mFaceProps, singleSensorAuthId)) {
|
||||
mBiometricView = (AuthBiometricFaceView)
|
||||
factory.inflate(R.layout.auth_biometric_face_view, null, false);
|
||||
|
||||
@@ -58,6 +58,20 @@ public interface AuthDialog {
|
||||
@IntDef({SIZE_UNKNOWN, SIZE_SMALL, SIZE_MEDIUM, SIZE_LARGE})
|
||||
@interface DialogSize {}
|
||||
|
||||
/**
|
||||
* Parameters used when laying out {@link AuthBiometricView}, its sublclasses, and
|
||||
* {@link AuthPanelController}.
|
||||
*/
|
||||
class LayoutParams {
|
||||
final int mMediumHeight;
|
||||
final int mMediumWidth;
|
||||
|
||||
LayoutParams(int mediumWidth, int mediumHeight) {
|
||||
mMediumWidth = mediumWidth;
|
||||
mMediumHeight = mediumHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animation duration, from small to medium dialog, including back panel, icon translation, etc
|
||||
*/
|
||||
|
||||
@@ -22,11 +22,16 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.PromptInfo;
|
||||
import android.hardware.biometrics.SensorProperties;
|
||||
import android.hardware.fingerprint.FingerprintSensorProperties;
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||
import android.os.Bundle;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
@@ -62,6 +67,7 @@ public class AuthBiometricViewTest extends SysuiTestCase {
|
||||
@Mock private TextView mDescriptionView;
|
||||
@Mock private TextView mIndicatorView;
|
||||
@Mock private ImageView mIconView;
|
||||
@Mock private View mIconHolderView;
|
||||
|
||||
private TestableBiometricView mBiometricView;
|
||||
|
||||
@@ -200,6 +206,7 @@ public class AuthBiometricViewTest extends SysuiTestCase {
|
||||
@Test
|
||||
public void testBackgroundClicked_whenSmallDialog_neverSendsUserCanceled() {
|
||||
initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector());
|
||||
mBiometricView.mLayoutParams = new AuthDialog.LayoutParams(0, 0);
|
||||
mBiometricView.updateSize(AuthDialog.SIZE_SMALL);
|
||||
|
||||
View view = new View(mContext);
|
||||
@@ -275,7 +282,7 @@ public class AuthBiometricViewTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegativeButton_whenDeviceCredentialAllowed() throws InterruptedException {
|
||||
public void testNegativeButton_whenDeviceCredentialAllowed() {
|
||||
Button negativeButton = new Button(mContext);
|
||||
initDialog(mContext, true /* allowDeviceCredential */, mCallback, new MockInjector() {
|
||||
@Override
|
||||
@@ -290,6 +297,32 @@ public class AuthBiometricViewTest extends SysuiTestCase {
|
||||
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUdfpsBottomSpacerCalculation() {
|
||||
final int displayHeightPx = 3000;
|
||||
final int navbarHeightPx = 10;
|
||||
final int dialogBottomMarginPx = 20;
|
||||
|
||||
final View buttonBar = mock(View.class);
|
||||
when(buttonBar.getMeasuredHeight()).thenReturn(100);
|
||||
|
||||
final View textIndicator = mock(View.class);
|
||||
when(textIndicator.getMeasuredHeight()).thenReturn(200);
|
||||
|
||||
final int sensorLocationX = 540;
|
||||
final int sensorLocationY = 1600;
|
||||
final int sensorRadius = 100;
|
||||
final FingerprintSensorPropertiesInternal props = new FingerprintSensorPropertiesInternal(
|
||||
0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
|
||||
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
|
||||
true /* resetLockoutRequiresHardwareAuthToken */, sensorLocationX, sensorLocationY,
|
||||
sensorRadius);
|
||||
|
||||
assertEquals(970, AuthBiometricUdfpsView.calculateBottomSpacerHeight(
|
||||
displayHeightPx, navbarHeightPx, dialogBottomMarginPx, buttonBar, textIndicator,
|
||||
props));
|
||||
}
|
||||
|
||||
private PromptInfo buildPromptInfo(boolean allowDeviceCredential) {
|
||||
PromptInfo promptInfo = new PromptInfo();
|
||||
promptInfo.setTitle("Title");
|
||||
@@ -362,6 +395,11 @@ public class AuthBiometricViewTest extends SysuiTestCase {
|
||||
return mIconView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getIconHolderView() {
|
||||
return mIconHolderView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDelayAfterError() {
|
||||
return 0; // Keep this at 0 for tests to invoke callback immediately.
|
||||
|
||||
Reference in New Issue
Block a user