Handle cancellation properly for setDeviceCredentialAllowed(true)

Keep the current auth session until ConfirmDeviceCredential succeeds
or fails. ConfirmDeviceCredential's BP and LSKF screens can be canceled
now.

Bug: 123378871
Bug: 128747871

Test: With modified BiometricPromptDemo, ConfirmDeviceCredential's
      BiometricPrompt and LSKF screens can be canceled

Change-Id: Icaf3f0c55b07fd138a2ee9d214941ea83408f0ee
diff --git a/Android.bp b/Android.bp
index 5b8e6e1..cbb8bbb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -161,6 +161,7 @@
         ":libcamera_client_framework_aidl",
         "core/java/android/hardware/IConsumerIrService.aidl",
         "core/java/android/hardware/ISerialManager.aidl",
+        "core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl",
         "core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl",
         "core/java/android/hardware/biometrics/IBiometricService.aidl",
         "core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl",
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index a696eeb..6c497d4 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -207,5 +207,22 @@
             Slog.w(TAG, "onConfirmDeviceCredentialError(): Service not connected");
         }
     }
+
+    /**
+     * TODO(b/123378871): Remove when moved.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void registerCancellationCallback(IBiometricConfirmDeviceCredentialCallback callback) {
+        if (mService != null) {
+            try {
+                mService.registerCancellationCallback(callback);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Slog.w(TAG, "registerCancellationCallback(): Service not connected");
+        }
+    }
 }
 
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 08035972..1142a07 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -82,6 +82,11 @@
      * @hide
      */
     public static final String KEY_ALLOW_DEVICE_CREDENTIAL = "allow_device_credential";
