Pass operationId to LSS, add HAT to KeyStore

Since we're now allowing auth-per-use credential keys, we need to
pass the operationId to SystemUI, which owns the call to verify
credential. Upon receiving a non-null HAT, it should be sent to
KeyStore.

Bug: 148425329

Test: atest com.android.systemui.biometrics
Test: atest com.android.server.biometrics

Change-Id: Iea737bf2dc0d81d87419df96d5cb43d51f10c6e3
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index cbb1982..0018d33 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -99,6 +99,8 @@
 
     // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
     @Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason;
+    // HAT received from LockSettingsService when credential is verified.
+    @Nullable byte[] mCredentialAttestation;
 
     static class Config {
         Context mContext;
@@ -109,6 +111,7 @@
         String mOpPackageName;
         int mModalityMask;
         boolean mSkipIntro;
+        long mOperationId;
     }
 
     public static class Builder {
@@ -149,6 +152,11 @@
             return this;
         }
 
+        public Builder setOperationId(long operationId) {
+            mConfig.mOperationId = operationId;
+            return this;
+        }
+
         public AuthContainerView build(int modalityMask) {
             mConfig.mModalityMask = modalityMask;
             return new AuthContainerView(mConfig, new Injector());
@@ -224,7 +232,8 @@
 
     final class CredentialCallback implements AuthCredentialView.Callback {
         @Override
-        public void onCredentialMatched() {
+        public void onCredentialMatched(byte[] attestation) {
+            mCredentialAttestation = attestation;
             animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
         }
     }
@@ -341,6 +350,7 @@
 
         mCredentialView.setContainerView(this);
         mCredentialView.setUserId(mConfig.mUserId);
+        mCredentialView.setOperationId(mConfig.mOperationId);
         mCredentialView.setEffectiveUserId(mEffectiveUserId);
         mCredentialView.setCredentialType(credentialType);
         mCredentialView.setCallback(mCredentialCallback);
@@ -558,7 +568,7 @@
     private void sendPendingCallbackIfNotNull() {
         Log.d(TAG, "pendingCallback: " + mPendingCallbackReason);
         if (mPendingCallbackReason != null) {
-            mConfig.mCallback.onDismissed(mPendingCallbackReason);
+            mConfig.mCallback.onDismissed(mPendingCallbackReason, mCredentialAttestation);
             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 6149b0b..c30477c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -20,6 +20,7 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
@@ -99,7 +100,8 @@
 
                 try {
                     if (mReceiver != null) {
-                        mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
+                        mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
+                                null /* credentialAttestation */);
                         mReceiver = null;
                     }
                 } catch (RemoteException e) {
@@ -124,7 +126,8 @@
                         mCurrentDialog = null;
                         if (mReceiver != null) {
                             mReceiver.onDialogDismissed(
-                                    BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
+                                    BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
+                                    null /* credentialAttestation */);
                             mReceiver = null;
                         }
                     }
@@ -162,35 +165,42 @@
     }
 
     @Override
-    public void onDismissed(@DismissedReason int reason) {
+    public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation) {
         switch (reason) {
             case AuthDialogCallback.DISMISSED_USER_CANCELED:
-                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
+                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
+                        credentialAttestation);
                 break;
 
             case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE:
-                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
+                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE,
+                        credentialAttestation);
                 break;
 
             case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE:
-                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
+                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED,
+                        credentialAttestation);
                 break;
 
             case AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED:
                 sendResultAndCleanUp(
-                        BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED);
+                        BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED,
+                        credentialAttestation);
                 break;
 
             case AuthDialogCallback.DISMISSED_ERROR:
-                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR);
+                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR,
+                        credentialAttestation);
                 break;
 
             case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER:
-                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
+                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED,
+                        credentialAttestation);
                 break;
 
             case AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED:
-                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED);
+                sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED,
+                        credentialAttestation);
                 break;
 
             default:
@@ -199,13 +209,14 @@
         }
     }
 
