Merge "Adjust UDFPS prompt for 3-button nav and landscape" into sc-dev

This commit is contained in:
Curtis Belmonte
2021-03-10 18:50:57 +00:00
committed by Android (Google) Code Review
7 changed files with 367 additions and 85 deletions

View File

@@ -39,37 +39,34 @@
<Space android:id="@+id/space_above_icon" <Space android:id="@+id/space_above_icon"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp" />
android:visibility="visible" />
<!-- Use a frame layout since certain biometrics (such as UDFPS) require the icon to be centered <FrameLayout
within a certain area on the display. This makes it easy to 1) guarantee max size, and android:id="@+id/biometric_icon_frame"
2) center the icon within the reserved area. -->
<FrameLayout android:id="@+id/biometric_icon_frame"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center_horizontal"> android:layout_gravity="center_horizontal">
<ImageView <ImageView
android:id="@+id/biometric_icon" android:id="@+id/biometric_icon"
android:layout_width="@dimen/biometric_dialog_biometric_icon_size" android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
android:layout_height="@dimen/biometric_dialog_biometric_icon_size" android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
android:layout_gravity="center" android:layout_gravity="center"
android:scaleType="fitXY" /> android:scaleType="fitXY" />
</FrameLayout> </FrameLayout>
<!-- For sensors such as UDFPS, this view is used during custom measurement/layoutto add extra <!-- For sensors such as UDFPS, this view is used during custom measurement/layout to add extra
padding so that the biometric icon is always in the right physical position. --> padding so that the biometric icon is always in the right physical position. -->
<Space android:id="@+id/space_below_icon" <Space android:id="@+id/space_below_icon"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="12dp" />
android:visibility="gone" />
<TextView <TextView
android:id="@+id/indicator" android:id="@+id/indicator"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingHorizontal="24dp" android:paddingHorizontal="24dp"
android:paddingVertical="12dp"
android:textSize="12sp" android:textSize="12sp"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:accessibilityLiveRegion="polite" android:accessibilityLiveRegion="polite"
@@ -84,6 +81,7 @@
style="?android:attr/buttonBarStyle" style="?android:attr/buttonBarStyle"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingTop="16dp"> android:paddingTop="16dp">
<Space android:id="@+id/leftSpacer" <Space android:id="@+id/leftSpacer"
android:layout_width="8dp" android:layout_width="8dp"
android:layout_height="match_parent" android:layout_height="match_parent"

View File

@@ -244,6 +244,8 @@
<item name="android:paddingTop">12dp</item> <item name="android:paddingTop">12dp</item>
<item name="android:paddingHorizontal">24dp</item> <item name="android:paddingHorizontal">24dp</item>
<item name="android:textSize">24sp</item> <item name="android:textSize">24sp</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">marquee</item>
</style> </style>
<style name="TextAppearance.AuthCredential.Subtitle"> <style name="TextAppearance.AuthCredential.Subtitle">
@@ -251,6 +253,8 @@
<item name="android:paddingTop">8dp</item> <item name="android:paddingTop">8dp</item>
<item name="android:paddingHorizontal">24dp</item> <item name="android:paddingHorizontal">24dp</item>
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">marquee</item>
</style> </style>
<style name="TextAppearance.AuthCredential.Description"> <style name="TextAppearance.AuthCredential.Description">
@@ -258,6 +262,8 @@
<item name="android:paddingTop">8dp</item> <item name="android:paddingTop">8dp</item>
<item name="android:paddingHorizontal">24dp</item> <item name="android:paddingHorizontal">24dp</item>
<item name="android:textSize">14sp</item> <item name="android:textSize">14sp</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">marquee</item>
</style> </style>
<style name="TextAppearance.AuthCredential.Error"> <style name="TextAppearance.AuthCredential.Error">

View File

