Merge "Give platform permissions a dummy group"
diff --git a/Android.bp b/Android.bp
index 25e738c..41d5f28 100644
--- a/Android.bp
+++ b/Android.bp
@@ -158,9 +158,9 @@
         "core/java/android/hardware/IConsumerIrService.aidl",
         "core/java/android/hardware/ISerialManager.aidl",
         "core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl",
-        "core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl",
         "core/java/android/hardware/biometrics/IBiometricService.aidl",
         "core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl",
+        "core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl",
         "core/java/android/hardware/biometrics/IBiometricServiceLockoutResetCallback.aidl",
         "core/java/android/hardware/display/IDisplayManager.aidl",
         "core/java/android/hardware/display/IDisplayManagerCallback.aidl",
diff --git a/api/current.txt b/api/current.txt
index 0dce8d4..855ac8c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11339,7 +11339,7 @@
     method public abstract int checkSignatures(java.lang.String, java.lang.String);
     method public abstract int checkSignatures(int, int);
     method public abstract void clearInstantAppCookie();
-    method public abstract void clearPackagePreferredActivities(java.lang.String);
+    method public abstract deprecated void clearPackagePreferredActivities(java.lang.String);
     method public abstract java.lang.String[] currentToCanonicalPackageNames(java.lang.String[]);
     method public abstract void extendVerificationTimeout(int, int, long);
     method public abstract android.graphics.drawable.Drawable getActivityBanner(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -11383,8 +11383,8 @@
     method public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int);
     method public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.pm.PermissionInfo getPermissionInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public abstract int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String);
-    method public abstract java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int);
+    method public abstract deprecated int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String);
+    method public abstract deprecated java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int);
     method public abstract android.content.pm.ProviderInfo getProviderInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.pm.ActivityInfo getReceiverInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.res.Resources getResourcesForActivity(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -49074,6 +49074,7 @@
     method public float getTranslationX();
     method public float getTranslationY();
     method public float getTranslationZ();
+    method public long getUniqueDrawingId();
     method public int getVerticalFadingEdgeLength();
     method public int getVerticalScrollbarPosition();
     method public int getVerticalScrollbarWidth();
diff --git a/api/system-current.txt b/api/system-current.txt
index 41b5bc7..7661d49c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1244,7 +1244,7 @@
     method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, android.os.UserHandle);
     method public abstract void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
     method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
-    method public void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName);
+    method public deprecated void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName);
     method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
     method public void sendDeviceCustomizationReadyBroadcast();
     method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
diff --git a/api/test-current.txt b/api/test-current.txt
index b871c78..74b974e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -943,6 +943,10 @@
 
 package android.os.storage {
 
+  public class StorageManager {
+    method public static boolean hasIsolatedStorage();
+  }
+
   public final class StorageVolume implements android.os.Parcelable {
     method public java.lang.String getPath();
   }
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 25bd033..3c31667 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -2695,7 +2695,6 @@
 Lcom/android/internal/telephony/dataconnection/DcTracker;->onSetUserDataEnabled(Z)V
 Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Lcom/android/internal/telephony/dataconnection/ApnContext;)Z
 Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Ljava/lang/String;)Z
-Lcom/android/internal/telephony/dataconnection/DcTracker;->registerForAllDataDisconnected(Landroid/os/Handler;ILjava/lang/Object;)V
 Lcom/android/internal/telephony/dataconnection/DcTracker;->registerSettingsObserver()V
 Lcom/android/internal/telephony/dataconnection/DcTracker;->resetPollStats()V
 Lcom/android/internal/telephony/dataconnection/DcTracker;->restartDataStallAlarm()V
@@ -2709,7 +2708,6 @@
 Lcom/android/internal/telephony/dataconnection/DcTracker;->stopDataStallAlarm()V
 Lcom/android/internal/telephony/dataconnection/DcTracker;->stopNetStatPoll()V
 Lcom/android/internal/telephony/dataconnection/DcTracker;->unregisterForAllDataDisconnected(Landroid/os/Handler;)V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->updateRecords()V
 Lcom/android/internal/telephony/DctConstants$Activity;->DATAIN:Lcom/android/internal/telephony/DctConstants$Activity;
 Lcom/android/internal/telephony/DctConstants$Activity;->DATAINANDOUT:Lcom/android/internal/telephony/DctConstants$Activity;
 Lcom/android/internal/telephony/DctConstants$Activity;->DATAOUT:Lcom/android/internal/telephony/DctConstants$Activity;
@@ -3201,7 +3199,6 @@
 Lcom/android/internal/telephony/Phone;->isWifiCallingEnabled()Z
 Lcom/android/internal/telephony/Phone;->mCi:Lcom/android/internal/telephony/CommandsInterface;
 Lcom/android/internal/telephony/Phone;->mContext:Landroid/content/Context;
-Lcom/android/internal/telephony/Phone;->mDcTracker:Lcom/android/internal/telephony/dataconnection/DcTracker;
 Lcom/android/internal/telephony/Phone;->mIccRecords:Ljava/util/concurrent/atomic/AtomicReference;
 Lcom/android/internal/telephony/Phone;->mImsPhone:Lcom/android/internal/telephony/Phone;
 Lcom/android/internal/telephony/Phone;->mMmiRegistrants:Landroid/os/RegistrantList;
@@ -3294,7 +3291,6 @@
 Lcom/android/internal/telephony/ProxyController;->mRadioCapabilitySessionId:I
 Lcom/android/internal/telephony/ProxyController;->mSetRadioAccessFamilyStatus:[I
 Lcom/android/internal/telephony/ProxyController;->mUniqueIdGenerator:Ljava/util/concurrent/atomic/AtomicInteger;
-Lcom/android/internal/telephony/ProxyController;->registerForAllDataDisconnected(ILandroid/os/Handler;ILjava/lang/Object;)V
 Lcom/android/internal/telephony/ProxyController;->sendRadioCapabilityRequest(IIIILjava/lang/String;II)V
 Lcom/android/internal/telephony/ProxyController;->sProxyController:Lcom/android/internal/telephony/ProxyController;
 Lcom/android/internal/telephony/RadioCapability;->getRadioAccessFamily()I
@@ -3394,7 +3390,6 @@
 Lcom/android/internal/telephony/ServiceStateTracker;->notifyDataRegStateRilRadioTechnologyChanged()V
 Lcom/android/internal/telephony/ServiceStateTracker;->notifySignalStrength()Z
 Lcom/android/internal/telephony/ServiceStateTracker;->pollState()V
-Lcom/android/internal/telephony/ServiceStateTracker;->powerOffRadioSafely(Lcom/android/internal/telephony/dataconnection/DcTracker;)V
 Lcom/android/internal/telephony/ServiceStateTracker;->reRegisterNetwork(Landroid/os/Message;)V
 Lcom/android/internal/telephony/ServiceStateTracker;->resetServiceStateInIwlanMode()V
 Lcom/android/internal/telephony/ServiceStateTracker;->setOperatorIdd(Ljava/lang/String;)V
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 437039d..7d5202d 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -97,8 +97,7 @@
      *
      * @hide
      */
-    public static final boolean DEPRECATE_DATA_COLUMNS = SystemProperties
-            .getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false);
+    public static final boolean DEPRECATE_DATA_COLUMNS = StorageManager.hasIsolatedStorage();
 
     /**
      * Special filesystem path prefix which indicates that a path should be
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6421dc5..b7df2bf 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5372,6 +5372,10 @@
     public abstract void removePackageFromPreferred(String packageName);
 
     /**
+     * @deprecated This function no longer does anything; it was an old
+     * approach to managing preferred activities, which has been superseded
+     * by (and conflicts with) the modern activity-based preferences.
+     *
      * Retrieve the list of all currently configured preferred packages. The
      * first package on the list is the most preferred, the last is the least
      * preferred.
@@ -5380,6 +5384,7 @@
      * @return A List of PackageInfo objects, one for each preferred
      *         application, in order of preference.
      */
+    @Deprecated
     public abstract List<PackageInfo> getPreferredPackages(@PackageInfoFlags int flags);
 
     /**
@@ -5406,11 +5411,16 @@
             ComponentName[] set, ComponentName activity);
 
     /**
+     * @deprecated This is a protected API that should not have been available
+     * to third party applications.  It is the platform's responsibility for
+     * assigning preferred activities and this cannot be directly modified.
+     *
      * Same as {@link #addPreferredActivity(IntentFilter, int,
             ComponentName[], ComponentName)}, but with a specific userId to apply the preference
             to.
      * @hide
      */
+    @Deprecated
     @UnsupportedAppUsage
     public void addPreferredActivityAsUser(IntentFilter filter, int match,
             ComponentName[] set, ComponentName activity, @UserIdInt int userId) {
@@ -5444,6 +5454,10 @@
             ComponentName[] set, ComponentName activity);
 
     /**
+     * @deprecated This is a protected API that should not have been available
+     * to third party applications.  It is the platform's responsibility for
+     * assigning preferred activities and this cannot be directly modified.
+     *
      * Replaces an existing preferred activity mapping to the system, and if that were not present
      * adds a new preferred activity.  This will be used to automatically select the given activity
      * component when {@link Context#startActivity(Intent) Context.startActivity()} finds multiple
@@ -5459,6 +5473,7 @@
      *
      * @hide
      */
+    @Deprecated
     @SystemApi
     public void replacePreferredActivity(@NonNull IntentFilter filter, int match,
             @NonNull List<ComponentName> set, @NonNull ComponentName activity) {
@@ -5476,6 +5491,10 @@
     }
 
     /**
+     * @deprecated This function no longer does anything; it was an old
+     * approach to managing preferred activities, which has been superseded
+     * by (and conflicts with) the modern activity-based preferences.
+     *
      * Remove all preferred activity mappings, previously added with
      * {@link #addPreferredActivity}, from the
      * system whose activities are implemented in the given package name.
@@ -5484,9 +5503,14 @@
      * @param packageName The name of the package whose preferred activity
      * mappings are to be removed.
      */
+    @Deprecated
     public abstract void clearPackagePreferredActivities(String packageName);
 
     /**
+     * @deprecated This function no longer does anything; it was an old
+     * approach to managing preferred activities, which has been superseded
+     * by (and conflicts with) the modern activity-based preferences.
+     *
      * Retrieve all preferred activities, previously added with
      * {@link #addPreferredActivity}, that are
      * currently registered with the system.
@@ -5503,6 +5527,7 @@
      * (the number of distinct IntentFilter records, not the number of unique
      * activity components) that were found.
      */
+    @Deprecated
     public abstract int getPreferredActivities(@NonNull List<IntentFilter> outFilters,
             @NonNull List<ComponentName> outActivities, String packageName);
 
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 49189e5..ac18dca 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2513,7 +2513,7 @@
         // If the storage model feature flag is disabled, we need to fiddle
         // around with permission definitions to return us to pre-Q behavior.
         // STOPSHIP(b/112545973): remove once feature enabled by default
-        if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+        if (!StorageManager.hasIsolatedStorage()) {
             if ("android".equals(pkg.packageName)) {
                 final ArraySet<String> newGroups = new ArraySet<>();
                 newGroups.add(android.Manifest.permission_group.MEDIA_AURAL);
diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
index 79e15a7a..0ec812f 100644
--- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java
+++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
@@ -30,19 +30,29 @@
 public interface BiometricAuthenticator {
 
     /**
+     * No biometric methods or nothing has been enrolled.
+     * Move/expose these in BiometricPrompt if we ever want to allow applications to "blacklist"
+     * modalities when calling authenticate().
      * @hide
      */
-    int TYPE_FINGERPRINT = 1;
+    int TYPE_NONE = 0;
+    /**
+     * Constant representing fingerprint.
+     * @hide
+     */
+    int TYPE_FINGERPRINT = 1 << 0;
 
     /**
+     * Constant representing iris.
      * @hide
      */
-    int TYPE_IRIS = 2;
+    int TYPE_IRIS = 1 << 1;
 
     /**
+     * Constant representing face.
      * @hide
      */
-    int TYPE_FACE = 3;
+    int TYPE_FACE = 1 << 2;
 
     /**
      * Container for biometric data
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index bd149fd..b238d77 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -251,9 +251,40 @@
     private Executor mExecutor;
     private AuthenticationCallback mAuthenticationCallback;
 
-    IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() {
+    private final IBiometricServiceReceiver mBiometricServiceReceiver =
+            new IBiometricServiceReceiver.Stub() {
+
         @Override
-        public void onDialogDismissed(int reason) {
+        public void onAuthenticationSucceeded() throws RemoteException {
+            mExecutor.execute(() -> {
+                final AuthenticationResult result = new AuthenticationResult(mCryptoObject);
+                mAuthenticationCallback.onAuthenticationSucceeded(result);
+            });
+        }
+
+        @Override
+        public void onAuthenticationFailed() throws RemoteException {
+            mExecutor.execute(() -> {
+                mAuthenticationCallback.onAuthenticationFailed();
+            });
+        }
+
+        @Override
+        public void onError(int error, String message) throws RemoteException {
+            mExecutor.execute(() -> {
+                mAuthenticationCallback.onAuthenticationError(error, message);
+            });
+        }
+
+        @Override
+        public void onAcquired(int acquireInfo, String message) throws RemoteException {
+            mExecutor.execute(() -> {
+                mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message);
+            });
+        }
+
+        @Override
+        public void onDialogDismissed(int reason) throws RemoteException {
             // Check the reason and invoke OnClickListener(s) if necessary
             if (reason == DISMISSED_REASON_POSITIVE) {
                 mPositiveButtonInfo.executor.execute(() -> {
@@ -267,40 +298,6 @@
         }
     };
 
-    IBiometricServiceReceiver mBiometricServiceReceiver =
-            new IBiometricServiceReceiver.Stub() {
-
-        @Override
-        public void onAuthenticationSucceeded(long deviceId) throws RemoteException {
-            mExecutor.execute(() -> {
-                final AuthenticationResult result = new AuthenticationResult(mCryptoObject);
-                mAuthenticationCallback.onAuthenticationSucceeded(result);
-            });
-        }
-
-        @Override
-        public void onAuthenticationFailed(long deviceId) throws RemoteException {
-            mExecutor.execute(() -> {
-                mAuthenticationCallback.onAuthenticationFailed();
-            });
-        }
-
-        @Override
-        public void onError(long deviceId, int error, String message)
-                throws RemoteException {
-            mExecutor.execute(() -> {
-                mAuthenticationCallback.onAuthenticationError(error, message);
-            });
-        }
-
-        @Override
-        public void onAcquired(long deviceId, int acquireInfo, String message) {
-            mExecutor.execute(() -> {
-                mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message);
-            });
-        }
-    };
-
     private BiometricPrompt(Context context, Bundle bundle,
             ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) {
         mContext = context;
@@ -557,9 +554,8 @@
             mExecutor = executor;
             mAuthenticationCallback = callback;
             final long sessionId = crypto != null ? crypto.getOpId() : 0;
-            mService.authenticate(mToken, sessionId, userId,
-                    mBiometricServiceReceiver, 0 /* flags */, mContext.getOpPackageName(),
-                    mBundle, mDialogReceiver);
+            mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver,
+                    mContext.getOpPackageName(), mBundle);
         } catch (RemoteException e) {
             Log.e(TAG, "Remote exception while authenticating", e);
             mExecutor.execute(() -> {
diff --git a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl
deleted file mode 100644
index 27d25b8..0000000
--- a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2018 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 from the BiometricPrompt (SysUI) back to AuthenticationClient.
- * @hide
- */
-oneway interface IBiometricPromptReceiver {
-    void onDialogDismissed(int reason);
-}
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index e17feff..53a0761 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -18,7 +18,6 @@
 
 import android.os.Bundle;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
-import android.hardware.biometrics.IBiometricPromptReceiver;
 import android.hardware.biometrics.IBiometricServiceReceiver;
 
 /**
@@ -32,8 +31,7 @@
     // Requests authentication. The service choose the appropriate biometric to use, and show
     // the corresponding BiometricDialog.
     void authenticate(IBinder token, long sessionId, int userId,
-            IBiometricServiceReceiver receiver, int flags, String opPackageName,
-            in Bundle bundle, IBiometricPromptReceiver dialogReceiver);
+            IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle);
 
     // Cancel authentication for the given sessionId
     void cancelAuthentication(IBinder token, String opPackageName);
@@ -46,4 +44,8 @@
 
     // Explicitly set the active user.
     void setActiveUser(int userId);
+
+    // Notify BiometricService when <Biometric>Service is ready to start the prepared client.
+    // Client lifecycle is still managed in <Biometric>Service.
+    void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId);
 }
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
index a6e3696..22ef33e 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
@@ -16,12 +16,18 @@
 package android.hardware.biometrics;
 
 /**
- * Communication channel from the BiometricService back to BiometricPrompt.
+ * Communication channel from BiometricService back to BiometricPrompt
  * @hide
  */
 oneway interface IBiometricServiceReceiver {
-    void onAuthenticationSucceeded(long deviceId);
-    void onAuthenticationFailed(long deviceId);
-    void onError(long deviceId, int error, String message);
-    void onAcquired(long deviceId, int acquiredInfo, String message);
+    // Notify BiometricPrompt that authentication was successful
+    void onAuthenticationSucceeded();
+    // Noties that authentication failed.
+    void onAuthenticationFailed();
+    // Notify BiometricPrompt that an error has occurred.
+    void onError(int error, String message);
+    // Notifies that a biometric has been acquired.
+    void onAcquired(int acquiredInfo, String message);
+    // Notifies that the SystemUI dialog has been dismissed.
+    void onDialogDismissed(int reason);
 }
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl
new file mode 100644
index 0000000..180daaf
--- /dev/null
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 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 from
+ *   1) BiometricDialogImpl (SysUI) back to BiometricService
+ *   2) <Biometric>Service back to BiometricService
+ * Receives messages from the above and does some handling before forwarding to BiometricPrompt
+ * via IBiometricServiceReceiver.
+ * @hide
+ */
+oneway interface IBiometricServiceReceiverInternal {
+    // Notify BiometricService that authentication was successful. If user confirmation is required,
+    // the auth token must be submitted into KeyStore.
+    void onAuthenticationSucceeded(boolean requireConfirmation, in byte[] token);
+    // Notify BiometricService that an error has occurred.
+    void onAuthenticationFailed(int cookie, boolean requireConfirmation);
+    // Notify BiometricService than an error has occured. Forward to the correct receiver depending
+    // on the cookie.
+    void onError(int cookie, int error, String message);
+    // Notifies that a biometric has been acquired.
+    void onAcquired(int acquiredInfo, String message);
+    // Notifies that the SystemUI dialog has been dismissed.
+    void onDialogDismissed(int reason);
+    // Notifies that the user has pressed the "try again" button on SystemUI
+    void onTryAgainPressed();
+}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 47df8e8..a15dcec 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -15,9 +15,7 @@
  */
 package android.hardware.face;
 
-import android.os.Bundle;
-import android.hardware.biometrics.IBiometricPromptReceiver;
-import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.face.IFaceServiceReceiver;
 import android.hardware.face.Face;
@@ -32,19 +30,24 @@
     void authenticate(IBinder token, long sessionId,
             IFaceServiceReceiver receiver, int flags, String opPackageName);
 
-    // This method invokes the BiometricDialog. The arguments are almost the same as above,
-    // but should only be called from (BiometricPromptService).
-    void authenticateFromService(boolean requireConfirmation, IBinder token, long sessionId,
-            int userId, IBiometricServiceReceiver receiver, int flags, String opPackageName,
-            in Bundle bundle, IBiometricPromptReceiver dialogReceiver,
-            int callingUid, int callingPid, int callingUserId);
+    // This method prepares the service to start authenticating, but doesn't start authentication.
+    // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
+    // called from BiometricService. The additional uid, pid, userId arguments should be determined
+    // by BiometricService. To start authentication after the clients are ready, use
+    // startPreparedClient().
+    void prepareForAuthentication(boolean requireConfirmation, IBinder token, long sessionId,
+            int userId, IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
+            int cookie, int callingUid, int callingPid, int callingUserId);
+
+    // Starts authentication with the previously prepared client.
+    void startPreparedClient(int cookie);
 
     // Cancel authentication for the given sessionId
     void cancelAuthentication(IBinder token, String opPackageName);
 
     // Same as above, with extra arguments.
     void cancelAuthenticationFromService(IBinder token, String opPackageName,
-            int callingUid, int callingPid, int callingUserId);
+            int callingUid, int callingPid, int callingUserId, boolean fromClient);
 
     // Start face enrollment
     void enroll(IBinder token, in byte [] cryptoToken, int userId, IFaceServiceReceiver receiver,
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 2662a11..dd6b29d 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -15,9 +15,7 @@
  */
 package android.hardware.fingerprint;
 
-import android.os.Bundle;
-import android.hardware.biometrics.IBiometricPromptReceiver;
-import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.fingerprint.IFingerprintClientActiveCallback;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -35,22 +33,25 @@
     void authenticate(IBinder token, long sessionId, int userId,
             IFingerprintServiceReceiver receiver, int flags, String opPackageName);
 
-    // This method invokes the BiometricDialog. The arguments are almost the same as above, except
-    // this is protected by the MANAGE_BIOMETRIC signature permission. This method should only be
-    // called from BiometricPromptService. The additional uid, pid, userId arguments should be
-    // determined by BiometricPromptService.
-    void authenticateFromService(IBinder token, long sessionId, int userId,
-            IBiometricServiceReceiver receiver, int flags, String opPackageName,
-            in Bundle bundle, IBiometricPromptReceiver dialogReceiver,
+    // This method prepares the service to start authenticating, but doesn't start authentication.
+    // This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
+    // called from BiometricService. The additional uid, pid, userId arguments should be determined
+    // by BiometricService. To start authentication after the clients are ready, use
+    // startPreparedClient().
+    void prepareForAuthentication(IBinder token, long sessionId, int userId,
+            IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName, int cookie,
             int callingUid, int callingPid, int callingUserId);
 
+    // Starts authentication with the previously prepared client.
+    void startPreparedClient(int cookie);
+
     // Cancel authentication for the given sessionId
     void cancelAuthentication(IBinder token, String opPackageName);
 
     // Same as above, except this is protected by the MANAGE_BIOMETRIC signature permission. Takes
     // an additional uid, pid, userid.
     void cancelAuthenticationFromService(IBinder token, String opPackageName,
-            int callingUid, int callingPid, int callingUserId);
+            int callingUid, int callingPid, int callingUserId, boolean fromClient);
 
     // Start fingerprint enrollment
     void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver,
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index b42f1c4..8e11d85 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -25,6 +25,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.annotation.WorkerThread;
 import android.app.Activity;
@@ -1533,6 +1534,12 @@
         return SystemProperties.getBoolean(PROP_HAS_ADOPTABLE, false);
     }
 