-    private void sendResultAndCleanUp(@DismissedReason int reason) {
+    private void sendResultAndCleanUp(@DismissedReason int reason,
+            @Nullable byte[] credentialAttestation) {
         if (mReceiver == null) {
             Log.e(TAG, "sendResultAndCleanUp: Receiver is null");
             return;
         }
         try {
-            mReceiver.onDialogDismissed(reason);
+            mReceiver.onDialogDismissed(reason, credentialAttestation);
         } catch (RemoteException e) {
             Log.w(TAG, "Remote exception", e);
         }
@@ -251,13 +262,15 @@
 
     @Override
     public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
-            int biometricModality, boolean requireConfirmation, int userId, String opPackageName) {
+            int biometricModality, boolean requireConfirmation, int userId, String opPackageName,
+            long operationId) {
         final int authenticators = Utils.getAuthenticators(bundle);
 
         if (DEBUG) {
             Log.d(TAG, "showAuthenticationDialog, authenticators: " + authenticators
                     + ", biometricModality: " + biometricModality
-                    + ", requireConfirmation: " + requireConfirmation);
+                    + ", requireConfirmation: " + requireConfirmation
+                    + ", operationId: " + operationId);
         }
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = bundle;
@@ -266,6 +279,7 @@
         args.arg3 = requireConfirmation;
         args.argi2 = userId;
         args.arg4 = opPackageName;
+        args.arg5 = operationId;
 
         boolean skipAnimation = false;
         if (mCurrentDialog != null) {
@@ -354,6 +368,7 @@
         final boolean requireConfirmation = (boolean) args.arg3;
         final int userId = args.argi2;
         final String opPackageName = (String) args.arg4;
+        final long operationId = (long) args.arg5;
 
         // Create a new dialog but do not replace the current one yet.
         final AuthDialog newDialog = buildDialog(
@@ -362,7 +377,8 @@
                 userId,
                 type,
                 opPackageName,
-                skipAnimation);
+                skipAnimation,
+                operationId);
 
         if (newDialog == null) {
             Log.e(TAG, "Unsupported type: " + type);
@@ -429,7 +445,7 @@
     }
 
     protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation,
-            int userId, int type, String opPackageName, boolean skipIntro) {
+            int userId, int type, String opPackageName, boolean skipIntro, long operationId) {
         return new AuthContainerView.Builder(mContext)
                 .setCallback(this)
                 .setBiometricPromptBundle(biometricPromptBundle)
@@ -437,6 +453,7 @@
                 .setUserId(userId)
                 .setOpPackageName(opPackageName)
                 .setSkipIntro(skipIntro)
+                .setOperationId(operationId)
                 .build(type);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index 82c8a46..b986f6c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -103,14 +103,16 @@
                 return;
             }
 
-            mPendingLockCheck = LockPatternChecker.checkCredential(mLockPatternUtils,
-                    password, mEffectiveUserId, this::onCredentialChecked);
+            mPendingLockCheck = LockPatternChecker.verifyCredential(mLockPatternUtils,
+                    password, mOperationId, mEffectiveUserId, this::onCredentialVerified);
         }
     }
 
     @Override