@@ -16,17 +16,22 @@
package com.android.systemui.biometrics; package com.android.systemui.biometrics;
import android.annotation.IdRes;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect; import android.graphics.Rect;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.Surface;
import android.view.View; import android.view.View;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.FrameLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R; import com.android.systemui.R;
/** /**
@@ -51,53 +56,38 @@ public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView {
mSensorProps = prop; mSensorProps = prop;
} }
/** @Override
* For devices where the sensor is too high up, calculates the amount of padding necessary to @NonNull
* move/center the biometric icon within the sensor's physical location. AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
*/ final int displayRotation = getDisplay().getRotation();
static int calculateBottomSpacerHeight(int displayHeightPx, int navbarHeightPx, switch (displayRotation) {
int dialogBottomMarginPx, @NonNull View buttonBar, @NonNull View textIndicator, case Surface.ROTATION_0:
@NonNull FingerprintSensorPropertiesInternal sensorProperties) { return onMeasureInternalPortrait(width, height);
final int sensorDistanceFromBottom = displayHeightPx - sensorProperties.sensorLocationY case Surface.ROTATION_90:
- sensorProperties.sensorRadius; case Surface.ROTATION_270:
return onMeasureInternalLandscape(width, height);
final int spacerHeight = sensorDistanceFromBottom default:
- textIndicator.getMeasuredHeight() Log.e(TAG, "Unsupported display rotation: " + displayRotation);
- buttonBar.getMeasuredHeight() return super.onMeasureInternal(width, height);
- dialogBottomMarginPx }
- navbarHeightPx;
Log.d(TAG, "Display height: " + displayHeightPx
+ ", Distance from bottom: " + sensorDistanceFromBottom
+ ", Bottom margin: " + dialogBottomMarginPx
+ ", Navbar height: " + navbarHeightPx
+ ", Spacer height: " + spacerHeight);
return spacerHeight;
} }
@Override @NonNull
AuthDialog.LayoutParams onMeasureInternal(int width, int height) { private AuthDialog.LayoutParams onMeasureInternalPortrait(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 // Get the height of the everything below the icon. Currently, that's the indicator and
// button bar // button bar.
final View textIndicator = findViewById(R.id.indicator); final int textIndicatorHeight = getViewHeightPx(R.id.indicator);
final View buttonBar = findViewById(R.id.button_bar); final int buttonBarHeight = getViewHeightPx(R.id.button_bar);
// Figure out where the bottom of the sensor anim should be. // Figure out where the bottom of the sensor anim should be.
// Navbar + dialogMargin + buttonBar + textIndicator + spacerHeight = sensorDistFromBottom // Navbar + dialogMargin + buttonBar + textIndicator + spacerHeight = sensorDistFromBottom
final int dialogBottomMarginPx = getResources() final int dialogMargin = getDialogMarginPx();
.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding); final WindowManager windowManager = getContext().getSystemService(WindowManager.class);
final WindowManager wm = getContext().getSystemService(WindowManager.class); final int displayHeight = getWindowBounds(windowManager).height();
final Rect bounds = wm.getCurrentWindowMetrics().getBounds(); final Insets navbarInsets = getNavbarInsets(windowManager);
final int navbarHeight = wm.getCurrentWindowMetrics().getWindowInsets() final int bottomSpacerHeight = calculateBottomSpacerHeightForPortrait(
.getInsets(WindowInsets.Type.navigationBars()).toRect().height(); mSensorProps, displayHeight, textIndicatorHeight, buttonBarHeight,
final int displayHeight = bounds.height(); dialogMargin, navbarInsets.bottom);
final int spacerHeight = calculateBottomSpacerHeight(displayHeight, navbarHeight,
dialogBottomMarginPx, buttonBar, textIndicator, mSensorProps);
// Go through each of the children and do the custom measurement. // Go through each of the children and do the custom measurement.
int totalHeight = 0; int totalHeight = 0;
@@ -105,22 +95,25 @@ public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView {
final int sensorDiameter = mSensorProps.sensorRadius * 2; final int sensorDiameter = mSensorProps.sensorRadius * 2;
for (int i = 0; i < numChildren; i++) { for (int i = 0; i < numChildren; i++) {
final View child = getChildAt(i); final View child = getChildAt(i);
if (child.getId() == R.id.biometric_icon_frame) { if (child.getId() == R.id.biometric_icon_frame) {
// Create a frame that's exactly the size of the sensor circle final FrameLayout iconFrame = (FrameLayout) child;
child.measure( final View icon = iconFrame.getChildAt(0);
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY)); // Ensure that the icon is never larger than the sensor.
} else if (child.getId() == R.id.biometric_icon) { icon.measure(
// Icon should never be larger than the circle
child.measure(
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST)); MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST));
// Create a frame that's exactly the height of the sensor circle.
iconFrame.measure(
MeasureSpec.makeMeasureSpec(
child.getLayoutParams().width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY));
} else if (child.getId() == R.id.space_above_icon) { } else if (child.getId() == R.id.space_above_icon) {
child.measure( child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(child.getLayoutParams().height, MeasureSpec.makeMeasureSpec(
MeasureSpec.EXACTLY)); child.getLayoutParams().height, MeasureSpec.EXACTLY));
} else if (child.getId() == R.id.button_bar) { } else if (child.getId() == R.id.button_bar) {
child.measure( child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
@@ -128,8 +121,9 @@ public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView {
MeasureSpec.EXACTLY)); MeasureSpec.EXACTLY));
} else if (child.getId() == R.id.space_below_icon) { } else if (child.getId() == R.id.space_below_icon) {
// Set the spacer height so the fingerprint icon is on the physical sensor area // Set the spacer height so the fingerprint icon is on the physical sensor area
child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), child.measure(
MeasureSpec.makeMeasureSpec(spacerHeight, MeasureSpec.EXACTLY)); MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(bottomSpacerHeight, MeasureSpec.EXACTLY));
} else { } else {
child.measure( child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
@@ -143,4 +137,184 @@ public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView {
return new AuthDialog.LayoutParams(width, totalHeight); return new AuthDialog.LayoutParams(width, totalHeight);
} }
@NonNull
private AuthDialog.LayoutParams onMeasureInternalLandscape(int width, int height) {
// Find the spacer height needed to vertically align the icon with the sensor.
final int titleHeight = getViewHeightPx(R.id.title);
final int subtitleHeight = getViewHeightPx(R.id.subtitle);
final int descriptionHeight = getViewHeightPx(R.id.description);
final int topSpacerHeight = getViewHeightPx(R.id.space_above_icon);
final int textIndicatorHeight = getViewHeightPx(R.id.indicator);
final int buttonBarHeight = getViewHeightPx(R.id.button_bar);
final WindowManager windowManager = getContext().getSystemService(WindowManager.class);
final Insets navbarInsets = getNavbarInsets(windowManager);
final int bottomSpacerHeight = calculateBottomSpacerHeightForLandscape(titleHeight,
subtitleHeight, descriptionHeight, topSpacerHeight, textIndicatorHeight,
buttonBarHeight, navbarInsets.bottom);
// Find the spacer width needed to horizontally align the icon with the sensor.
final int displayWidth = getWindowBounds(windowManager).width();
final int dialogMargin = getDialogMarginPx();
final int horizontalInset = navbarInsets.left + navbarInsets.right;
final int horizontalSpacerWidth = calculateHorizontalSpacerWidthForLandscape(
mSensorProps, displayWidth, dialogMargin, horizontalInset);
final int sensorDiameter = mSensorProps.sensorRadius * 2;
final int remeasuredWidth = sensorDiameter + 2 * horizontalSpacerWidth;
int remeasuredHeight = 0;
final int numChildren = getChildCount();
for (int i = 0; i < numChildren; i++) {
final View child = getChildAt(i);
if (child.getId() == R.id.biometric_icon_frame) {
final FrameLayout iconFrame = (FrameLayout) child;
final View icon = iconFrame.getChildAt(0);
// Ensure that the icon is never larger than the sensor.
icon.measure(
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST));
// Create a frame that's exactly the height of the sensor circle.
iconFrame.measure(
MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY));
} else if (child.getId() == R.id.space_above_icon || child.getId() == R.id.button_bar) {
// Adjust the width of the top spacer and button bar while preserving their heights.
child.measure(
MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(
child.getLayoutParams().height, MeasureSpec.EXACTLY));
} else if (child.getId() == R.id.space_below_icon) {
// Adjust the bottom spacer height to align the fingerprint icon with the sensor.
child.measure(
MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(bottomSpacerHeight, MeasureSpec.EXACTLY));
} else {
// Use the remeasured width for all other child views.
child.measure(
MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
}
if (child.getVisibility() != View.GONE) {
remeasuredHeight += child.getMeasuredHeight();
}
}
return new AuthDialog.LayoutParams(remeasuredWidth, remeasuredHeight);
}
private int getViewHeightPx(@IdRes int viewId) {
final View view = findViewById(viewId);
return view != null ? view.getMeasuredHeight() : 0;
}
private int getDialogMarginPx() {
return getResources().getDimensionPixelSize(R.dimen.biometric_dialog_border_padding);
}
@NonNull
private static Insets getNavbarInsets(@Nullable WindowManager windowManager) {
return windowManager != null && windowManager.getCurrentWindowMetrics() != null
? windowManager.getCurrentWindowMetrics().getWindowInsets()
.getInsets(WindowInsets.Type.navigationBars())
: Insets.NONE;
}
@NonNull
private static Rect getWindowBounds(@Nullable WindowManager windowManager) {
return windowManager != null && windowManager.getCurrentWindowMetrics() != null
? windowManager.getCurrentWindowMetrics().getBounds()
: new Rect();
}
/**
* For devices in portrait orientation where the sensor is too high up, calculates the amount of
* padding necessary to center the biometric icon within the sensor's physical location.
*/
@VisibleForTesting
static int calculateBottomSpacerHeightForPortrait(
@NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayHeightPx,
int textIndicatorHeightPx, int buttonBarHeightPx, int dialogMarginPx,
int navbarBottomInsetPx) {
final int sensorDistanceFromBottom = displayHeightPx
- sensorProperties.sensorLocationY
- sensorProperties.sensorRadius;
final int spacerHeight = sensorDistanceFromBottom
- textIndicatorHeightPx
- buttonBarHeightPx
- dialogMarginPx
- navbarBottomInsetPx;
Log.d(TAG, "Display height: " + displayHeightPx
+ ", Distance from bottom: " + sensorDistanceFromBottom
+ ", Bottom margin: " + dialogMarginPx
+ ", Navbar bottom inset: " + navbarBottomInsetPx
+ ", Bottom spacer height (portrait): " + spacerHeight);
return spacerHeight;
}
/**
* For devices in landscape orientation where the sensor is too high up, calculates the amount
* of padding necessary to center the biometric icon within the sensor's physical location.
*/
@VisibleForTesting
static int calculateBottomSpacerHeightForLandscape(int titleHeightPx, int subtitleHeightPx,
int descriptionHeightPx, int topSpacerHeightPx, int textIndicatorHeightPx,
int buttonBarHeightPx, int navbarBottomInsetPx) {
final int dialogHeightAboveIcon = titleHeightPx
+ subtitleHeightPx
+ descriptionHeightPx
+ topSpacerHeightPx;
final int dialogHeightBelowIcon = textIndicatorHeightPx + buttonBarHeightPx;
final int bottomSpacerHeight = dialogHeightAboveIcon
- dialogHeightBelowIcon
- navbarBottomInsetPx;
Log.d(TAG, "Title height: " + titleHeightPx
+ ", Subtitle height: " + subtitleHeightPx
+ ", Description height: " + descriptionHeightPx
+ ", Top spacer height: " + topSpacerHeightPx
+ ", Text indicator height: " + textIndicatorHeightPx
+ ", Button bar height: " + buttonBarHeightPx
+ ", Navbar bottom inset: " + navbarBottomInsetPx
+ ", Bottom spacer height (landscape): " + bottomSpacerHeight);
return bottomSpacerHeight;
}
/**
* For devices in landscape orientation where the sensor is too left/right, calculates the
* amount of padding necessary to center the biometric icon within the sensor's physical
* location.
*/
@VisibleForTesting
static int calculateHorizontalSpacerWidthForLandscape(
@NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayWidthPx,
int dialogMarginPx, int navbarHorizontalInsetPx) {
final int sensorDistanceFromEdge = displayWidthPx
- sensorProperties.sensorLocationY
- sensorProperties.sensorRadius;
final int horizontalPadding = sensorDistanceFromEdge
- dialogMarginPx
- navbarHorizontalInsetPx;
Log.d(TAG, "Display width: " + displayWidthPx
+ ", Distance from edge: " + sensorDistanceFromEdge
+ ", Dialog margin: " + dialogMarginPx
+ ", Navbar horizontal inset: " + navbarHorizontalInsetPx
+ ", Horizontal spacer width (landscape): " + horizontalPadding);
return horizontalPadding;
}
} }