+    /**
+     * @hide
+     */
+    public static final String KEY_FROM_CONFIRM_DEVICE_CREDENTIAL
+            = "from_confirm_device_credential";
 
     /**
      * Error/help message will show for this amount of time.
@@ -271,6 +276,17 @@
         }
 
         /**
+         * TODO(123378871): Remove when moved.
+         * @return
+         * @hide
+         */
+        @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+        @NonNull public Builder setFromConfirmDeviceCredential() {
+            mBundle.putBoolean(KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, true);
+            return this;
+        }
+
+        /**
          * Creates a {@link BiometricPrompt}.
          * @return a {@link BiometricPrompt}
          * @throws IllegalArgumentException if any of the required fields are not set.
@@ -494,7 +510,8 @@
     public void authenticateUser(@NonNull CancellationSignal cancel,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AuthenticationCallback callback,
-            int userId) {
+            int userId,
+            IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) {
         if (cancel == null) {
             throw new IllegalArgumentException("Must supply a cancellation signal");
         }
@@ -504,7 +521,8 @@
         if (callback == null) {
             throw new IllegalArgumentException("Must supply a callback");
         }
-        authenticateInternal(null /* crypto */, cancel, executor, callback, userId);
+        authenticateInternal(null /* crypto */, cancel, executor, callback, userId,
+                confirmDeviceCredentialCallback);
     }
 
     /**
@@ -555,7 +573,8 @@
         if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)) {
             throw new IllegalArgumentException("Device credential not supported with crypto");
         }
-        authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId());
+        authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId(),
+                null /* confirmDeviceCredentialCallback */);
     }
 
     /**
@@ -597,7 +616,8 @@
         if (callback == null) {
             throw new IllegalArgumentException("Must supply a callback");
         }
-        authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId());
+        authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId(),
+                null /* confirmDeviceCredentialCallback */);
     }
 
     private void cancelAuthentication() {
@@ -614,7 +634,8 @@
             @NonNull CancellationSignal cancel,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AuthenticationCallback callback,
-            int userId) {
+            int userId,
+            IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) {
         try {
             if (cancel.isCanceled()) {
                 Log.w(TAG, "Authentication already canceled");
@@ -629,7 +650,7 @@
             final long sessionId = crypto != null ? crypto.getOpId() : 0;
             if (BiometricManager.hasBiometrics(mContext)) {
                 mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver,
-                        mContext.getOpPackageName(), mBundle);
+                        mContext.getOpPackageName(), mBundle, confirmDeviceCredentialCallback);
             } else {
                 mExecutor.execute(() -> {
                     callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT,
diff --git a/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl b/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl
new file mode 100644
index 0000000..8b35852
--- /dev/null
+++ b/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+/**
+ * Communication channel between ConfirmDeviceCredential / ConfirmLock* and BiometricService.
+ * @hide
+ */
+interface IBiometricConfirmDeviceCredentialCallback {
+    // Invoked when authentication should be canceled.
+    oneway void cancel();
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 4971911..90d4921 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -17,6 +17,7 @@
 package android.hardware.biometrics;
 
 import android.os.Bundle;
+import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricServiceReceiver;
 
@@ -30,8 +31,10 @@
 interface IBiometricService {
     // Requests authentication. The service choose the appropriate biometric to use, and show
     // the corresponding BiometricDialog.
+    // TODO(b/123378871): Remove callback when moved.
     void authenticate(IBinder token, long sessionId, int userId,
-            IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle);
+            IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle,
+            IBiometricConfirmDeviceCredentialCallback callback);
 
     // Cancel authentication for the given sessionId
     void cancelAuthentication(IBinder token, String opPackageName);
@@ -59,4 +62,8 @@
     void onConfirmDeviceCredentialSuccess();
     // TODO(b/123378871): Remove when moved.
     void onConfirmDeviceCredentialError(int error, String message);
+    // TODO(b/123378871): Remove when moved.
+    // When ConfirmLock* is invoked from BiometricPrompt, it needs to register a callback so that
+    // it can receive the cancellation signal.
+    void registerCancellationCallback(IBiometricConfirmDeviceCredentialCallback callback);
 }
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 516844d..c558b5f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -42,6 +42,7 @@
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -86,6 +87,7 @@
 public class BiometricService extends SystemService {
 
     private static final String TAG = "BiometricService";
+    private static final boolean DEBUG = true;
 
     private static final int MSG_ON_TASK_STACK_CHANGED = 1;
     private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
@@ -97,6 +99,9 @@
     private static final int MSG_ON_READY_FOR_AUTHENTICATION = 8;
     private static final int MSG_AUTHENTICATE = 9;
     private static final int MSG_CANCEL_AUTHENTICATION = 10;
+    private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS = 11;
+    private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR = 12;
+    private static final int MSG_REGISTER_CANCELLATION_CALLBACK = 13;
 
     private static final int[] FEATURE_ID = {
         TYPE_FINGERPRINT,
@@ -129,8 +134,12 @@
      * Authentication is successful, but we're waiting for the user to press "confirm" button.
      */
     private static final int STATE_AUTH_PENDING_CONFIRM = 5;
+    /**
+     * Biometric authentication was canceled, but the device is now showing ConfirmDeviceCredential
+     */
+    private static final int STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC = 6;
 
-    private final class AuthSession {
+    private final class AuthSession implements IBinder.DeathRecipient {
         // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from
         // <Biometric>Services before we can start authenticating. Pairs that have been returned
         // are moved to mModalitiesMatched.
@@ -165,10 +174,14 @@
         // Timestamp when hardware authentication occurred
         private long mAuthenticatedTimeMs;
 
+        // TODO(b/123378871): Remove when moved.
+        private IBiometricConfirmDeviceCredentialCallback mConfirmDeviceCredentialCallback;
+
         AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId,
                 int userId, IBiometricServiceReceiver receiver, String opPackageName,
                 Bundle bundle, int callingUid, int callingPid, int callingUserId,
-                int modality, boolean requireConfirmation) {
+                int modality, boolean requireConfirmation,
+                IBiometricConfirmDeviceCredentialCallback callback) {
             mModalitiesWaiting = modalities;
             mToken = token;
             mSessionId = sessionId;
@@ -181,12 +194,25 @@
             mCallingUserId = callingUserId;
             mModality = modality;
             mRequireConfirmation = requireConfirmation;
+            mConfirmDeviceCredentialCallback = callback;
+
+            if (isFromConfirmDeviceCredential()) {
+                try {
+                    token.linkToDeath(this, 0 /* flags */);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to link to death", e);
+                }
+            }
         }
 
         boolean isCrypto() {
             return mSessionId != 0;
         }
 
+        boolean isFromConfirmDeviceCredential() {
+            return mBundle.getBoolean(BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false);
+        }
+
         boolean containsCookie(int cookie) {
             if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) {
                 return true;
@@ -196,6 +222,25 @@
             }
             return false;
         }
+
+        // TODO(b/123378871): Remove when moved.
+        @Override
+        public void binderDied() {
+            mHandler.post(() -> {
+                Slog.e(TAG, "Binder died, killing ConfirmDeviceCredential");
+                if (mConfirmDeviceCredentialCallback == null) {
+                    Slog.e(TAG, "Callback is null");
+                    return;
+                }
+
+                try {
+                    mConfirmDeviceCredentialCallback.cancel();
+                    mConfirmDeviceCredentialCallback = null;
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to send cancel", e);
+                }
+            });
+        }
     }
 
     private final class BiometricTaskStackListener extends TaskStackListener {
@@ -235,6 +280,14 @@
     private AuthSession mCurrentAuthSession;
     private AuthSession mPendingAuthSession;
 
+    // TODO(b/123378871): Remove when moved.
+    // When BiometricPrompt#setAllowDeviceCredentials is set to true, we need to store the
+    // client (app) receiver. BiometricService internally launches CDCA which invokes
+    // BiometricService to start authentication (normal path). When auth is success/rejected,
+    // CDCA will use an aidl method to poke BiometricService - the result will then be forwarded
+    // to this receiver.
+    private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver;
+
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
         @Override
         public void handleMessage(Message msg) {
@@ -312,7 +365,8 @@
                             (Bundle) args.arg5 /* bundle */,
                             args.argi2 /* callingUid */,
                             args.argi3 /* callingPid */,
-                            args.argi4 /* callingUserId */);
+                            args.argi4 /* callingUserId */,
+                            (IBiometricConfirmDeviceCredentialCallback) args.arg6 /* callback */);
                     args.recycle();
                     break;
                 }
@@ -326,7 +380,28 @@
                     break;
                 }
 
+                case MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS: {
+                    handleOnConfirmDeviceCredentialSuccess();
+                    break;
+                }
+
+                case MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleOnConfirmDeviceCredentialError(
+                            args.argi1 /* error */,
+                            (String) args.arg1 /* errorMsg */);
+                    args.recycle();
+                    break;
+                }
+
+                case MSG_REGISTER_CANCELLATION_CALLBACK: {
+                    handleRegisterCancellationCallback(
+                            (IBiometricConfirmDeviceCredentialCallback) msg.obj /* callback */);
+                    break;
+                }
+
                 default:
+                    Slog.e(TAG, "Unknown message: " + msg);
                     break;
             }
         }