-    protected void onCredentialChecked(boolean matched, int timeoutMs) {
-        super.onCredentialChecked(matched, timeoutMs);
+    protected void onCredentialVerified(byte[] attestation, int timeoutMs) {
+        super.onCredentialVerified(attestation, timeoutMs);
+
+        final boolean matched = attestation != null;
 
         if (matched) {
             mImm.hideSoftInputFromWindow(getWindowToken(), 0 /* flags */);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
index 03136a4..6d16f43 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
@@ -61,21 +61,22 @@
 
             if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
                 // Pattern size is less than the minimum, do not count it as a failed attempt.
-                onPatternChecked(false /* matched */, 0 /* timeoutMs */);
+                onPatternVerified(null /* attestation */, 0 /* timeoutMs */);
                 return;
             }
 
             try (LockscreenCredential credential = LockscreenCredential.createPattern(pattern)) {
-                mPendingLockCheck = LockPatternChecker.checkCredential(
+                mPendingLockCheck = LockPatternChecker.verifyCredential(
                         mLockPatternUtils,
                         credential,
+                        mOperationId,
                         mEffectiveUserId,
-                        this::onPatternChecked);
+                        this::onPatternVerified);
             }
         }
 
-        private void onPatternChecked(boolean matched, int timeoutMs) {
-            AuthCredentialPatternView.this.onCredentialChecked(matched, timeoutMs);
+        private void onPatternVerified(byte[] attestation, int timeoutMs) {
+            AuthCredentialPatternView.this.onCredentialVerified(attestation, timeoutMs);
             if (timeoutMs > 0) {
                 mLockPatternView.setEnabled(false);
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 48c6621..0d9d426 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -42,7 +42,7 @@
 
 /**
  * Abstract base class for Pin, Pattern, or Password authentication, for
- * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)}
+ * {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)}}
  */
 public abstract class AuthCredentialView extends LinearLayout {
 
@@ -70,11 +70,12 @@
     protected Callback mCallback;
     protected AsyncTask<?, ?, ?> mPendingLockCheck;
     protected int mUserId;
+    protected long mOperationId;
     protected int mEffectiveUserId;
     protected ErrorTimer mErrorTimer;
 
     interface Callback {
-        void onCredentialMatched();
+        void onCredentialMatched(byte[] attestation);
     }
 
     protected static class ErrorTimer extends CountDownTimer {
@@ -148,6 +149,10 @@
         mUserId = userId;
     }
 
+    void setOperationId(long operationId) {
+        mOperationId = operationId;
+    }
+
     void setEffectiveUserId(int effectiveUserId) {
         mEffectiveUserId = effectiveUserId;
     }
@@ -245,10 +250,13 @@
 
     protected void onErrorTimeoutFinish() {}
 
-    protected void onCredentialChecked(boolean matched, int timeoutMs) {
+    protected void onCredentialVerified(byte[] attestation, int timeoutMs) {
+
+        final boolean matched = attestation != null;
+
         if (matched) {
             mClearErrorRunnable.run();
-            mCallback.onCredentialMatched();
+            mCallback.onCredentialMatched(attestation);
         } else {
             if (timeoutMs > 0) {
                 mHandler.removeCallbacks(mClearErrorRunnable);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
index 12bb122..a47621d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 
 /**
  * Callback interface for dialog views. These should be implemented by the controller (e.g.
@@ -44,8 +45,9 @@
     /**
      * Invoked when the dialog is dismissed
      * @param reason
+     * @param credentialAttestation the HAT received from LockSettingsService upon verification
      */
-    void onDismissed(@DismissedReason int reason);
+    void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation);
 
     /**
      * Invoked when the "try again" button is clicked
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 3fe348f..94afde78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -262,7 +262,8 @@
 
         default void showAuthenticationDialog(Bundle bundle,
                 IBiometricServiceReceiverInternal receiver, int biometricModality,
-                boolean requireConfirmation, int userId, String opPackageName) { }
+                boolean requireConfirmation, int userId, String opPackageName,
+                long operationId) { }
         default void onBiometricAuthenticated() { }
         default void onBiometricHelp(String message) { }
         default void onBiometricError(int modality, int error, int vendorCode) { }
@@ -780,7 +781,8 @@
 
     @Override
     public void showAuthenticationDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
-            int biometricModality, boolean requireConfirmation, int userId, String opPackageName) {
+            int biometricModality, boolean requireConfirmation, int userId, String opPackageName,
+            long operationId) {
         synchronized (mLock) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = bundle;
@@ -789,6 +791,7 @@
             args.arg3 = requireConfirmation;
             args.argi2 = userId;
             args.arg4 = opPackageName;
+            args.arg5 = operationId;
             mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args)
                     .sendToTarget();
         }
@@ -1164,7 +1167,8 @@
                                 someArgs.argi1 /* biometricModality */,
                                 (boolean) someArgs.arg3 /* requireConfirmation */,
                                 someArgs.argi2 /* userId */,
-                                (String) someArgs.arg4 /* opPackageName */);
+                                (String) someArgs.arg4 /* opPackageName */,
+                                (long) someArgs.arg5 /* operationId */);
                     }
                     someArgs.recycle();
                     break;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index c6c7b87..1db8e4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -78,7 +78,9 @@
 
         mAuthContainer.mBiometricCallback.onAction(
                 AuthBiometricView.Callback.ACTION_AUTHENTICATED);
-        verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED));
+        verify(mCallback).onDismissed(
+                eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
+                eq(null) /* credentialAttestation */);
     }
 
     @Test
@@ -87,7 +89,9 @@
 
         mAuthContainer.mBiometricCallback.onAction(
                 AuthBiometricView.Callback.ACTION_USER_CANCELED);
-        verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_USER_CANCELED));
+        verify(mCallback).onDismissed(
+                eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+                eq(null) /* credentialAttestation */);
     }
 
     @Test
@@ -96,7 +100,9 @@
 
         mAuthContainer.mBiometricCallback.onAction(
                 AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
-        verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE));
+        verify(mCallback).onDismissed(
+                eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
+                eq(null) /* credentialAttestation */);
     }
 
     @Test
@@ -114,7 +120,9 @@
 
         mAuthContainer.mBiometricCallback.onAction(
                 AuthBiometricView.Callback.ACTION_ERROR);
-        verify(mCallback).onDismissed(AuthDialogCallback.DISMISSED_ERROR);
+        verify(mCallback).onDismissed(
+                eq(AuthDialogCallback.DISMISSED_ERROR),
+                eq(null) /* credentialAttestation */);
     }
 
     @Test
@@ -219,7 +227,8 @@
 
         @Override
         public void animateAway(int reason) {
-            mConfig.mCallback.onDismissed(reason);
+            // TODO: Credential attestation should be testable/tested
+            mConfig.mCallback.onDismissed(reason, null /* credentialAttestation */);
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 65399bf..fc1ddf7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -57,12 +57,14 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.AdditionalMatchers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Random;
 
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -110,52 +112,75 @@
     @Test
     public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
         showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
-        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
-        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
+        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
+                null /* credentialAttestation */);
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
+                eq(null) /* credentialAttestation */);
     }
 
     @Test
     public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
         showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
