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:
Kevin Chyn
2020-10-26 18:43:48 -07:00
parent 5b8380869c
commit 90e442a741
9 changed files with 362 additions and 49 deletions

View File

@@ -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() {

View File

@@ -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

View 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>

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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
*/

View File

@@ -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.