+    /** {@hide} */
+    @TestApi
+    public static boolean hasIsolatedStorage() {
+        return SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, false);
+    }
+
     /**
      * @deprecated disabled now that FUSE has been replaced by sdcardfs
      * @hide
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2767505..981a0df 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -23545,6 +23545,16 @@
     }
 
     /**
+     * Get the identifier used for this view by the drawing system.
+     *
+     * @see RenderNode#getUniqueId()
+     * @return A long that uniquely identifies this view's drawing component
+     */
+    public long getUniqueDrawingId() {
+        return mRenderNode.getUniqueId();
+    }
+
+    /**
      * Returns this view's tag.
      *
      * @return the Object stored in this view as a tag, or {@code null} if not
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 604537f..600b1b3 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -18,7 +18,7 @@
 
 import android.content.ComponentName;
 import android.graphics.Rect;
-import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.os.Bundle;
 import android.service.notification.StatusBarNotification;
 
@@ -141,7 +141,7 @@
     void showShutdownUi(boolean isReboot, String reason);
 
     // Used to show the dialog when BiometricService starts authentication
-    void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type,
+    void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
             boolean requireConfirmation, int userId);
     // Used to hide the dialog when a biometric is authenticated
     void onBiometricAuthenticated();
@@ -151,4 +151,6 @@
     void onBiometricError(String error);
     // Used to hide the biometric dialog when the AuthenticationClient is stopped
     void hideBiometricDialog();
+    // Used to request the "try again" button for authentications which requireConfirmation=true
+    void showBiometricTryAgain();
 }
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index b7ffb57..bf82dc61 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -21,7 +21,7 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.service.notification.StatusBarNotification;
-import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 
 import com.android.internal.statusbar.IStatusBar;
 import com.android.internal.statusbar.StatusBarIcon;
@@ -92,7 +92,7 @@
     void showPinningEscapeToast();
 
     // Used to show the dialog when BiometricService starts authentication
-    void showBiometricDialog(in Bundle bundle, IBiometricPromptReceiver receiver, int type,
+    void showBiometricDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver, int type,
             boolean requireConfirmation, int userId);
     // Used to hide the dialog when a biometric is authenticated
     void onBiometricAuthenticated();
@@ -102,4 +102,6 @@
     void onBiometricError(String error);
     // Used to hide the biometric dialog when the AuthenticationClient is stopped
     void hideBiometricDialog();
+    // Used to request the "try again" button for authentications which requireConfirmation=true
+    void showBiometricTryAgain();
 }
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 8495850..b97a9fa 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -934,7 +934,7 @@
         // If the storage model feature flag is disabled, we need to fiddle
         // around with permission definitions to return us to pre-Q behavior.
         // STOPSHIP(b/112545973): remove once feature enabled by default
-        if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+        if (!StorageManager.hasIsolatedStorage()) {
             if (newPermissions.contains(android.Manifest.permission.READ_MEDIA_AUDIO) ||
                     newPermissions.contains(android.Manifest.permission.READ_MEDIA_VIDEO) ||
                     newPermissions.contains(android.Manifest.permission.READ_MEDIA_IMAGES)) {
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index bd6d976..477f894 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1444,9 +1444,14 @@
 
     <!-- Title shown when the system-provided biometric dialog is shown, asking the user to authenticate. [CHAR LIMIT=40] -->
     <string name="biometric_dialog_default_title">Application <xliff:g id="app" example="Gmail">%s</xliff:g> wants to authenticate.</string>
-
     <!-- Message shown when biometric hardware is not available [CHAR LIMIT=50] -->
     <string name="biometric_error_hw_unavailable">Biometric hardware unavailable</string>
+    <!-- Message shown when biometric authentication was canceled by the user [CHAR LIMIT=50] -->
+    <string name="biometric_error_user_canceled">Authentication canceled</string>
+    <!-- Message shown by the biometric dialog when biometric is not recognized -->
+    <string name="biometric_not_recognized">Not recognized</string>
+    <!-- Message shown when biometric authentication has been canceled [CHAR LIMIT=50] -->
+    <string name="biometric_error_canceled">Authentication canceled</string>
 
     <!-- Message shown during fingerprint acquisision when the fingerprint cannot be recognized -->
     <string name="fingerprint_acquired_partial">Partial fingerprint detected. Please try again.</string>
@@ -1462,8 +1467,6 @@
     <string-array name="fingerprint_acquired_vendor">
     </string-array>
 
-    <!-- Message shown by the biometric dialog when biometric is not recognized -->
-    <string name="biometric_not_recognized">Not recognized</string>
     <!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] -->
     <string name="fingerprint_authenticated">Fingerprint authenticated</string>
     <!-- Accessibility message announced when a face has been authenticated [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e251e27..b815fea 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2406,7 +2406,9 @@
   <!-- Biometric messages -->
   <java-symbol type="string" name="biometric_dialog_default_title" />
   <java-symbol type="string" name="biometric_error_hw_unavailable" />
+  <java-symbol type="string" name="biometric_error_user_canceled" />
   <java-symbol type="string" name="biometric_not_recognized" />
+  <java-symbol type="string" name="biometric_error_canceled" />
 
   <!-- Fingerprint messages -->
   <java-symbol type="string" name="fingerprint_error_unable_to_process" />
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index b047f8d..111dd0f 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -29,6 +29,7 @@
 import android.content.res.AssetFileDescriptor;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer2.DrmInfo;
 import android.media.MediaPlayer2Proto.PlayerMessage;
 import android.media.MediaPlayer2Proto.Value;
 import android.net.Uri;
@@ -72,7 +73,11 @@
 import java.util.Queue;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
@@ -300,21 +305,7 @@
     private volatile float mVolume = 1.0f;
     private VideoSize mVideoSize = new VideoSize(0, 0);
 
-    // TODO: create per-source drm fields in SourceInfo
-    // Modular DRM
-    private final Object mDrmLock = new Object();
-    //--- guarded by |mDrmLock| start
-    private UUID mDrmUUID;
-    private DrmInfo mDrmInfo;
-    private MediaDrm mDrmObj;
-    private byte[] mDrmSessionId;
-    private boolean mDrmInfoResolved;
-    private boolean mActiveDrmScheme;
-    private boolean mDrmConfigAllowed;
-    private boolean mDrmProvisioningInProgress;
-    private boolean mPrepareDrmInProgress;
-    private ProvisioningThread mDrmProvisioningThread;
-    //--- guarded by |mDrmLock| end
+    private ExecutorService mDrmThreadPool = Executors.newCachedThreadPool();
 
     // Creating a dummy audio track, used for keeping session id alive
     private final Object mSessionIdLock = new Object();
@@ -328,6 +319,7 @@
     private final List<Task> mPendingTasks = new LinkedList<>();
     @GuardedBy("mTaskLock")
     private Task mCurrentTask;
+    private final AtomicLong mTaskIdGenerator = new AtomicLong(0);
 
     @GuardedBy("mTaskLock")
     boolean mIsPreviousCommandSeekTo = false;
@@ -413,15 +405,13 @@
             mHandlerThread = null;
         }
 
-        setCurrentSourceInfo(null);
-        clearNextSourceInfos();
+        clearSourceInfos();
 
         // Modular DRM clean up
         mOnDrmConfigHelper = null;
         synchronized (mDrmEventCbLock) {
             mDrmEventCallbackRecords.clear();
         }
-        resetDrmState();
 
         native_release();
 
@@ -461,13 +451,8 @@
         synchronized (mDrmEventCbLock) {
             mDrmEventCallbackRecords.clear();
         }
-        setCurrentSourceInfo(null);
-        clearNextSourceInfos();
 
-        synchronized (mTaskLock) {
-            mPendingTasks.clear();
-            mIsPreviousCommandSeekTo = false;
-        }
+        clearSourceInfos();
 
         stayAwake(false);
         native_reset();
@@ -481,7 +466,6 @@
             mTaskHandler.removeCallbacksAndMessages(null);
         }
 
-        resetDrmState();
     }
 
     private native void native_reset();
@@ -706,13 +690,14 @@
                     }
 
                     synchronized (mSrcLock) {
-                        setCurrentSourceInfo(new SourceInfo(dsd));
+                        setCurrentSourceInfo_l(new SourceInfo(dsd));
                         handleDataSource(true /* isCurrent */, dsd, mCurrentSourceInfo.mId);
                     }
                 } finally {
                     dsd.close();
                 }
             }
+
         });
     }
 
@@ -732,7 +717,7 @@
             void process() {
                 Media2Utils.checkArgument(dsd != null, "the DataSourceDesc cannot be null");
                 synchronized (mSrcLock) {
-                    clearNextSourceInfos();
+                    clearNextSourceInfos_l();
                     mNextSourceInfos.add(new SourceInfo(dsd));
                 }
                 prepareNextDataSource();
@@ -758,7 +743,7 @@
                 }
 
                 synchronized (mSrcLock) {
-                    clearNextSourceInfos();
+                    clearNextSourceInfos_l();
                     for (DataSourceDesc dsd : dsds) {
                         if (dsd != null) {
                             mNextSourceInfos.add(new SourceInfo(dsd));
@@ -781,7 +766,9 @@
         return addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) {
             @Override
             void process() {
-                clearNextSourceInfos();
+                synchronized (mSrcLock) {
+                    clearNextSourceInfos_l();
+                }
             }
         });
     }