@@ -526,14 +601,6 @@
      * cancelAuthentication() can go to the right place.
      */
     private final class BiometricServiceWrapper extends IBiometricService.Stub {
-        // TODO(b/123378871): Remove when moved.
-        // When BiometricPrompt#setAllowDeviceCredentials is set to true, we need to store the
-        // client (app) receiver. BiometricService internally launches CDCA which invokes
-        // BiometricService to start authentication (normal path). When auth is success/rejected,
-        // CDCA will use an aidl method to poke BiometricService - the result will then be forwarded
-        // to this receiver.
-        private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver;
-
         @Override // Binder call
         public void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId) {
             checkInternalPermission();
@@ -547,12 +614,18 @@
 
         @Override // Binder call
         public void authenticate(IBinder token, long sessionId, int userId,
-                IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle)
+                IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
+                IBiometricConfirmDeviceCredentialCallback callback)
                 throws RemoteException {
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final int callingUserId = UserHandle.getCallingUserId();
 
+            // TODO(b/123378871): Remove when moved.
+            if (callback != null) {
+                checkInternalPermission();
+            }
+
             // In the BiometricServiceBase, check do the AppOps and foreground check.
             if (userId == callingUserId) {
                 // Check the USE_BIOMETRIC permission here.
@@ -569,6 +642,12 @@
                 return;
             }
 
+            final boolean isFromConfirmDeviceCredential =
+                    bundle.getBoolean(BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false);
+            if (isFromConfirmDeviceCredential) {
+                checkInternalPermission();
+            }
+
             // Check the usage of this in system server. Need to remove this check if it becomes
             // a public API.
             final boolean useDefaultTitle =
@@ -646,6 +725,7 @@
             args.argi2 = callingUid;
             args.argi3 = callingPid;
             args.argi4 = callingUserId;
+            args.arg6 = callback;
 
             mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget();
         }