-        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
-        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
+        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE,
+                null /* credentialAttestation */);
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_NEGATIVE),
+                eq(null) /* credentialAttestation */);
     }
 
     @Test
     public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
         showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
-        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
-        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
+        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE,
+                null /* credentialAttestation */);
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED),
+                eq(null) /* credentialAttestation */);
     }
 
     @Test
     public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
         showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
-        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
+        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED,
+                null /* credentialAttestation */);
         verify(mReceiver).onDialogDismissed(
-                BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED);
+                eq(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED),
+                eq(null) /* credentialAttestation */);
     }
 
     @Test
     public void testSendsReasonError_whenDismissedByError() throws Exception {
         showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
-        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR);
-        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
+        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR,
+                null /* credentialAttestation */);
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_ERROR),
+                eq(null) /* credentialAttestation */);
     }
 
     @Test
     public void testSendsReasonServerRequested_whenDismissedByServer() throws Exception {
         showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
-        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER);
-        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
+        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER,
+                null /* credentialAttestation */);
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED),
+                eq(null) /* credentialAttestation */);
     }
 
     @Test
     public void testSendsReasonCredentialConfirmed_whenDeviceCredentialAuthenticated()
             throws Exception {
         showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
-        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
-        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED);
+
+        final byte[] credentialAttestation = generateRandomHAT();
+
+        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED,
+                credentialAttestation);
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
+                AdditionalMatchers.aryEq(credentialAttestation));
     }
 
     // Statusbar tests
@@ -302,8 +327,13 @@
         showDialog(Authenticators.DEVICE_CREDENTIAL, BiometricPrompt.TYPE_NONE);
         verify(mDialog1).show(any(), any());
 
-        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
-        verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED);
+        final byte[] credentialAttestation = generateRandomHAT();
+
+        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED,
+                credentialAttestation);
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
+                AdditionalMatchers.aryEq(credentialAttestation));
 
         mAuthController.hideAuthenticationDialog();
     }
@@ -395,20 +425,24 @@
         assertNull(mAuthController.mCurrentDialog);
         assertNull(mAuthController.mReceiver);
         verify(mDialog1).dismissWithoutCallback(true /* animate */);
-        verify(mReceiver).onDialogDismissed(eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL));
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
+                eq(null) /* credentialAttestation */);
     }
 
     @Test
     public void testDoesNotCrash_whenTryAgainPressedAfterDismissal() {
         showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
-        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
+        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
+                null /* credentialAttestation */);
         mAuthController.onTryAgainPressed();
     }
 
     @Test
     public void testDoesNotCrash_whenDeviceCredentialPressedAfterDismissal() {
         showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
-        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
+        mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
+                null /* credentialAttestation */);
         mAuthController.onDeviceCredentialPressed();
     }
 
@@ -422,7 +456,9 @@
         assertNull(mAuthController.mCurrentDialog);
         assertNull(mAuthController.mReceiver);
         verify(mDialog1).dismissWithoutCallback(true /* animate */);
-        verify(mReceiver).onDialogDismissed(eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL));
+        verify(mReceiver).onDialogDismissed(
+                eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
+                eq(null) /* credentialAttestation */);
     }
 
     // Helpers
@@ -433,7 +469,8 @@
                 biometricModality,
                 true /* requireConfirmation */,
                 0 /* userId */,
-                "testPackage");
+                "testPackage",
+                0 /* operationId */);
     }
 
     private Bundle createTestDialogBundle(int authenticators) {
@@ -453,6 +490,13 @@
         return bundle;
     }
 
+    private byte[] generateRandomHAT() {
+        byte[] HAT = new byte[69];
+        Random random = new Random();
+        random.nextBytes(HAT);
+        return HAT;
+    }
+
     private final class TestableAuthController extends AuthController {
         private int mBuildCount = 0;
         private Bundle mLastBiometricPromptBundle;
@@ -464,7 +508,7 @@
         @Override
         protected AuthDialog buildDialog(Bundle biometricPromptBundle,
                 boolean requireConfirmation, int userId, int type, String opPackageName,
-                boolean skipIntro) {
+                boolean skipIntro, long operationId) {
 
             mLastBiometricPromptBundle = biometricPromptBundle;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 7c6da63..cffcabb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -409,11 +409,12 @@
     public void testShowAuthenticationDialog() {
         Bundle bundle = new Bundle();
         String packageName = "test";
+        final long operationId = 1;
         mCommandQueue.showAuthenticationDialog(bundle, null /* receiver */, 1, true, 3,
-                packageName);
+                packageName, operationId);
         waitForIdleSync();
         verify(mCallbacks).showAuthenticationDialog(eq(bundle), eq(null), eq(1), eq(true), eq(3),
-                eq(packageName));
+                eq(packageName), eq(operationId));
     }
 
     @Test