@@ -1073,7 +1060,7 @@
                 SourceInfo nextSourceInfo = mNextSourceInfos.peek();
                 if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_PREPARED) {
                     // Switch to next source only when it has been prepared.
-                    setCurrentSourceInfo(mNextSourceInfos.poll());
+                    setCurrentSourceInfo_l(mNextSourceInfos.poll());
 
                     long srcId = mCurrentSourceInfo.mId;
                     try {
@@ -2173,7 +2160,7 @@
             final int what = msg.arg1;
             final int extra = msg.arg2;
 
-            final SourceInfo sourceInfo = getSourceInfoById(srcId);
+            final SourceInfo sourceInfo = getSourceInfo(srcId);
             if (sourceInfo == null) {
                 return;
             }
@@ -2227,11 +2214,11 @@
                         Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL");
                     } else if (msg.obj instanceof byte[]) {
                         // The PlayerMessage was parsed already in postEventFromNative
-                        final DrmInfo drmInfo;
 
-                        synchronized (mDrmLock) {
-                            if (mDrmInfo != null) {
-                                drmInfo = mDrmInfo.makeCopy();
+                        final DrmInfo drmInfo;
+                        synchronized (sourceInfo) {
+                            if (sourceInfo.mDrmInfo != null) {
+                                drmInfo = sourceInfo.mDrmInfo.makeCopy();
                             } else {
                                 drmInfo = null;
                             }
@@ -2303,7 +2290,7 @@
                         }
                     });
 
-                    SourceInfo src = getSourceInfoById(srcId);
+                    SourceInfo src = getSourceInfo(srcId);
                     if (src != null) {
                         src.mBufferedPercentage.set(percent);
                     }
@@ -2504,6 +2491,7 @@
             return;
         }
 
+        final SourceInfo sourceInfo = mp.getSourceInfo(srcId);
         switch (what) {
             case MEDIA_DRM_INFO:
                 // We need to derive mDrmInfo before prepare() returns so processing it here
@@ -2511,7 +2499,7 @@
                 // notification looper so its handleMessage might process the event after prepare()
                 // has returned.
                 Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO");
-                if (obj != null) {
+                if (obj != null && sourceInfo != null) {
                     PlayerMessage playerMsg;
                     try {
                         playerMsg = PlayerMessage.parseFrom(obj);
@@ -2520,11 +2508,12 @@
                         break;
                     }
                     DrmInfo drmInfo = new DrmInfo(playerMsg);
-                    synchronized (mp.mDrmLock) {
-                        mp.mDrmInfo = drmInfo;
+                    synchronized (sourceInfo) {
+                        sourceInfo.mDrmInfo = drmInfo;
                     }
                 } else {
-                    Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj);
+                    Log.w(TAG, "MEDIA_DRM_INFO sourceInfo " + sourceInfo
+                            + " msg.obj of unexpected type " + obj);
                 }
                 break;
 
@@ -2533,8 +2522,10 @@
                 // mainly for prepare() use case. For prepare(), this still can run to a race
                 // condition b/c MediaPlayerNative releases the prepare() lock before calling notify
                 // so we also set mDrmInfoResolved in prepare().
-                synchronized (mp.mDrmLock) {
-                    mp.mDrmInfoResolved = true;
+                if (sourceInfo != null) {
+                    synchronized (sourceInfo) {
+                        sourceInfo.mDrmInfoResolved = true;
+                    }
                 }
                 break;
         }
@@ -3211,9 +3202,7 @@
      */
     // This is a synchronous call.
     public void setOnDrmConfigHelper(OnDrmConfigHelper listener) {
-        synchronized (mDrmLock) {
-            mOnDrmConfigHelper = listener;
-        }
+        mOnDrmConfigHelper = listener;
     }
 
     private OnDrmConfigHelper mOnDrmConfigHelper;
@@ -3358,24 +3347,27 @@
      * @throws IllegalStateException if called before being prepared
      */
     public DrmInfo getDrmInfo(@NonNull DataSourceDesc dsd) {
-        // TODO: this implementation only works when dsd is the only data source
-        DrmInfo drmInfo = null;
+        final SourceInfo sourceInfo = getSourceInfo(dsd);
+        if (sourceInfo != null) {
+            DrmInfo drmInfo = null;
 
-        // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet;
-        // regardless below returns drmInfo anyway instead of raising an exception
-        synchronized (mDrmLock) {
-            if (!mDrmInfoResolved && mDrmInfo == null) {
-                final String msg = "The Player has not been prepared yet";
-                Log.v(TAG, msg);
-                throw new IllegalStateException(msg);
-            }
+            // there is not much point if the app calls getDrmInfo within an OnDrmInfoListener;
+            // regardless below returns drmInfo anyway instead of raising an exception
+            synchronized (sourceInfo) {
+                if (!sourceInfo.mDrmInfoResolved && sourceInfo.mDrmInfo == null) {
+                    final String msg = "The Player has not been prepared yet";
+                    Log.v(TAG, msg);
+                    throw new IllegalStateException(msg);
+                }
 
-            if (mDrmInfo != null) {
-                drmInfo = mDrmInfo.makeCopy();
-            }
-        }  // synchronized
+                if (sourceInfo.mDrmInfo != null) {
+                    drmInfo  = sourceInfo.mDrmInfo.makeCopy();
+                }
+            }   // synchronized
 
-        return drmInfo;
+            return drmInfo;
+        }
+        return null;
     }
 
     /**
@@ -3411,15 +3403,28 @@
      */
     // This is an asynchronous call.
     public Object prepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) {
-        // TODO: this implementation only works when dsd is the only data source
         return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) {
             @Override
             void process() {
-                int status = PREPARE_DRM_STATUS_SUCCESS;
+                final SourceInfo sourceInfo = getSourceInfo(dsd);
+                int status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
                 boolean sendEvent = true;
 
+                if (sourceInfo == null) {
+                    Log.e(TAG, "prepareDrm(): DataSource not found.");
+                } else if (sourceInfo.mDrmInfo == null) {
+                    // only allowing if tied to a protected source;
+                    // might relax for releasing offline keys
+                    Log.e(TAG, "prepareDrm(): Wrong usage: The player must be prepared and "
+                            + "DRM info be retrieved before this call.");
+                } else {
+                    status = PREPARE_DRM_STATUS_SUCCESS;
+                }
+
                 try {
-                    doPrepareDrm(dsd, uuid);
+                    if (status == PREPARE_DRM_STATUS_SUCCESS) {
+                        sourceInfo.mDrmHandle.prepare(uuid);
+                    }
                 } catch (ResourceBusyException e) {
                     status = PREPARE_DRM_STATUS_RESOURCE_BUSY;
                 } catch (UnsupportedSchemeException e) {
@@ -3428,14 +3433,14 @@
                     Log.w(TAG, "prepareDrm: NotProvisionedException");
 
                     // handle provisioning internally; it'll reset mPrepareDrmInProgress
-                    status = handleProvisioninig(dsd, uuid);
+                    status = sourceInfo.mDrmHandle.handleProvisioninig(uuid, mTaskId);
 
                     if (status == PREPARE_DRM_STATUS_SUCCESS) {
                         // DrmEventCallback will be fired in provisioning
                         sendEvent = false;
                     } else {
-                        synchronized (mDrmLock) {
-                            cleanDrmObj();
+                        synchronized (sourceInfo.mDrmHandle) {
+                            sourceInfo.mDrmHandle.cleanDrmObj();
                         }
 
                         switch (status) {
@@ -3478,95 +3483,6 @@
         });
     }
 
-    private void doPrepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid)
-            throws UnsupportedSchemeException, ResourceBusyException,
-                   NotProvisionedException {
-        Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper);
-
-        synchronized (mDrmLock) {
-            // only allowing if tied to a protected source; might relax for releasing offline keys
-            if (mDrmInfo == null) {
-                final String msg = "prepareDrm(): Wrong usage: The player must be prepared and "
-                        + "DRM info be retrieved before this call.";
-                Log.e(TAG, msg);
-                throw new IllegalStateException(msg);
-            }
-
-            if (mActiveDrmScheme) {
-                final String msg = "prepareDrm(): Wrong usage: There is already "
-                        + "an active DRM scheme with " + mDrmUUID;
-                Log.e(TAG, msg);
-                throw new IllegalStateException(msg);
-            }
-
-            if (mPrepareDrmInProgress) {
-                final String msg = "prepareDrm(): Wrong usage: There is already "
-                        + "a pending prepareDrm call.";
-                Log.e(TAG, msg);
-                throw new IllegalStateException(msg);
-            }
-
-            if (mDrmProvisioningInProgress) {
-                final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress.";
-                Log.e(TAG, msg);
-                throw new IllegalStateException(msg);
-            }
-
-            // shouldn't need this; just for safeguard
-            cleanDrmObj();
-
-            mPrepareDrmInProgress = true;
-
-            try {
-                // only creating the DRM object to allow pre-openSession configuration
-                prepareDrm_createDrmStep(uuid);
-            } catch (Exception e) {
-                Log.w(TAG, "prepareDrm(): Exception ", e);
-                mPrepareDrmInProgress = false;
-                throw e;
-            }
-
-            mDrmConfigAllowed = true;
-        }  // synchronized
-
-        // call the callback outside the lock
-        if (mOnDrmConfigHelper != null)  {
-            mOnDrmConfigHelper.onDrmConfig(this, dsd);
-        }
-
-        synchronized (mDrmLock) {
-            mDrmConfigAllowed = false;
-            boolean earlyExit = false;
-
-            try {
-                prepareDrm_openSessionStep(uuid);
-
-                mDrmUUID = uuid;
-                mActiveDrmScheme = true;
-                mPrepareDrmInProgress = false;
-            } catch (IllegalStateException e) {
-                final String msg = "prepareDrm(): Wrong usage: The player must be "
-                        + "in the prepared state to call prepareDrm().";
-                Log.e(TAG, msg);
-                earlyExit = true;
-                mPrepareDrmInProgress = false;
-                throw new IllegalStateException(msg);
-            } catch (NotProvisionedException e) {
-                Log.w(TAG, "prepareDrm: NotProvisionedException", e);
-                throw e;
-            } catch (Exception e) {
-                Log.e(TAG, "prepareDrm: Exception " + e);
-                earlyExit = true;
-                mPrepareDrmInProgress = false;
-                throw e;
-            } finally {
-                if (earlyExit) {  // clean up object if didn't succeed
-                    cleanDrmObj();
-                }
-            }  // finally
-        }  // synchronized
-    }
-
     /**
      * Releases the DRM session for the given data source
      * <p>
@@ -3581,35 +3497,10 @@
     // This is a synchronous call.
     public void releaseDrm(@NonNull DataSourceDesc dsd)
             throws NoDrmSchemeException {
-        // TODO: this implementation only works when dsd is the only data source
-        synchronized (mDrmLock) {
-            Log.v(TAG, "releaseDrm:");
-
-            if (!mActiveDrmScheme) {
-                Log.e(TAG, "releaseDrm(): No active DRM scheme to release.");
-                throw new NoDrmSchemeException(
-                        "releaseDrm: No active DRM scheme to release.");
-            }
-
-            try {
-                // we don't have the player's state in this layer. The below call raises
-                // exception if we're in a non-stopped/prepared state.
-
-                // for cleaning native/mediaserver crypto object
-                native_releaseDrm();
-
-                // for cleaning client-side MediaDrm object; only called if above has succeeded
-                cleanDrmObj();
-
-                mActiveDrmScheme = false;
-            } catch (IllegalStateException e) {
-                Log.w(TAG, "releaseDrm: Exception ", e);
-                throw new IllegalStateException(
-                        "releaseDrm: The player is not in a valid state.");
-            } catch (Exception e) {
-                Log.e(TAG, "releaseDrm: Exception ", e);
-            }
-        }  // synchronized
+        final SourceInfo sourceInfo = getSourceInfo(dsd);
+        if (sourceInfo != null) {
+            sourceInfo.mDrmHandle.release();
+        }
     }
 
     private native void native_releaseDrm();
@@ -3653,51 +3544,22 @@
      *
      * @throws NoDrmSchemeException if there is no active DRM session
      */
-    @NonNull
     public MediaDrm.KeyRequest getDrmKeyRequest(
             @NonNull DataSourceDesc dsd,
             @Nullable byte[] keySetId, @Nullable byte[] initData,
             @Nullable String mimeType, @MediaDrmKeyType int keyType,
             @Nullable Map<String, String> optionalParameters)
             throws NoDrmSchemeException {
-        // TODO: this implementation only works when dsd is the only data source
-        Log.v(TAG, "getDrmKeyRequest: "
-                + " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType
-                + " keyType: " + keyType + " optionalParameters: " + optionalParameters);
+        Log.v(TAG, "getDrmKeyRequest: " +
+                " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType +
+                " keyType: " + keyType + " optionalParameters: " + optionalParameters);
 
-        synchronized (mDrmLock) {
-            if (!mActiveDrmScheme) {
-                Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
-                throw new NoDrmSchemeException(
-                        "getDrmKeyRequest: Has to set a DRM scheme first.");
-            }
-
-            try {
-                byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE)
-                        ? mDrmSessionId :  // sessionId for KEY_TYPE_STREAMING/OFFLINE
-                        keySetId;  // keySetId for KEY_TYPE_RELEASE
-
-                HashMap<String, String> hmapOptionalParameters =
-                                                (optionalParameters != null)
-                                                ? new HashMap<String, String>(optionalParameters) :
-                                                null;
-
-                MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType,
-                                                              keyType, hmapOptionalParameters);
-                Log.v(TAG, "getDrmKeyRequest:   --> request: " + request);
-
-                return request;
-
-            } catch (NotProvisionedException e) {
-                Log.w(TAG, "getDrmKeyRequest NotProvisionedException: "
-                        + "Unexpected. Shouldn't have reached here.");
-                throw new IllegalStateException("getDrmKeyRequest: Unexpected provisioning error.");
-            } catch (Exception e) {
-                Log.w(TAG, "getDrmKeyRequest Exception " + e);
-                throw e;
-            }
-
-        }  // synchronized
+        final SourceInfo sourceInfo = getSourceInfo(dsd);
+        if (sourceInfo != null) {
+            return sourceInfo.mDrmHandle.getDrmKeyRequest(
+                    keySetId, initData, mimeType, keyType, optionalParameters);
+        }
+        return null;
     }
 
     /**
@@ -3727,40 +3589,13 @@
             @NonNull DataSourceDesc dsd,
             @Nullable byte[] keySetId, @NonNull byte[] response)
             throws NoDrmSchemeException, DeniedByServerException {
-        // TODO: this implementation only works when dsd is the only data source
         Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response);
 
-        synchronized (mDrmLock) {
-
-            if (!mActiveDrmScheme) {
-                Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
-                throw new NoDrmSchemeException(
-                        "getDrmKeyRequest: Has to set a DRM scheme first.");
-            }
-
-            try {
-                byte[] scope = (keySetId == null)
-                                ? mDrmSessionId :     // sessionId for KEY_TYPE_STREAMING/OFFLINE
-                                keySetId;           // keySetId for KEY_TYPE_RELEASE
-
-                byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response);
-
-                Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response
-                        + " --> " + keySetResult);
-
-
-                return keySetResult;
-
-            } catch (NotProvisionedException e) {
-                Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: "
-                        + "Unexpected. Shouldn't have reached here.");
-                throw new IllegalStateException("provideDrmKeyResponse: "
-                        + "Unexpected provisioning error.");
-            } catch (Exception e) {
-                Log.w(TAG, "provideDrmKeyResponse Exception " + e);
-                throw e;
-            }
-        }  // synchronized
+        final SourceInfo sourceInfo = getSourceInfo(dsd);
+        if (sourceInfo != null) {
+            return sourceInfo.mDrmHandle.provideDrmKeyResponse(keySetId, response);
+        }
+        return null;
     }
 
     /**
@@ -3779,23 +3614,12 @@
             @NonNull DataSourceDesc dsd,
             @NonNull byte[] keySetId)
             throws NoDrmSchemeException {
-        // TODO: this implementation only works when dsd is the only data source
         Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId);
 
-        synchronized (mDrmLock) {
-            if (!mActiveDrmScheme) {
-                Log.w(TAG, "restoreDrmKeys NoDrmSchemeException");
-                throw new NoDrmSchemeException(
-                        "restoreDrmKeys: Has to set a DRM scheme first.");
-            }
-
-            try {
-                mDrmObj.restoreKeys(mDrmSessionId, keySetId);
-            } catch (Exception e) {
-                Log.w(TAG, "restoreKeys Exception " + e);
-                throw e;
-            }
-        }  // synchronized
+        final SourceInfo sourceInfo = getSourceInfo(dsd);
+        if (sourceInfo != null) {
+            sourceInfo.mDrmHandle.restoreDrmKeys(keySetId);
+        }
     }
 
     /**
@@ -3812,34 +3636,17 @@
      *
      * @throws NoDrmSchemeException if there is no active DRM session
      */
-    @NonNull
     public String getDrmPropertyString(
             @NonNull DataSourceDesc dsd,
             @NonNull @MediaDrmStringProperty String propertyName)
             throws NoDrmSchemeException {
-        // TODO: this implementation only works when dsd is the only data source
         Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName);
 
-        String value;
-        synchronized (mDrmLock) {
-
-            if (!mActiveDrmScheme && !mDrmConfigAllowed) {
-                Log.w(TAG, "getDrmPropertyString NoDrmSchemeException");
-                throw new NoDrmSchemeException(
-                        "getDrmPropertyString: Has to prepareDrm() first.");
-            }
-
-            try {
-                value = mDrmObj.getPropertyString(propertyName);
-            } catch (Exception e) {
-                Log.w(TAG, "getDrmPropertyString Exception " + e);
-                throw e;
-            }
-        }  // synchronized
-
-        Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value);
-
-        return value;
+        final SourceInfo sourceInfo = getSourceInfo(dsd);
+        if (sourceInfo != null) {
+            return sourceInfo.mDrmHandle.getDrmPropertyString(propertyName);
+        }
+        return null;
     }
 
     /**
@@ -3863,21 +3670,10 @@
         // TODO: this implementation only works when dsd is the only data source
         Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value);
 
-        synchronized (mDrmLock) {
-
-            if (!mActiveDrmScheme && !mDrmConfigAllowed) {
-                Log.w(TAG, "setDrmPropertyString NoDrmSchemeException");
-                throw new NoDrmSchemeException(
-                        "setDrmPropertyString: Has to prepareDrm() first.");
-            }
-
-            try {
-                mDrmObj.setPropertyString(propertyName, value);
-            } catch (Exception e) {
-                Log.w(TAG, "setDrmPropertyString Exception " + e);
-                throw e;
-            }
-        }  // synchronized
+        final SourceInfo sourceInfo = getSourceInfo(dsd);
+        if (sourceInfo != null) {
+            sourceInfo.mDrmHandle.setDrmPropertyString(propertyName, value);
+        }
     }
 
     /**
@@ -4029,43 +3825,6 @@
 
     private native void native_prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId);
 
-    // Modular DRM helpers
-
-    private void prepareDrm_createDrmStep(@NonNull UUID uuid)
-            throws UnsupportedSchemeException {
-        Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
-
-        try {
-            mDrmObj = new MediaDrm(uuid);
-            Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
-        } catch (Exception e) { // UnsupportedSchemeException
-            Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
-            throw e;
-        }
-    }
-
-    private void prepareDrm_openSessionStep(@NonNull UUID uuid)
-            throws NotProvisionedException, ResourceBusyException {
-        Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid);
-
-        // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do
-        // it anyway so it raises provisioning error if needed. We'd rather handle provisioning
-        // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse
-        try {
-            mDrmSessionId = mDrmObj.openSession();
-            Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId);
-
-            // Sending it down to native/mediaserver to create the crypto object
-            // This call could simply fail due to bad player state, e.g., after play().
-            native_prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId);
-            Log.v(TAG, "prepareDrm_openSessionStep: native_prepareDrm/Crypto succeeded");
-
-        } catch (Exception e) { //ResourceBusyException, NotProvisionedException
-            Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e);
-            throw e;
-        }
-    }
-
     // Instantiated from the native side
     @SuppressWarnings("unused")
     private static class StreamEventCallback extends AudioTrack.StreamEventCallback {
@@ -4097,227 +3856,28 @@
         }
     }
 
-    private class ProvisioningThread extends Thread {
-        public static final int TIMEOUT_MS = 60000;
-
-        private final DataSourceDesc mDSD;
-        private UUID mUuid;
-        private String mUrlStr;
-        private Object mDrmLock;
-        private MediaPlayer2 mMediaPlayer;
-        private int mStatus;
-        public  int status() {
-            return mStatus;
-        }
-
-        public ProvisioningThread(MediaDrm.ProvisionRequest request,
-                DataSourceDesc dsd,
-                UUID uuid, MediaPlayer2 mediaPlayer) {
-            // lock is held by the caller
-            mDSD = dsd;
-            mDrmLock = mediaPlayer.mDrmLock;
-            mMediaPlayer = mediaPlayer;
-
-            mUrlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
-            mUuid = uuid;
-
-            mStatus = PREPARE_DRM_STATUS_PREPARATION_ERROR;
-
-            Log.v(TAG, "handleProvisioninig: Thread is initialised url: " + mUrlStr);
-        }
-
-        public void run() {
-
-            byte[] response = null;
-            boolean provisioningSucceeded = false;
-            try {
-                URL url = new URL(mUrlStr);
-                final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-                try {
-                    connection.setRequestMethod("POST");
-                    connection.setDoOutput(false);
-                    connection.setDoInput(true);
-                    connection.setConnectTimeout(TIMEOUT_MS);
-                    connection.setReadTimeout(TIMEOUT_MS);
-
-                    connection.connect();
-                    response = readInputStreamFully(connection.getInputStream());
-
-                    Log.v(TAG, "handleProvisioninig: Thread run: response "
-                            + response.length + " " + response);
-                } catch (Exception e) {
-                    mStatus = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
-                    Log.w(TAG, "handleProvisioninig: Thread run: connect " + e + " url: " + url);
-                } finally {
-                    connection.disconnect();
-                }
-            } catch (Exception e)   {
-                mStatus = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
-                Log.w(TAG, "handleProvisioninig: Thread run: openConnection " + e);
-            }
-
-            if (response != null) {
-                try {
-                    mDrmObj.provideProvisionResponse(response);
-                    Log.v(TAG, "handleProvisioninig: Thread run: "
-                            + "provideProvisionResponse SUCCEEDED!");
-
-                    provisioningSucceeded = true;
-                } catch (Exception e) {
-                    mStatus = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
-                    Log.w(TAG, "handleProvisioninig: Thread run: "
-                            + "provideProvisionResponse " + e);
-                }
-            }
-
-            boolean succeeded = false;
-
-            synchronized (mDrmLock) {
-                // continuing with prepareDrm
-                if (provisioningSucceeded) {
-                    succeeded = mMediaPlayer.resumePrepareDrm(mUuid);
-                    mStatus = (succeeded)
-                            ? PREPARE_DRM_STATUS_SUCCESS :
-                            PREPARE_DRM_STATUS_PREPARATION_ERROR;
-                }
-                mMediaPlayer.mDrmProvisioningInProgress = false;
-                mMediaPlayer.mPrepareDrmInProgress = false;
-                if (!succeeded) {
-                    cleanDrmObj();  // cleaning up if it hasn't gone through while in the lock
-                }
-            }  // synchronized
-
-            // calling the callback outside the lock
-            sendDrmEvent(new DrmEventNotifier() {
-                @Override
-                public void notify(DrmEventCallback callback) {
-                    callback.onDrmPrepared(
-                            mMediaPlayer, mDSD, mStatus);
-                }
-            });
-
-            synchronized (mTaskLock) {
-                if (mCurrentTask != null
-                        && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM
-                        && mCurrentTask.mNeedToWaitForEventToComplete) {
-                    mCurrentTask = null;
-                    processPendingTask_l();
-                }
-            }
-        }
-
-        /**
-         * Returns a byte[] containing the remainder of 'in', closing it when done.
-         */
-        private byte[] readInputStreamFully(InputStream in) throws IOException {
-            try {
-                return readInputStreamFullyNoClose(in);
-            } finally {
-                in.close();
-            }
-        }
-
-        /**
-         * Returns a byte[] containing the remainder of 'in'.
-         */
-        private byte[] readInputStreamFullyNoClose(InputStream in) throws IOException {
-            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-            byte[] buffer = new byte[1024];
-            int count;
-            while ((count = in.read(buffer)) != -1) {
-                bytes.write(buffer, 0, count);
-            }
-            return bytes.toByteArray();
-        }
-    }  // ProvisioningThread
-
-    private int handleProvisioninig(DataSourceDesc dsd, UUID uuid) {
-        synchronized (mDrmLock) {
-            if (mDrmProvisioningInProgress) {
-                Log.e(TAG, "handleProvisioninig: Unexpected mDrmProvisioningInProgress");
-                return PREPARE_DRM_STATUS_PREPARATION_ERROR;
-            }
-
-            MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
-            if (provReq == null) {
-                Log.e(TAG, "handleProvisioninig: getProvisionRequest returned null.");
-                return PREPARE_DRM_STATUS_PREPARATION_ERROR;
-            }
-
-            Log.v(TAG, "handleProvisioninig provReq "
-                    + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl());
-
-            // networking in a background thread
-            mDrmProvisioningInProgress = true;
-
-            mDrmProvisioningThread = new ProvisioningThread(provReq, dsd, uuid, this);
-            mDrmProvisioningThread.start();
-
-            return PREPARE_DRM_STATUS_SUCCESS;
-        }
-    }
-
-    private boolean resumePrepareDrm(UUID uuid) {
-        Log.v(TAG, "resumePrepareDrm: uuid: " + uuid);
-
-        // mDrmLock is guaranteed to be held
-        boolean success = false;
+    /**
+     * Returns a byte[] containing the remainder of 'in', closing it when done.
+     */
+    private static byte[] readInputStreamFully(InputStream in) throws IOException {
         try {
-            // resuming
-            prepareDrm_openSessionStep(uuid);
-
-            mDrmUUID = uuid;
-            mActiveDrmScheme = true;
-
-            success = true;
-        } catch (Exception e) {
-            Log.w(TAG, "handleProvisioninig: Thread run native_prepareDrm resume failed with " + e);
-            // mDrmObj clean up is done by the caller
+            return readInputStreamFullyNoClose(in);
+        } finally {
+            in.close();
         }
-
-        return success;
     }
 
-    private void resetDrmState() {
-        synchronized (mDrmLock) {
-            Log.v(TAG, "resetDrmState:"
-                    + " mDrmInfo=" + mDrmInfo
-                    + " mDrmProvisioningThread=" + mDrmProvisioningThread
-                    + " mPrepareDrmInProgress=" + mPrepareDrmInProgress
-                    + " mActiveDrmScheme=" + mActiveDrmScheme);
-
-            mDrmInfoResolved = false;
-            mDrmInfo = null;
-
-            if (mDrmProvisioningThread != null) {
-                // timeout; relying on HttpUrlConnection
-                try {
-                    mDrmProvisioningThread.join();
-                } catch (InterruptedException e) {
-                    Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e);
-                }
-                mDrmProvisioningThread = null;
-            }
-
-            mPrepareDrmInProgress = false;
-            mActiveDrmScheme = false;
-
-            cleanDrmObj();
-        }  // synchronized
-    }
-
-    private void cleanDrmObj() {
-        // the caller holds mDrmLock
-        Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
-
-        if (mDrmSessionId != null)    {
-            mDrmObj.closeSession(mDrmSessionId);
-            mDrmSessionId = null;
+    /**
+     * Returns a byte[] containing the remainder of 'in'.
+     */
+    private static byte[] readInputStreamFullyNoClose(InputStream in) throws IOException {
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int count;
+        while ((count = in.read(buffer)) != -1) {
+            bytes.write(buffer, 0, count);
         }
-        if (mDrmObj != null) {
-            mDrmObj.release();
-            mDrmObj = null;
-        }
+        return bytes.toByteArray();
     }
 
     private static byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
@@ -4333,8 +3893,6 @@
         return uuidBytes;
     }
 
-    // Modular DRM end
-
     private static class TimedTextUtil {
         // These keys must be in sync with the keys in TextDescription2.h
         private static final int KEY_START_TIME                     = 7; // int
@@ -4410,6 +3968,7 @@
     }
 
     private abstract class Task implements Runnable {
+        final long mTaskId = mTaskIdGenerator.getAndIncrement();
         private final int mMediaCallType;
         private final boolean mNeedToWaitForEventToComplete;
         private DataSourceDesc mDSD;
@@ -4501,7 +4060,503 @@
         }
     };
 