@@ -653,35 +733,30 @@
         @Override // Binder call
         public void onConfirmDeviceCredentialSuccess() {
             checkInternalPermission();
-            mHandler.post(() -> {
-                if (mConfirmDeviceCredentialReceiver == null) {
-                    Slog.w(TAG, "onCDCASuccess null!");
-                    return;
-                }
-                try {
-                    mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded();
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "RemoteException", e);
-                }
-                mConfirmDeviceCredentialReceiver = null;
-            });
+
+            mHandler.sendEmptyMessage(MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS);
         }
 
         @Override // Binder call
         public void onConfirmDeviceCredentialError(int error, String message) {
             checkInternalPermission();
-            mHandler.post(() -> {
-                if (mConfirmDeviceCredentialReceiver == null) {
-                    Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message);
-                    return;
-                }
-                try {
-                    mConfirmDeviceCredentialReceiver.onError(error, message);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "RemoteException", e);
-                }
-                mConfirmDeviceCredentialReceiver = null;
-            });
+
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = error;
+            args.arg1 = message;
+            mHandler.obtainMessage(MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR, args).sendToTarget();
+        }
+
+        @Override // Binder call
+        public void registerCancellationCallback(
+                IBiometricConfirmDeviceCredentialCallback callback) {
+            // TODO(b/123378871): Remove when moved.
+            // This callback replaces the one stored in the current session. If the session is null
+            // we can ignore this, since it means ConfirmDeviceCredential was launched by something
+            // else (not BiometricPrompt)
+            checkInternalPermission();
+
+            mHandler.obtainMessage(MSG_REGISTER_CANCELLATION_CALLBACK, callback).sendToTarget();
         }
 
         @Override // Binder call
@@ -1117,6 +1192,52 @@
         }
     }
 
+    private void handleOnConfirmDeviceCredentialSuccess() {
+        if (mConfirmDeviceCredentialReceiver == null) {
+            Slog.w(TAG, "onCDCASuccess null!");
+            return;
+        }
+        try {
+            mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+            mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded();
+            if (mCurrentAuthSession != null) {
+                mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                mCurrentAuthSession = null;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "RemoteException", e);
+        }
+        mConfirmDeviceCredentialReceiver = null;
+    }
+
+    private void handleOnConfirmDeviceCredentialError(int error, String message) {
+        if (mConfirmDeviceCredentialReceiver == null) {
+            Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message);
+            return;
+        }
+        try {
+            mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+            mConfirmDeviceCredentialReceiver.onError(error, message);
+            if (mCurrentAuthSession != null) {
+                mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                mCurrentAuthSession = null;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "RemoteException", e);
+        }
+        mConfirmDeviceCredentialReceiver = null;
+    }
+
+    private void handleRegisterCancellationCallback(
+            IBiometricConfirmDeviceCredentialCallback callback) {
+        if (mCurrentAuthSession == null) {
+            Slog.d(TAG, "Current auth session null");
+            return;
+        }
+        Slog.d(TAG, "Updating cancel callback");
+        mCurrentAuthSession.mConfirmDeviceCredentialCallback = callback;
+    }
+
     private void handleOnError(int cookie, int error, String message) {
         Slog.d(TAG, "Error: " + error + " cookie: " + cookie);
         // Errors can either be from the current auth session or the pending auth session.
@@ -1127,7 +1248,18 @@
         // of their intended receivers.
         try {
             if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) {
-                if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
+
+                if (mCurrentAuthSession.isFromConfirmDeviceCredential()) {
+                    // If we were invoked by ConfirmDeviceCredential, do not delete the current
+                    // auth session since we still need to respond to cancel signal while
+                    if (DEBUG) Slog.d(TAG, "From CDC, transition to CANCELED_SHOWING_CDC state");
+
+                    // Send the error to ConfirmDeviceCredential so that it goes to Pin/Pattern/Pass
+                    // screen
+                    mCurrentAuthSession.mClientReceiver.onError(error, message);
+                    mCurrentAuthSession.mState = STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC;
+                    mStatusBarService.hideBiometricDialog();
+                } else if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
                     mStatusBarService.onBiometricError(message);
                     if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
                         mActivityTaskManager.unregisterTaskStackListener(
@@ -1227,9 +1359,16 @@
                 KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow);
                 mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
             }
-            mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
-            mCurrentAuthSession.mState = STATE_AUTH_IDLE;
-            mCurrentAuthSession = null;
+
+            // Do not clean up yet if we are from ConfirmDeviceCredential. We should be in the
+            // STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC. The session should only be removed when
+            // ConfirmDeviceCredential is confirmed or canceled.
+            // TODO(b/123378871): Remove when moved
+            if (!mCurrentAuthSession.isFromConfirmDeviceCredential()) {
+                mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+                mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                mCurrentAuthSession = null;
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
@@ -1248,7 +1387,8 @@
                 mCurrentAuthSession.mCallingUid,
                 mCurrentAuthSession.mCallingPid,
                 mCurrentAuthSession.mCallingUserId,
-                mCurrentAuthSession.mModality);
+                mCurrentAuthSession.mModality,
+                mCurrentAuthSession.mConfirmDeviceCredentialCallback);
     }
 
     private void handleOnReadyForAuthentication(int cookie, boolean requireConfirmation,
@@ -1303,7 +1443,8 @@
 
     private void handleAuthenticate(IBinder token, long sessionId, int userId,
             IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
-            int callingUid, int callingPid, int callingUserId) {
+            int callingUid, int callingPid, int callingUserId,
+            IBiometricConfirmDeviceCredentialCallback callback) {
 
         mHandler.post(() -> {
             final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
@@ -1341,7 +1482,7 @@
             // Start preparing for authentication. Authentication starts when
             // all modalities requested have invoked onReadyForAuthentication.
             authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
-                    callingUid, callingPid, callingUserId, modality);
+                    callingUid, callingPid, callingUserId, modality, callback);
         });
     }
 
