diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java index 76fb49a730a35..c4f58806c26c8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java @@ -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); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index f37495ef5f481..d5f74951c6f74 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -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)); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index c4f78e7782a21..fd1313fc9d1d9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java index cbd166e6e0b1c..ff31e499f6e8e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java @@ -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. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java index 1ad2b9ca856cd..7ccfb865cd5a2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java @@ -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() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java index 82bf041e42729..fcdf702495d27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java @@ -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 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()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java index ee13d23662851..88b4039fd2cd8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java @@ -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;