-    private final class SourceInfo {
+    // Modular DRM
+    final class DrmHandle {
+
+        static final int PROVISION_TIMEOUT_MS = 60000;
+
+        final DataSourceDesc mDSD;
+
+        //--- guarded by |this| start
+        MediaDrm mDrmObj;
+        byte[] mDrmSessionId;
+        UUID mActiveDrmUUID;
+        boolean mDrmConfigAllowed;
+        boolean mDrmProvisioningInProgress;
+        boolean mPrepareDrmInProgress;
+        Future<?> mProvisionResult;
+        //--- guarded by |this| end
+
+        DrmHandle(DataSourceDesc dsd) {
+            mDSD = dsd;
+        }
+
+        void prepare(UUID uuid) throws UnsupportedSchemeException,
+                ResourceBusyException, NotProvisionedException {
+            final OnDrmConfigHelper onDrmConfigHelper = mOnDrmConfigHelper;
+            Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + onDrmConfigHelper);
+
+            synchronized (this) {
+                if (mActiveDrmUUID != null) {
+                    final String msg = "prepareDrm(): Wrong usage: There is already "
+                            + "an active DRM scheme with " + uuid;
+                    Log.e(TAG, msg);
+                    throw new IllegalStateException(msg);
+                }
+
+                if (mPrepareDrmInProgress) {
+                    final String msg = "prepareDrm(): Wrong usage: There is already "
+                            + "a pending prepareDrm call.";
+                    Log.e(TAG, msg);
+                    throw new IllegalStateException(msg);
+                }
+
+                if (mDrmProvisioningInProgress) {
+                    final String msg = "prepareDrm(): Unexpectd: Provisioning already in progress";
+                    Log.e(TAG, msg);
+                    throw new IllegalStateException(msg);
+                }
+
+                // shouldn't need this; just for safeguard
+                cleanDrmObj();
+
+                mPrepareDrmInProgress = true;
+
+                try {
+                    // only creating the DRM object to allow pre-openSession configuration
+                    prepareDrm_createDrmStep(uuid);
+                } catch (Exception e) {
+                    Log.w(TAG, "prepareDrm(): Exception ", e);
+                    mPrepareDrmInProgress = false;
+                    throw e;
+                }
+
+                mDrmConfigAllowed = true;
+            }  // synchronized
+
+            // call the callback outside the lock
+            if (onDrmConfigHelper != null)  {
+                onDrmConfigHelper.onDrmConfig(MediaPlayer2.this, mDSD);
+            }
+
+            synchronized (this) {
+                mDrmConfigAllowed = false;
+                boolean earlyExit = false;
+
+                try {
+                    prepareDrm_openSessionStep(uuid);
+
+                    this.mActiveDrmUUID = uuid;
+                    mPrepareDrmInProgress = false;
+                } catch (IllegalStateException e) {
+                    final String msg = "prepareDrm(): Wrong usage: The player must be "
+                            + "in the prepared state to call prepareDrm().";
+                    Log.e(TAG, msg);
+                    earlyExit = true;
+                    mPrepareDrmInProgress = false;
+                    throw new IllegalStateException(msg);
+                } catch (NotProvisionedException e) {
+                    Log.w(TAG, "prepareDrm: NotProvisionedException", e);
+                    throw e;
+                } catch (Exception e) {
+                    Log.e(TAG, "prepareDrm: Exception " + e);
+                    earlyExit = true;
+                    mPrepareDrmInProgress = false;
+                    throw e;
+                } finally {
+                    if (earlyExit) {  // clean up object if didn't succeed
+                        cleanDrmObj();
+                    }
+                }  // finally
+            }  // synchronized
+        }
+
+        void prepareDrm_createDrmStep(UUID uuid)
+                throws UnsupportedSchemeException {
+            Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
+
+            try {
+                mDrmObj = new MediaDrm(uuid);
+                Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
+            } catch (Exception e) { // UnsupportedSchemeException
+                Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
+                throw e;
+            }
+        }
+
+        void prepareDrm_openSessionStep(UUID uuid)
+                throws NotProvisionedException, ResourceBusyException {
+            Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid);
+
+            // TODO:
+            // don't need an open session for a future specialKeyReleaseDrm mode but we should do
+            // it anyway so it raises provisioning error if needed. We'd rather handle provisioning
+            // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse
+            try {
+                mDrmSessionId = mDrmObj.openSession();
+                Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId);
+
+                // Sending it down to native/mediaserver to create the crypto object
+                // This call could simply fail due to bad player state, e.g., after play().
+                MediaPlayer2.this.native_prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId);
+                Log.v(TAG, "prepareDrm_openSessionStep: native_prepareDrm/Crypto succeeded");
+
+            } catch (Exception e) { //ResourceBusyException, NotProvisionedException
+                Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e);
+                throw e;
+            }
+
+        }
+
+        int handleProvisioninig(UUID uuid, long taskId) {
+            synchronized (this) {
+                if (mDrmProvisioningInProgress) {
+                    Log.e(TAG, "handleProvisioninig: Unexpected mDrmProvisioningInProgress");
+                    return PREPARE_DRM_STATUS_PREPARATION_ERROR;
+                }
+
+                MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
+                if (provReq == null) {
+                    Log.e(TAG, "handleProvisioninig: getProvisionRequest returned null.");
+                    return PREPARE_DRM_STATUS_PREPARATION_ERROR;
+                }
+
+                Log.v(TAG, "handleProvisioninig provReq "
+                        + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl());
+
+                // networking in a background thread
+                mDrmProvisioningInProgress = true;
+
+                mProvisionResult = mDrmThreadPool.submit(newProvisioningTask(uuid, taskId));
+
+                return PREPARE_DRM_STATUS_SUCCESS;
+            }
+        }
+
+        void provision(UUID uuid, long taskId) {
+
+            MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
+            String urlStr = provReq.getDefaultUrl();
+            urlStr += "&signedRequest=" + new String(provReq.getData());
+            Log.v(TAG, "handleProvisioninig: Thread is initialised url: " + urlStr);
+
+            byte[] response = null;
+            boolean provisioningSucceeded = false;
+            int status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
+            try {
+                URL url = new URL(urlStr);
+                final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+                try {
+                    connection.setRequestMethod("POST");
+                    connection.setDoOutput(false);
+                    connection.setDoInput(true);
+                    connection.setConnectTimeout(PROVISION_TIMEOUT_MS);
+                    connection.setReadTimeout(PROVISION_TIMEOUT_MS);
+
+                    connection.connect();
+                    response = readInputStreamFully(connection.getInputStream());
+
+                    Log.v(TAG, "handleProvisioninig: Thread run: response " +
+                            response.length + " " + response);
+                } catch (Exception e) {
+                    status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
+                    Log.w(TAG, "handleProvisioninig: Thread run: connect " + e + " url: " + url);
+                } finally {
+                    connection.disconnect();
+                }
+            } catch (Exception e)   {
+                status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
+                Log.w(TAG, "handleProvisioninig: Thread run: openConnection " + e);
+            }
+
+            if (response != null) {
+                try {
+                    mDrmObj.provideProvisionResponse(response);
+                    Log.v(TAG, "handleProvisioninig: Thread run: " +
+                            "provideProvisionResponse SUCCEEDED!");
+
+                    provisioningSucceeded = true;
+                } catch (Exception e) {
+                    status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
+                    Log.w(TAG, "handleProvisioninig: Thread run: " +
+                            "provideProvisionResponse " + e);
+                }
+            }
+
+            boolean succeeded = false;
+
+            synchronized (this) {
+                // continuing with prepareDrm
+                if (provisioningSucceeded) {
+                    succeeded = resumePrepare(uuid);
+                    status = (succeeded) ?
+                            PREPARE_DRM_STATUS_SUCCESS :
+                            PREPARE_DRM_STATUS_PREPARATION_ERROR;
+                }
+                mDrmProvisioningInProgress = false;
+                mPrepareDrmInProgress = false;
+                if (!succeeded) {
+                    cleanDrmObj();  // cleaning up if it hasn't gone through while in the lock
+                }
+            }  // synchronized
+
+            // calling the callback outside the lock
+            final int finalStatus = status;
+            sendDrmEvent(new DrmEventNotifier() {
+                @Override
+                public void notify(DrmEventCallback callback) {
+                    callback.onDrmPrepared(
+                            MediaPlayer2.this, mDSD, finalStatus);
+                }
+            });
+
+            synchronized (mTaskLock) {
+                if (mCurrentTask != null
+                        && mCurrentTask.mTaskId == taskId
+                        && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM
+                        && mCurrentTask.mNeedToWaitForEventToComplete) {
+                    mCurrentTask = null;
+                    processPendingTask_l();
+                }
+            }
+        }
+
+        Runnable newProvisioningTask(UUID uuid, long taskId) {
+            return new Runnable() {
+                @Override
+                public void run() {
+                    provision(uuid, taskId);
+                }
+            };
+        }
+
+        boolean resumePrepare(UUID uuid) {
+            Log.v(TAG, "resumePrepareDrm: uuid: " + uuid);
+
+            // mDrmLock is guaranteed to be held
+            boolean success = false;
+            try {
+                // resuming
+                prepareDrm_openSessionStep(uuid);
+
+                this.mActiveDrmUUID = uuid;
+
+                success = true;
+            } catch (Exception e) {
+                Log.w(TAG, "handleProvisioninig: Thread run native_prepareDrm resume failed:" + e);
+                // mDrmObj clean up is done by the caller
+            }
+
+            return success;
+        }
+
+        void cleanDrmObj() {
+            // the caller holds mDrmLock
+            Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
+
+            if (mDrmSessionId != null)    {
+                mDrmObj.closeSession(mDrmSessionId);
+                mDrmSessionId = null;
+            }
+            if (mDrmObj != null) {
+                mDrmObj.close();
+                mDrmObj = null;
+            }
+        }
+
+        void release() throws NoDrmSchemeException {
+            synchronized (this) {
+                Log.v(TAG, "releaseDrm:");
+
+                if (mActiveDrmUUID == null) {
+                    Log.e(TAG, "releaseDrm(): No active DRM scheme to release.");
+                    throw new NoDrmSchemeException(
+                            "releaseDrm: No active DRM scheme to release.");
+                }
+
+                try {
+                    // we don't have the player's state in this layer. The below call raises
+                    // exception if we're in a non-stopped/prepared state.
+
+                    // for cleaning native/mediaserver crypto object
+                    native_releaseDrm();
+
+                    // for cleaning client-side MediaDrm object; only called if above has succeeded
+                    cleanDrmObj();
+
+                    this.mActiveDrmUUID = null;
+                } catch (IllegalStateException e) {
+                    Log.w(TAG, "releaseDrm: Exception ", e);
+                    throw new IllegalStateException(
+                            "releaseDrm: The player is not in a valid state.");
+                } catch (Exception e) {
+                    Log.e(TAG, "releaseDrm: Exception ", e);
+                }
+            }  // synchronized
+        }
+
+        void cleanup() {
+            synchronized (this) {
+                Log.v(TAG, "cleanupDrm: " +
+                        " mProvisioningTask=" + mProvisionResult +
+                        " mPrepareDrmInProgress=" + mPrepareDrmInProgress +
+                        " mActiveDrmScheme=" + mActiveDrmUUID);
+
+                if (mProvisionResult != null) {
+                    // timeout; relying on HttpUrlConnection
+                    try {
+                        mProvisionResult.get();
+                    }
+                    catch (InterruptedException | ExecutionException e) {
+                        Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e);
+                    }
+                }
+
+                // set to false to avoid duplicate release calls
+                this.mActiveDrmUUID = null;
+
+                cleanDrmObj();
+            }   // synchronized
+        }
+
+        Runnable newCleanupTask() {
+            return new Runnable() {
+                @Override
+                public void run() {
+                    cleanup();
+                }
+            };
+        }
+
+        MediaDrm.KeyRequest getDrmKeyRequest(
+                byte[] keySetId, byte[] initData,
+                String mimeType, int keyType,
+                Map<String, String> optionalParameters)
+                throws NoDrmSchemeException {
+            synchronized (this) {
+                if (mActiveDrmUUID == null) {
+                    Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
+                    throw new NoDrmSchemeException(
+                            "getDrmKeyRequest: Has to set a DRM scheme first.");
+                }
+
+                try {
+                    byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
+                            mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
+                            keySetId;                  // keySetId for KEY_TYPE_RELEASE
+
+                    HashMap<String, String> hmapOptionalParameters =
+                            (optionalParameters != null)
+                            ? new HashMap<String, String>(optionalParameters)
+                            : null;
+
+                    MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(
+                            scope, initData, mimeType, keyType, hmapOptionalParameters);
+                    Log.v(TAG, "getDrmKeyRequest:   --> request: " + request);
+
+                    return request;
+
+                } catch (NotProvisionedException e) {
+                    Log.w(TAG, "getDrmKeyRequest NotProvisionedException: " +
+                            "Unexpected. Shouldn't have reached here.");
+                    throw new IllegalStateException("getDrmKeyRequest: provisioning error.");
+                } catch (Exception e) {
+                    Log.w(TAG, "getDrmKeyRequest Exception " + e);
+                    throw e;
+                }
+
+            }
+        }
+
+        byte[] provideDrmKeyResponse(byte[] keySetId, byte[] response)
+                throws NoDrmSchemeException, DeniedByServerException {
+            synchronized (this) {
+
+                if (mActiveDrmUUID == null) {
+                    Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
+                    throw new NoDrmSchemeException(
+                            "getDrmKeyRequest: Has to set a DRM scheme first.");
+                }
+
+                try {
+                    byte[] scope = (keySetId == null) ?
+                                    mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
+                                    keySetId;                  // keySetId for KEY_TYPE_RELEASE
+
+                    byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response);
+
+                    Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId
+                            + " response: " + response + " --> " + keySetResult);
+
+
+                    return keySetResult;
+
+                } catch (NotProvisionedException e) {
+                    Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: " +
+                            "Unexpected. Shouldn't have reached here.");
+                    throw new IllegalStateException("provideDrmKeyResponse: " +
+                            "Unexpected provisioning error.");
+                } catch (Exception e) {
+                    Log.w(TAG, "provideDrmKeyResponse Exception " + e);
+                    throw e;
+                }
+            }
+        }
+
+        void restoreDrmKeys(byte[] keySetId)
+                throws NoDrmSchemeException {
+            synchronized (this) {
+                if (mActiveDrmUUID == null) {
+                    Log.w(TAG, "restoreDrmKeys NoDrmSchemeException");
+                    throw new NoDrmSchemeException(
+                            "restoreDrmKeys: Has to set a DRM scheme first.");
+                }
+
+                try {
+                    mDrmObj.restoreKeys(mDrmSessionId, keySetId);
+                } catch (Exception e) {
+                    Log.w(TAG, "restoreKeys Exception " + e);
+                    throw e;
+                }
+            }
+        }
+
+        String getDrmPropertyString(String propertyName)
+                throws NoDrmSchemeException {
+            String v;
+            synchronized (this) {
+
+                if (mActiveDrmUUID == null && !mDrmConfigAllowed) {
+                    Log.w(TAG, "getDrmPropertyString NoDrmSchemeException");
+                    throw new NoDrmSchemeException(
+                            "getDrmPropertyString: Has to prepareDrm() first.");
+                }
+
+                try {
+                    v = mDrmObj.getPropertyString(propertyName);
+                } catch (Exception e) {
+                    Log.w(TAG, "getDrmPropertyString Exception " + e);
+                    throw e;
+                }
+            }   // synchronized
+
+            Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + v);
+
+            return v;
+        }
+
+        void setDrmPropertyString(String propertyName, String value)
+                throws NoDrmSchemeException {
+            synchronized (this) {
+
+                if ( mActiveDrmUUID == null && !mDrmConfigAllowed ) {
+                    Log.w(TAG, "setDrmPropertyString NoDrmSchemeException");
+                    throw new NoDrmSchemeException(
+                            "setDrmPropertyString: Has to prepareDrm() first.");
+                }
+
+                try {
+                    mDrmObj.setPropertyString(propertyName, value);
+                } catch ( Exception e ) {
+                    Log.w(TAG, "setDrmPropertyString Exception " + e);
+                    throw e;
+                }
+            }
+        }
+
+    }
+
+    final class SourceInfo {
         final DataSourceDesc mDSD;
         final long mId = mSrcIdGenerator.getAndIncrement();
         AtomicInteger mBufferedPercentage = new AtomicInteger(0);
@@ -4513,8 +4568,14 @@
         int mStateAsNextSource = NEXT_SOURCE_STATE_INIT;
         boolean mPlayPendingAsNextSource = false;
 
+        // Modular DRM
+        final DrmHandle mDrmHandle;
+        DrmInfo mDrmInfo;
+        boolean mDrmInfoResolved;
+
         SourceInfo(DataSourceDesc dsd) {
             this.mDSD = dsd;
+            mDrmHandle = new DrmHandle(dsd);
         }
 
         void close() {
@@ -4535,7 +4596,7 @@
 
     }
 
-    private SourceInfo getSourceInfoById(long srcId) {
+    private SourceInfo getSourceInfo(long srcId) {
         synchronized (mSrcLock) {
             if (isCurrentSource(srcId)) {
                 return mCurrentSourceInfo;
@@ -4547,34 +4608,65 @@
         return null;
     }
 
+    private SourceInfo getSourceInfo(DataSourceDesc dsd) {
+        synchronized (mSrcLock) {
+            if (isCurrentSource(dsd)) {
+                return mCurrentSourceInfo;
+            }
+            if (isNextSource(dsd)) {
+                return mNextSourceInfos.peek();
+            }
+        }
+        return null;
+    }
+
     private boolean isCurrentSource(long srcId) {
         synchronized (mSrcLock) {
             return mCurrentSourceInfo != null && mCurrentSourceInfo.mId == srcId;
         }
     }
 
+    private boolean isCurrentSource(DataSourceDesc dsd) {
+        synchronized (mSrcLock) {
+            return mCurrentSourceInfo != null && mCurrentSourceInfo.mDSD == dsd;
+        }
+    }
+
     private boolean isNextSource(long srcId) {
         SourceInfo nextSourceInfo = mNextSourceInfos.peek();
         return nextSourceInfo != null && nextSourceInfo.mId == srcId;
     }
 
-    private void setCurrentSourceInfo(SourceInfo newSourceInfo) {
-        synchronized (mSrcLock) {
-            if (mCurrentSourceInfo != null) {
-                mCurrentSourceInfo.close();
-            }
-            mCurrentSourceInfo = newSourceInfo;
+    private boolean isNextSource(DataSourceDesc dsd) {
+        SourceInfo nextSourceInfo = mNextSourceInfos.peek();
+        return nextSourceInfo != null && nextSourceInfo.mDSD == dsd;
+    }
+
+    @GuardedBy("mSrcLock")
+    private void setCurrentSourceInfo_l(SourceInfo sourceInfo) {
+        cleanupSourceInfo(mCurrentSourceInfo);
+        mCurrentSourceInfo = sourceInfo;
+    }
+
+    @GuardedBy("mSrcLock")
+    private void clearNextSourceInfos_l() {
+        while (!mNextSourceInfos.isEmpty()) {
+            cleanupSourceInfo(mNextSourceInfos.poll());
         }
     }
 
-    private void clearNextSourceInfos() {
+    private void cleanupSourceInfo(SourceInfo sourceInfo) {
+        if (sourceInfo != null) {
+            sourceInfo.close();
+            Runnable task = sourceInfo.mDrmHandle.newCleanupTask();
+            mDrmThreadPool.submit(task);
+        }
+    }
+
+    private void clearSourceInfos() {
         synchronized (mSrcLock) {
-            for (SourceInfo sourceInfo : mNextSourceInfos) {
-                if (sourceInfo != null) {
-                    sourceInfo.close();
-                }
-            }
-            mNextSourceInfos.clear();
+            setCurrentSourceInfo_l(null);
+            clearNextSourceInfos_l();
         }
     }
 
diff --git a/packages/SystemUI/res/layout/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml
index 5ca34b0..1e8cd5a 100644
--- a/packages/SystemUI/res/layout/biometric_dialog.xml
+++ b/packages/SystemUI/res/layout/biometric_dialog.xml
@@ -160,6 +160,15 @@
                         android:maxLines="2"
                         android:text="@string/biometric_dialog_confirm"
                         android:visibility="gone"/>
+                    <!-- Try Again Button -->
+                    <Button android:id="@+id/button_try_again"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent"
+                        style="@*android:style/Widget.DeviceDefault.Button.Colored"
+                        android:gravity="center"
+                        android:maxLines="2"
+                        android:text="@string/biometric_dialog_try_again"
+                        android:visibility="gone"/>
                     <Space android:id="@+id/rightSpacer"
                         android:layout_width="12dip"
                         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2148e27..ec7c9b2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -272,6 +272,8 @@
     <string name="accessibility_biometric_dialog_help_area">Help message area</string>
     <!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] -->
     <string name="biometric_dialog_confirm">Confirm</string>
+    <!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR_LIMIT=30] -->
+    <string name="biometric_dialog_try_again">Try again</string>
 
     <!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication -->
     <string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
index c0047c0..a90a7d2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
@@ -21,7 +21,7 @@
 import android.content.res.Configuration;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricPrompt;
-import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -52,15 +52,20 @@
     private static final int MSG_BUTTON_NEGATIVE = 6;
     private static final int MSG_USER_CANCELED = 7;
     private static final int MSG_BUTTON_POSITIVE = 8;
+    private static final int MSG_BIOMETRIC_SHOW_TRY_AGAIN = 9;
+    private static final int MSG_TRY_AGAIN_PRESSED = 10;
 
     private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view
     private SomeArgs mCurrentDialogArgs;
     private BiometricDialogView mCurrentDialog;
     private WindowManager mWindowManager;
-    private IBiometricPromptReceiver mReceiver;
+    private IBiometricServiceReceiverInternal mReceiver;
     private boolean mDialogShowing;
     private Callback mCallback = new Callback();
 
+    private boolean mTryAgainShowing; // No good place to save state before config change :/
+    private boolean mConfirmShowing; // No good place to save state before config change :/
+
     private Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -89,6 +94,15 @@
                 case MSG_BUTTON_POSITIVE:
                     handleButtonPositive();
                     break;
+                case MSG_BIOMETRIC_SHOW_TRY_AGAIN:
+                    handleShowTryAgain();
+                    break;
+                case MSG_TRY_AGAIN_PRESSED:
+                    handleTryAgainPressed();
+                    break;
+                default:
+                    Log.w(TAG, "Unknown message: " + msg.what);
+                    break;
             }
         }
     };
@@ -96,7 +110,7 @@
     private class Callback implements DialogViewCallback {
         @Override
         public void onUserCanceled() {
-            mHandler.obtainMessage(BiometricDialogImpl.MSG_USER_CANCELED).sendToTarget();
+            mHandler.obtainMessage(MSG_USER_CANCELED).sendToTarget();
         }
 
         @Override
@@ -107,12 +121,17 @@
 
         @Override
         public void onNegativePressed() {
-            mHandler.obtainMessage(BiometricDialogImpl.MSG_BUTTON_NEGATIVE).sendToTarget();
+            mHandler.obtainMessage(MSG_BUTTON_NEGATIVE).sendToTarget();
         }
 
         @Override
         public void onPositivePressed() {
-            mHandler.obtainMessage(BiometricDialogImpl.MSG_BUTTON_POSITIVE).sendToTarget();
+            mHandler.obtainMessage(MSG_BUTTON_POSITIVE).sendToTarget();
+        }
+
+        @Override
+        public void onTryAgainPressed() {
+            mHandler.obtainMessage(MSG_TRY_AGAIN_PRESSED).sendToTarget();
         }
     }
 
@@ -139,13 +158,14 @@
     }
 
     @Override
-    public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type,
-            boolean requireConfirmation, int userId) {
+    public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
+            int type, boolean requireConfirmation, int userId) {
         if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type);
         // Remove these messages as they are part of the previous client
         mHandler.removeMessages(MSG_BIOMETRIC_ERROR);
         mHandler.removeMessages(MSG_BIOMETRIC_HELP);
         mHandler.removeMessages(MSG_BIOMETRIC_AUTHENTICATED);
+        mHandler.removeMessages(MSG_HIDE_DIALOG);
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = bundle;
         args.arg2 = receiver;
@@ -179,6 +199,12 @@
         mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
     }
 
+    @Override
+    public void showBiometricTryAgain() {
+        if (DEBUG) Log.d(TAG, "showBiometricTryAgain");
+        mHandler.obtainMessage(MSG_BIOMETRIC_SHOW_TRY_AGAIN).sendToTarget();
+    }
+
     private void handleShowDialog(SomeArgs args, boolean skipAnimation) {
         mCurrentDialogArgs = args;
         final int type = args.argi1;
@@ -193,11 +219,13 @@
             Log.w(TAG, "Dialog already showing");
             return;
         }
-        mReceiver = (IBiometricPromptReceiver) args.arg2;
+        mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
         mCurrentDialog.setBundle((Bundle)args.arg1);
         mCurrentDialog.setRequireConfirmation((boolean) args.arg3);
         mCurrentDialog.setUserId(args.argi2);
         mCurrentDialog.setSkipIntro(skipAnimation);
+        mCurrentDialog.setPendingTryAgain(mTryAgainShowing);
+        mCurrentDialog.setPendingConfirm(mConfirmShowing);
         mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams());
         mDialogShowing = true;
     }
@@ -209,7 +237,8 @@
                 mContext.getResources()
                         .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
         if (mCurrentDialog.requiresConfirmation()) {
-            mCurrentDialog.showConfirmationButton();
+            mConfirmShowing = true;
+            mCurrentDialog.showConfirmationButton(true /* show */);
         } else {
             handleHideDialog(false /* userCanceled */);
         }
@@ -226,6 +255,7 @@
             if (DEBUG) Log.d(TAG, "Dialog already dismissed");
             return;
         }