@@ -1356,7 +1497,8 @@
      */
     private void authenticateInternal(IBinder token, long sessionId, int userId,
             IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
-            int callingUid, int callingPid, int callingUserId, int modality) {
+            int callingUid, int callingPid, int callingUserId, int modality,
+            IBiometricConfirmDeviceCredentialCallback callback) {
         try {
             boolean requireConfirmation = bundle.getBoolean(
                     BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
@@ -1376,7 +1518,7 @@
             authenticators.put(modality, cookie);
             mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId,
                     receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
-                    modality, requireConfirmation);
+                    modality, requireConfirmation, callback);
             mPendingAuthSession.mState = STATE_AUTH_CALLED;
             // No polymorphism :(
             if ((modality & TYPE_FINGERPRINT) != 0) {
@@ -1403,10 +1545,23 @@
             return;
         }
 
-        // We need to check the current authenticators state. If we're pending confirm
-        // or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client,
-        // since we won't be getting an onError from the driver.
-        if (mCurrentAuthSession != null && mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
+        if (mCurrentAuthSession != null
+                && mCurrentAuthSession.mState == STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC) {
+            if (DEBUG) Slog.d(TAG, "Cancel received while ConfirmDeviceCredential showing");
+            try {
+                mCurrentAuthSession.mConfirmDeviceCredentialCallback.cancel();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to cancel ConfirmDeviceCredential", e);
+            }
+
+            // TODO(b/123378871): Remove when moved. Piggy back on this for now to clean up.
+            handleOnConfirmDeviceCredentialError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+                    getContext().getString(R.string.biometric_error_canceled));
+        } else if (mCurrentAuthSession != null
+                && mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
+            // We need to check the current authenticators state. If we're pending confirm
+            // or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client,
+            // since we won't be getting an onError from the driver.
             try {
                 // Send error to client
                 mCurrentAuthSession.mClientReceiver.onError(
@@ -1422,11 +1577,22 @@
                 Slog.e(TAG, "Remote exception", e);
             }
         } else {
-            cancelInternal(token, opPackageName, true /* fromClient */);
+            boolean fromCDC = false;
+            if (mCurrentAuthSession != null) {
+                fromCDC = mCurrentAuthSession.mBundle.getBoolean(
+                        BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false);
+            }
+
+            if (fromCDC) {
+                if (DEBUG) Slog.d(TAG, "Cancelling from CDC");
+                cancelInternal(token, opPackageName, false /* fromClient */);
+            } else {
+                cancelInternal(token, opPackageName, true /* fromClient */);
+            }
+
         }
     }
 
-
     void cancelInternal(IBinder token, String opPackageName, boolean fromClient) {
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();