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();