+        mTryAgainShowing = false;
         mCurrentDialog.showErrorMessage(error);
     }
 
@@ -246,6 +276,8 @@
         }
         mReceiver = null;
         mDialogShowing = false;
+        mConfirmShowing = false;
+        mTryAgainShowing = false;
         mCurrentDialog.startDismiss();
     }
 
@@ -259,6 +291,7 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Remote exception when handling negative button", e);
         }
+        mTryAgainShowing = false;
         handleHideDialog(false /* userCanceled */);
     }
 
@@ -272,13 +305,31 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Remote exception when handling positive button", e);
         }
+        mConfirmShowing = false;
         handleHideDialog(false /* userCanceled */);
     }
 
     private void handleUserCanceled() {
+        mTryAgainShowing = false;
+        mConfirmShowing = false;
         handleHideDialog(true /* userCanceled */);
     }
 
+    private void handleShowTryAgain() {
+        mCurrentDialog.showTryAgainButton(true /* show */);
+        mTryAgainShowing = true;
+    }
+
+    private void handleTryAgainPressed() {
+        try {
+            mCurrentDialog.clearTemporaryMessage();
+            mTryAgainShowing = false;
+            mReceiver.onTryAgainPressed();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException when handling try again", e);
+        }
+    }
+
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
index 38427ad..e085f23 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
@@ -87,6 +87,9 @@
     protected boolean mRequireConfirmation;
     private int mUserId; // used to determine if we should show work background
 
+    private boolean mPendingShowTryAgain;
+    private boolean mPendingShowConfirm;
+
     protected abstract void updateIcon(int lastState, int newState);
     protected abstract int getHintStringResourceId();
     protected abstract int getAuthenticatedAccessibilityResourceId();
@@ -178,6 +181,7 @@
         final Button negative = mLayout.findViewById(R.id.button2);
         final Button positive = mLayout.findViewById(R.id.button1);
         final ImageView icon = mLayout.findViewById(R.id.biometric_icon);
+        final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
 
         icon.setContentDescription(getResources().getString(getIconDescriptionResourceId()));
 
@@ -193,6 +197,11 @@
             mCallback.onPositivePressed();
         });
 
+        tryAgain.setOnClickListener((View v) -> {
+            showTryAgainButton(false /* show */);
+            mCallback.onTryAgainPressed();
+        });
+
         mLayout.setFocusableInTouchMode(true);
         mLayout.requestFocus();
     }
@@ -207,7 +216,6 @@
         final TextView subtitle = mLayout.findViewById(R.id.subtitle);
         final TextView description = mLayout.findViewById(R.id.description);
         final Button negative = mLayout.findViewById(R.id.button2);
-        final Button positive = mLayout.findViewById(R.id.button1);
         final ImageView backgroundView = mLayout.findViewById(R.id.background);
 
         if (mUserManager.isManagedProfile(mUserId)) {
@@ -233,8 +241,6 @@
         title.setText(titleText);
         title.setSelected(true);
 
-        positive.setVisibility(View.INVISIBLE);
-
         final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
         if (TextUtils.isEmpty(subtitleText)) {
             subtitle.setVisibility(View.GONE);
@@ -243,7 +249,8 @@
             subtitle.setText(subtitleText);
         }
 
-        final CharSequence descriptionText = mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
+        final CharSequence descriptionText =
+                mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
         if (TextUtils.isEmpty(descriptionText)) {
             description.setVisibility(View.GONE);
         } else {
@@ -253,6 +260,9 @@
 
         negative.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
 
+        showTryAgainButton(mPendingShowTryAgain);
+        showConfirmationButton(mPendingShowConfirm);
+
         if (mWasForceRemoved || mSkipIntro) {
             // Show the dialog immediately
             mLayout.animate().cancel();
@@ -281,11 +291,17 @@
     public void startDismiss() {
         mAnimatingAway = true;
 
+        // This is where final cleanup should occur.
         final Runnable endActionRunnable = new Runnable() {
             @Override
             public void run() {
                 mWindowManager.removeView(BiometricDialogView.this);
                 mAnimatingAway = false;
+                // Set the icons / text back to normal state
+                handleClearMessage();
+                showTryAgainButton(false /* show */);
+                mPendingShowTryAgain = false;
+                mPendingShowConfirm = false;
             }
         };
 
@@ -345,9 +361,13 @@
         return mRequireConfirmation;
     }
 
-    public void showConfirmationButton() {
+    public void showConfirmationButton(boolean show) {
         final Button positive = mLayout.findViewById(R.id.button1);
-        positive.setVisibility(View.VISIBLE);
+        if (show) {
+            positive.setVisibility(View.VISIBLE);
+        } else {
+            positive.setVisibility(View.GONE);
+        }
     }
 
     public void setUserId(int userId) {
@@ -376,12 +396,18 @@
                 BiometricPrompt.HIDE_DIALOG_DELAY);
     }
 
+    public void clearTemporaryMessage() {
+        mHandler.removeMessages(MSG_CLEAR_MESSAGE);
+        mHandler.obtainMessage(MSG_CLEAR_MESSAGE).sendToTarget();
+    }
+
     public void showHelpMessage(String message) {
         showTemporaryMessage(message);
     }
 
     public void showErrorMessage(String error) {
         showTemporaryMessage(error);
+        showTryAgainButton(false /* show */);
         mCallback.onErrorShown();
     }
 
@@ -390,6 +416,25 @@
         mLastState = newState;
     }
 
+    public void showTryAgainButton(boolean show) {
+        final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
+        if (show) {
+            tryAgain.setVisibility(View.VISIBLE);
+        } else {
+            tryAgain.setVisibility(View.GONE);
+        }
+    }
+
+    // Set the state before the window is attached, so we know if the dialog should be started
+    // with or without the button. This is because there's no good onPause signal
+    public void setPendingTryAgain(boolean show) {
+        mPendingShowTryAgain = show;
+    }
+
+    public void setPendingConfirm(boolean show) {
+        mPendingShowConfirm = show;
+    }
+
     public WindowManager.LayoutParams getLayoutParams() {
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
index f388d9c..24fd22e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
@@ -43,4 +43,9 @@
      * should be dismissed.
      */
     void onPositivePressed();
+
+    /**
+     * Invoked when the "try again" button is pressed.
+     */
+    void onTryAgainPressed();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 0c8f487..8b93995 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -21,7 +21,7 @@
 import android.app.StatusBarManager;
 import android.content.ComponentName;
 import android.graphics.Rect;
-import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -96,6 +96,7 @@
     private static final int MSG_SHOW_CHARGING_ANIMATION       = 44 << MSG_SHIFT;
     private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT;
     private static final int MSG_SHOW_PINNING_TOAST_ESCAPE     = 46 << MSG_SHIFT;
+    private static final int MSG_BIOMETRIC_TRY_AGAIN           = 47 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -163,12 +164,13 @@
 
         default void onRotationProposal(int rotation, boolean isValid) { }
 
-        default void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver,
+        default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
                 int type, boolean requireConfirmation, int userId) { }
         default void onBiometricAuthenticated() { }
         default void onBiometricHelp(String message) { }
         default void onBiometricError(String error) { }
         default void hideBiometricDialog() { }
+        default void showBiometricTryAgain() { }
     }
 
     @VisibleForTesting
@@ -523,8 +525,8 @@
     }
 
     @Override
-    public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type,
-            boolean requireConfirmation, int userId) {
+    public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
+            int type, boolean requireConfirmation, int userId) {
         synchronized (mLock) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = bundle;
@@ -565,6 +567,13 @@
         }
     }
 
+    @Override
+    public void showBiometricTryAgain() {
+        synchronized (mLock) {
+            mHandler.obtainMessage(MSG_BIOMETRIC_TRY_AGAIN).sendToTarget();
+        }
+    }
+
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
@@ -774,7 +783,7 @@
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).showBiometricDialog(
                                 (Bundle) someArgs.arg1,
-                                (IBiometricPromptReceiver) someArgs.arg2,
+                                (IBiometricServiceReceiverInternal) someArgs.arg2,
                                 someArgs.argi1 /* type */,
                                 (boolean) someArgs.arg3 /* requireConfirmation */,
                                 someArgs.argi2 /* userId */);
@@ -816,6 +825,11 @@
                         mCallbacks.get(i).showPinningEscapeToast();
                     }
                     break;
+                case MSG_BIOMETRIC_TRY_AGAIN:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).showBiometricTryAgain();
+                    }
+                    break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 713bd90..fef28cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -375,12 +375,9 @@
 
                 // Find the amount to translate up. This is needed in order to understand the
                 // direction of the remove animation (either downwards or upwards)
-                ExpandableViewState viewState =
-                        ((ExpandableView) event.viewAfterChangingView).getViewState();
-                int actualHeight = changingView.getActualHeight();
                 // upwards by default
                 float translationDirection = -1.0f;
-                if (viewState != null) {
+                if (event.viewAfterChangingView != null) {
                     float ownPosition = changingView.getTranslationY();
                     if (changingView instanceof ExpandableNotificationRow
                             && event.viewAfterChangingView instanceof ExpandableNotificationRow) {
@@ -396,8 +393,11 @@
                             ownPosition = changingRow.getTranslationWhenRemoved();
                         }
                     }
+                    int actualHeight = changingView.getActualHeight();
                     // there was a view after this one, Approximate the distance the next child
                     // travelled
+                    ExpandableViewState viewState =
+                            ((ExpandableView) event.viewAfterChangingView).getViewState();
                     translationDirection = ((viewState.yTranslation
                             - (ownPosition + actualHeight / 2.0f)) * 2 /
                             actualHeight);
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 356a4da..8d912fa 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -692,7 +692,7 @@
                     }
                 });
 
-        if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+        if (!StorageManager.hasIsolatedStorage()) {
             StorageManagerInternal storageManagerInternal = LocalServices.getService(
                     StorageManagerInternal.class);
             storageManagerInternal.addExternalStoragePolicy(
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0e6f8dd..e933bd0 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -183,8 +183,7 @@
     private static final String ZRAM_ENABLED_PROPERTY =
             "persist.sys.zram_enabled";
 
-    private static final boolean ENABLE_ISOLATED_STORAGE = SystemProperties
-            .getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false);
+    private static final boolean ENABLE_ISOLATED_STORAGE = StorageManager.hasIsolatedStorage();
 
     public static class Lifecycle extends SystemService {
         private StorageManagerService mStorageManagerService;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 80f47d5..7f4675d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19346,7 +19346,7 @@
 
         @Override
         public boolean isAppStorageSandboxed(int pid, int uid) {
-            if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+            if (!StorageManager.hasIsolatedStorage()) {
                 return false;
             }
             if (uid == SHELL_UID || uid == ROOT_UID) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index a0977be..8c39d75 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -58,8 +58,9 @@
 /**
  * BROADCASTS
  *
- * We keep two broadcast queues and associated bookkeeping, one for those at
- * foreground priority, and one for normal (background-priority) broadcasts.
+ * We keep three broadcast queues and associated bookkeeping, one for those at
+ * foreground priority, and one for normal (background-priority) broadcasts, and one to
+ * offload special broadcasts that we know take a long time, such as BOOT_COMPLETED.
  */
 public final class BroadcastQueue {
     private static final String TAG = "BroadcastQueue";
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 4b19398..7991783 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -27,7 +27,6 @@
 import static android.os.Process.getTotalMemory;
 import static android.os.Process.killProcessQuiet;
 import static android.os.Process.startWebView;
-import static android.os.storage.StorageManager.PROP_ISOLATED_STORAGE;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
@@ -73,6 +72,7 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.os.storage.StorageManager;
 import android.os.storage.StorageManagerInternal;
 import android.text.TextUtils;
 import android.util.EventLog;
@@ -1281,8 +1281,7 @@
                     final IPackageManager pm = AppGlobals.getPackageManager();
                     permGids = pm.getPackageGids(app.info.packageName,
                             MATCH_DIRECT_BOOT_AUTO, app.userId);
-                    if (SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, false)
-                            && mountExtStorageFull) {
+                    if (StorageManager.hasIsolatedStorage() && mountExtStorageFull) {
                         mountExternal = Zygote.MOUNT_EXTERNAL_FULL;
                     } else {
                         StorageManagerInternal storageManagerInternal = LocalServices.getService(
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
index 2c2d404..eaa7a83 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationClient.java
@@ -19,19 +19,11 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricPrompt;
-import android.hardware.biometrics.IBiometricPromptReceiver;
-import android.os.Bundle;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.security.KeyStore;
-import android.text.TextUtils;
 import android.util.Slog;
 
-import com.android.internal.statusbar.IStatusBarService;
-
 import java.util.ArrayList;
 
 /**
@@ -39,88 +31,15 @@
  */
 public abstract class AuthenticationClient extends ClientMonitor {
     private long mOpId;
-    private Handler mHandler;
 
     public abstract int handleFailedAttempt();
     public abstract void resetFailedAttempts();
-    public abstract String getErrorString(int error, int vendorCode);
-    public abstract String getAcquiredString(int acquireInfo, int vendorCode);
-    /**
-      * @return one of {@link #TYPE_FINGERPRINT} {@link #TYPE_IRIS} or {@link #TYPE_FACE}
-      */
-    public abstract int getBiometricType();
 
     public static final int LOCKOUT_NONE = 0;
     public static final int LOCKOUT_TIMED = 1;
     public static final int LOCKOUT_PERMANENT = 2;
 
     private final boolean mRequireConfirmation;
-    // Callback mechanism received from the client
-    // (BiometricPrompt -> BiometricPromptService -> <Biometric>Service -> AuthenticationClient)
-    private IBiometricPromptReceiver mDialogReceiverFromClient;
-    private Bundle mBundle;
-    private IStatusBarService mStatusBarService;
-    private boolean mInLockout;
-    private TokenEscrow mEscrow;
-    protected boolean mDialogDismissed;
-
-    /**
-     * Container that holds the identifier and authToken. For biometrics that require user
-     * confirmation, these should not be sent to their final destinations until the user confirms.
-     */
-    class TokenEscrow {
-        final BiometricAuthenticator.Identifier mIdentifier;
-        final ArrayList<Byte> mToken;
-
-        TokenEscrow(BiometricAuthenticator.Identifier identifier, ArrayList<Byte> token) {
-            mIdentifier = identifier;
-            mToken = token;
-        }
-
-        BiometricAuthenticator.Identifier getIdentifier() {
-            return mIdentifier;
-        }
-
-        ArrayList<Byte> getToken() {
-            return mToken;
-        }
-    }
-
-    // Receives events from SystemUI and handles them before forwarding them to BiometricDialog
-    protected IBiometricPromptReceiver mDialogReceiver = new IBiometricPromptReceiver.Stub() {
-        @Override // binder call
-        public void onDialogDismissed(int reason) {
-            if (mBundle != null && mDialogReceiverFromClient != null) {
-                try {
-                    if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) {
-                        // Positive button is used by passive modalities as a "confirm" button,
-                        // do not send to client
-                        mDialogReceiverFromClient.onDialogDismissed(reason);
-                    }
-                    if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) {
-                        onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
-                                0 /* vendorCode */);
-                    } else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) {
-                        // Have the service send the token to KeyStore, and send onAuthenticated
-                        // to the application.
-                        if (mEscrow != null) {
-                            if (DEBUG) Slog.d(getLogTag(), "Confirmed");
-                            addTokenToKeyStore(mEscrow.getToken());
-                            notifyClientAuthenticationSucceeded(mEscrow.getIdentifier());
-                            mEscrow = null;
-                            onAuthenticationConfirmed();
-                        } else {
-                            Slog.e(getLogTag(), "Escrow is null!!!");
-                        }
-                    }
-                    mDialogDismissed = true;
-                } catch (RemoteException e) {
-                    Slog.e(getLogTag(), "Remote exception", e);
-                }
-                stop(true /* initiatedByClient */);
-            }
-        }
-    };
 
     /**
      * This method is called when authentication starts.
@@ -133,25 +52,13 @@
      */
     public abstract void onStop();
 
-    /**
-     * This method is called when biometric authentication was confirmed by the user. The client
-     * should be removed.
-     */
-    public abstract void onAuthenticationConfirmed();
-
     public AuthenticationClient(Context context, Metrics metrics,
             BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
             BiometricServiceBase.ServiceListener listener, int targetUserId, int groupId, long opId,
-            boolean restricted, String owner, Bundle bundle,
-            IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService,
-            boolean requireConfirmation) {
+            boolean restricted, String owner, int cookie, boolean requireConfirmation) {
         super(context, metrics, daemon, halDeviceId, token, listener, targetUserId, groupId,
-                restricted, owner);
+                restricted, owner, cookie);
         mOpId = opId;
-        mBundle = bundle;
-        mDialogReceiverFromClient = dialogReceiver;
-        mStatusBarService = statusBarService;
-        mHandler = new Handler(Looper.getMainLooper());
         mRequireConfirmation = requireConfirmation;
     }
 
@@ -164,175 +71,99 @@
         stop(false /* initiatedByClient */);
     }
 
-    @Override
-    public boolean onAcquired(int acquiredInfo, int vendorCode) {
-        // If the dialog is showing, the client doesn't need to receive onAcquired messages.
-        if (mBundle != null) {
-            try {
-                if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
-                    mStatusBarService.onBiometricHelp(getAcquiredString(acquiredInfo, vendorCode));
-                }
-                return false; // acquisition continues
-            } catch (RemoteException e) {
-                Slog.e(getLogTag(), "Remote exception when sending acquired message", e);
-                return true; // client failed
-            } finally {
-                // Good scans will keep the device awake
-                if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
-                    notifyUserActivity();
-                }
-            }
-        } else {
-            return super.onAcquired(acquiredInfo, vendorCode);
-        }
-    }
-
-    @Override
-    public boolean onError(long deviceId, int error, int vendorCode) {
-        if (mDialogDismissed) {
-            // If user cancels authentication, the application has already received the
-            // ERROR_USER_CANCELED message from onDialogDismissed()
-            // and stopped the biometric hardware, so there is no need to send a
-            // ERROR_CANCELED message.
-            return true;
-        }
-        if (mBundle != null && error != BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED) {
-            try {
-                mStatusBarService.onBiometricError(getErrorString(error, vendorCode));
-            } catch (RemoteException e) {
-                Slog.e(getLogTag(), "Remote exception when sending error", e);
-            }
-        }
-        return super.onError(deviceId, error, vendorCode);
-    }
-
-    public void setTitleIfEmpty(CharSequence title) {
-        if (TextUtils.isEmpty(mBundle.getCharSequence(BiometricPrompt.KEY_TITLE))) {
-            mBundle.putCharSequence(BiometricPrompt.KEY_TITLE, title);
-        }
-    }
-
     public boolean isBiometricPrompt() {
-        return mBundle != null;
+        return getCookie() != 0;
     }
 
-    private void notifyClientAuthenticationSucceeded(BiometricAuthenticator.Identifier identifier)
-            throws RemoteException {
-        final BiometricServiceBase.ServiceListener listener = getListener();
-        // Explicitly have if/else here to make it super obvious in case the code is
-        // touched in the future.
-        if (!getIsRestricted()) {
-            listener.onAuthenticationSucceeded(
-                    getHalDeviceId(), identifier, getTargetUserId());
-        } else {
-            listener.onAuthenticationSucceeded(
-                    getHalDeviceId(), null, getTargetUserId());
-        }
-    }
-
-    private void addTokenToKeyStore(ArrayList<Byte> token) {
-        // Send the token to KeyStore
-        final byte[] byteToken = new byte[token.size()];
-        for (int i = 0; i < token.size(); i++) {
-            byteToken[i] = token.get(i);
-        }
-        KeyStore.getInstance().addAuthToken(byteToken);
+    public boolean getRequireConfirmation() {
+        return mRequireConfirmation;
     }
 
     @Override
     public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier,
             boolean authenticated, ArrayList<Byte> token) {
-        if (authenticated) {
-            mAlreadyDone = true;
-            if (mRequireConfirmation) {
-                // Store the token so it can be sent to keystore after the user presses confirm
-                mEscrow = new TokenEscrow(identifier, token);
-            } else {
-                addTokenToKeyStore(token);
-            }
-        }
+        final BiometricServiceBase.ServiceListener listener = getListener();
 
+        mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated);
         boolean result = false;
 