View File

@@ -742,6 +742,7 @@ public abstract class AuthBiometricView extends LinearLayout {
* @param height Height to constrain the measurements to. * @param height Height to constrain the measurements to.
* @return See {@link AuthDialog.LayoutParams} * @return See {@link AuthDialog.LayoutParams}
*/ */
@NonNull
AuthDialog.LayoutParams onMeasureInternal(int width, int height) { AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
int totalHeight = 0; int totalHeight = 0;
final int numChildren = getChildCount(); final int numChildren = getChildCount();

View File

@@ -32,8 +32,10 @@ import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.os.UserManager; import android.os.UserManager;
import android.util.Log; import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Surface;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowInsets; import android.view.WindowInsets;
@@ -433,6 +435,29 @@ public class AuthContainerView extends LinearLayout
+ mConfig.mPromptInfo.getAuthenticators()); + mConfig.mPromptInfo.getAuthenticators());
} }
if (mBiometricView instanceof AuthBiometricUdfpsView) {
final int displayRotation = getDisplay().getRotation();
switch (displayRotation) {
case Surface.ROTATION_0:
mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
break;
case Surface.ROTATION_90:
mPanelController.setPosition(AuthPanelController.POSITION_RIGHT);
setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
break;
case Surface.ROTATION_270:
mPanelController.setPosition(AuthPanelController.POSITION_LEFT);
setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
break;
default:
Log.e(TAG, "Unsupported display rotation: " + displayRotation);
mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
break;
}
}
if (mConfig.mSkipIntro) { if (mConfig.mSkipIntro) {
mContainerState = STATE_SHOWING; mContainerState = STATE_SHOWING;
} else { } else {
@@ -476,6 +501,13 @@ public class AuthContainerView extends LinearLayout
} }
} }
private void setScrollViewGravity(int gravity) {
final FrameLayout.LayoutParams params =
(FrameLayout.LayoutParams) mBiometricScrollView.getLayoutParams();
params.gravity = gravity;
mBiometricScrollView.setLayoutParams(params);
}
@Override @Override
public void onDetachedFromWindow() { public void onDetachedFromWindow() {
super.onDetachedFromWindow(); super.onDetachedFromWindow();

View File

@@ -18,6 +18,7 @@ package com.android.systemui.biometrics;
import android.animation.AnimatorSet; import android.animation.AnimatorSet;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.content.Context; import android.content.Context;
import android.graphics.Outline; import android.graphics.Outline;
import android.util.Log; import android.util.Log;
@@ -27,10 +28,20 @@ import android.view.animation.AccelerateDecelerateInterpolator;
import com.android.systemui.R; import com.android.systemui.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* Controls the back panel and its animations for the BiometricPrompt UI. * Controls the back panel and its animations for the BiometricPrompt UI.
*/ */
public class AuthPanelController extends ViewOutlineProvider { public class AuthPanelController extends ViewOutlineProvider {
public static final int POSITION_BOTTOM = 1;
public static final int POSITION_LEFT = 2;
public static final int POSITION_RIGHT = 3;
@IntDef({POSITION_BOTTOM, POSITION_LEFT, POSITION_RIGHT})
@Retention(RetentionPolicy.SOURCE)
public @interface Position {}
private static final String TAG = "BiometricPrompt/AuthPanelController"; private static final String TAG = "BiometricPrompt/AuthPanelController";
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
@@ -38,6 +49,7 @@ public class AuthPanelController extends ViewOutlineProvider {
private final Context mContext; private final Context mContext;
private final View mPanelView; private final View mPanelView;
@Position private int mPosition = POSITION_BOTTOM;
private boolean mUseFullScreen; private boolean mUseFullScreen;
private int mContainerWidth; private int mContainerWidth;
@@ -51,21 +63,44 @@ public class AuthPanelController extends ViewOutlineProvider {
@Override @Override
public void getOutline(View view, Outline outline) { public void getOutline(View view, Outline outline) {
final int left = (mContainerWidth - mContentWidth) / 2; final int left = getLeftBound(mPosition);
final int right = mContainerWidth - left; final int right = left + mContentWidth;
// If the content fits within the container, shrink the height to wrap the content. // If the content fits in the container, shrink the height to wrap it. Otherwise, expand to
// Otherwise, set the outline to be the display size minus the margin - the content within // fill the display (minus the margin), since the content is scrollable.
// is scrollable. final int top = getTopBound(mPosition);
final int top = mContentHeight < mContainerHeight final int bottom = Math.min(top + mContentHeight, mContainerHeight - mMargin);
? mContainerHeight - mContentHeight - mMargin
: mMargin;
// TODO(b/139954942) Likely don't need to "+1" after we resolve the navbar styling.
final int bottom = mContainerHeight - mMargin + 1;
outline.setRoundRect(left, top, right, bottom, mCornerRadius); outline.setRoundRect(left, top, right, bottom, mCornerRadius);
} }
private int getLeftBound(@Position int position) {
switch (position) {
case POSITION_BOTTOM:
return (mContainerWidth - mContentWidth) / 2;
case POSITION_LEFT:
return mMargin;
case POSITION_RIGHT:
return mContainerWidth - mContentWidth - mMargin;
default:
Log.e(TAG, "Unrecognized position: " + position);
return getLeftBound(POSITION_BOTTOM);
}
}
private int getTopBound(@Position int position) {
switch (position) {
case POSITION_BOTTOM:
return Math.max(mContainerHeight - mContentHeight - mMargin, mMargin);
case POSITION_LEFT:
case POSITION_RIGHT:
return Math.max((mContainerHeight - mContentHeight) / 2, mMargin);
default:
Log.e(TAG, "Unrecognized position: " + position);
return getTopBound(POSITION_BOTTOM);
}
}
public void setContainerDimensions(int containerWidth, int containerHeight) { public void setContainerDimensions(int containerWidth, int containerHeight) {
if (DEBUG) { if (DEBUG) {
Log.v(TAG, "Container Width: " + containerWidth + " Height: " + containerHeight); Log.v(TAG, "Container Width: " + containerWidth + " Height: " + containerHeight);
@@ -74,6 +109,10 @@ public class AuthPanelController extends ViewOutlineProvider {
mContainerHeight = containerHeight; mContainerHeight = containerHeight;
} }
public void setPosition(@Position int position) {
mPosition = position;
}
public void setUseFullScreen(boolean fullScreen) { public void setUseFullScreen(boolean fullScreen) {
mUseFullScreen = fullScreen; mUseFullScreen = fullScreen;
} }

View File

@@ -22,10 +22,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context; import android.content.Context;
import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.PromptInfo;
@@ -333,16 +331,12 @@ public class AuthBiometricViewTest extends SysuiTestCase {
} }
@Test @Test
public void testUdfpsBottomSpacerCalculation() { public void testUdfpsBottomSpacerHeightForPortrait() {
final int displayHeightPx = 3000; final int displayHeightPx = 3000;
final int navbarHeightPx = 10; final int navbarHeightPx = 10;
final int dialogBottomMarginPx = 20; final int dialogBottomMarginPx = 20;
final int buttonBarHeightPx = 100;
final View buttonBar = mock(View.class); final int textIndicatorHeightPx = 200;
when(buttonBar.getMeasuredHeight()).thenReturn(100);
final View textIndicator = mock(View.class);
when(textIndicator.getMeasuredHeight()).thenReturn(200);
final int sensorLocationX = 540; final int sensorLocationX = 540;
final int sensorLocationY = 1600; final int sensorLocationY = 1600;
@@ -353,9 +347,47 @@ public class AuthBiometricViewTest extends SysuiTestCase {
true /* resetLockoutRequiresHardwareAuthToken */, sensorLocationX, sensorLocationY, true /* resetLockoutRequiresHardwareAuthToken */, sensorLocationX, sensorLocationY,
sensorRadius); sensorRadius);
assertEquals(970, AuthBiometricUdfpsView.calculateBottomSpacerHeight( assertEquals(970,
displayHeightPx, navbarHeightPx, dialogBottomMarginPx, buttonBar, textIndicator, AuthBiometricUdfpsView.calculateBottomSpacerHeightForPortrait(
props)); props, displayHeightPx, textIndicatorHeightPx, buttonBarHeightPx,
dialogBottomMarginPx, navbarHeightPx
));
}
@Test
public void testUdfpsBottomSpacerHeightForLandscape() {
final int titleHeightPx = 320;
final int subtitleHeightPx = 240;
final int descriptionHeightPx = 200;
final int topSpacerHeightPx = 550;
final int textIndicatorHeightPx = 190;
final int buttonBarHeightPx = 160;
final int navbarBottomInsetPx = 75;
assertEquals(885,
AuthBiometricUdfpsView.calculateBottomSpacerHeightForLandscape(
titleHeightPx, subtitleHeightPx, descriptionHeightPx, topSpacerHeightPx,
textIndicatorHeightPx, buttonBarHeightPx, navbarBottomInsetPx));
}
@Test
public void testUdfpsHorizontalSpacerWidthForLandscape() {
final int displayWidthPx = 3000;
final int dialogMarginPx = 20;
final int navbarHorizontalInsetPx = 75;
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(1205,
AuthBiometricUdfpsView.calculateHorizontalSpacerWidthForLandscape(
props, displayWidthPx, dialogMarginPx, navbarHorizontalInsetPx));
} }
private PromptInfo buildPromptInfo(boolean allowDeviceCredential) { private PromptInfo buildPromptInfo(boolean allowDeviceCredential) {