Merge "Fix BiometricPrompt layout for UDFPS in landscape" into sc-dev

This commit is contained in:
Curtis Belmonte
2021-07-01 20:57:29 +00:00
committed by Android (Google) Code Review
7 changed files with 194 additions and 48 deletions

View File

@@ -19,17 +19,19 @@ package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R; import com.android.systemui.R;
@@ -87,11 +89,9 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
} }
} }
@Modality @Modality private int mActiveSensorType = TYPE_FACE;
private int mActiveSensorType = TYPE_FACE; @Nullable private FingerprintSensorPropertiesInternal mFingerprintSensorProps;
@Nullable private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter;
@Nullable
private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter;
public AuthBiometricFaceToFingerprintView(Context context) { public AuthBiometricFaceToFingerprintView(Context context) {
super(context); super(context);
@@ -106,14 +106,17 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
super(context, attrs, injector); super(context, attrs, injector);
} }
void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) { @Modality
if (!sensorProps.isAnyUdfpsType()) { int getActiveSensorType() {
return; return mActiveSensorType;
} }
if (mUdfpsMeasureAdapter == null || mUdfpsMeasureAdapter.getSensorProps() != sensorProps) { boolean isFingerprintUdfps() {
mUdfpsMeasureAdapter = new UdfpsDialogMeasureAdapter(this, sensorProps); return mFingerprintSensorProps.isAnyUdfpsType();
} }
void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
mFingerprintSensorProps = sensorProps;
} }
@Override @Override
@@ -193,8 +196,34 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
@NonNull @NonNull
AuthDialog.LayoutParams onMeasureInternal(int width, int height) { AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height); final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height);
return mUdfpsMeasureAdapter != null return isFingerprintUdfps()
? mUdfpsMeasureAdapter.onMeasureInternal(width, height, layoutParams) ? getUdfpsMeasureAdapter().onMeasureInternal(width, height, layoutParams)
: layoutParams; : layoutParams;
} }
@NonNull
private UdfpsDialogMeasureAdapter getUdfpsMeasureAdapter() {
if (mUdfpsMeasureAdapter == null
|| mUdfpsMeasureAdapter.getSensorProps() != mFingerprintSensorProps) {
mUdfpsMeasureAdapter = new UdfpsDialogMeasureAdapter(this, mFingerprintSensorProps);
}
return mUdfpsMeasureAdapter;
}
@Override
public void onSaveState(@NonNull Bundle outState) {
super.onSaveState(outState);
outState.putInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, mActiveSensorType);
outState.putParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS, mFingerprintSensorProps);
}
@Override
public void restoreState(@Nullable Bundle savedState) {
super.restoreState(savedState);
if (savedState != null) {
mActiveSensorType = savedState.getInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, TYPE_FACE);
mFingerprintSensorProps =
savedState.getParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS);
}
}
} }

View File

@@ -593,11 +593,13 @@ public abstract class AuthBiometricView extends LinearLayout {
} }
public void onSaveState(@NonNull Bundle outState) { public void onSaveState(@NonNull Bundle outState) {
outState.putInt(AuthDialog.KEY_BIOMETRIC_CONFIRM_VISIBILITY,
mConfirmButton.getVisibility());
outState.putInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY, outState.putInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY,
mTryAgainButton.getVisibility()); mTryAgainButton.getVisibility());
outState.putInt(AuthDialog.KEY_BIOMETRIC_STATE, mState); outState.putInt(AuthDialog.KEY_BIOMETRIC_STATE, mState);
outState.putString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING, outState.putString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING,
mIndicatorView.getText().toString()); mIndicatorView.getText() != null ? mIndicatorView.getText().toString() : "");
outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING, outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING,
mHandler.hasCallbacks(mResetErrorRunnable)); mHandler.hasCallbacks(mResetErrorRunnable));
outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_HELP_SHOWING, outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_HELP_SHOWING,
@@ -754,9 +756,12 @@ public abstract class AuthBiometricView extends LinearLayout {
// Restore as much state as possible first // Restore as much state as possible first
updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE)); updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
// Restore positive button state // Restore positive button(s) state
mConfirmButton.setVisibility(
mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_CONFIRM_VISIBILITY));
mTryAgainButton.setVisibility( mTryAgainButton.setVisibility(
mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY)); mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
} }
} }

View File