-        // If the biometric dialog is showing, notify authentication succeeded
-        if (mBundle != null) {
-            try {
-                if (authenticated) {
-                    mStatusBarService.onBiometricAuthenticated();
-                } else {
-                    mStatusBarService.onBiometricHelp(getContext().getResources().getString(
-                            com.android.internal.R.string.biometric_not_recognized));
+        try {
+            if (authenticated) {
+                mAlreadyDone = true;
+                if (DEBUG) Slog.v(getLogTag(), "onAuthenticated(" + getOwnerString()
+                        + ", ID:" + identifier.getBiometricId()
+                        + ", isBP: " + isBiometricPrompt()
+                        + ", listener: " + listener
+                        + ", requireConfirmation: " + mRequireConfirmation);
+                if (listener != null) {
+                    vibrateSuccess();
                 }
-            } catch (RemoteException e) {
-                Slog.e(getLogTag(), "Failed to notify Authenticated:", e);
-            }
-        }
+                result = true;
+                resetFailedAttempts();
+                onStop();
 
-        final BiometricServiceBase.ServiceListener listener = getListener();
-        if (listener != null) {
-            try {
-                mMetricsLogger.action(mMetrics.actionBiometricAuth(), authenticated);
-                if (!authenticated) {
-                    listener.onAuthenticationFailed(getHalDeviceId());
-                } else {
-                    if (DEBUG) {
-                        Slog.v(getLogTag(), "onAuthenticated(owner=" + getOwnerString()
-                                + ", id=" + identifier.getBiometricId());
-                    }
-                    if (!mRequireConfirmation) {
-                        notifyClientAuthenticationSucceeded(identifier);
-                    }
+                final byte[] byteToken = new byte[token.size()];
+                for (int i = 0; i < token.size(); i++) {
+                    byteToken[i] = token.get(i);
                 }
-            } catch (RemoteException e) {
-                Slog.w(getLogTag(), "Failed to notify Authenticated:", e);
-                result = true; // client failed
-            }
-        } else {
-            result = true; // client not listening
-        }
-        if (!authenticated) {
-            if (listener != null) {
-                vibrateError();
-            }
-            // allow system-defined limit of number of attempts before giving up
-            int lockoutMode =  handleFailedAttempt();
-            if (lockoutMode != LOCKOUT_NONE) {
-                try {
-                    mInLockout = true;
-                    Slog.w(getLogTag(), "Forcing lockout (fp driver code should do this!), mode(" +
-                            lockoutMode + ")");
+                if (isBiometricPrompt() && listener != null) {
+                    // BiometricService will add the token to keystore
+                    listener.onAuthenticationSucceededInternal(mRequireConfirmation, byteToken);
+                } else if (!isBiometricPrompt() && listener != null) {
+                    KeyStore.getInstance().addAuthToken(byteToken);
+                    try {
+                        // Explicitly have if/else here to make it super obvious in case the code is
+                        // touched in the future.
+                        if (!getIsRestricted()) {
+                            listener.onAuthenticationSucceeded(
+                                    getHalDeviceId(), identifier, getTargetUserId());
+                        } else {
+                            listener.onAuthenticationSucceeded(
+                                    getHalDeviceId(), null, getTargetUserId());
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(getLogTag(), "Remote exception", e);
+                    }
+                } else {
+                    // Client not listening
+                    Slog.w(getLogTag(), "Client not listening");
+                    result = true;
+                }
+            } else {
+                if (listener != null) {
+                    vibrateError();
+                }
+                // Allow system-defined limit of number of attempts before giving up
+                final int lockoutMode = handleFailedAttempt();
+                if (lockoutMode != LOCKOUT_NONE) {
+                    Slog.w(getLogTag(), "Forcing lockout (driver code should do this!), mode("
+                            + lockoutMode + ")");
                     stop(false);
-                    int errorCode = lockoutMode == LOCKOUT_TIMED ?
-                            BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :
-                            BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
-
-                    // Send the lockout message to the system dialog
-                    if (mBundle != null) {
-                        mStatusBarService.onBiometricError(
-                                getErrorString(errorCode, 0 /* vendorCode */));
-                        mHandler.postDelayed(() -> {
-                            try {
-                                listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
-                            } catch (RemoteException e) {
-                                Slog.w(getLogTag(), "RemoteException while sending error");
-                            }
-                        }, BiometricPrompt.HIDE_DIALOG_DELAY);
-                    } else {
-                        listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
+                    final int errorCode = lockoutMode == LOCKOUT_TIMED
+                            ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
+                            : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+                    if (listener != null) {
+                        listener.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */,
+                                getCookie());
                     }
-                } catch (RemoteException e) {
-                    Slog.w(getLogTag(), "Failed to notify lockout:", e);
+                } else {
+                    // Don't send onAuthenticationFailed if we're in lockout, it causes a
+                    // janky UI on Keyguard/BiometricPrompt since "authentication failed"
+                    // will show briefly and be replaced by "device locked out" message.
+                    if (listener != null) {
+                        if (isBiometricPrompt()) {
+                            listener.onAuthenticationFailedInternal(getCookie(),
+                                    getRequireConfirmation());
+                        } else {
+                            listener.onAuthenticationFailed(getHalDeviceId());
+                        }
+                    }
                 }
+                result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
             }
-            result |= lockoutMode != LOCKOUT_NONE; // in a lockout mode
-        } else {
-            if (listener != null) {
-                vibrateSuccess();
-            }
-            // we have a valid biometric that doesn't require confirmation, done
-            result |= !mRequireConfirmation;
-            resetFailedAttempts();
-            onStop();
+        } catch (RemoteException e) {
+            Slog.e(getLogTag(), "Remote exception", e);
+            result = true;
         }
         return result;
     }
@@ -353,16 +184,6 @@
                 return result;
             }
             if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating...");
-
-            // If authenticating with system dialog, show the dialog
-            if (mBundle != null) {
-                try {
-                    mStatusBarService.showBiometricDialog(mBundle, mDialogReceiver,
-                            getBiometricType(), mRequireConfirmation, getTargetUserId());
-                } catch (RemoteException e) {
-                    Slog.e(getLogTag(), "Unable to show biometric dialog", e);
-                }
-            }
         } catch (RemoteException e) {
             Slog.e(getLogTag(), "startAuthentication failed", e);
             return ERROR_ESRCH;
@@ -390,18 +211,6 @@
         } catch (RemoteException e) {
             Slog.e(getLogTag(), "stopAuthentication failed", e);
             return ERROR_ESRCH;
-        } finally {
-            // If the user already cancelled authentication (via some interaction with the
-            // dialog, we do not need to hide it since it's already hidden.
-            // If the device is in lockout, don't hide the dialog - it will automatically hide
-            // after BiometricPrompt.HIDE_DIALOG_DELAY
-            if (mBundle != null && !mDialogDismissed && !mInLockout) {
-                try {
-                    mStatusBarService.hideBiometricDialog();
-                } catch (RemoteException e) {
-                    Slog.e(getLogTag(), "Unable to hide biometric dialog", e);
-                }
-            }
         }
 
         mAlreadyCancelled = true;
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 5f09189..add55ea 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -19,9 +19,17 @@
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.Manifest.permission.USE_FINGERPRINT;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
 
 import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
 import android.app.AppOpsManager;
+import android.app.IActivityTaskManager;
+import android.app.TaskStackListener;
 import android.app.UserSwitchObserver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -32,9 +40,9 @@
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
-import android.hardware.biometrics.IBiometricPromptReceiver;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.face.FaceManager;
 import android.hardware.face.IFaceService;
 import android.hardware.fingerprint.FingerprintManager;
@@ -50,14 +58,21 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.security.KeyStore;
+import android.text.TextUtils;
 import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.server.SystemService;
 
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.Random;
 
 /**
  * System service that arbitrates the modality for BiometricPrompt to use.
@@ -66,32 +81,10 @@
 
     private static final String TAG = "BiometricService";
 
-    /**
-     * No biometric methods or nothing has been enrolled.
-     * Move/expose these in BiometricPrompt if we ever want to allow applications to "blacklist"
-     * modalities when calling authenticate().
-     */
-    private static final int BIOMETRIC_NONE = 0;
-
-    /**
-     * Constant representing fingerprint.
-     */
-    private static final int BIOMETRIC_FINGERPRINT = 1 << 0;
-
-    /**
-     * Constant representing iris.
-     */
-    private static final int BIOMETRIC_IRIS = 1 << 1;
-
-    /**
-     * Constant representing face.
-     */
-    private static final int BIOMETRIC_FACE = 1 << 2;
-
     private static final int[] FEATURE_ID = {
-            BIOMETRIC_FINGERPRINT,
-            BIOMETRIC_IRIS,
-            BIOMETRIC_FACE
+        TYPE_FINGERPRINT,
+        TYPE_IRIS,
+        TYPE_FACE
     };
 
     private final AppOpsManager mAppOps;
@@ -242,10 +235,367 @@
      */
     private final class BiometricServiceWrapper extends IBiometricService.Stub {
 
+        /**
+         * Authentication either just called and we have not transitioned to the CALLED state, or
+         * authentication terminated (success or error).
+         */
+        private static final int STATE_AUTH_IDLE = 0;
+        /**
+         * Authentication was called and we are waiting for the <Biometric>Services to return their
+         * cookies before starting the hardware and showing the BiometricPrompt.
+         */
+        private static final int STATE_AUTH_CALLED = 1;
+        /**
+         * Authentication started, BiometricPrompt is showing and the hardware is authenticating.
+         */
+        private static final int STATE_AUTH_STARTED = 2;
+        /**
+         * Authentication is paused, waiting for the user to press "try again" button. Since the
+         * try again button requires us to cancel authentication, this represents the state where
+         * ERROR_CANCELED is not received yet.
+         */
+        private static final int STATE_AUTH_PAUSED = 3;
+        /**
+         * Same as above, except the ERROR_CANCELED has been received.
+         */
+        private static final int STATE_AUTH_PAUSED_CANCELED = 4;
+        /**
+         * Authentication is successful, but we're waiting for the user to press "confirm" button.
+         */
+        private static final int STATE_AUTH_PENDING_CONFIRM = 5;
+
+        final class AuthSession {
+            // 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.
+            final HashMap<Integer, Integer> mModalitiesWaiting;
+            // Pairs that have been matched.
+            final HashMap<Integer, Integer> mModalitiesMatched = new HashMap<>();
+
+            // The following variables are passed to authenticateInternal, which initiates the
+            // appropriate <Biometric>Services.
+            final IBinder mToken;
+            final long mSessionId;
+            final int mUserId;
+            // Original receiver from BiometricPrompt.
+            final IBiometricServiceReceiver mClientReceiver;
+            final String mOpPackageName;
+            // Info to be shown on BiometricDialog when all cookies are returned.
+            final Bundle mBundle;
+            final int mCallingUid;
+            final int mCallingPid;
+            final int mCallingUserId;
+            // Continue authentication with the same modality/modalities after "try again" is
+            // pressed
+            final int mModality;
+
+            // The current state, which can be either idle, called, or started
+            private int mState = STATE_AUTH_IDLE;
+            // For explicit confirmation, do not send to keystore until the user has confirmed
+            // the authentication.
+            byte[] mTokenEscrow;
+
+            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) {
+                mModalitiesWaiting = modalities;
+                mToken = token;
+                mSessionId = sessionId;
+                mUserId = userId;
+                mClientReceiver = receiver;
+                mOpPackageName = opPackageName;
+                mBundle = bundle;
+                mCallingUid = callingUid;
+                mCallingPid = callingPid;
+                mCallingUserId = callingUserId;
+                mModality = modality;
+            }
+
+            boolean containsCookie(int cookie) {
+                if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) {
+                    return true;
+                }
+                if (mModalitiesMatched != null && mModalitiesMatched.containsValue(cookie)) {
+                    return true;
+                }
+                return false;
+            }
+        }
+
+        final class BiometricTaskStackListener extends TaskStackListener {
+            @Override
+            public void onTaskStackChanged() {
+                try {
+                    final List<ActivityManager.RunningTaskInfo> runningTasks =
+                            mActivityTaskManager.getTasks(1);
+                    if (!runningTasks.isEmpty()) {
+                        final String topPackage = runningTasks.get(0).topActivity.getPackageName();
+                        if (mCurrentAuthSession != null
+                                && !topPackage.contentEquals(mCurrentAuthSession.mOpPackageName)
+                                && mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
+                            // We only care about this state, since <Biometric>Service will
+                            // cancel any client that's still in STATE_AUTH_STARTED
+                            mStatusBarService.hideBiometricDialog();
+                            mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+                            mCurrentAuthSession.mClientReceiver.onError(
+                                    BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+                                    getContext().getString(
+                                            com.android.internal.R.string.biometric_error_canceled)
+                            );
+                            mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                            mCurrentAuthSession = null;
+                        }
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to get running tasks", e);
+                }
+            }
+        }
+
+        private final IActivityTaskManager mActivityTaskManager = getContext().getSystemService(
+                ActivityTaskManager.class).getService();
+        private final IStatusBarService mStatusBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+        private final BiometricTaskStackListener mTaskStackListener =
+                new BiometricTaskStackListener();
+        private final Random mRandom = new Random();
+
+        // The current authentication session, null if idle/done. We need to track both the current
+        // and pending sessions since errors may be sent to either.
+        private AuthSession mCurrentAuthSession;
+        private AuthSession mPendingAuthSession;
+
+        // Wrap the client's receiver so we can do things with the BiometricDialog first
+        private final IBiometricServiceReceiverInternal mInternalReceiver =
+                new IBiometricServiceReceiverInternal.Stub() {
+            @Override
+            public void onAuthenticationSucceeded(boolean requireConfirmation, byte[] token)
+                    throws RemoteException {
+                try {
+                    if (!requireConfirmation) {
+                        mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+                        KeyStore.getInstance().addAuthToken(token);
+                        mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
+                        mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                        mCurrentAuthSession = null;
+                    } else {
+                        // Store the auth token and submit it to keystore after the confirmation
+                        // button has been pressed.
+                        mCurrentAuthSession.mTokenEscrow = token;
+                        mCurrentAuthSession.mState = STATE_AUTH_PENDING_CONFIRM;
+                    }
+
+                    // Notify SysUI that the biometric has been authenticated. SysUI already knows
+                    // the implicit/explicit state and will react accordingly.
+                    mStatusBarService.onBiometricAuthenticated();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+
+            @Override
+            public void onAuthenticationFailed(int cookie, boolean requireConfirmation)
+                    throws RemoteException {
+                try {
+                    mStatusBarService.onBiometricHelp(getContext().getResources().getString(
+                            com.android.internal.R.string.biometric_not_recognized));
+                    if (requireConfirmation) {
+                        mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
+                        mStatusBarService.showBiometricTryAgain();
+                        // Cancel authentication. Skip the token/package check since we are
+                        // cancelling from system server. The interface is permission protected so
+                        // this is fine.
+                        cancelInternal(null /* token */, null /* package */,
+                                false /* fromClient */);
+                    }
+                    mCurrentAuthSession.mClientReceiver.onAuthenticationFailed();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+
+            @Override
+            public void onError(int cookie, int error, String message) throws RemoteException {
+                Slog.d(TAG, "Error: " + error + " cookie: " + cookie);
+                // Errors can either be from the current auth session or the pending auth session.
+                // The pending auth session may receive errors such as ERROR_LOCKOUT before
+                // it becomes the current auth session. Similarly, the current auth session may
+                // receive errors such as ERROR_CANCELED while the pending auth session is preparing
+                // to be started. Thus we must match error messages with their cookies to be sure
+                // of their intended receivers.
+                try {
+                    if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) {
+                        if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
+                            mStatusBarService.onBiometricError(message);
+                            mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+                            if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
+                                    mCurrentAuthSession.mClientReceiver.onError(error, message);
+                                    mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                                    mCurrentAuthSession = null;
+                                    mStatusBarService.hideBiometricDialog();
+                            } else {
+                                // Send errors after the dialog is dismissed.
+                                mHandler.postDelayed(() -> {
+                                    try {
+                                        mCurrentAuthSession.mClientReceiver.onError(error, message);
+                                        mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                                        mCurrentAuthSession = null;
+                                    } catch (RemoteException e) {
+                                        Slog.e(TAG, "Remote exception", e);
+                                    }
+                                }, BiometricPrompt.HIDE_DIALOG_DELAY);
+                            }
+                        } else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED
+                                || mCurrentAuthSession.mState == STATE_AUTH_PAUSED_CANCELED) {
+                            if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED
+                                    && error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
+                                // Skip the first ERROR_CANCELED message when this happens, since
+                                // "try again" requires us to cancel authentication but keep
+                                // the prompt showing.
+                                mCurrentAuthSession.mState = STATE_AUTH_PAUSED_CANCELED;
+                            } else {
+                                // In the "try again" state, we should forward canceled errors to
+                                // the client and and clean up.
+                                mCurrentAuthSession.mClientReceiver.onError(error, message);
+                                mStatusBarService.onBiometricError(message);
+                                mActivityTaskManager.unregisterTaskStackListener(
+                                        mTaskStackListener);
+                                mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                                mCurrentAuthSession = null;
+                            }
+                        } else {
+                            Slog.e(TAG, "Impossible session error state: "
+                                    + mCurrentAuthSession.mState);
+                        }
+                    } else if (mPendingAuthSession != null
+                            && mPendingAuthSession.containsCookie(cookie)) {
+                        if (mPendingAuthSession.mState == STATE_AUTH_CALLED) {
+                            mPendingAuthSession.mClientReceiver.onError(error, message);
+                            mPendingAuthSession.mState = STATE_AUTH_IDLE;
+                            mPendingAuthSession = null;
+                        } else {
+                            Slog.e(TAG, "Impossible pending session error state: "
+                                    + mPendingAuthSession.mState);
+                        }
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+
+            @Override
+            public void onAcquired(int acquiredInfo, String message) throws RemoteException {
+                if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
+                    try {
+                        mStatusBarService.onBiometricHelp(message);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Remote exception", e);
+                    }
+                }
+            }
+
+            @Override
+            public void onDialogDismissed(int reason) throws RemoteException {
+                if (reason != BiometricPrompt.DISMISSED_REASON_POSITIVE) {
+                    // Positive button is used by passive modalities as a "confirm" button,
+                    // do not send to client
+                    mCurrentAuthSession.mClientReceiver.onDialogDismissed(reason);
+                    // Cancel authentication. Skip the token/package check since we are cancelling
+                    // from system server. The interface is permission protected so this is fine.
+                    cancelInternal(null /* token */, null /* package */, false /* fromClient */);
+                }
+                if (reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL) {
+                    mCurrentAuthSession.mClientReceiver.onError(
+                            BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
+                            getContext().getString(
+                                    com.android.internal.R.string.biometric_error_user_canceled));
+                } else if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) {
+                    // Have the service send the token to KeyStore, and send onAuthenticated
+                    // to the application
+                    KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow);
+                    mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
+                }
+                mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+                mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                mCurrentAuthSession = null;
+            }
+
+            @Override
+            public void onTryAgainPressed() {
+                Slog.d(TAG, "onTryAgainPressed");
+                // No need to check permission, since it can only be invoked by SystemUI
+                // (or system server itself).
+                mHandler.post(() -> {
+                    authenticateInternal(mCurrentAuthSession.mToken,
+                            mCurrentAuthSession.mSessionId,
+                            mCurrentAuthSession.mUserId,
+                            mCurrentAuthSession.mClientReceiver,
+                            mCurrentAuthSession.mOpPackageName,
+                            mCurrentAuthSession.mBundle,
+                            mCurrentAuthSession.mCallingUid,
+                            mCurrentAuthSession.mCallingPid,
+                            mCurrentAuthSession.mCallingUserId,
+                            mCurrentAuthSession.mModality);
+                });
+            }
+        };
+
+        @Override // Binder call
+        public void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId) {
+            checkInternalPermission();
+
+            Iterator it = mPendingAuthSession.mModalitiesWaiting.entrySet().iterator();
+            while (it.hasNext()) {
+                Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
+                if (pair.getValue() == cookie) {
+                    mPendingAuthSession.mModalitiesMatched.put(pair.getKey(), pair.getValue());
+                    mPendingAuthSession.mModalitiesWaiting.remove(pair.getKey());
+                    Slog.d(TAG, "Matched cookie: " + cookie + ", "
+                            + mPendingAuthSession.mModalitiesWaiting.size() + " remaining");
+                    break;
+                }
+            }
+
+            if (mPendingAuthSession.mModalitiesWaiting.isEmpty()) {
+                final boolean mContinuing = mCurrentAuthSession != null
+                        && mCurrentAuthSession.mState == STATE_AUTH_PAUSED;
+                mCurrentAuthSession = mPendingAuthSession;
+                mPendingAuthSession = null;
+
+                mCurrentAuthSession.mState = STATE_AUTH_STARTED;
+                try {
+                    int modality = TYPE_NONE;
+                    it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator();
+                    while (it.hasNext()) {
+                        Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
+                        if (pair.getKey() == TYPE_FINGERPRINT) {
+                            mFingerprintService.startPreparedClient(pair.getValue());
+                        } else if (pair.getKey() == TYPE_IRIS) {
+                            Slog.e(TAG, "Iris unsupported");
+                        } else if (pair.getKey() == TYPE_FACE) {
+                            mFaceService.startPreparedClient(pair.getValue());
+                        } else {
+                            Slog.e(TAG, "Unknown modality: " + pair.getKey());
+                        }
+                        modality |= pair.getKey();
+                    }
+
+                    if (!mContinuing) {
+                        mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
+                                mInternalReceiver, modality, requireConfirmation, userId);
+                        mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+        }
+
         @Override // Binder call
         public void authenticate(IBinder token, long sessionId, int userId,
-                IBiometricServiceReceiver receiver, int flags, String opPackageName,
-                Bundle bundle, IBiometricPromptReceiver dialogReceiver) throws RemoteException {
+                IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle)
+                throws RemoteException {
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final int callingUserId = UserHandle.getCallingUserId();
@@ -261,16 +611,45 @@
                 checkInternalPermission();
             }
 
-            if (token == null || receiver == null || opPackageName == null || bundle == null
-                    || dialogReceiver == null) {
+            if (token == null || receiver == null || opPackageName == null || bundle == null) {
                 Slog.e(TAG, "Unable to authenticate, one or more null arguments");
                 return;
             }
 
             // Check the usage of this in system server. Need to remove this check if it becomes
             // a public API.
-            if (bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false)) {
+            final boolean useDefaultTitle =
+                    bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false);
+            if (useDefaultTitle) {
                 checkInternalPermission();
+                // Set the default title if necessary
+                try {
+                    if (useDefaultTitle) {
+                        final List<ActivityManager.RunningAppProcessInfo> procs =
+                                ActivityManager.getService().getRunningAppProcesses();
+                        for (int i = 0; i < procs.size(); i++) {
+                            final ActivityManager.RunningAppProcessInfo info = procs.get(i);
+                            if (info.uid == callingUid
+                                    && info.importance == IMPORTANCE_FOREGROUND) {
+                                PackageManager pm = getContext().getPackageManager();
+                                final CharSequence label = pm.getApplicationLabel(
+                                        pm.getApplicationInfo(info.processName,
+                                                PackageManager.GET_META_DATA));
+                                final String title = getContext()
+                                        .getString(R.string.biometric_dialog_default_title, label);
+                                if (TextUtils.isEmpty(
+                                        bundle.getCharSequence(BiometricPrompt.KEY_TITLE))) {
+                                    bundle.putCharSequence(BiometricPrompt.KEY_TITLE, title);
+                                }
+                                break;
+                            }
+                        }
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                } catch (PackageManager.NameNotFoundException e) {
+                    Slog.e(TAG, "Name not found", e);
+                }
             }
 
             mHandler.post(() -> {
@@ -285,13 +664,13 @@
                                 getContext().getString(R.string.biometric_error_hw_unavailable);
                         switch (error) {
                             case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
-                                receiver.onError(0 /* deviceId */, error, hardwareUnavailable);
+                                receiver.onError(error, hardwareUnavailable);
                                 break;
                             case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
-                                receiver.onError(0 /* deviceId */, error, hardwareUnavailable);
+                                receiver.onError(error, hardwareUnavailable);
                                 break;
                             case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
-                                receiver.onError(0 /* deviceId */, error,
+                                receiver.onError(error,
                                         getErrorString(modality, error, 0 /* vendorCode */));
                                 break;
                             default:
@@ -304,60 +683,91 @@
                     return;
                 }
 
-                // Actually start authentication
                 mCurrentModality = modality;
-                try {
-                    // No polymorphism :(
-                    if (mCurrentModality == BIOMETRIC_FINGERPRINT) {
-                        mFingerprintService.authenticateFromService(token, sessionId, userId,
-                                receiver, flags, opPackageName, bundle, dialogReceiver,
-                                callingUid, callingPid, callingUserId);
-                    } else if (mCurrentModality == BIOMETRIC_IRIS) {
-                        Slog.w(TAG, "Unsupported modality");
-                    } else if (mCurrentModality == BIOMETRIC_FACE) {
-                        mFaceService.authenticateFromService(true /* requireConfirmation */,
-                                token, sessionId, userId, receiver, flags, opPackageName,
-                                bundle, dialogReceiver, callingUid, callingPid, callingUserId);
-                    } else {
-                        Slog.w(TAG, "Unsupported modality");
-                    }
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to start authentication", e);
-                }
+
+                // Actually start authentication
+                authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
+                        callingUid, callingPid, callingUserId, modality);
             });
         }
 
+        /**
+         * authenticate() (above) which is called from BiometricPrompt determines which
+         * modality/modalities to start authenticating with. authenticateInternal() should only be
+         * used for:
+         * 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is,
+         *    invoked, shortly after which BiometricPrompt is shown and authentication starts
+         * 2) Preparing <Biometric>Services for authentication when BiometricPrompt is already shown
+         *    and the user has pressed "try again"
+         */
+        private void authenticateInternal(IBinder token, long sessionId, int userId,
+                IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
+                int callingUid, int callingPid, int callingUserId, int modality) {
+            try {
+                // Generate random cookies to pass to the services that should prepare to start
+                // authenticating. Store the cookie here and wait for all services to "ack"
+                // with the cookie. Once all cookies are received, we can show the prompt
+                // and let the services start authenticating. The cookie should be non-zero.
+                final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
+                Slog.d(TAG, "Creating auth session. Modality: " + modality
+                        + ", cookie: " + cookie);
+                final HashMap<Integer, Integer> authenticators = new HashMap<>();
+                authenticators.put(modality, cookie);
+                mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId,
+                        receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
+                        modality);
+                mPendingAuthSession.mState = STATE_AUTH_CALLED;
+                // No polymorphism :(
+                if ((modality & TYPE_FINGERPRINT) != 0) {
+                    mFingerprintService.prepareForAuthentication(token, sessionId, userId,
+                            mInternalReceiver, opPackageName, cookie,
+                            callingUid, callingPid, callingUserId);
+                }
+                if ((modality & TYPE_IRIS) != 0) {
+                    Slog.w(TAG, "Iris unsupported");
+                }
+                if ((modality & TYPE_FACE) != 0) {
+                    mFaceService.prepareForAuthentication(true /* requireConfirmation */,
+                            token, sessionId, userId, mInternalReceiver, opPackageName,
+                            cookie, callingUid, callingPid, callingUserId);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to start authentication", e);
+            }
+        }
+
         @Override // Binder call
         public void cancelAuthentication(IBinder token, String opPackageName)
                 throws RemoteException {
             checkPermission();
-
             if (token == null || opPackageName == null) {
                 Slog.e(TAG, "Unable to cancel, one or more null arguments");
                 return;
             }
 
-            final int callingUid = Binder.getCallingUid();
-            final int callingPid = Binder.getCallingPid();
-            final int callingUserId = UserHandle.getCallingUserId();
+            // 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) {
+                mHandler.post(() -> {
+                    try {
+                        // Send error to client
+                        mCurrentAuthSession.mClientReceiver.onError(
+                                BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+                                getContext().getString(
+                                        com.android.internal.R.string.biometric_error_user_canceled)
+                        );
 
-            mHandler.post(() -> {
-                try {
-                    if (mCurrentModality == BIOMETRIC_FINGERPRINT) {
-                        mFingerprintService.cancelAuthenticationFromService(token, opPackageName,
-                                callingUid, callingPid, callingUserId);
-                    } else if (mCurrentModality == BIOMETRIC_IRIS) {
-                        Slog.w(TAG, "Unsupported modality");
-                    } else if (mCurrentModality == BIOMETRIC_FACE) {
-                        mFaceService.cancelAuthenticationFromService(token, opPackageName,
-                                callingUid, callingPid, callingUserId);
-                    } else {
-                        Slog.w(TAG, "Unsupported modality");
+                        mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                        mCurrentAuthSession = null;
+                        mStatusBarService.hideBiometricDialog();
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Remote exception", e);
                     }
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to cancel authentication");
-                }
-            });
+                });
+            } else {
+                cancelInternal(token, opPackageName, true /* fromClient */);
+            }
         }
 
         @Override // Binder call
@@ -402,6 +812,31 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
+
+        void cancelInternal(IBinder token, String opPackageName, boolean fromClient) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int callingUserId = UserHandle.getCallingUserId();
+            mHandler.post(() -> {
+                try {
+                    // TODO: For multiple modalities, send a single ERROR_CANCELED only when all
+                    // drivers have canceled authentication.
+                    if ((mCurrentModality & TYPE_FINGERPRINT) != 0) {
+                        mFingerprintService.cancelAuthenticationFromService(token, opPackageName,
+                                callingUid, callingPid, callingUserId, fromClient);
+                    }
+                    if ((mCurrentModality & TYPE_IRIS) != 0) {
+                        Slog.w(TAG, "Iris unsupported");
+                    }
+                    if ((mCurrentModality & TYPE_FACE) != 0) {
+                        mFaceService.cancelAuthenticationFromService(token, opPackageName,
+                                callingUid, callingPid, callingUserId, fromClient);
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to cancel authentication");
+                }
+            });
+        }
     }
 
     private void checkAppOp(String opPackageName, int callingUid) {
@@ -413,7 +848,7 @@
     }
 
     private void checkInternalPermission() {
-        getContext().enforceCallingPermission(USE_BIOMETRIC_INTERNAL,
+        getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL,
                 "Must have USE_BIOMETRIC_INTERNAL permission");
     }
 
@@ -490,16 +925,19 @@
      * returns errors through the callback (no biometric feature, hardware not detected, no
      * templates enrolled, etc). This service must not start authentication if errors are sent.
      *
-     * @Returns A pair [Modality, Error] with Modality being one of {@link #BIOMETRIC_NONE},
-     * {@link #BIOMETRIC_FINGERPRINT}, {@link #BIOMETRIC_IRIS}, {@link #BIOMETRIC_FACE}
+     * @Returns A pair [Modality, Error] with Modality being one of
+     * {@link BiometricAuthenticator#TYPE_NONE},
+     * {@link BiometricAuthenticator#TYPE_FINGERPRINT},
+     * {@link BiometricAuthenticator#TYPE_IRIS},
+     * {@link BiometricAuthenticator#TYPE_FACE}
      * and the error containing one of the {@link BiometricConstants} errors.
      */
     private Pair<Integer, Integer> checkAndGetBiometricModality(int callingUid) {
-        int modality = BIOMETRIC_NONE;
+        int modality = TYPE_NONE;
 
         // No biometric features, send error
         if (mAuthenticators.isEmpty()) {
-            return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
+            return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
         }
 
         // Assuming that authenticators are listed in priority-order, the rest of this function
@@ -512,13 +950,13 @@
         boolean hasTemplatesEnrolled = false;
         boolean enabledForApps = false;
 
-        int firstHwAvailable = BIOMETRIC_NONE;
+        int firstHwAvailable = TYPE_NONE;
         for (int i = 0; i < mAuthenticators.size(); i++) {
             modality = mAuthenticators.get(i).getType();
             BiometricAuthenticator authenticator = mAuthenticators.get(i).getAuthenticator();
             if (authenticator.isHardwareDetected()) {
                 isHardwareDetected = true;
-                if (firstHwAvailable == BIOMETRIC_NONE) {
+                if (firstHwAvailable == TYPE_NONE) {
                     // Store the first one since we want to return the error in correct priority
                     // order.
                     firstHwAvailable = modality;
@@ -538,13 +976,13 @@
 
         // Check error conditions
         if (!isHardwareDetected) {
-            return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+            return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
         } else if (!hasTemplatesEnrolled) {
             // Return the modality here so the correct error string can be sent. This error is
             // preferred over !enabledForApps
             return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS);
         } else if (!enabledForApps) {
-            return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+            return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
         }
 
         return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS);
@@ -552,11 +990,11 @@
 
     private boolean isEnabledForApp(int modality) {
         switch(modality) {
-            case BIOMETRIC_FINGERPRINT:
+            case TYPE_FINGERPRINT:
                 return true;
-            case BIOMETRIC_IRIS:
+            case TYPE_IRIS:
                 return true;
-            case BIOMETRIC_FACE:
+            case TYPE_FACE:
                 return mSettingObserver.getFaceEnabledForApps();
             default:
                 Slog.w(TAG, "Unsupported modality: " + modality);
@@ -566,12 +1004,12 @@
 
     private String getErrorString(int type, int error, int vendorCode) {
         switch (type) {
-            case BIOMETRIC_FINGERPRINT:
+            case TYPE_FINGERPRINT:
                 return FingerprintManager.getErrorString(getContext(), error, vendorCode);
-            case BIOMETRIC_IRIS:
+            case TYPE_IRIS:
                 Slog.w(TAG, "Modality not supported");
                 return null; // not supported
-            case BIOMETRIC_FACE:
+            case TYPE_FACE:
                 return FaceManager.getErrorString(getContext(), error, vendorCode);
             default:
                 Slog.w(TAG, "Unable to get error string for modality: " + type);
@@ -581,12 +1019,12 @@
 
     private BiometricAuthenticator getAuthenticator(int type) {
         switch (type) {
-            case BIOMETRIC_FINGERPRINT:
+            case TYPE_FINGERPRINT:
                 return (FingerprintManager)
                         getContext().getSystemService(Context.FINGERPRINT_SERVICE);
-            case BIOMETRIC_IRIS:
+            case TYPE_IRIS:
                 return null;
-            case BIOMETRIC_FACE:
+            case TYPE_FACE:
                 return (FaceManager)
                         getContext().getSystemService(Context.FACE_SERVICE);
             default:
@@ -596,11 +1034,11 @@
 
     private boolean hasFeature(int type) {
         switch (type) {
-            case BIOMETRIC_FINGERPRINT:
+            case TYPE_FINGERPRINT:
                 return mHasFeatureFingerprint;
-            case BIOMETRIC_IRIS:
+            case TYPE_IRIS:
                 return mHasFeatureIris;
-            case BIOMETRIC_FACE:
+            case TYPE_FACE:
                 return mHasFeatureFace;
             default:
                 return false;
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 74d742a..9649ccd 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -16,7 +16,6 @@
 
 package com.android.server.biometrics;
 
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
 
 import android.app.ActivityManager;
@@ -36,8 +35,9 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.fingerprint.Fingerprint;
 import android.os.Binder;
 import android.os.Bundle;
@@ -56,7 +56,6 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
-import com.android.internal.R;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.server.SystemService;
@@ -106,6 +105,7 @@
     protected final AppOpsManager mAppOps;
     protected final H mHandler = new H();
 
+    private IBiometricService mBiometricService;
     private ClientMonitor mCurrentClient;
     private ClientMonitor mPendingClient;
     private PerformanceStats mPerformanceStats;
@@ -223,12 +223,9 @@
 
         public AuthenticationClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
                 IBinder token, ServiceListener listener, int targetUserId, int groupId, long opId,
-                boolean restricted, String owner, Bundle bundle,
-                IBiometricPromptReceiver dialogReceiver,
-                IStatusBarService statusBarService, boolean requireConfirmation) {
-            super(context, getMetrics(), daemon, halDeviceId, token, listener,
-                    targetUserId, groupId, opId, restricted, owner, bundle, dialogReceiver,
-                    statusBarService, requireConfirmation);
+                boolean restricted, String owner, int cookie, boolean requireConfirmation) {
+            super(context, getMetrics(), daemon, halDeviceId, token, listener, targetUserId,
+                    groupId, opId, restricted, owner, cookie, requireConfirmation);
         }
 
         @Override
@@ -279,11 +276,6 @@
             }
             return AuthenticationClient.LOCKOUT_NONE;
         }
-
-        @Override
-        public void onAuthenticationConfirmed() {
-            removeClient(mCurrentClient);
-        }
     }
 
     protected class EnrollClientImpl extends EnrollClient {
@@ -345,18 +337,28 @@
         default void onEnrollResult(BiometricAuthenticator.Identifier identifier,
                 int remaining) throws RemoteException {};
 
-        void onAcquired(long deviceId, int acquiredInfo, int vendorCode)
-                throws RemoteException;
+        void onAcquired(long deviceId, int acquiredInfo, int vendorCode) throws RemoteException;
 
-        void onAuthenticationSucceeded(long deviceId,
-                BiometricAuthenticator.Identifier biometric, int userId)
-                throws RemoteException;
+        default void onAuthenticationSucceeded(long deviceId,
+                BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException {
+            throw new UnsupportedOperationException("Stub!");
+        }
 
-        void onAuthenticationFailed(long deviceId)
-                throws RemoteException;
+        default void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token)
+                throws RemoteException {
+            throw new UnsupportedOperationException("Stub!");
+        }
 
-        void onError(long deviceId, int error, int vendorCode)
-                throws RemoteException;
+        default void onAuthenticationFailed(long deviceId) throws RemoteException {
+            throw new UnsupportedOperationException("Stub!");
+        }
+
+        default void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)
+                throws RemoteException {
+            throw new UnsupportedOperationException("Stub!");
+        }
+
+        void onError(long deviceId, int error, int vendorCode, int cookie) throws RemoteException;
 
         default void onRemoved(BiometricAuthenticator.Identifier identifier,
                 int remaining) throws RemoteException {};
@@ -366,6 +368,37 @@
     }
 
     /**
+     * Wraps the callback interface from Service -> BiometricPrompt
+     */
+    protected abstract class BiometricServiceListener implements ServiceListener {
+        private IBiometricServiceReceiverInternal mWrapperReceiver;
+
+        public BiometricServiceListener(IBiometricServiceReceiverInternal wrapperReceiver) {
+            mWrapperReceiver = wrapperReceiver;
+        }
+
+        public IBiometricServiceReceiverInternal getWrapperReceiver() {
+            return mWrapperReceiver;
+        }
+
+        @Override
+        public void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token)
+                throws RemoteException {
+            if (getWrapperReceiver() != null) {
+                getWrapperReceiver().onAuthenticationSucceeded(requireConfirmation, token);
+            }
+        }
+
+        @Override
+        public void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)
+                throws RemoteException {
+            if (getWrapperReceiver() != null) {
+                getWrapperReceiver().onAuthenticationFailed(cookie, requireConfirmation);
+            }
+        }
+    }
+
+    /**
      * Wraps a portion of the interface from Service -> Daemon that is used by the ClientMonitor
      * subclasses.
      */
@@ -706,30 +739,6 @@
         }
 
         mHandler.post(() -> {
-            if (client.isBiometricPrompt()) {
-                try {
-                    final List<ActivityManager.RunningAppProcessInfo> procs =
-                            ActivityManager.getService().getRunningAppProcesses();
-                    for (int i = 0; i < procs.size(); i++) {
-                        final ActivityManager.RunningAppProcessInfo info = procs.get(i);
-                        if (info.uid == callingUid && info.importance == IMPORTANCE_FOREGROUND) {
-                            PackageManager pm = getContext().getPackageManager();
-                            final CharSequence label = pm.getApplicationLabel(
-                                    pm.getApplicationInfo(info.processName,
-                                            PackageManager.GET_META_DATA));
-                            final String title = getContext()
-                                    .getString(R.string.biometric_dialog_default_title, label);
-                            client.setTitleIfEmpty(title);
-                            break;
-                        }
-                    }
-                } catch (RemoteException e) {
-                    Slog.e(getTag(), "Unable to get application name", e);
-                } catch (PackageManager.NameNotFoundException e) {
-                    Slog.e(getTag(), "Unable to get application name", e);
-                }
-            }
-
             mMetricsLogger.histogram(getMetrics().tagAuthToken(), opId != 0L ? 1 : 0);
 
             // Get performance stats object for this user.
@@ -751,29 +760,37 @@
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
         final int callingUserId = UserHandle.getCallingUserId();
-        cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, callingUserId);
+        cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid, callingUserId,
+                true /* fromClient */);
     }
 
     protected void cancelAuthenticationInternal(final IBinder token, final String opPackageName,
-            int callingUid, int callingPid, int callingUserId) {
-        if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
-                callingUserId)) {
-            if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName);
-            return;
+            int callingUid, int callingPid, int callingUserId, boolean fromClient) {
+        if (fromClient) {
+            // Only check this if cancel was called from the client (app). If cancel was called
+            // from BiometricService, it means the dialog was dismissed due to user interaction.
+            if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
+                    callingUserId)) {
+                if (DEBUG) Slog.v(getTag(), "cancelAuthentication(): reject " + opPackageName);
+                return;
+            }
         }
 
         mHandler.post(() -> {
             ClientMonitor client = mCurrentClient;
             if (client instanceof AuthenticationClient) {
-                if (client.getToken() == token) {
-                    if (DEBUG) Slog.v(getTag(), "stop client " + client.getOwnerString());
+                if (client.getToken() == token || !fromClient) {
+                    if (DEBUG) Slog.v(getTag(), "Stopping client " + client.getOwnerString()
+                            + ", fromClient: " + fromClient);
+                    // If cancel was from BiometricService, it means the dialog was dismissed
+                    // and authentication should be canceled.
                     client.stop(client.getToken() == token);
                 } else {
-                    if (DEBUG) Slog.v(getTag(), "can't stop client "
-                            + client.getOwnerString() + " since tokens don't match");
+                    if (DEBUG) Slog.v(getTag(), "Can't stop client " + client.getOwnerString()
+                            + " since tokens don't match. fromClient: " + fromClient);
                 }
             } else if (client != null) {
-                if (DEBUG) Slog.v(getTag(), "can't cancel non-authenticating client "
+                if (DEBUG) Slog.v(getTag(), "Can't cancel non-authenticating client "
                         + client.getOwnerString());
             }
         });
@@ -805,8 +822,7 @@
 
         int lockoutMode = getLockoutMode();
         if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
-            Slog.v(getTag(), "In lockout mode(" + lockoutMode +
-                    ") ; disallowing authentication");
+            Slog.v(getTag(), "In lockout mode(" + lockoutMode + ") ; disallowing authentication");
             int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?
                     BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :
                     BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
@@ -919,7 +935,6 @@
         if (currentClient != null) {
             if (DEBUG) Slog.v(getTag(), "request stop current client " +
                     currentClient.getOwnerString());
-
             // This check only matters for FingerprintService, since enumerate may call back
             // multiple times.
             if (currentClient instanceof FingerprintService.EnumerateClientImpl ||
@@ -940,17 +955,51 @@
             mHandler.removeCallbacks(mResetClientState);
             mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
         } else if (newClient != null) {
-            mCurrentClient = newClient;
-            if (DEBUG) Slog.v(getTag(), "starting client "
-                    + newClient.getClass().getSuperclass().getSimpleName()
-                    + "(" + newClient.getOwnerString() + ")"
-                    + ", initiatedByClient = " + initiatedByClient);
-            notifyClientActiveCallbacks(true);
+            // For BiometricPrompt clients, do not start until
+            // <Biometric>Service#startPreparedClient is called. BiometricService waits until all
+            // modalities are ready before initiating authentication.
+            if (newClient instanceof AuthenticationClient) {
+                AuthenticationClient client = (AuthenticationClient) newClient;
+                if (client.isBiometricPrompt()) {
+                    if (DEBUG) Slog.v(getTag(), "Returning cookie: " + client.getCookie());
+                    mCurrentClient = newClient;
+                    if (mBiometricService == null) {
+                        mBiometricService = IBiometricService.Stub.asInterface(
+                                ServiceManager.getService(Context.BIOMETRIC_SERVICE));
+                    }
+                    try {
+                        mBiometricService.onReadyForAuthentication(client.getCookie(),
+                                client.getRequireConfirmation(), client.getTargetUserId());
+                    } catch (RemoteException e) {
+                        Slog.e(getTag(), "Remote exception", e);
+                    }
+                    return;
+                }
+            }
 
-            newClient.start();
+            // We are not a BiometricPrompt client, start the client immediately
+            mCurrentClient = newClient;
+            startCurrentClient(mCurrentClient.getCookie());
         }
     }
 
+    protected void startCurrentClient(int cookie) {
+        if (mCurrentClient == null) {
+            Slog.e(getTag(), "Trying to start null client!");
+            return;
+        }
+        if (DEBUG) Slog.v(getTag(), "starting client "
+                + mCurrentClient.getClass().getSuperclass().getSimpleName()
+                + "(" + mCurrentClient.getOwnerString() + ")"
+                + " cookie: " + cookie + "/" + mCurrentClient.getCookie());
+        if (cookie != mCurrentClient.getCookie()) {
+            Slog.e(getTag(), "Mismatched cookie");
+            return;
+        }
+        notifyClientActiveCallbacks(true);
+        mCurrentClient.start();
+    }
+
     protected void removeClient(ClientMonitor client) {
         if (client != null) {
             client.destroy();
diff --git a/services/core/java/com/android/server/biometrics/ClientMonitor.java b/services/core/java/com/android/server/biometrics/ClientMonitor.java
index a7ada2f..d19aff6 100644
--- a/services/core/java/com/android/server/biometrics/ClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/ClientMonitor.java
@@ -58,6 +58,9 @@
 
     private IBinder mToken;
     private BiometricServiceBase.ServiceListener mListener;
+    // Currently only used for authentication client. The cookie generated by BiometricService
+    // is never 0.
+    private final int mCookie;
 
     protected final MetricsLogger mMetricsLogger;
     protected final Metrics mMetrics;
@@ -80,7 +83,7 @@
     public ClientMonitor(Context context, Metrics metrics,
             BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
             BiometricServiceBase.ServiceListener listener, int userId, int groupId,
-            boolean restricted, String owner) {
+            boolean restricted, String owner, int cookie) {
         mContext = context;
         mMetrics = metrics;
         mDaemon = daemon;
@@ -91,6 +94,7 @@
         mGroupId = groupId;
         mIsRestricted = restricted;
         mOwner = owner;
+        mCookie = cookie;
         mSuccessVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
         mErrorVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
         mMetricsLogger = new MetricsLogger();
@@ -107,6 +111,10 @@
         return mMetrics.logTag();
     }
 
+    public int getCookie() {
+        return mCookie;
+    }
+
     /**
      * Contacts the biometric's HAL to start the client.
      * @return 0 on success, errno from driver on failure
@@ -174,7 +182,7 @@
     public boolean onError(long deviceId, int error, int vendorCode) {
         try {
             if (mListener != null) {
-                mListener.onError(deviceId, error, vendorCode);
+                mListener.onError(deviceId, error, vendorCode, getCookie());
             }
         } catch (RemoteException e) {
             Slog.w(getLogTag(), "Failed to invoke sendError", e);
diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java
index 76dc5a9..f858ef5 100644
--- a/services/core/java/com/android/server/biometrics/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/EnrollClient.java
@@ -40,7 +40,7 @@
             BiometricServiceBase.ServiceListener listener, int userId, int groupId,
             byte[] cryptoToken, boolean restricted, String owner, BiometricUtils utils) {
         super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
-                owner);
+                owner, 0 /* cookie */);
         mBiometricUtils = utils;
         mCryptoToken = Arrays.copyOf(cryptoToken, cryptoToken.length);
     }
diff --git a/services/core/java/com/android/server/biometrics/EnumerateClient.java b/services/core/java/com/android/server/biometrics/EnumerateClient.java
index 47dc7ff..df6220c 100644
--- a/services/core/java/com/android/server/biometrics/EnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/EnumerateClient.java
@@ -34,7 +34,7 @@
             BiometricServiceBase.ServiceListener listener, int groupId, int userId,
             boolean restricted, String owner) {
         super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
-                owner);
+                owner, 0 /* cookie */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/RemovalClient.java b/services/core/java/com/android/server/biometrics/RemovalClient.java
index 15b3773..be233ec 100644
--- a/services/core/java/com/android/server/biometrics/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/RemovalClient.java
@@ -37,7 +37,7 @@
             BiometricServiceBase.ServiceListener listener, int biometricId, int groupId, int userId,
             boolean restricted, String owner, BiometricUtils utils) {
         super(context, metrics, daemon, halDeviceId, token, listener, userId, groupId, restricted,
-                owner);
+                owner, 0 /* cookie */);
         mBiometricId = biometricId;
         mBiometricUtils = utils;
     }
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 7aa2e47..557af04 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -27,9 +27,8 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.IBiometricPromptReceiver;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
-import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.face.V1_0.IBiometricsFace;
 import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
 import android.hardware.biometrics.face.V1_0.Status;
@@ -38,7 +37,6 @@
 import android.hardware.face.IFaceService;
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
-import android.os.Bundle;
 import android.os.Environment;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -50,7 +48,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.DumpUtils;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.biometrics.BiometricServiceBase;
@@ -89,27 +86,9 @@
         public FaceAuthClient(Context context,
                 DaemonWrapper daemon, long halDeviceId, IBinder token,
                 ServiceListener listener, int targetUserId, int groupId, long opId,
-                boolean restricted, String owner, Bundle bundle,
-                IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService,
-                boolean requireConfirmation) {
+                boolean restricted, String owner, int cookie, boolean requireConfirmation) {
             super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
-                    restricted, owner, bundle, dialogReceiver, statusBarService,
-                    requireConfirmation);
-        }
-
-        @Override
-        public String getErrorString(int error, int vendorCode) {
-            return FaceManager.getErrorString(getContext(), error, vendorCode);
-        }
-
-        @Override
-        public String getAcquiredString(int acquireInfo, int vendorCode) {
-            return FaceManager.getAcquiredString(getContext(), acquireInfo, vendorCode);
-        }
-
-        @Override
-        public int getBiometricType() {
-            return BiometricAuthenticator.TYPE_FACE;
+                    restricted, owner, cookie, requireConfirmation);
         }
     }
 
@@ -162,28 +141,33 @@
             final AuthenticationClientImpl client = new FaceAuthClient(getContext(),
                     mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
                     mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName,
-                    null /* bundle */, null /* dialogReceiver */, mStatusBarService,
-                    false /* requireConfirmation */);
+                    0 /* cookie */, false /* requireConfirmation */);
             authenticateInternal(client, opId, opPackageName);
         }
 
         @Override // Binder call
-        public void authenticateFromService(boolean requireConfirmation, IBinder token, long opId,
-                int groupId, IBiometricServiceReceiver receiver, int flags,
-                String opPackageName, Bundle bundle, IBiometricPromptReceiver dialogReceiver,
-                int callingUid, int callingPid, int callingUserId) {
+        public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long opId,
+                int groupId, IBiometricServiceReceiverInternal wrapperReceiver,
+                String opPackageName, int cookie, int callingUid, int callingPid,
+                int callingUserId) {
             checkPermission(USE_BIOMETRIC_INTERNAL);
             final boolean restricted = true; // BiometricPrompt is always restricted
             final AuthenticationClientImpl client = new FaceAuthClient(getContext(),
                     mDaemonWrapper, mHalDeviceId, token,
-                    new BiometricPromptServiceListenerImpl(receiver),
-                    mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName,
-                    bundle, dialogReceiver, mStatusBarService, true /* requireConfirmation */);
+                    new BiometricPromptServiceListenerImpl(wrapperReceiver),
+                    mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName, cookie,
+                    true /* requireConfirmation */);
             authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
                     callingUserId);
         }
 
         @Override // Binder call
