Merge "Fix BiometricPrompt layout for UDFPS in landscape" into sc-dev am: 9dff4b394f
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15175065 Change-Id: I2454ccc3cadc8e17b01a9f74a03c8beedb06748b
This commit is contained in:
@@ -19,17 +19,19 @@ package com.android.systemui.biometrics;
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.hardware.biometrics.BiometricAuthenticator.Modality;
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.systemui.R;
|
||||
|
||||
@@ -87,11 +89,9 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
|
||||
}
|
||||
}
|
||||
|
||||
@Modality
|
||||
private int mActiveSensorType = TYPE_FACE;
|
||||
|
||||
@Nullable
|
||||
private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter;
|
||||
@Modality private int mActiveSensorType = TYPE_FACE;
|
||||
@Nullable private FingerprintSensorPropertiesInternal mFingerprintSensorProps;
|
||||
@Nullable private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter;
|
||||
|
||||
public AuthBiometricFaceToFingerprintView(Context context) {
|
||||
super(context);
|
||||
@@ -106,14 +106,17 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
|
||||
super(context, attrs, injector);
|
||||
}
|
||||
|
||||
void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
|
||||
if (!sensorProps.isAnyUdfpsType()) {
|
||||
return;
|
||||
}
|
||||
@Modality
|
||||
int getActiveSensorType() {
|
||||
return mActiveSensorType;
|
||||
}
|
||||
|
||||
if (mUdfpsMeasureAdapter == null || mUdfpsMeasureAdapter.getSensorProps() != sensorProps) {
|
||||
mUdfpsMeasureAdapter = new UdfpsDialogMeasureAdapter(this, sensorProps);
|
||||
}
|
||||
boolean isFingerprintUdfps() {
|
||||
return mFingerprintSensorProps.isAnyUdfpsType();
|
||||
}
|
||||
|
||||
void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
|
||||
mFingerprintSensorProps = sensorProps;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -193,8 +196,34 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
|
||||
@NonNull
|
||||
AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
|
||||
final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height);
|
||||
return mUdfpsMeasureAdapter != null
|
||||
? mUdfpsMeasureAdapter.onMeasureInternal(width, height, layoutParams)
|
||||
return isFingerprintUdfps()
|
||||
? getUdfpsMeasureAdapter().onMeasureInternal(width, height, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,11 +593,13 @@ public abstract class AuthBiometricView extends LinearLayout {
|
||||
}
|
||||
|
||||
public void onSaveState(@NonNull Bundle outState) {
|
||||
outState.putInt(AuthDialog.KEY_BIOMETRIC_CONFIRM_VISIBILITY,
|
||||
mConfirmButton.getVisibility());
|
||||
outState.putInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY,
|
||||
mTryAgainButton.getVisibility());
|
||||
outState.putInt(AuthDialog.KEY_BIOMETRIC_STATE, mState);
|
||||
outState.putString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING,
|
||||
mIndicatorView.getText().toString());
|
||||
mIndicatorView.getText() != null ? mIndicatorView.getText().toString() : "");
|
||||
outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING,
|
||||
mHandler.hasCallbacks(mResetErrorRunnable));
|
||||
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
|
||||
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(
|
||||
mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.systemui.biometrics;
|
||||
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
|
||||
import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
@@ -23,6 +24,7 @@ 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.BiometricAuthenticator.Modality;
|
||||
import android.hardware.biometrics.BiometricConstants;
|
||||
import android.hardware.biometrics.PromptInfo;
|
||||
@@ -487,31 +489,8 @@ public class AuthContainerView extends LinearLayout
|
||||
+ 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;
|
||||
|
||||
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 (shouldUpdatePositionForUdfps()) {
|
||||
updatePositionForUdfps();
|
||||
}
|
||||
|
||||
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) {
|
||||
final FrameLayout.LayoutParams params =
|
||||
(FrameLayout.LayoutParams) mBiometricScrollView.getLayoutParams();
|
||||
@@ -605,6 +626,13 @@ public class AuthContainerView extends LinearLayout
|
||||
@Override
|
||||
public void onAuthenticationFailed(@Modality int modality, String failureReason) {
|
||||
mBiometricView.onAuthenticationFailed(modality, failureReason);
|
||||
if (mBiometricView instanceof AuthBiometricFaceToFingerprintView
|
||||
&& ((AuthBiometricFaceToFingerprintView) mBiometricView).isFingerprintUdfps()
|
||||
&& modality == BiometricAuthenticator.TYPE_FACE) {
|
||||
updatePositionForUdfps();
|
||||
mPanelView.invalidateOutline();
|
||||
mBiometricView.requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,6 +35,7 @@ public interface AuthDialog {
|
||||
String KEY_BIOMETRIC_SHOWING = "biometric_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_STATE = "state";
|
||||
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_DIALOG_SIZE = "size";
|
||||
|
||||
String KEY_BIOMETRIC_SENSOR_TYPE = "sensor_type";
|
||||
String KEY_BIOMETRIC_SENSOR_PROPS = "sensor_props";
|
||||
|
||||
int SIZE_UNKNOWN = 0;
|
||||
/**
|
||||
* Minimal UI, showing only biometric icon.
|
||||
|
||||
@@ -180,17 +180,25 @@ public class UdfpsDialogMeasureAdapter {
|
||||
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.
|
||||
} else if (child.getId() == R.id.space_above_icon) {
|
||||
// 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(
|
||||
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.
|
||||
final int newBottomSpacerHeight = Math.max(bottomSpacerHeight, 0);
|
||||
child.measure(
|
||||
MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(bottomSpacerHeight, MeasureSpec.EXACTLY));
|
||||
MeasureSpec.makeMeasureSpec(newBottomSpacerHeight, MeasureSpec.EXACTLY));
|
||||
} else {
|
||||
// Use the remeasured width for all other child views.
|
||||
child.measure(
|
||||
@@ -208,7 +216,7 @@ public class UdfpsDialogMeasureAdapter {
|
||||
|
||||
private int getViewHeightPx(@IdRes int 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() {
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
package com.android.systemui.biometrics;
|
||||
|
||||
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.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
@@ -27,6 +29,11 @@ import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
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.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
@@ -35,6 +42,8 @@ import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
@@ -45,6 +54,9 @@ import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
@SmallTest
|
||||
@@ -170,6 +182,50 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
|
||||
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 TestableView(Context context) {
|
||||
super(context, null, new MockInjector());
|
||||
|
||||
@@ -72,7 +72,7 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUdfpsBottomSpacerHeightForLandscape() {
|
||||
public void testUdfpsBottomSpacerHeightForLandscape_whenMoreSpaceAboveIcon() {
|
||||
final int titleHeightPx = 320;
|
||||
final int subtitleHeightPx = 240;
|
||||
final int descriptionHeightPx = 200;
|
||||
@@ -87,6 +87,22 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase {
|
||||
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
|
||||
public void testUdfpsHorizontalSpacerWidthForLandscape() {
|
||||
final int displayWidthPx = 3000;
|
||||
|
||||
Reference in New Issue
Block a user