@@ -16,6 +16,7 @@
package com.android.systemui.biometrics; package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.annotation.IntDef; import android.annotation.IntDef;
@@ -23,6 +24,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.PromptInfo;
@@ -487,31 +489,8 @@ public class AuthContainerView extends LinearLayout
+ mConfig.mPromptInfo.getAuthenticators()); + mConfig.mPromptInfo.getAuthenticators());
} }
if (mBiometricView instanceof AuthBiometricUdfpsView) { if (shouldUpdatePositionForUdfps()) {
final int displayRotation = getDisplay().getRotation(); updatePositionForUdfps();
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;
case Surface.ROTATION_180:
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) {
@@ -557,6 +536,48 @@ public class AuthContainerView extends LinearLayout
} }
} }
private boolean shouldUpdatePositionForUdfps() {
if (mBiometricView instanceof AuthBiometricUdfpsView) {
return true;
}
if (mBiometricView instanceof AuthBiometricFaceToFingerprintView) {
AuthBiometricFaceToFingerprintView faceToFingerprintView =
(AuthBiometricFaceToFingerprintView) mBiometricView;
return faceToFingerprintView.getActiveSensorType() == TYPE_FINGERPRINT
&& faceToFingerprintView.isFingerprintUdfps();
}
return false;
}
private void updatePositionForUdfps() {
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;
case Surface.ROTATION_180:
default:
Log.e(TAG, "Unsupported display rotation: " + displayRotation);
mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
break;
}
}
private void setScrollViewGravity(int gravity) { private void setScrollViewGravity(int gravity) {
final FrameLayout.LayoutParams params = final FrameLayout.LayoutParams params =
(FrameLayout.LayoutParams) mBiometricScrollView.getLayoutParams(); (FrameLayout.LayoutParams) mBiometricScrollView.getLayoutParams();
@@ -605,6 +626,13 @@ public class AuthContainerView extends LinearLayout
@Override @Override
public void onAuthenticationFailed(@Modality int modality, String failureReason) { public void onAuthenticationFailed(@Modality int modality, String failureReason) {
mBiometricView.onAuthenticationFailed(modality, failureReason); mBiometricView.onAuthenticationFailed(modality, failureReason);
if (mBiometricView instanceof AuthBiometricFaceToFingerprintView
&& ((AuthBiometricFaceToFingerprintView) mBiometricView).isFingerprintUdfps()
&& modality == BiometricAuthenticator.TYPE_FACE) {
updatePositionForUdfps();
mPanelView.invalidateOutline();
mBiometricView.requestLayout();
}
} }
@Override @Override

View File

@@ -35,6 +35,7 @@ public interface AuthDialog {
String KEY_BIOMETRIC_SHOWING = "biometric_showing"; String KEY_BIOMETRIC_SHOWING = "biometric_showing";
String KEY_CREDENTIAL_SHOWING = "credential_showing"; String KEY_CREDENTIAL_SHOWING = "credential_showing";
String KEY_BIOMETRIC_CONFIRM_VISIBILITY = "confirm_visibility";
String KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY = "try_agian_visibility"; String KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY = "try_agian_visibility";
String KEY_BIOMETRIC_STATE = "state"; String KEY_BIOMETRIC_STATE = "state";
String KEY_BIOMETRIC_INDICATOR_STRING = "indicator_string"; // error / help / hint String KEY_BIOMETRIC_INDICATOR_STRING = "indicator_string"; // error / help / hint
@@ -42,6 +43,9 @@ public interface AuthDialog {
String KEY_BIOMETRIC_INDICATOR_HELP_SHOWING = "hint_is_temporary"; String KEY_BIOMETRIC_INDICATOR_HELP_SHOWING = "hint_is_temporary";
String KEY_BIOMETRIC_DIALOG_SIZE = "size"; String KEY_BIOMETRIC_DIALOG_SIZE = "size";
String KEY_BIOMETRIC_SENSOR_TYPE = "sensor_type";
String KEY_BIOMETRIC_SENSOR_PROPS = "sensor_props";
int SIZE_UNKNOWN = 0; int SIZE_UNKNOWN = 0;
/** /**
* Minimal UI, showing only biometric icon. * Minimal UI, showing only biometric icon.

View File

@@ -180,17 +180,25 @@ public class UdfpsDialogMeasureAdapter {
iconFrame.measure( iconFrame.measure(
MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY)); MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY));
} else if (child.getId() == R.id.space_above_icon || child.getId() == R.id.button_bar) { } else if (child.getId() == R.id.space_above_icon) {
// Adjust the width of the top spacer and button bar while preserving their heights. // Adjust the width and height of the top spacer if necessary.
final int newTopSpacerHeight = child.getLayoutParams().height
- Math.min(bottomSpacerHeight, 0);
child.measure(
MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(newTopSpacerHeight, MeasureSpec.EXACTLY));
} else if (child.getId() == R.id.button_bar) {
// Adjust the width of the button bar while preserving its height.
child.measure( child.measure(
MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec( MeasureSpec.makeMeasureSpec(
child.getLayoutParams().height, MeasureSpec.EXACTLY)); child.getLayoutParams().height, MeasureSpec.EXACTLY));
} else if (child.getId() == R.id.space_below_icon) { } else if (child.getId() == R.id.space_below_icon) {
// Adjust the bottom spacer height to align the fingerprint icon with the sensor. // Adjust the bottom spacer height to align the fingerprint icon with the sensor.
final int newBottomSpacerHeight = Math.max(bottomSpacerHeight, 0);
child.measure( child.measure(
MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(bottomSpacerHeight, MeasureSpec.EXACTLY)); MeasureSpec.makeMeasureSpec(newBottomSpacerHeight, MeasureSpec.EXACTLY));
} else { } else {
// Use the remeasured width for all other child views. // Use the remeasured width for all other child views.
child.measure( child.measure(
@@ -208,7 +216,7 @@ public class UdfpsDialogMeasureAdapter {
private int getViewHeightPx(@IdRes int viewId) { private int getViewHeightPx(@IdRes int viewId) {
final View view = mView.findViewById(viewId); final View view = mView.findViewById(viewId);
return view != null ? view.getMeasuredHeight() : 0; return view != null && view.getVisibility() != View.GONE ? view.getMeasuredHeight() : 0;
} }
private int getDialogMarginPx() { private int getDialogMarginPx() {

View File

@@ -17,8 +17,10 @@
package com.android.systemui.biometrics; package com.android.systemui.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
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.inOrder; import static org.mockito.Mockito.inOrder;
@@ -27,6 +29,11 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import android.content.Context; import android.content.Context;
import android.hardware.biometrics.ComponentInfoInternal;
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.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper; import android.testing.TestableLooper;
@@ -35,6 +42,8 @@ import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
@@ -45,6 +54,9 @@ import org.mockito.InOrder;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
@RunWith(AndroidTestingRunner.class) @RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper @TestableLooper.RunWithLooper
@SmallTest @SmallTest
@@ -170,6 +182,50 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR)); eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR));
} }
@Test
public void testOnSaveState() {
final FingerprintSensorPropertiesInternal sensorProps = createFingerprintSensorProps();
mFaceToFpView.setFingerprintSensorProps(sensorProps);
final Bundle savedState = new Bundle();
mFaceToFpView.onSaveState(savedState);
assertEquals(savedState.getInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE),
mFaceToFpView.getActiveSensorType());
assertEquals(savedState.getParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS), sensorProps);
}
@Test
public void testRestoreState() {
final Bundle savedState = new Bundle();
savedState.putInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, TYPE_FINGERPRINT);
savedState.putParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS,
createFingerprintSensorProps());
mFaceToFpView.restoreState(savedState);
assertEquals(mFaceToFpView.getActiveSensorType(), TYPE_FINGERPRINT);
assertTrue(mFaceToFpView.isFingerprintUdfps());
}
@NonNull
private static FingerprintSensorPropertiesInternal createFingerprintSensorProps() {
final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
componentInfo.add(new ComponentInfoInternal("componentId", "hardwareVersion",
"firmwareVersion", "serialNumber", "softwareVersion"));
return new FingerprintSensorPropertiesInternal(
0 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
5 /* maxEnrollmentsPerUser */,
componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
true /* resetLockoutRequiresHardwareAuthToken */,
540 /* sensorLocationX */,
1600 /* sensorLocationY */,
100 /* sensorRadius */);
}
public class TestableView extends AuthBiometricFaceToFingerprintView { public class TestableView extends AuthBiometricFaceToFingerprintView {
public TestableView(Context context) { public TestableView(Context context) {
super(context, null, new MockInjector()); super(context, null, new MockInjector());

View File

@@ -72,7 +72,7 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase {
} }
@Test @Test
public void testUdfpsBottomSpacerHeightForLandscape() { public void testUdfpsBottomSpacerHeightForLandscape_whenMoreSpaceAboveIcon() {
final int titleHeightPx = 320; final int titleHeightPx = 320;
final int subtitleHeightPx = 240; final int subtitleHeightPx = 240;
final int descriptionHeightPx = 200; final int descriptionHeightPx = 200;
@@ -87,6 +87,22 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase {
textIndicatorHeightPx, buttonBarHeightPx, navbarBottomInsetPx)); textIndicatorHeightPx, buttonBarHeightPx, navbarBottomInsetPx));
} }
@Test
public void testUdfpsBottomSpacerHeightForLandscape_whenMoreSpaceBelowIcon() {
final int titleHeightPx = 315;
final int subtitleHeightPx = 160;
final int descriptionHeightPx = 75;
final int topSpacerHeightPx = 220;
final int textIndicatorHeightPx = 290;
final int buttonBarHeightPx = 360;
final int navbarBottomInsetPx = 205;
assertEquals(-85,
UdfpsDialogMeasureAdapter.calculateBottomSpacerHeightForLandscape(
titleHeightPx, subtitleHeightPx, descriptionHeightPx, topSpacerHeightPx,
textIndicatorHeightPx, buttonBarHeightPx, navbarBottomInsetPx));
}
@Test @Test
public void testUdfpsHorizontalSpacerWidthForLandscape() { public void testUdfpsHorizontalSpacerWidthForLandscape() {
final int displayWidthPx = 3000; final int displayWidthPx = 3000;