13/n: persist device credential across configuration changes
1) AuthContainerView can be started in either `biometric` or `credential`
views. This is to potentially support an API where only credential
is allowed/requested.
2) When onSaveState, AuthContainerView saves both the states of
`biometric` and `credential` visibility. In the case of configuration
change, AuthController is responsibile for checking the visibility
and creating a AuthContainerView with the correct initial view.
3) When AuthCredentialView is the initial view, it owns the panel
expansion.
4) Added landscape layout
Bug: 140127687
Test: atest com.android.systemui.biometrics
Test: BiometricPromptDemo, use pattern, rotate device. auth, cancel,
demo logs are correct.
Change-Id: I1f501cf13b924353f251a69757fdb9d7e0bf1d21
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index db23e62..a0ce67d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -107,11 +107,25 @@
String mOpPackageName;
int mModalityMask;
boolean mSkipIntro;
+ @Builder.InitialView int mInitialView;
}
public static class Builder {
Config mConfig;
+ /**
+ * Start the prompt with biometric UI. May flow to credential view if
+ * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} is set to true.
+ */
+ public static final int INITIAL_VIEW_BIOMETRIC = 1;
+ /**
+ * Start the prompt with credential UI
+ */
+ public static final int INITIAL_VIEW_CREDENTIAL = 2;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({INITIAL_VIEW_BIOMETRIC, INITIAL_VIEW_CREDENTIAL})
+ @interface InitialView {}
+
public Builder(Context context) {
mConfig = new Config();
mConfig.mContext = context;
@@ -147,6 +161,11 @@
return this;
}
+ public Builder setInitialView(@InitialView int initialView) {
+ mConfig.mInitialView = initialView;
+ return this;
+ }
+
public AuthContainerView build(int modalityMask) {
mConfig.mModalityMask = modalityMask;
return new AuthContainerView(mConfig);
@@ -175,7 +194,7 @@
break;
case AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL:
mConfig.mCallback.onDeviceCredentialPressed();
- showCredentialView();
+ addCredentialView(false /* animatePanel */);
break;
default:
Log.e(TAG, "Unhandled action: " + action);
@@ -226,6 +245,7 @@
return;
}
+ mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
mBackgroundView = mFrameLayout.findViewById(R.id.background);
UserManager userManager = mContext.getSystemService(UserManager.class);
@@ -239,15 +259,6 @@
mBackgroundView.setImageDrawable(image);
}
- mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
- mBiometricView.setPanelController(mPanelController);
- mBiometricView.setBiometricPromptBundle(config.mBiometricPromptBundle);
- mBiometricView.setCallback(mBiometricCallback);
- mBiometricView.setBackgroundView(mBackgroundView);
- mBiometricView.setUserId(mConfig.mUserId);
-
- mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview);
- mBiometricScrollView.addView(mBiometricView);
addView(mFrameLayout);
setOnKeyListener((v, keyCode, event) -> {
@@ -264,13 +275,30 @@
requestFocus();
}
- private void showCredentialView() {
+ private void addBiometricView() {
+ mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
+ mBiometricView.setPanelController(mPanelController);
+ mBiometricView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle);
+ mBiometricView.setCallback(mBiometricCallback);
+ mBiometricView.setBackgroundView(mBackgroundView);
+ mBiometricView.setUserId(mConfig.mUserId);
+ mBiometricScrollView.addView(mBiometricView);
+ }
+
+ /**
+ * Adds the credential view. When going from biometric to credential view, the biometric
+ * view starts the panel expansion animation. If the credential view is being shown first,
+ * it should own the panel expansion.
+ * @param animatePanel if the credential view needs to own the panel expansion animation
+ */
+ private void addCredentialView(boolean animatePanel) {
final LayoutInflater factory = LayoutInflater.from(mContext);
mCredentialView = (AuthCredentialView) factory.inflate(
R.layout.auth_credential_view, null, false);
mCredentialView.setUser(mConfig.mUserId);
mCredentialView.setCallback(mCredentialCallback);
mCredentialView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle);
+ mCredentialView.setPanelController(mPanelController, animatePanel);
mFrameLayout.addView(mCredentialView);
}
@@ -285,6 +313,21 @@
super.onAttachedToWindow();
mWakefulnessLifecycle.addObserver(this);
+ Log.v(TAG, "Initial view: " + mConfig.mInitialView);
+
+ switch (mConfig.mInitialView) {
+ case Builder.INITIAL_VIEW_BIOMETRIC:
+ addBiometricView();
+ break;
+ case Builder.INITIAL_VIEW_CREDENTIAL:
+ addCredentialView(true /* animatePanel */);
+ break;
+ default:
+ Log.e(TAG, "Initial view not supported: " + mConfig.mInitialView);
+ break;
+ }
+
+
if (mConfig.mSkipIntro) {
mContainerState = STATE_SHOWING;
} else {
@@ -373,6 +416,11 @@
@Override
public void onSaveState(@NonNull Bundle outState) {
outState.putInt(AuthDialog.KEY_CONTAINER_STATE, mContainerState);
+ // In the case where biometric and credential are both allowed, we can assume that
+ // biometric isn't showing if credential is showing since biometric is shown first.
+ outState.putBoolean(AuthDialog.KEY_BIOMETRIC_SHOWING,
+ mBiometricView != null && mCredentialView == null);
+ outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null);
mBiometricView.onSaveState(outState);
}
@@ -441,11 +489,6 @@
});
}
- private boolean isDeviceCredentialAllowed() {
- return mConfig.mBiometricPromptBundle.getBoolean(
- BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false);
- }
-
private void sendPendingCallbackIfNotNull() {
Log.d(TAG, "pendingCallback: " + mPendingCallbackReason);
if (mPendingCallbackReason != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index eb87834..1e8c26d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -218,7 +218,8 @@
Log.w(TAG, "mCurrentDialog: " + mCurrentDialog);
skipAnimation = true;
}
- showDialog(args, skipAnimation, null /* savedState */);
+ showDialog(args, skipAnimation, null /* savedState */,
+ AuthContainerView.Builder.INITIAL_VIEW_BIOMETRIC);
}
@Override
@@ -253,7 +254,8 @@
mCurrentDialog.dismissFromSystemServer();
}
- private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
+ private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState,
+ @AuthContainerView.Builder.InitialView int initialView) {
mCurrentDialogArgs = args;
final int type = args.argi1;
final Bundle biometricPromptBundle = (Bundle) args.arg1;
@@ -268,7 +270,8 @@
userId,
type,
opPackageName,
- skipAnimation);
+ skipAnimation,
+ initialView);
if (newDialog == null) {
Log.e(TAG, "Unsupported type: " + type);
@@ -280,7 +283,8 @@
+ " savedState: " + savedState
+ " mCurrentDialog: " + mCurrentDialog
+ " newDialog: " + newDialog
- + " type: " + type);
+ + " type: " + type
+ + " initialView: " + initialView);
}
if (mCurrentDialog != null) {
@@ -320,13 +324,23 @@
// to send its pending callback immediately.
if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE)
!= AuthContainerView.STATE_ANIMATING_OUT) {
- showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
+ final boolean credentialShowing =
+ savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING);
+
+ // We can assume if credential is showing, then biometric doesn't need to be shown,
+ // since credential is always after biometric.
+ final int initialView = credentialShowing
+ ? AuthContainerView.Builder.INITIAL_VIEW_CREDENTIAL
+ : AuthContainerView.Builder.INITIAL_VIEW_BIOMETRIC;
+
+ showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState, initialView);
}
}
}
protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation,
- int userId, int type, String opPackageName, boolean skipIntro) {
+ int userId, int type, String opPackageName, boolean skipIntro,
+ @AuthContainerView.Builder.InitialView int initialView) {
return new AuthContainerView.Builder(mContext)
.setCallback(this)
.setBiometricPromptBundle(biometricPromptBundle)
@@ -334,6 +348,7 @@
.setUserId(userId)
.setOpPackageName(opPackageName)
.setSkipIntro(skipIntro)
+ .setInitialView(initialView)
.build(type);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index dabe84b..8eac8f5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -57,6 +57,8 @@
private Callback mCallback;
private ErrorTimer mErrorTimer;
private Bundle mBiometricPromptBundle;
+ private AuthPanelController mPanelController;
+ private boolean mShouldAnimatePanel;
private TextView mTitleView;
private TextView mSubtitleView;
@@ -213,6 +215,11 @@
mBiometricPromptBundle = bundle;
}
+ void setPanelController(AuthPanelController panelController, boolean animatePanel) {
+ mPanelController = panelController;
+ mShouldAnimatePanel = animatePanel;
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -258,4 +265,17 @@
mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
}
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (mShouldAnimatePanel) {
+ // Credential view is always full screen.
+ mPanelController.setUseFullScreen(true);
+ mPanelController.updateForContentDimensions(mPanelController.getContainerWidth(),
+ mPanelController.getContainerHeight(), 0 /* animateDurationMs */);
+ mShouldAnimatePanel = false;
+ }
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index 29b84bb..461f237 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -31,6 +31,8 @@
public interface AuthDialog {
String KEY_CONTAINER_STATE = "container_state";
+ String KEY_BIOMETRIC_SHOWING = "biometric_showing";
+ String KEY_CREDENTIAL_SHOWING = "credential_showing";
String KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY = "try_agian_visibility";
String KEY_BIOMETRIC_STATE = "state";
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
index 27040f0..18c1b4b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
@@ -90,18 +90,19 @@
return;
}
+ final int margin = mUseFullScreen ? 0 : (int) mContext.getResources()
+ .getDimension(R.dimen.biometric_dialog_border_padding);
+ final float cornerRadius = mUseFullScreen ? 0 : mContext.getResources()
+ .getDimension(R.dimen.biometric_dialog_corner_size);
+
if (animateDurationMs > 0) {
// Animate margin
- final int margin = mUseFullScreen ? 0 : (int) mContext.getResources()
- .getDimension(R.dimen.biometric_dialog_border_padding);
ValueAnimator marginAnimator = ValueAnimator.ofInt(mMargin, margin);
marginAnimator.addUpdateListener((animation) -> {
mMargin = (int) animation.getAnimatedValue();
});
// Animate corners
- final float cornerRadius = mUseFullScreen ? 0 : mContext.getResources()
- .getDimension(R.dimen.biometric_dialog_corner_size);
ValueAnimator cornerAnimator = ValueAnimator.ofFloat(mCornerRadius, cornerRadius);
cornerAnimator.addUpdateListener((animation) -> {
mCornerRadius = (float) animation.getAnimatedValue();
@@ -127,6 +128,8 @@
as.playTogether(cornerAnimator, heightAnimator, widthAnimator, marginAnimator);
as.start();
} else {
+ mMargin = margin;
+ mCornerRadius = cornerRadius;
mContentWidth = contentWidth;
mContentHeight = contentHeight;
mPanelView.invalidateOutline();