+        public void startPreparedClient(int cookie) {
+            checkPermission(MANAGE_BIOMETRIC);
+            startCurrentClient(cookie);
+        }
+
+        @Override // Binder call
         public void cancelAuthentication(final IBinder token, final String opPackageName) {
             checkPermission(USE_BIOMETRIC_INTERNAL);
             cancelAuthenticationInternal(token, opPackageName);
@@ -191,10 +175,10 @@
 
         @Override // Binder call
         public void cancelAuthenticationFromService(final IBinder token, final String opPackageName,
-                int callingUid, int callingPid, int callingUserId) {
+                int callingUid, int callingPid, int callingUserId, boolean fromClient) {
             checkPermission(USE_BIOMETRIC_INTERNAL);
-            cancelAuthenticationInternal(token, opPackageName,
-                    callingUid, callingPid, callingUserId);
+            cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid,
+                    callingUserId, fromClient);
         }
 
         @Override // Binder call
@@ -405,12 +389,9 @@
      * Receives callbacks from the ClientMonitor implementations. The results are forwarded to
      * BiometricPrompt.
      */
-    private class BiometricPromptServiceListenerImpl implements ServiceListener {
-
-        private IBiometricServiceReceiver mBiometricServiceReceiver;
-
-        public BiometricPromptServiceListenerImpl(IBiometricServiceReceiver receiver) {
-            mBiometricServiceReceiver = receiver;
+    private class BiometricPromptServiceListenerImpl extends BiometricServiceListener {
+        BiometricPromptServiceListenerImpl(IBiometricServiceReceiverInternal wrapperReceiver) {
+            super(wrapperReceiver);
         }
 
         @Override
@@ -419,32 +400,18 @@
             /**
              * Map the acquired codes onto existing {@link BiometricConstants} acquired codes.
              */
-            if (mBiometricServiceReceiver != null) {
-                mBiometricServiceReceiver.onAcquired(deviceId,
+            if (getWrapperReceiver() != null) {
+                getWrapperReceiver().onAcquired(
                         FaceManager.getMappedAcquiredInfo(acquiredInfo, vendorCode),
                         FaceManager.getAcquiredString(getContext(), acquiredInfo, vendorCode));
             }
         }
 
         @Override
-        public void onAuthenticationSucceeded(long deviceId,
-                BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException {
-            if (mBiometricServiceReceiver != null) {
-                mBiometricServiceReceiver.onAuthenticationSucceeded(deviceId);
-            }
-        }
-
-        @Override
-        public void onAuthenticationFailed(long deviceId) throws RemoteException {
-            if (mBiometricServiceReceiver != null) {
-                mBiometricServiceReceiver.onAuthenticationFailed(deviceId);
-            }
-        }
-
-        @Override
-        public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
-            if (mBiometricServiceReceiver != null) {
-                mBiometricServiceReceiver.onError(deviceId, error,
+        public void onError(long deviceId, int error, int vendorCode, int cookie)
+                throws RemoteException {
+            if (getWrapperReceiver() != null) {
+                getWrapperReceiver().onError(cookie, error,
                         FaceManager.getErrorString(getContext(), error, vendorCode));
             }
         }
@@ -455,7 +422,6 @@
      * the FaceManager.
      */
     private class ServiceListenerImpl implements ServiceListener {
-
         private IFaceServiceReceiver mFaceServiceReceiver;
 
         public ServiceListenerImpl(IFaceServiceReceiver receiver) {
@@ -501,7 +467,8 @@
         }
 
         @Override
-        public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
+        public void onError(long deviceId, int error, int vendorCode, int cookie)
+                throws RemoteException {
             if (mFaceServiceReceiver != null) {
                 mFaceServiceReceiver.onError(deviceId, error, vendorCode);
             }
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index b0b788f..6a5bc61 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -30,9 +30,8 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.IBiometricPromptReceiver;
-import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
 import android.hardware.fingerprint.Fingerprint;
@@ -42,7 +41,6 @@
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.os.Binder;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.Environment;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -55,7 +53,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.DumpUtils;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.biometrics.AuthenticationClient;
@@ -109,27 +106,10 @@
         public FingerprintAuthClient(Context context,
                 DaemonWrapper daemon, long halDeviceId, IBinder token,
                 ServiceListener listener, int targetUserId, int groupId, long opId,
-                boolean restricted, String owner, Bundle bundle,
-                IBiometricPromptReceiver dialogReceiver, IStatusBarService statusBarService,
+                boolean restricted, String owner, int cookie,
                 boolean requireConfirmation) {
             super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
-                    restricted, owner, bundle, dialogReceiver, statusBarService,
-                    requireConfirmation);
-        }
-
-        @Override
-        public String getErrorString(int error, int vendorCode) {
-            return FingerprintManager.getErrorString(getContext(), error, vendorCode);
-        }
-
-        @Override
-        public String getAcquiredString(int acquireInfo, int vendorCode) {
-            return FingerprintManager.getAcquiredString(getContext(), acquireInfo, vendorCode);
-        }
-
-        @Override
-        public int getBiometricType() {
-            return BiometricAuthenticator.TYPE_FINGERPRINT;
+                    restricted, owner, cookie, requireConfirmation);
         }
     }
 
@@ -182,38 +162,44 @@
             final boolean restricted = isRestricted();
             final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(),
                     mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
-                    mCurrentUserId, groupId, opId, restricted, opPackageName, null /* bundle */,
-                    null /* dialogReceiver */, mStatusBarService, false /* requireConfirmation */);
+                    mCurrentUserId, groupId, opId, restricted, opPackageName,
+                    0 /* cookie */, false /* requireConfirmation */);
             authenticateInternal(client, opId, opPackageName);
         }
 
         @Override // Binder call
-        public void authenticateFromService(IBinder token, long opId, int groupId,
-                IBiometricServiceReceiver receiver, int flags, String opPackageName,
-                Bundle bundle, IBiometricPromptReceiver dialogReceiver,
-                int callingUid, int callingPid, int callingUserId) {
+        public void prepareForAuthentication(IBinder token, long opId, int groupId,
+                IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
+                int cookie, int callingUid, int callingPid, int callingUserId) {
             checkPermission(MANAGE_BIOMETRIC);
             final boolean restricted = true; // BiometricPrompt is always restricted
             final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(),
                     mDaemonWrapper, mHalDeviceId, token,
-                    new BiometricPromptServiceListenerImpl(receiver),
-                    mCurrentUserId, groupId, opId, restricted, opPackageName, bundle,
-                    dialogReceiver, mStatusBarService, false /* requireConfirmation */);
+                    new BiometricPromptServiceListenerImpl(wrapperReceiver),
+                    mCurrentUserId, groupId, opId, restricted, opPackageName, cookie,
+                    false /* requireConfirmation */);
             authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
                     callingUserId);
         }
 
         @Override // Binder call
+        public void startPreparedClient(int cookie) {
+            checkPermission(MANAGE_BIOMETRIC);
+            startCurrentClient(cookie);
+        }
+
+
+        @Override // Binder call
         public void cancelAuthentication(final IBinder token, final String opPackageName) {
             cancelAuthenticationInternal(token, opPackageName);
         }
 
         @Override // Binder call
         public void cancelAuthenticationFromService(final IBinder token, final String opPackageName,
-                int callingUid, int callingPid, int callingUserId) {
+                int callingUid, int callingPid, int callingUserId, boolean fromClient) {
             checkPermission(MANAGE_BIOMETRIC);
-            cancelAuthenticationInternal(token, opPackageName,
-                    callingUid, callingPid, callingUserId);
+            cancelAuthenticationInternal(token, opPackageName, callingUid, callingPid,
+                    callingUserId, fromClient);
         }
 
         @Override // Binder call
@@ -388,43 +374,25 @@
      * Receives callbacks from the ClientMonitor implementations. The results are forwarded to
      * BiometricPrompt.
      */
-    private class BiometricPromptServiceListenerImpl implements ServiceListener {
-
-        private IBiometricServiceReceiver mBiometricServiceReceiver;
-
-        public BiometricPromptServiceListenerImpl(IBiometricServiceReceiver receiver) {
-            mBiometricServiceReceiver = receiver;
+    private class BiometricPromptServiceListenerImpl extends BiometricServiceListener {
+        BiometricPromptServiceListenerImpl(IBiometricServiceReceiverInternal wrapperReceiver) {
+            super(wrapperReceiver);
         }
 
         @Override
         public void onAcquired(long deviceId, int acquiredInfo, int vendorCode)
                 throws RemoteException {
-            if (mBiometricServiceReceiver != null) {
-                mBiometricServiceReceiver.onAcquired(deviceId, acquiredInfo,
-                        FingerprintManager.getAcquiredString(
+            if (getWrapperReceiver() != null) {
+                getWrapperReceiver().onAcquired(acquiredInfo, FingerprintManager.getAcquiredString(
                             getContext(), acquiredInfo, vendorCode));
             }
         }
 
         @Override
-        public void onAuthenticationSucceeded(long deviceId,
-                BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException {
-            if (mBiometricServiceReceiver != null) {
-                mBiometricServiceReceiver.onAuthenticationSucceeded(deviceId);
-            }
-        }
-
-        @Override
-        public void onAuthenticationFailed(long deviceId) throws RemoteException {
-            if (mBiometricServiceReceiver != null) {
-                mBiometricServiceReceiver.onAuthenticationFailed(deviceId);
-            }
-        }
-
-        @Override
-        public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
-            if (mBiometricServiceReceiver != null) {
-                mBiometricServiceReceiver.onError(deviceId, error,
+        public void onError(long deviceId, int error, int vendorCode, int cookie)
+                throws RemoteException {
+            if (getWrapperReceiver() != null) {
+                getWrapperReceiver().onError(cookie, error,
                         FingerprintManager.getErrorString(getContext(), error, vendorCode));
             }
         }
@@ -435,7 +403,6 @@
      * the FingerprintManager.
      */
     private class ServiceListenerImpl implements ServiceListener {
-
         private IFingerprintServiceReceiver mFingerprintServiceReceiver;
 
         public ServiceListenerImpl(IFingerprintServiceReceiver receiver) {
@@ -483,7 +450,8 @@
         }
 
         @Override
-        public void onError(long deviceId, int error, int vendorCode) throws RemoteException {
+        public void onError(long deviceId, int error, int vendorCode, int cookie)
+                throws RemoteException {
             if (mFingerprintServiceReceiver != null) {
                 mFingerprintServiceReceiver.onError(deviceId, error, vendorCode);
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b76eaaf..8abb500 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -20135,7 +20135,7 @@
                 if (Process.isIsolated(uid)) {
                     return Zygote.MOUNT_EXTERNAL_NONE;
                 }
-                if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+                if (StorageManager.hasIsolatedStorage()) {
                     return checkUidPermission(WRITE_MEDIA_STORAGE, uid) == PERMISSION_GRANTED
                             ? Zygote.MOUNT_EXTERNAL_FULL
                             : Zygote.MOUNT_EXTERNAL_WRITE;
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index e9b9930..68fe1d8 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -189,7 +189,7 @@
     private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
     static {
         // STOPSHIP(b/112545973): remove once feature enabled by default
-        if (!SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+        if (!StorageManager.hasIsolatedStorage()) {
             STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
             STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
         }
@@ -198,7 +198,7 @@
     private static final Set<String> MEDIA_AURAL_PERMISSIONS = new ArraySet<>();
     static {
         // STOPSHIP(b/112545973): remove once feature enabled by default
-        if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+        if (StorageManager.hasIsolatedStorage()) {
             MEDIA_AURAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_AUDIO);
         }
     }
@@ -206,7 +206,7 @@
     private static final Set<String> MEDIA_VISUAL_PERMISSIONS = new ArraySet<>();
     static {
         // STOPSHIP(b/112545973): remove once feature enabled by default
-        if (SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)) {
+        if (StorageManager.hasIsolatedStorage()) {
             MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_VIDEO);
             MEDIA_VISUAL_PERMISSIONS.add(Manifest.permission.READ_MEDIA_IMAGES);
         }
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 21adc47..f0ebb75 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -195,10 +195,8 @@
             "/system/bin/traced",  // Perfetto.
             "/system/bin/traced_probes",  // Perfetto.
             "webview_zygote",
-            // Temporarily excluded zygote to investigate its forking consequences in
-            // NativeProcessMemoryState.
-            // "zygote",
-            // "zygote64",
+            "zygote",
+            "zygote64",
     };
 
     private static final int CPU_TIME_PER_THREAD_FREQ_NUM_FREQUENCIES = 8;
@@ -1090,6 +1088,7 @@
     private void pullNativeProcessMemoryState(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
+        final List<String> processNames = Arrays.asList(MEMORY_INTERESTING_NATIVE_PROCESSES);
         int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES);
         for (int i = 0; i < pids.length; i++) {
             int pid = pids[i];
@@ -1099,6 +1098,12 @@
             }
             int uid = getUidForPid(pid);
             String processName = readCmdlineFromProcfs(pid);
+            // Sometimes we get here processName that is not included in the whitelist. It comes
+            // from forking the zygote for an app. We can ignore that sample because this process
+            // is collected by ProcessMemoryState.
+            if (!processNames.contains(processName)) {
+                continue;
+            }
             StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
             e.writeInt(uid);
             e.writeString(processName);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 0d66a2c8..e645b84 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -24,7 +24,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Rect;
-import android.hardware.biometrics.IBiometricPromptReceiver;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -598,8 +598,8 @@
     }
 
     @Override
-    public void showBiometricDialog(Bundle bundle, IBiometricPromptReceiver receiver, int type,
-            boolean requireConfirmation, int userId) {
+    public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
+            int type, boolean requireConfirmation, int userId) {
         enforceBiometricDialog();
         if (mBar != null) {
             try {
@@ -654,6 +654,17 @@
     }
 
     @Override
+    public void showBiometricTryAgain() {
+        enforceBiometricDialog();
+        if (mBar != null) {
+            try {
+                mBar.showBiometricTryAgain();
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
+    @Override
     public void disable(int what, IBinder token, String pkg) {
         disableForUser(what, token, pkg, mCurrentUserId);
     }