Merge "Formalizing states in BatterySaverStateMachine."
diff --git a/Android.bp b/Android.bp
index 4594425..4219d36 100644
--- a/Android.bp
+++ b/Android.bp
@@ -933,6 +933,11 @@
         "core/java/android/net/ipmemorystore/**/*.aidl",
     ],
     api_dir: "aidl/networkstack",
+    backend: {
+        java: {
+            sdk_version: "28",
+        },
+    },
 }
 
 filegroup {
diff --git a/api/current.txt b/api/current.txt
index ece3536..9190a13 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6623,7 +6623,7 @@
     method @Nullable public String[] getAccountTypesWithManagementDisabled();
     method @Nullable public java.util.List<android.content.ComponentName> getActiveAdmins();
     method @NonNull public java.util.Set<java.lang.String> getAffiliationIds(@NonNull android.content.ComponentName);
-    method @Nullable public java.util.List<java.lang.String> getAlwaysOnVpnLockdownWhitelist(@NonNull android.content.ComponentName);
+    method @Nullable public java.util.Set<java.lang.String> getAlwaysOnVpnLockdownWhitelist(@NonNull android.content.ComponentName);
     method @Nullable public String getAlwaysOnVpnPackage(@NonNull android.content.ComponentName);
     method @WorkerThread @NonNull public android.os.Bundle getApplicationRestrictions(@Nullable android.content.ComponentName, String);
     method @Deprecated @Nullable public String getApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName);
@@ -6736,7 +6736,7 @@
     method public void setAccountManagementDisabled(@NonNull android.content.ComponentName, String, boolean);
     method public void setAffiliationIds(@NonNull android.content.ComponentName, @NonNull java.util.Set<java.lang.String>);
     method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean, @Nullable java.util.List<java.lang.String>) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public void setAlwaysOnVpnPackage(@NonNull android.content.ComponentName, @Nullable String, boolean, @Nullable java.util.Set<java.lang.String>) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean setApplicationHidden(@NonNull android.content.ComponentName, String, boolean);
     method @WorkerThread public void setApplicationRestrictions(@Nullable android.content.ComponentName, String, android.os.Bundle);
     method @Deprecated public void setApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName, @Nullable String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -17366,15 +17366,16 @@
     method @Nullable public java.util.Set<android.util.Size> getInputSizes(int);
     method @NonNull public java.util.Set<java.lang.Integer> getOutputFormats();
     method @IntRange(from=0) public long getOutputMinFrameDuration(int, @NonNull android.util.Size);
-    method public <T> long getOutputMinFrameDuration(@NonNull Class<T>, @NonNull android.util.Size);
+    method @IntRange(from=0) public <T> long getOutputMinFrameDuration(@NonNull Class<T>, @NonNull android.util.Size);
     method @Nullable public java.util.Set<android.util.Size> getOutputSizes(int);
-    method public <T> java.util.Set<android.util.Size> getOutputSizes(@NonNull Class<T>);
+    method @Nullable public <T> java.util.Set<android.util.Size> getOutputSizes(@NonNull Class<T>);
     method @IntRange(from=0) public long getOutputStallDuration(int, @NonNull android.util.Size);
-    method public <T> long getOutputStallDuration(@NonNull Class<T>, @NonNull android.util.Size);
+    method @IntRange(from=0) public <T> long getOutputStallDuration(@NonNull Class<T>, @NonNull android.util.Size);
     method public int getRecommendedUseCase();
     method @Nullable public java.util.Set<java.lang.Integer> getValidOutputFormatsForInput(int);
     method public boolean isOutputSupportedFor(int);
     method public boolean isOutputSupportedFor(@NonNull android.view.Surface);
+    field public static final int USECASE_LOW_LATENCY_SNAPSHOT = 6; // 0x6
     field public static final int USECASE_PREVIEW = 0; // 0x0
     field public static final int USECASE_RAW = 5; // 0x5
     field public static final int USECASE_RECORD = 1; // 0x1
@@ -22965,6 +22966,7 @@
     method public void unregisterGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback);
     method public void unregisterGnssNavigationMessageCallback(@NonNull android.location.GnssNavigationMessage.Callback);
     method public void unregisterGnssStatusCallback(@NonNull android.location.GnssStatus.Callback);
+    field public static final String EXTRA_PROVIDER_NAME = "android.location.extra.PROVIDER_NAME";
     field public static final String GPS_PROVIDER = "gps";
     field public static final String KEY_LOCATION_CHANGED = "location";
     field public static final String KEY_PROVIDER_ENABLED = "providerEnabled";
@@ -39005,10 +39007,12 @@
     method public static long getLong(android.content.ContentResolver, String) throws android.provider.Settings.SettingNotFoundException;
     method public static String getString(android.content.ContentResolver, String);
     method public static android.net.Uri getUriFor(String);
+    method @Deprecated public static boolean isLocationProviderEnabled(android.content.ContentResolver, String);
     method public static boolean putFloat(android.content.ContentResolver, String, float);
     method public static boolean putInt(android.content.ContentResolver, String, int);
     method public static boolean putLong(android.content.ContentResolver, String, long);
     method public static boolean putString(android.content.ContentResolver, String, String);
+    method @Deprecated public static void setLocationProviderEnabled(android.content.ContentResolver, String, boolean);
     field public static final String ACCESSIBILITY_DISPLAY_INVERSION_ENABLED = "accessibility_display_inversion_enabled";
     field public static final String ACCESSIBILITY_ENABLED = "accessibility_enabled";
     field @Deprecated public static final String ACCESSIBILITY_SPEAK_PASSWORD = "speak_password";
@@ -45244,7 +45248,7 @@
     field public static final String EXTRA_CARRIER_ID = "android.telephony.extra.CARRIER_ID";
     field public static final String EXTRA_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME";
     field public static final String EXTRA_HIDE_PUBLIC_SETTINGS = "android.telephony.extra.HIDE_PUBLIC_SETTINGS";
-    field public static final String EXTRA_INCOMING_NUMBER = "incoming_number";
+    field @Deprecated public static final String EXTRA_INCOMING_NUMBER = "incoming_number";
     field public static final String EXTRA_IS_REFRESH = "android.telephony.extra.IS_REFRESH";
     field public static final String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
     field public static final String EXTRA_NETWORK_COUNTRY = "android.telephony.extra.NETWORK_COUNTRY";
diff --git a/api/removed.txt b/api/removed.txt
index 5108dd0..fa07094 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -314,17 +314,11 @@
 package android.net {
 
   public class ConnectivityManager {
-    method @Deprecated public void getLatestTetheringEntitlementValue(int, boolean, @NonNull android.net.ConnectivityManager.TetheringEntitlementValueListener, @Nullable android.os.Handler);
     method @Deprecated public boolean requestRouteToHost(int, int);
     method @Deprecated public int startUsingNetworkFeature(int, String);
     method @Deprecated public int stopUsingNetworkFeature(int, String);
   }
 
-  @Deprecated public abstract static class ConnectivityManager.TetheringEntitlementValueListener {
-    ctor public ConnectivityManager.TetheringEntitlementValueListener();
-    method public void onEntitlementResult(int);
-  }
-
   @Deprecated public class NetworkBadging {
     method @NonNull public static android.graphics.drawable.Drawable getWifiIcon(@IntRange(from=0, to=4) int, int, @Nullable android.content.res.Resources.Theme);
     field public static final int BADGING_4K = 30; // 0x1e
@@ -553,11 +547,6 @@
     field @Deprecated public static final String CONTACT_METADATA_SYNC = "contact_metadata_sync";
   }
 
-  public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
-    method @Deprecated public static boolean isLocationProviderEnabled(android.content.ContentResolver, String);
-    method @Deprecated public static void setLocationProviderEnabled(android.content.ContentResolver, String, boolean);
-  }
-
   public static final class Settings.System extends android.provider.Settings.NameValueTable {
     field public static final String APPEND_FOR_LAST_AUDIBLE = "_last_audible";
     field public static final String VOLUME_ALARM = "volume_alarm";
diff --git a/api/test-current.txt b/api/test-current.txt
index 4ccfa1c..5cb918f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2548,7 +2548,8 @@
     method public int getCarrierIdListVersion();
     method public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
-    method public void setCarrierTestOverride(String, String, String, String, String, String, String);
+    method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
+    method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String);
     field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
     field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 254d7d5..c023e6f 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -319,10 +319,11 @@
         return;
     }
 
-    flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
     // Setup the bucket start time and number.
     int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
-    mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
+    int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
+    flushCurrentBucketLocked(eventTimeNs, nextBucketNs);
+
     mCurrentBucketNum += numBucketsForward;
     VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
          (long long)mCurrentBucketStartTimeNs);
@@ -375,6 +376,7 @@
     // Only resets the counters, but doesn't setup the times nor numbers.
     // (Do not clear since the old one is still referenced in mAnomalyTrackers).
     mCurrentSlicedCounter = std::make_shared<DimToValMap>();
+    mCurrentBucketStartTimeNs = nextBucketStartTimeNs;
 }
 
 // Rough estimate of CountMetricProducer buffer stored. This number will be
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 7dc4e2d..615c7f2 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -559,26 +559,10 @@
         return;
     }
     VLOG("flushing...........");
-    for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin();
-            whatIt != mCurrentSlicedDurationTrackerMap.end();) {
-        for (auto it = whatIt->second.begin(); it != whatIt->second.end();) {
-            if (it->second->flushIfNeeded(eventTimeNs, &mPastBuckets)) {
-                VLOG("erase bucket for key %s %s",
-                     whatIt->first.toString().c_str(), it->first.toString().c_str());
-                it = whatIt->second.erase(it);
-            } else {
-                ++it;
-            }
-        }
-        if (whatIt->second.empty()) {
-            whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt);
-        } else {
-            whatIt++;
-        }
-    }
-
     int numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
-    mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
+    int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
+    flushCurrentBucketLocked(eventTimeNs, nextBucketNs);
+
     mCurrentBucketNum += numBucketsForward;
 }
 
@@ -602,6 +586,7 @@
         }
     }
     StatsdStats::getInstance().noteBucketCount(mMetricId);
+    mCurrentBucketStartTimeNs = nextBucketStartTimeNs;
 }
 
 void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 2b6cac8..2f9afa5 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -529,11 +529,11 @@
         return;
     }
 
-    flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
-
     // Adjusts the bucket start and end times.
     int64_t numBucketsForward = 1 + (eventTimeNs - currentBucketEndTimeNs) / mBucketSizeNs;
-    mCurrentBucketStartTimeNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
+    int64_t nextBucketNs = currentBucketEndTimeNs + (numBucketsForward - 1) * mBucketSizeNs;
+    flushCurrentBucketLocked(eventTimeNs, nextBucketNs);
+
     mCurrentBucketNum += numBucketsForward;
     VLOG("Gauge metric %lld: new bucket start time: %lld", (long long)mMetricId,
          (long long)mCurrentBucketStartTimeNs);
@@ -578,6 +578,7 @@
 
     StatsdStats::getInstance().noteBucketCount(mMetricId);
     mCurrentSlicedBucket = std::make_shared<DimToGaugeAtomsMap>();
+    mCurrentBucketStartTimeNs = nextBucketStartTimeNs;
 }
 
 size_t GaugeMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index a0a1348..12cec5d 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -178,7 +178,7 @@
 
     void pullAndMatchEventsLocked(const int64_t timestampNs, ConditionState condition);
 
-    void accumulateEvents(const std::vector<std::shared_ptr<LogEvent>>& allData, 
+    void accumulateEvents(const std::vector<std::shared_ptr<LogEvent>>& allData,
                           int64_t originalPullTimeNs, int64_t eventElapsedTimeNs,
                           ConditionState condition);
 
diff --git a/cmds/statsd/src/shell/ShellSubscriber.cpp b/cmds/statsd/src/shell/ShellSubscriber.cpp
index 52d5ffc..f7e32d4 100644
--- a/cmds/statsd/src/shell/ShellSubscriber.cpp
+++ b/cmds/statsd/src/shell/ShellSubscriber.cpp
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#define DEBUG true  // STOPSHIP if true
+#define DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
 #include "ShellSubscriber.h"
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c0a702f..08239a1 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3115,6 +3115,10 @@
         if (!r.stopped) {
             throw new IllegalStateException("Can't start activity that is not stopped.");
         }
+        if (r.activity.mFinished) {
+            // TODO(lifecycler): How can this happen?
+            return;
+        }
 
         // Start
         activity.performStart("handleStartActivity");
@@ -3237,8 +3241,6 @@
             if (!r.activity.mFinished && pendingActions != null) {
                 pendingActions.setOldState(r.state);
                 pendingActions.setRestoreInstanceState(true);
-            }
-            if (pendingActions != null) {
                 pendingActions.setCallOnPostCreate(true);
             }
         } else {
@@ -3940,7 +3942,7 @@
         if (localLOGV) {
             Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished);
         }
-        if (r == null) {
+        if (r == null || r.activity.mFinished) {
             return null;
         }
         if (r.getLifecycleState() == ON_RESUME) {
@@ -4210,6 +4212,12 @@
     private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, String reason,
             PendingTransactionActions pendingActions) {
         if (r.paused) {
+            if (r.activity.mFinished) {
+                // If we are finishing, we won't call onResume() in certain cases.
+                // So here we likewise don't want to call onPause() if the activity
+                // isn't resumed.
+                return null;
+            }
             RuntimeException e = new RuntimeException(
                     "Performing pause of activity that is not resumed: "
                     + r.intent.getComponent().toShortString());
@@ -4329,13 +4337,20 @@
             boolean saveState, boolean finalStateRequest, String reason) {
         if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
         if (r != null) {
-            if (!keepShown && r.stopped && !finalStateRequest) {
-                // Double stop request is possible if activity receives 'sleep' followed by 'stop'.
-                final RuntimeException e = new RuntimeException(
-                        "Performing stop of activity that is already stopped: "
-                                + r.intent.getComponent().toShortString());
-                Slog.e(TAG, e.getMessage(), e);
-                Slog.e(TAG, r.getStateString());
+            if (!keepShown && r.stopped) {
+                if (r.activity.mFinished) {
+                    // If we are finishing, we won't call onResume() in certain
+                    // cases.  So here we likewise don't want to call onStop()
+                    // if the activity isn't resumed.
+                    return;
+                }
+                if (!finalStateRequest) {
+                    final RuntimeException e = new RuntimeException(
+                            "Performing stop of activity that is already stopped: "
+                                    + r.intent.getComponent().toShortString());
+                    Slog.e(TAG, e.getMessage(), e);
+                    Slog.e(TAG, r.getStateString());
+                }
             }
 
             // One must first be paused before stopped...
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9a4e215..9436145 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -112,6 +112,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
@@ -5171,7 +5172,8 @@
      * </ul>
      * The call will fail if called with the package name of an unsupported VPN app.
      * <p> Enabling lockdown via {@code lockdownEnabled} argument carries the risk that any failure
-     * of the VPN provider could break networking for all apps.
+     * of the VPN provider could break networking for all apps. This method clears any lockdown
+     * whitelist set by {@link #setAlwaysOnVpnPackage(ComponentName, String, boolean, Set)}.
      *
      * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} to
      *        remove an existing always-on VPN configuration.
@@ -5181,11 +5183,11 @@
      * @throws NameNotFoundException if {@code vpnPackage} is not installed.
      * @throws UnsupportedOperationException if {@code vpnPackage} exists but does not support being
      *         set as always-on, or if always-on VPN is not available.
-     * @see #setAlwaysOnVpnPackage(ComponentName, String, boolean, List)
+     * @see #setAlwaysOnVpnPackage(ComponentName, String, boolean, Set)
      */
     public void setAlwaysOnVpnPackage(@NonNull ComponentName admin, @Nullable String vpnPackage,
             boolean lockdownEnabled) throws NameNotFoundException {
-        setAlwaysOnVpnPackage(admin, vpnPackage, lockdownEnabled, Collections.emptyList());
+        setAlwaysOnVpnPackage(admin, vpnPackage, lockdownEnabled, Collections.emptySet());
     }
 
     /**
@@ -5195,6 +5197,11 @@
      * System apps can always bypass VPN.
      * <p> Note that the system doesn't update the whitelist when packages are installed or
      * uninstalled, the admin app must call this method to keep the list up to date.
+     * <p> When {@code lockdownEnabled} is false {@code lockdownWhitelist} is ignored . When
+     * {@code lockdownEnabled} is {@code true} and {@code lockdownWhitelist} is {@code null} or
+     * empty, only system apps can bypass VPN.
+     * <p> Setting always-on VPN package to {@code null} or using
+     * {@link #setAlwaysOnVpnPackage(ComponentName, String, boolean)} clears lockdown whitelist.
      *
      * @param vpnPackage package name for an installed VPN app on the device, or {@code null}
      *         to remove an existing always-on VPN configuration
@@ -5211,13 +5218,13 @@
      *         available.
      */
     public void setAlwaysOnVpnPackage(@NonNull ComponentName admin, @Nullable String vpnPackage,
-            boolean lockdownEnabled, @Nullable List<String> lockdownWhitelist)
+            boolean lockdownEnabled, @Nullable Set<String> lockdownWhitelist)
             throws NameNotFoundException {
         throwIfParentInstance("setAlwaysOnVpnPackage");
         if (mService != null) {
             try {
-                mService.setAlwaysOnVpnPackage(
-                        admin, vpnPackage, lockdownEnabled, lockdownWhitelist);
+                mService.setAlwaysOnVpnPackage(admin, vpnPackage, lockdownEnabled,
+                        lockdownWhitelist == null ? null : new ArrayList<>(lockdownWhitelist));
             } catch (ServiceSpecificException e) {
                 switch (e.errorCode) {
                     case ERROR_VPN_PACKAGE_NOT_FOUND:
@@ -5255,7 +5262,7 @@
     }
 
     /**
-     * Called by device or profile owner to query the list of packages that are allowed to access
+     * Called by device or profile owner to query the set of packages that are allowed to access
      * the network directly when always-on VPN is in lockdown mode but not connected. Returns
      * {@code null} when always-on VPN is not active or not in lockdown mode.
      *
@@ -5263,13 +5270,15 @@
      *
      * @throws SecurityException if {@code admin} is not a device or a profile owner.
      *
-     * @see #setAlwaysOnVpnPackage(ComponentName, String, boolean, List)
+     * @see #setAlwaysOnVpnPackage(ComponentName, String, boolean, Set)
      */
-    public @Nullable List<String> getAlwaysOnVpnLockdownWhitelist(@NonNull ComponentName admin) {
+    public @Nullable Set<String> getAlwaysOnVpnLockdownWhitelist(@NonNull ComponentName admin) {
         throwIfParentInstance("getAlwaysOnVpnLockdownWhitelist");
         if (mService != null) {
             try {
-                return mService.getAlwaysOnVpnLockdownWhitelist(admin);
+                final List<String> whitelist =
+                        mService.getAlwaysOnVpnLockdownWhitelist(admin);
+                return whitelist == null ? null : new HashSet<>(whitelist);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 49a29f0a..bb1501f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4016,6 +4016,16 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link com.android.server.attention.AttentionManagerService} for attention services.
+     *
+     * @see #getSystemService(String)
+     * @see android.server.attention.AttentionManagerService
+     * @hide
+     */
+    public static final String ATTENTION_SERVICE = "attention";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.view.inputmethod.InputMethodManager} for accessing input
      * methods.
      *
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index 6ea35f0..bae0fd3 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -315,7 +315,7 @@
     /**
      * The sensor is dirty. The user should be informed to clean the sensor.
      */
-    public static final int SENSOR_DIRTY = 21;
+    public static final int FACE_ACQUIRED_SENSOR_DIRTY = 21;
 
     /**
      * Hardware vendors may extend this list if there are conditions that do not fall under one of
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index e751b2c..08035972 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -523,6 +523,11 @@
      * cancelled notification through {@link AuthenticationCallback#onAuthenticationError(int,
      * CharSequence)}.
      *
+     * Note: Applications generally should not cancel and start authentication in quick succession.
+     * For example, to properly handle authentication across configuration changes, it's recommended
+     * to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, the
+     * application will not need to cancel/restart authentication during the configuration change.
+     *
      * @throws IllegalArgumentException If any of the arguments are null
      *
      * @param crypto Object associated with the call
@@ -568,6 +573,11 @@
      * authentication. The interrupted client will receive a cancelled notification through {@link
      * AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
      *
+     * Note: Applications generally should not cancel and start authentication in quick succession.
+     * For example, to properly handle authentication across configuration changes, it's recommended
+     * to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, the
+     * application will not need to cancel/restart authentication during the configuration change.
+     *
      * @throws IllegalArgumentException If any of the arguments are null
      *
      * @param cancel An object that can be used to cancel authentication
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 7ae673c..c39796b 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -336,6 +336,7 @@
      * <li>{@link RecommendedStreamConfigurationMap#USECASE_SNAPSHOT}</li>
      * <li>{@link RecommendedStreamConfigurationMap#USECASE_RAW}</li>
      * <li>{@link RecommendedStreamConfigurationMap#USECASE_ZSL}</li>
+     * <li>{@link RecommendedStreamConfigurationMap#USECASE_LOW_LATENCY_SNAPSHOT}</li>
      * </ul>
      * </p>
      *
@@ -400,7 +401,7 @@
     public @Nullable RecommendedStreamConfigurationMap getRecommendedStreamConfigurationMap(
             @RecommendedStreamConfigurationMap.RecommendedUsecase int usecase) {
         if (((usecase >= RecommendedStreamConfigurationMap.USECASE_PREVIEW) &&
-                (usecase <= RecommendedStreamConfigurationMap.USECASE_RAW)) ||
+                (usecase <= RecommendedStreamConfigurationMap.USECASE_LOW_LATENCY_SNAPSHOT)) ||
                 ((usecase >= RecommendedStreamConfigurationMap.USECASE_VENDOR_START) &&
                 (usecase < RecommendedStreamConfigurationMap.MAX_USECASE_COUNT))) {
             if (mRecommendedConfigurations == null) {
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 1cdf235..e909c00 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1113,8 +1113,10 @@
                         depthStreamDurationList.get(i), depthStreamStallList.get(i), depthScData);
             }
 
-            if ((scData.streamConfigurationArray == null) &&
-                    (depthScData.streamConfigurationArray == null)) {
+            if ((scData.streamConfigurationArray == null ||
+                    scData.streamConfigurationArray.length == 0) &&
+                    (depthScData.streamConfigurationArray == null ||
+                     depthScData.streamConfigurationArray.length == 0)) {
                 recommendedConfigurations.add(null);
                 continue;
             }
@@ -1125,6 +1127,7 @@
             switch (i) {
                 case RecommendedStreamConfigurationMap.USECASE_PREVIEW:
                 case RecommendedStreamConfigurationMap.USECASE_RAW:
+                case RecommendedStreamConfigurationMap.USECASE_LOW_LATENCY_SNAPSHOT:
                 case RecommendedStreamConfigurationMap.USECASE_VIDEO_SNAPSHOT:
                     map = new StreamConfigurationMap(scData.streamConfigurationArray,
                             scData.minDurationArray, scData.stallDurationArray,
diff --git a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java
index 068c0ce..2d72598 100644
--- a/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/RecommendedStreamConfigurationMap.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.ImageFormat;
+import android.graphics.ImageFormat.Format;
 import android.graphics.PixelFormat;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
@@ -137,6 +138,17 @@
     public static final int USECASE_RAW = 0x5;
 
     /**
+     * The recommended stream configuration map for use case low latency snapshot must contain
+     * subset of configurations with end-to-end latency that does not exceed 200 ms. under standard
+     * operating conditions (reasonable light levels, not loaded system). The expected output format
+     * will be primarily {@link android.graphics.ImageFormat#JPEG} however other image formats can
+     * be present as well.  Even if available for the camera device, high speed and input
+     * configurations will be absent. This suggested configuration map may be absent on some devices
+     * that can not support any low latency requests.
+     */
+    public static final int USECASE_LOW_LATENCY_SNAPSHOT = 0x6;
+
+    /**
      * Device specific use cases.
      * @hide
      */
@@ -150,7 +162,8 @@
         USECASE_VIDEO_SNAPSHOT,
         USECASE_SNAPSHOT,
         USECASE_ZSL,
-        USECASE_RAW })
+        USECASE_RAW,
+        USECASE_LOW_LATENCY_SNAPSHOT})
      public @interface RecommendedUsecase {};
 
     /**
@@ -214,7 +227,7 @@
      *
      * @return a non-modifiable set of Integer formats
      */
-    public @Nullable Set<Integer> getValidOutputFormatsForInput(int inputFormat) {
+    public @Nullable Set<Integer> getValidOutputFormatsForInput(@Format int inputFormat) {
         return getUnmodifiableIntegerSet(mRecommendedMap.getValidOutputFormatsForInput(
                     inputFormat));
     }
@@ -250,7 +263,7 @@
      * @param format a format from {@link #getInputFormats}
      * @return a non-modifiable set of sizes, or {@code null} if the format was not available.
      */
-    public @Nullable Set<Size> getInputSizes(int format) {
+    public @Nullable Set<Size> getInputSizes(@Format int format) {
         return getUnmodifiableSizeSet(mRecommendedMap.getInputSizes(format));
     }
 
@@ -272,7 +285,7 @@
      *          if the image format was not a defined named constant
      *          from either {@link ImageFormat} or {@link PixelFormat}
      */
-    public boolean isOutputSupportedFor(int format) {
+    public boolean isOutputSupportedFor(@Format int format) {
         return mRecommendedMap.isOutputSupportedFor(format);
     }
 
@@ -288,7 +301,7 @@
      * @return  a non-modifiable set of supported sizes,
      *          or {@code null} if the {@code format} is not a supported output
      */
-    public @Nullable Set<Size> getOutputSizes(int format) {
+    public @Nullable Set<Size> getOutputSizes(@Format int format) {
         return getUnmodifiableSizeSet(mRecommendedMap.getOutputSizes(format));
     }
 
@@ -372,7 +385,7 @@
      * @return a non-modifiable set of supported slower high-resolution sizes, or {@code null} if
      *         the BURST_CAPTURE capability is not supported
      */
-    public @Nullable Set<Size> getHighResolutionOutputSizes(int format) {
+    public @Nullable Set<Size> getHighResolutionOutputSizes(@Format int format) {
         return getUnmodifiableSizeSet(mRecommendedMap.getHighResolutionOutputSizes(format));
     }
 
@@ -392,7 +405,8 @@
      *
      * @throws IllegalArgumentException if {@code format} or {@code size} was not supported
      */
-    public @IntRange(from = 0) long getOutputMinFrameDuration(int format, @NonNull Size size) {
+    public @IntRange(from = 0) long getOutputMinFrameDuration(@Format int format,
+            @NonNull Size size) {
         return mRecommendedMap.getOutputMinFrameDuration(format, size);
     }
 
@@ -409,7 +423,7 @@
      *
      * @throws IllegalArgumentException if {@code format} or {@code size} was not supported
      */
-    public @IntRange(from = 0) long getOutputStallDuration(int format, @NonNull Size size) {
+    public @IntRange(from = 0) long getOutputStallDuration(@Format int format, @NonNull Size size) {
         return mRecommendedMap.getOutputStallDuration(format, size);
     }
 
@@ -425,7 +439,7 @@
      *          a non-modifiable set of supported sizes for {@link ImageFormat#PRIVATE} format,
      *          or {@code null} if the {@code klass} is not a supported output.
      */
-    public <T> @Nullable Set<Size> getOutputSizes(@NonNull Class<T> klass) {
+    public @Nullable <T> Set<Size> getOutputSizes(@NonNull Class<T> klass) {
         if (mSupportsPrivate) {
             return getUnmodifiableSizeSet(mRecommendedMap.getOutputSizes(klass));
         }
@@ -448,7 +462,7 @@
      *
      * @throws IllegalArgumentException if {@code klass} or {@code size} was not supported
      */
-    public <T> @IntRange(from = 0) long getOutputMinFrameDuration(@NonNull final Class<T> klass,
+    public @IntRange(from = 0) <T> long getOutputMinFrameDuration(@NonNull final Class<T> klass,
             @NonNull final Size size) {
         if (mSupportsPrivate) {
             return mRecommendedMap.getOutputMinFrameDuration(klass, size);
@@ -471,7 +485,7 @@
      *
      * @throws IllegalArgumentException if {@code klass} or {@code size} was not supported
      */
-    public <T> @IntRange(from = 0) long getOutputStallDuration(@NonNull final Class<T> klass,
+    public @IntRange(from = 0) <T> long getOutputStallDuration(@NonNull final Class<T> klass,
             @NonNull final Size size) {
         if (mSupportsPrivate) {
             return mRecommendedMap.getOutputStallDuration(klass, size);
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 139a5ee..3e8c334 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -634,6 +634,8 @@
                 return context.getString(R.string.face_acquired_obscured);
             case FACE_ACQUIRED_START:
                 return null;
+            case FACE_ACQUIRED_SENSOR_DIRTY:
+                return context.getString(R.string.face_acquired_sensor_dirty);
             case FACE_ACQUIRED_VENDOR: {
                 String[] msgArray = context.getResources().getStringArray(
                         R.array.face_acquired_vendor);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index d08379f..e5802c2 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2816,23 +2816,6 @@
     }
 
     /**
-     * @removed
-     * @deprecated This API would be removed when all of caller has been updated.
-     * */
-    @Deprecated
-    public abstract static class TetheringEntitlementValueListener  {
-        /**
-         * Called to notify entitlement result.
-         *
-         * @param resultCode a int value of entitlement result. It may be one of
-         *         {@link #TETHER_ERROR_NO_ERROR},
-         *         {@link #TETHER_ERROR_PROVISION_FAILED}, or
-         *         {@link #TETHER_ERROR_ENTITLEMENT_UNKONWN}.
-         */
-        public void onEntitlementResult(int resultCode) {}
-    }
-
-    /**
      * Get the last value of the entitlement check on this downstream. If the cached value is
      * {@link #TETHER_ERROR_NO_ERROR} or showEntitlementUi argument is false, it just return the
      * cached value. Otherwise, a UI-based entitlement check would be performed. It is not
@@ -2878,31 +2861,6 @@
     }
 
     /**
-     * @removed
-     * @deprecated This API would be removed when all of caller has been updated.
-     * */
-    @Deprecated
-    public void getLatestTetheringEntitlementValue(int type, boolean showEntitlementUi,
-            @NonNull final TetheringEntitlementValueListener listener, @Nullable Handler handler) {
-        Preconditions.checkNotNull(listener, "TetheringEntitlementValueListener cannot be null.");
-        ResultReceiver wrappedListener = new ResultReceiver(handler) {
-            @Override
-            protected void onReceiveResult(int resultCode, Bundle resultData) {
-                listener.onEntitlementResult(resultCode);
-            }
-        };
-
-        try {
-            String pkgName = mContext.getOpPackageName();
-            Log.i(TAG, "getLatestTetheringEntitlementValue:" + pkgName);
-            mService.getLatestTetheringEntitlementResult(type, wrappedListener,
-                    showEntitlementUi, pkgName);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Report network connectivity status.  This is currently used only
      * to alter status bar UI.
      * <p>This method requires the caller to hold the permission
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index 870d8b1..ea245a4 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -27,6 +27,8 @@
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.app.Service;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.IPackageManager;
@@ -48,6 +50,7 @@
 import java.net.Socket;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * VpnService is a base class for applications to extend and build their
@@ -138,7 +141,7 @@
      * provides users with the ability to set it as always-on, so that VPN connection is
      * persisted after device reboot and app upgrade. Always-on VPN can also be enabled by device
      * owner and profile owner apps through
-     * {@link android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage}.
+     * {@link DevicePolicyManager#setAlwaysOnVpnPackage}.
      *
      * <p>VPN apps not supporting this feature should opt out by adding this meta-data field to the
      * {@code VpnService} component of {@code AndroidManifest.xml}. In case there is more than one
@@ -370,7 +373,10 @@
     }
 
     /**
-     * Returns whether the service is running in always-on VPN mode.
+     * Returns whether the service is running in always-on VPN mode. In this mode the system ensures
+     * that the service is always running by restarting it when necessary, e.g. after reboot.
+     *
+     * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean, Set)
      */
     public final boolean isAlwaysOn() {
         try {
@@ -381,8 +387,11 @@
     }
 
     /**
-     * Returns whether the service is running in always-on VPN mode blocking connections without
-     * VPN.
+     * Returns whether the service is running in always-on VPN lockdown mode. In this mode the
+     * system ensures that the service is always running and that the apps aren't allowed to bypass
+     * the VPN.
+     *
+     * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean, Set)
      */
     public final boolean isLockdownEnabled() {
         try {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index df5da6c..313384d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9053,7 +9053,6 @@
          * @return true if the provider is enabled
          *
          * @deprecated use {@link LocationManager#isProviderEnabled(String)}
-         * @removed no longer supported
          */
         @Deprecated
         public static boolean isLocationProviderEnabled(ContentResolver cr, String provider) {
@@ -9063,12 +9062,12 @@
         }
 
         /**
-         * Thread-safe method for enabling or disabling a single location provider.
+         * Thread-safe method for enabling or disabling a single location provider. This will have
+         * no effect on Android Q and above.
          * @param cr the content resolver to use
          * @param provider the location provider to enable or disable
          * @param enabled true if the provider should be enabled
          * @deprecated This API is deprecated
-         * @removed no longer supported
          */
         @Deprecated
         public static void setLocationProviderEnabled(ContentResolver cr,
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index c794a69..c1d122a 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -16,11 +16,20 @@
 
 package android.view;
 
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
+
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
+import android.os.SystemClock;
+import android.util.StatsLog;
 
 /**
  * Detects various gestures and events using the supplied {@link MotionEvent}s.
@@ -251,8 +260,12 @@
     private boolean mAlwaysInTapRegion;
     private boolean mAlwaysInBiggerTapRegion;
     private boolean mIgnoreNextUpEvent;
+    // Whether a classification has been recorded by statsd for the current event stream. Reset on
+    // ACTION_DOWN.
+    private boolean mHasRecordedClassification;
 
     private MotionEvent mCurrentDownEvent;
+    private MotionEvent mCurrentMotionEvent;
     private MotionEvent mPreviousUpEvent;
 
     /**
@@ -297,6 +310,7 @@
                     break;
 
                 case LONG_PRESS:
+                    recordGestureClassification(msg.arg1);
                     dispatchLongPress();
                     break;
 
@@ -304,6 +318,8 @@
                     // If the user's finger is still down, do not count it as a tap
                     if (mDoubleTapListener != null) {
                         if (!mStillDown) {
+                            recordGestureClassification(
+                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
                             mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
                         } else {
                             mDeferConfirmSingleTap = true;
@@ -501,6 +517,11 @@
 
         final int action = ev.getAction();
 
+        if (mCurrentMotionEvent != null) {
+            mCurrentMotionEvent.recycle();
+        }
+        mCurrentMotionEvent = MotionEvent.obtain(ev);
+
         if (mVelocityTracker == null) {
             mVelocityTracker = VelocityTracker.obtain();
         }
@@ -569,6 +590,8 @@
                             && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
                         // This is a second tap
                         mIsDoubleTapping = true;
+                        recordGestureClassification(
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
                         // Give a callback with the first tap of the double-tap
                         handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
                         // Give a callback with down event of the double-tap
@@ -590,11 +613,17 @@
                 mStillDown = true;
                 mInLongPress = false;
                 mDeferConfirmSingleTap = false;
+                mHasRecordedClassification = false;
 
                 if (mIsLongpressEnabled) {
                     mHandler.removeMessages(LONG_PRESS);
-                    mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
-                            + ViewConfiguration.getLongPressTimeout());
+                    mHandler.sendMessageAtTime(
+                            mHandler.obtainMessage(
+                                    LONG_PRESS,
+                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
+                                    0 /* arg2 */),
+                            mCurrentDownEvent.getDownTime()
+                                    + ViewConfiguration.getLongPressTimeout());
                 }
                 mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
                         mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
@@ -613,6 +642,8 @@
                 final float scrollY = mLastFocusY - focusY;
                 if (mIsDoubleTapping) {
                     // Give the move events of the double-tap
+                    recordGestureClassification(
+                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
                     handled |= mDoubleTapListener.onDoubleTapEvent(ev);
                 } else if (mAlwaysInTapRegion) {
                     final int deltaX = (int) (focusX - mDownFocusX);
@@ -635,8 +666,12 @@
                             // reschedule long press with a modified timeout.
                             mHandler.removeMessages(LONG_PRESS);
                             final long longPressTimeout = ViewConfiguration.getLongPressTimeout();
-                            mHandler.sendEmptyMessageAtTime(LONG_PRESS, ev.getDownTime()
-                                    + (long) (longPressTimeout * multiplier));
+                            mHandler.sendMessageAtTime(
+                                    mHandler.obtainMessage(
+                                            LONG_PRESS,
+                                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
+                                            0 /* arg2 */),
+                                    ev.getDownTime() + (long) (longPressTimeout * multiplier));
                         }
                         // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll
                         // until the gesture is resolved.
@@ -646,6 +681,8 @@
                     }
 
                     if (distance > slopSquare) {
+                        recordGestureClassification(
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
                         handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                         mLastFocusX = focusX;
                         mLastFocusY = focusY;
@@ -659,6 +696,7 @@
                         mAlwaysInBiggerTapRegion = false;
                     }
                 } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
+                    recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);
                     handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                     mLastFocusX = focusX;
                     mLastFocusY = focusY;
@@ -667,7 +705,11 @@
                         motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
                 if (deepPress && hasPendingLongPress) {
                     mHandler.removeMessages(LONG_PRESS);
-                    mHandler.sendEmptyMessage(LONG_PRESS);
+                    mHandler.sendMessage(
+                            mHandler.obtainMessage(
+                                  LONG_PRESS,
+                                  TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS,
+                                  0 /* arg2 */));
                 }
                 break;
 
@@ -676,11 +718,15 @@
                 MotionEvent currentUpEvent = MotionEvent.obtain(ev);
                 if (mIsDoubleTapping) {
                     // Finally, give the up event of the double-tap
+                    recordGestureClassification(
+                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);
                     handled |= mDoubleTapListener.onDoubleTapEvent(ev);
                 } else if (mInLongPress) {
                     mHandler.removeMessages(TAP);
                     mInLongPress = false;
                 } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
+                    recordGestureClassification(
+                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
                     handled = mListener.onSingleTapUp(ev);
                     if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
                         mDoubleTapListener.onSingleTapConfirmed(ev);
@@ -821,4 +867,21 @@
         mInLongPress = true;
         mListener.onLongPress(mCurrentDownEvent);
     }
+
+    private void recordGestureClassification(int classification) {
+        if (mHasRecordedClassification
+                || classification
+                    == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) {
+            // Only record the first classification for an event stream.
+            return;
+        }
+        StatsLog.write(
+                StatsLog.TOUCH_GESTURE_CLASSIFIED,
+                getClass().getName(),
+                classification,
+                (int) (SystemClock.uptimeMillis() - mCurrentMotionEvent.getDownTime()),
+                (float) Math.hypot(mCurrentMotionEvent.getRawX() - mCurrentDownEvent.getRawX(),
+                                   mCurrentMotionEvent.getRawY() - mCurrentDownEvent.getRawY()));
+        mHasRecordedClassification = true;
+    }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b857f1e..49eb78d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17,6 +17,10 @@
 package android.view;
 
 import static android.content.res.Resources.ID_NULL;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
+import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
 
@@ -96,6 +100,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.StateSet;
+import android.util.StatsLog;
 import android.util.SuperNotCalledException;
 import android.util.TypedValue;
 import android.view.AccessibilityIterators.CharacterTextSegmentIterator;
@@ -14542,7 +14547,12 @@
                     if (clickable) {
                         setPressed(true, x, y);
                     }
-                    checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
+                    checkForLongClick(
+                            ViewConfiguration.getLongPressTimeout(),
+                            x,
+                            y,
+                            // This is not a touch gesture -- do not classify it as one.
+                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION);
                     return true;
                 }
             }
@@ -15283,7 +15293,11 @@
                     mHasPerformedLongPress = false;
 
                     if (!clickable) {
-                        checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
+                        checkForLongClick(
+                                ViewConfiguration.getLongPressTimeout(),
+                                x,
+                                y,
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                         break;
                     }
 
@@ -15307,7 +15321,11 @@
                     } else {
                         // Not inside a scrolling container, so show the feedback right away
                         setPressed(true, x, y);
-                        checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y);
+                        checkForLongClick(
+                                ViewConfiguration.getLongPressTimeout(),
+                                x,
+                                y,
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                     }
                     break;
 
@@ -15344,7 +15362,11 @@
                                     * ambiguousMultiplier);
                             // Subtract the time already spent
                             delay -= event.getEventTime() - event.getDownTime();
-                            checkForLongClick(delay, x, y);
+                            checkForLongClick(
+                                    delay,
+                                    x,
+                                    y,
+                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                         }
                         touchSlop *= ambiguousMultiplier;
                     }
@@ -15366,7 +15388,11 @@
                     if (deepPress && hasPendingLongPressCallback()) {
                         // process the long click action immediately
                         removeLongPressCallback();
-                        checkForLongClick(0 /* send immediately */, x, y);
+                        checkForLongClick(
+                                0 /* send immediately */,
+                                x,
+                                y,
+                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
                     }
 
                     break;
@@ -26031,7 +26057,7 @@
         }
     }
 
-    private void checkForLongClick(long delay, float x, float y) {
+    private void checkForLongClick(long delay, float x, float y, int classification) {
         if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
             mHasPerformedLongPress = false;
 
@@ -26041,6 +26067,7 @@
             mPendingCheckForLongPress.setAnchor(x, y);
             mPendingCheckForLongPress.rememberWindowAttachCount();
             mPendingCheckForLongPress.rememberPressedState();
+            mPendingCheckForLongPress.setClassification(classification);
             postDelayed(mPendingCheckForLongPress, delay);
         }
     }
@@ -27598,11 +27625,17 @@
         private float mX;
         private float mY;
         private boolean mOriginalPressedState;
+        /**
+         * The classification of the long click being checked: one of the
+         * StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__* constants.
+         */
+        private int mClassification;
 
         @Override
         public void run() {
             if ((mOriginalPressedState == isPressed()) && (mParent != null)
                     && mOriginalWindowAttachCount == mWindowAttachCount) {
+                recordGestureClassification(mClassification);
                 if (performLongClick(mX, mY)) {
                     mHasPerformedLongPress = true;
                 }
@@ -27621,6 +27654,10 @@
         public void rememberPressedState() {
             mOriginalPressedState = isPressed();
         }
+
+        public void setClassification(int classification) {
+            mClassification = classification;
+        }
     }
 
     private final class CheckForTap implements Runnable {
@@ -27633,17 +27670,28 @@
             setPressed(true, x, y);
             final long delay =
                     ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout();
-            checkForLongClick(delay, x, y);
+            checkForLongClick(delay, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
         }
     }
 
     private final class PerformClick implements Runnable {
         @Override
         public void run() {
+            recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
             performClickInternal();
         }
     }
 
+    /** Records a classification for the current event stream. */
+    private void recordGestureClassification(int classification) {
+        if (classification == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) {
+            return;
+        }
+        // To avoid negatively impacting View performance, the latency and displacement metrics
+        // are omitted.
+        StatsLog.write(StatsLog.TOUCH_GESTURE_CLASSIFIED, getClass().getName(), classification);
+    }
+
     /**
      * This method returns a ViewPropertyAnimator object, which can be used to animate
      * specific properties on this View.
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 5f23719..07b82d0 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -122,6 +122,12 @@
 
     private static boolean sPreloadComplete;
 
+    /**
+     * Cached classloader to use for the system server. Will only be populated in the system
+     * server process.
+     */
+    private static ClassLoader sCachedSystemServerClassLoader = null;
+
     static void preload(TimingsTraceLog bootTimingsTraceLog) {
         Log.d(TAG, "begin preload");
         bootTimingsTraceLog.traceBegin("BeginPreload");
@@ -443,7 +449,13 @@
 
         final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
         if (systemServerClasspath != null) {
-            performSystemServerDexOpt(systemServerClasspath);
+            if (performSystemServerDexOpt(systemServerClasspath)) {
+                // Throw away the cached classloader. If we compiled here, the classloader would
+                // not have had AoT-ed artifacts.
+                // Note: This only works in a very special environment where selinux enforcement is
+                // disabled, e.g., Mac builds.
+                sCachedSystemServerClassLoader = null;
+            }
             // Capturing profiles is only supported for debug or eng builds since selinux normally
             // prevents it.
             boolean profileSystemServer = SystemProperties.getBoolean(
@@ -476,10 +488,9 @@
 
             throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
         } else {
-            ClassLoader cl = null;
-            if (systemServerClasspath != null) {
-                cl = createPathClassLoader(systemServerClasspath, parsedArgs.mTargetSdkVersion);
-
+            createSystemServerClassLoader();
+            ClassLoader cl = sCachedSystemServerClassLoader;
+            if (cl != null) {
                 Thread.currentThread().setContextClassLoader(cl);
             }
 
@@ -494,6 +505,24 @@
     }
 
     /**
+     * Create the classloader for the system server and store it in
+     * {@link sCachedSystemServerClassLoader}. This function may be called through JNI in
+     * system server startup, when the runtime is in a critically low state. Do not do
+     * extended computation etc here.
+     */
+    private static void createSystemServerClassLoader() {
+        if (sCachedSystemServerClassLoader != null) {
+            return;
+        }
+        final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
+        // TODO: Should we run optimization here?
+        if (systemServerClasspath != null) {
+            sCachedSystemServerClassLoader = createPathClassLoader(systemServerClasspath,
+                    VMRuntime.SDK_VERSION_CUR_DEVELOPMENT);
+        }
+    }
+
+    /**
      * Note that preparing the profiles for system server does not require special selinux
      * permissions. From the installer perspective the system server is a regular package which can
      * capture profile information.
@@ -557,15 +586,16 @@
 
     /**
      * Performs dex-opt on the elements of {@code classPath}, if needed. We choose the instruction
-     * set of the current runtime.
+     * set of the current runtime. If something was compiled, return true.
      */
-    private static void performSystemServerDexOpt(String classPath) {
+    private static boolean performSystemServerDexOpt(String classPath) {
         final String[] classPathElements = classPath.split(":");
         final IInstalld installd = IInstalld.Stub
                 .asInterface(ServiceManager.getService("installd"));
         final String instructionSet = VMRuntime.getRuntime().vmInstructionSet();
 
         String classPathForElement = "";
+        boolean compiledSomething = false;
         for (String classPathElement : classPathElements) {
             // System server is fully AOTed and never profiled
             // for profile guided compilation.
@@ -607,6 +637,7 @@
                             uuid, classLoaderContext, seInfo, false /* downgrade */,
                             targetSdkVersion, /*profileName*/ null, /*dexMetadataPath*/ null,
                             "server-dexopt");
+                    compiledSomething = true;
                 } catch (RemoteException | ServiceSpecificException e) {
                     // Ignore (but log), we need this on the classpath for fallback mode.
                     Log.w(TAG, "Failed compiling classpath element for system server: "
@@ -617,6 +648,8 @@
             classPathForElement = encodeSystemServerClassPath(
                     classPathForElement, classPathElement);
         }
+
+        return compiledSomething;
     }
 
     /**
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 009a6ca..cde1884 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -24,6 +24,7 @@
 #endif
 
 #define LOG_TAG "Zygote"
+#define ATRACE_TAG ATRACE_TAG_DALVIK
 
 #include <async_safe/log.h>
 
@@ -73,6 +74,7 @@
 #include <cutils/multiuser.h>
 #include <private/android_filesystem_config.h>
 #include <utils/String8.h>
+#include <utils/Trace.h>
 #include <selinux/android.h>
 #include <seccomp_policy.h>
 #include <stats_event_list.h>
@@ -111,11 +113,16 @@
 
 static const char kIsolatedStorage[] = "persist.sys.isolated_storage";
 static const char kIsolatedStorageSnapshot[] = "sys.isolated_storage_snapshot";
-static const char kZygoteClassName[] = "com/android/internal/os/Zygote";
+
+static constexpr const char* kZygoteClassName = "com/android/internal/os/Zygote";
 static jclass gZygoteClass;
 static jmethodID gCallPostForkSystemServerHooks;
 static jmethodID gCallPostForkChildHooks;
 
+static constexpr const char* kZygoteInitClassName = "com/android/internal/os/ZygoteInit";
+static jclass gZygoteInitClass;
+static jmethodID gCreateSystemServerClassLoader;
+
 static bool g_is_security_enforced = true;
 
 /**
@@ -585,6 +592,8 @@
 }
 
 static int UnmountTree(const char* path) {
+  ATRACE_CALL();
+
   size_t path_len = strlen(path);
 
   FILE* fp = setmntent("/proc/mounts", "r");
@@ -627,6 +636,8 @@
 }
 
 static void CreatePkgSandboxTarget(userid_t user_id, fail_fn_t fail_fn) {
+  ATRACE_CALL();
+
   // Create /mnt/user/0/package
   std::string pkg_sandbox_dir = StringPrintf("/mnt/user/%d", user_id);
   CreateDir(pkg_sandbox_dir, 0751, AID_ROOT, AID_ROOT, fail_fn);
@@ -650,6 +661,8 @@
                                 uid_t uid,
                                 const char* dir_name,
                                 fail_fn_t fail_fn) {
+  ATRACE_CALL();
+
   std::string mnt_source_dir = StringPrintf("%s/Android/%s/%s",
       mnt_source_root.c_str(), dir_name, package_name.c_str());
 
@@ -662,6 +675,8 @@
 static void CreateSubDirs(int parent_fd, const std::string& parent_path,
                           const std::vector<std::string>& sub_dirs,
                           fail_fn_t fail_fn) {
+  ATRACE_CALL();
+
   for (auto& dir_name : sub_dirs) {
     struct stat sb;
     if (TEMP_FAILURE_RETRY(fstatat(parent_fd, dir_name.c_str(), &sb, 0)) == 0) {
@@ -686,6 +701,8 @@
                                   const std::vector<std::string>& package_names,
                                   bool create_sandbox_dir,
                                   fail_fn_t fail_fn) {
+  ATRACE_CALL();
+
   std::string android_dir = StringPrintf("%s/Android", path.c_str());
   android::base::unique_fd android_fd(open(android_dir.c_str(),
                                            O_RDONLY | O_DIRECTORY | O_CLOEXEC));
@@ -729,6 +746,7 @@
 }
 
 static void CreatePkgSandboxSource(const std::string& sandbox_source, fail_fn_t fail_fn) {
+  ATRACE_CALL();
 
   struct stat sb;
   if (TEMP_FAILURE_RETRY(stat(sandbox_source.c_str(), &sb)) == 0) {
@@ -751,6 +769,8 @@
 static void PreparePkgSpecificDirs(const std::vector<std::string>& package_names,
                                    bool mount_all_obbs, const std::string& sandbox_id,
                                    userid_t user_id, uid_t uid, fail_fn_t fail_fn) {
+  ATRACE_CALL();
+
   std::unique_ptr<DIR, decltype(&closedir)> dirp(opendir("/storage"), closedir);
   if (!dirp) {
     fail_fn(CREATE_ERROR("Failed to opendir /storage: %s", strerror(errno)));
@@ -807,6 +827,8 @@
                                      userid_t user_id,
                                      const std::string& sandbox_id,
                                      fail_fn_t fail_fn) {
+  ATRACE_CALL();
+
   std::string obb_mount_dir = StringPrintf("/mnt/user/%d/obb_mount", user_id);
   std::string obb_mount_file = StringPrintf("%s/%s", obb_mount_dir.c_str(), sandbox_id.c_str());
   if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
@@ -844,6 +866,7 @@
         const std::string& sandbox_id,
         fail_fn_t fail_fn) {
   // See storage config details at http://source.android.com/tech/storage/
+  ATRACE_CALL();
 
   String8 storage_source;
   if (mount_mode == MOUNT_EXTERNAL_DEFAULT) {
@@ -1420,6 +1443,15 @@
       fail_fn("Error calling post fork system server hooks.");
     }
 
+    // Prefetch the classloader for the system server. This is done early to
+    // allow a tie-down of the proper system server selinux domain.
+    env->CallStaticVoidMethod(gZygoteInitClass, gCreateSystemServerClassLoader);
+    if (env->ExceptionCheck()) {
+      // Be robust here. The Java code will attempt to create the classloader
+      // at a later point (but may not have rights to use AoT artifacts).
+      env->ExceptionClear();
+    }
+
     // TODO(oth): Remove hardcoded label here (b/117874058).
     static const char* kSystemServerLabel = "u:r:system_server:s0";
     if (selinux_android_setcon(kSystemServerLabel) != 0) {
@@ -1986,6 +2018,13 @@
   gCallPostForkChildHooks = GetStaticMethodIDOrDie(env, gZygoteClass, "callPostForkChildHooks",
                                                    "(IZZLjava/lang/String;)V");
 
-  return RegisterMethodsOrDie(env, "com/android/internal/os/Zygote", gMethods, NELEM(gMethods));
+  gZygoteInitClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, kZygoteInitClassName));
+  gCreateSystemServerClassLoader = GetStaticMethodIDOrDie(env, gZygoteInitClass,
+                                                          "createSystemServerClassLoader",
+                                                          "()V");
+
+  RegisterMethodsOrDie(env, "com/android/internal/os/Zygote", gMethods, NELEM(gMethods));
+
+  return JNI_OK;
 }
 }  // namespace android
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 2b13c4e..74970e8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1538,43 +1538,45 @@
     <string name="permdesc_useFaceAuthentication">Allows the app to use face authentication hardware for authentication</string>
 
     <!-- Message shown during face acquisition when the face cannot be recognized [CHAR LIMIT=50] -->
-    <string name="face_acquired_insufficient">Couldn\u2019t process face. Please try again.</string>
+    <string name="face_acquired_insufficient">Couldn\u2019t capture accurate face data. Try again.</string>
     <!-- Message shown during face acquisition when the image is too bright [CHAR LIMIT=50] -->
-    <string name="face_acquired_too_bright">Face is too bright. Please try in lower light.</string>
+    <string name="face_acquired_too_bright">Too bright. Try gentler lighting.</string>
     <!-- Message shown during face acquisition when the image is too dark [CHAR LIMIT=50] -->
-    <string name="face_acquired_too_dark">Face is too dark. Please uncover light source.</string>
+    <string name="face_acquired_too_dark">Too dark. Try brighter lighting.</string>
     <!-- Message shown during face acquisition when the user is too close to sensor [CHAR LIMIT=50] -->
-    <string name="face_acquired_too_close">Please move sensor farther from face.</string>
+    <string name="face_acquired_too_close">Move phone farther away.</string>
     <!-- Message shown during face acquisition when the user is too far from sensor [CHAR LIMIT=50] -->
-    <string name="face_acquired_too_far">Please bring sensor closer to face.</string>
+    <string name="face_acquired_too_far">Move phone closer.</string>
     <!-- Message shown during face acquisition when the user is too high relatively to sensor [CHAR LIMIT=50] -->
-    <string name="face_acquired_too_high">Please move sensor higher.</string>
+    <string name="face_acquired_too_high">Move phone higher.</string>
     <!-- Message shown during face acquisition when the user is too low relatively to sensor [CHAR LIMIT=50] -->
-    <string name="face_acquired_too_low">Please move sensor lower.</string>
+    <string name="face_acquired_too_low">Move phone lower.</string>
     <!-- Message shown during face acquisition when the user is too right relatively to sensor [CHAR LIMIT=50] -->
-    <string name="face_acquired_too_right">Please move sensor to the right.</string>
+    <string name="face_acquired_too_right">Move phone to the right.</string>
     <!-- Message shown during face acquisition when the user is too left relatively to sensor [CHAR LIMIT=50] -->
-    <string name="face_acquired_too_left">Please move sensor to the left.</string>
+    <string name="face_acquired_too_left">Move phone to the left.</string>
     <!-- Message shown during face acquisition when the user is not front facing the sensor [CHAR LIMIT=50] -->
-    <string name="face_acquired_poor_gaze">Please look at the sensor.</string>
+    <string name="face_acquired_poor_gaze">Look at the screen with your eyes open.</string>
     <!-- Message shown during face acquisition when the user is not detected [CHAR LIMIT=50] -->
-    <string name="face_acquired_not_detected">No face detected.</string>
+    <string name="face_acquired_not_detected">Can\u2019t see your face. Look at the phone.</string>
     <!-- Message shown during face acquisition when the device is not steady [CHAR LIMIT=50] -->
-    <string name="face_acquired_too_much_motion">Too much motion.</string>
+    <string name="face_acquired_too_much_motion">Too much motion. Hold phone steady.</string>
     <!-- Message shown during face acquisition when the sensor needs to be recalibrated [CHAR LIMIT=50] -->
     <string name="face_acquired_recalibrate">Please re-enroll your face.</string>
     <!-- Message shown during face enrollment when a different person's face is detected [CHAR LIMIT=50] -->
-    <string name="face_acquired_too_different">Different face detected.</string>
+    <string name="face_acquired_too_different">No longer able to recognize face. Try again.</string>
     <!-- Message shown during face enrollment when the face is too similar to a previous acquisition [CHAR LIMIT=50] -->
     <string name="face_acquired_too_similar">Too similar, please change your pose.</string>
     <!-- Message shown during acqusition when the user's face is turned too far left or right [CHAR LIMIT=50] -->
-    <string name="face_acquired_pan_too_extreme">Please look more directly at the camera.</string>
+    <string name="face_acquired_pan_too_extreme">Please look more directly at the screen.</string>
     <!-- Message shown during acqusition when the user's face is tilted too high or too low [CHAR LIMIT=50] -->
-    <string name="face_acquired_tilt_too_extreme">Please look more directly at the camera.</string>
+    <string name="face_acquired_tilt_too_extreme">Please look more directly at the screen.</string>
     <!-- Message shown during acquisiton when the user's face is tilted too far left or right [CHAR LIMIT=50] -->
     <string name="face_acquired_roll_too_extreme">Please straighten your head vertically.</string>
     <!-- Message shown during acquisition when the user's face is obscured [CHAR LIMIT=50] -->
-    <string name="face_acquired_obscured">Please uncover your face.</string>
+    <string name="face_acquired_obscured">Clear the space between your head and the phone.</string>
+    <!-- Message shown during acquisition when the sensor is dirty [CHAR LIMIT=50] -->
+    <string name="face_acquired_sensor_dirty">Please clean the camera.</string>
     <!-- Array containing custom messages shown during face acquisition from vendor.  Vendor is expected to add and translate these strings -->
     <string-array name="face_acquired_vendor">
     </string-array>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e5fc104..17ccc31 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2546,6 +2546,7 @@
   <java-symbol type="string" name="face_acquired_tilt_too_extreme" />
   <java-symbol type="string" name="face_acquired_roll_too_extreme" />
   <java-symbol type="string" name="face_acquired_obscured" />
+  <java-symbol type="string" name="face_acquired_sensor_dirty" />
   <java-symbol type="array" name="face_acquired_vendor" />
   <java-symbol type="string" name="face_name_template" />
   <java-symbol type="string" name="face_authenticated_no_confirmation_required" />
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
index 105af6e..51c4252 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
@@ -16,7 +16,6 @@
 
 package android.security.keystore;
 
-import libcore.util.EmptyArray;
 import android.security.Credentials;
 import android.security.GateKeeper;
 import android.security.KeyStore;
@@ -31,6 +30,8 @@
 import android.security.keystore.WrappedKeyEntry;
 import android.util.Log;
 
+import libcore.util.EmptyArray;
+
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -123,7 +124,14 @@
 
         final Certificate[] caList;
 
-        final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias, mUid);
+        // Suppress the key not found warning for this call. It seems that this error is exclusively
+        // being thrown when there is a self signed certificate chain, so when the keystore service
+        // attempts to query for the CA details, it obviously fails to find them and returns a
+        // key not found exception. This is WAI, and throwing a stack trace here can be very
+        // misleading since the trace is not clear.
+        final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias,
+                                             mUid,
+                                             true /* suppressKeyNotFoundWarning */);
         if (caBytes != null) {
             final Collection<X509Certificate> caChain = toCertificates(caBytes);
 
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
index c512a6b..9a95fdf 100644
--- a/libs/androidfw/Asset.cpp
+++ b/libs/androidfw/Asset.cpp
@@ -292,8 +292,10 @@
 
     pAsset = new _FileAsset;
     result = pAsset->openChunk(dataMap);
-    if (result != NO_ERROR)
+    if (result != NO_ERROR) {
+        delete pAsset;
         return NULL;
+    }
 
     pAsset->mAccessMode = mode;
     return pAsset;
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index dd179f3..050b2ec 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -167,11 +167,21 @@
 
     /**
      * Broadcast intent action when the set of enabled location providers changes. To check the
-     * status of a provider, use {@link #isProviderEnabled(String)}.
+     * status of a provider, use {@link #isProviderEnabled(String)}. Depending on API level, may
+     * include a string intent extra, {@link #EXTRA_PROVIDER_NAME}, with the name of the provider
+     * whose state has changed. See {@link #EXTRA_PROVIDER_NAME} for the supported API level.
+     *
+     * @see #EXTRA_PROVIDER_NAME
      */
     public static final String PROVIDERS_CHANGED_ACTION = "android.location.PROVIDERS_CHANGED";
 
     /**
+     * Intent extra included with {@link #PROVIDERS_CHANGED_ACTION} broadcasts, containing the name
+     * of the location provider that has changed, to be used with location provider APIs.
+     */
+    public static final String EXTRA_PROVIDER_NAME = "android.location.extra.PROVIDER_NAME";
+
+    /**
      * Broadcast intent action when the device location mode changes. To check the location mode,
      * use {@link #isLocationEnabled()}.
      */
diff --git a/media/java/android/media/MediaHTTPConnection.java b/media/java/android/media/MediaHTTPConnection.java
index d724762..3838a999 100644
--- a/media/java/android/media/MediaHTTPConnection.java
+++ b/media/java/android/media/MediaHTTPConnection.java
@@ -37,6 +37,7 @@
 import java.net.UnknownServiceException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 
 /** @hide */
 public class MediaHTTPConnection extends IMediaHTTPConnection.Stub {
@@ -46,27 +47,23 @@
     // connection timeout - 30 sec
     private static final int CONNECT_TIMEOUT_MS = 30 * 1000;
 
-    @UnsupportedAppUsage
-    private long mCurrentOffset = -1;
-    @UnsupportedAppUsage
-    private URL mURL = null;
-    @UnsupportedAppUsage
-    private Map<String, String> mHeaders = null;
-    @UnsupportedAppUsage
-    private HttpURLConnection mConnection = null;
-    @UnsupportedAppUsage
-    private long mTotalSize = -1;
-    private InputStream mInputStream = null;
-
-    @UnsupportedAppUsage
-    private boolean mAllowCrossDomainRedirect = true;
-    @UnsupportedAppUsage
-    private boolean mAllowCrossProtocolRedirect = true;
-
     // from com.squareup.okhttp.internal.http
     private final static int HTTP_TEMP_REDIRECT = 307;
     private final static int MAX_REDIRECTS = 20;
 
+    class ConnectionState {
+        public HttpURLConnection mConnection = null;
+        public InputStream mInputStream = null;
+        public long mCurrentOffset = -1;
+        public Map<String, String> mHeaders = null;
+        public URL mURL = null;
+        public long mTotalSize = -1;
+        public boolean mAllowCrossDomainRedirect = true;
+        public boolean mAllowCrossProtocolRedirect = true;
+    }
+    private final AtomicReference<ConnectionState> mConnectionStateHolder =
+            new AtomicReference<ConnectionState>();
+
     @UnsupportedAppUsage
     public MediaHTTPConnection() {
         CookieHandler cookieHandler = CookieHandler.getDefault();
@@ -84,13 +81,23 @@
             Log.d(TAG, "connect: uri=" + uri + ", headers=" + headers);
         }
 
+        ConnectionState connectionState = mConnectionStateHolder.get();
+        synchronized (this) {
+            if (connectionState == null) {
+                connectionState = new ConnectionState();
+                mConnectionStateHolder.set(connectionState);
+            }
+        }
+
         try {
             disconnect();
-            mAllowCrossDomainRedirect = true;
-            mURL = new URL(uri);
-            mHeaders = convertHeaderStringToMap(headers);
+            connectionState.mAllowCrossDomainRedirect = true;
+            connectionState.mURL = new URL(uri);
+            connectionState.mHeaders = convertHeaderStringToMap(headers, connectionState);
         } catch (MalformedURLException e) {
             return null;
+        } finally {
+            mConnectionStateHolder.set(connectionState);
         }
 
         return native_getIMemory();
@@ -106,18 +113,21 @@
     }
 
     /* returns true iff header is internal */
-    private boolean filterOutInternalHeaders(String key, String val) {
+    private boolean filterOutInternalHeaders(
+            String key, String val, ConnectionState connectionState) {
         if ("android-allow-cross-domain-redirect".equalsIgnoreCase(key)) {
-            mAllowCrossDomainRedirect = parseBoolean(val);
+            connectionState.mAllowCrossDomainRedirect = parseBoolean(val);
             // cross-protocol redirects are also controlled by this flag
-            mAllowCrossProtocolRedirect = mAllowCrossDomainRedirect;
+            connectionState.mAllowCrossProtocolRedirect =
+                    connectionState.mAllowCrossDomainRedirect;
         } else {
             return false;
         }
         return true;
     }
 
-    private Map<String, String> convertHeaderStringToMap(String headers) {
+    private Map<String, String> convertHeaderStringToMap(String headers,
+            ConnectionState connectionState) {
         HashMap<String, String> map = new HashMap<String, String>();
 
         String[] pairs = headers.split("\r\n");
@@ -127,7 +137,7 @@
                 String key = pair.substring(0, colonPos);
                 String val = pair.substring(colonPos + 1);
 
-                if (!filterOutInternalHeaders(key, val)) {
+                if (!filterOutInternalHeaders(key, val, connectionState)) {
                     map.put(key, val);
                 }
             }
@@ -139,25 +149,28 @@
     @Override
     @UnsupportedAppUsage
     public void disconnect() {
-        teardownConnection();
-        mHeaders = null;
-        mURL = null;
+        ConnectionState connectionState = mConnectionStateHolder.getAndSet(null);
+        if (connectionState != null) {
+            teardownConnection(connectionState);
+            connectionState.mHeaders = null;
+            connectionState.mURL = null;
+        }
     }
 
-    private void teardownConnection() {
-        if (mConnection != null) {
-            if (mInputStream != null) {
+    private void teardownConnection(ConnectionState connectionState) {
+        if (connectionState.mConnection != null) {
+            if (connectionState.mInputStream != null) {
                 try {
-                    mInputStream.close();
+                    connectionState.mInputStream.close();
                 } catch (IOException e) {
                 }
-                mInputStream = null;
+                connectionState.mInputStream = null;
             }
 
-            mConnection.disconnect();
-            mConnection = null;
+            connectionState.mConnection.disconnect();
+            connectionState.mConnection = null;
 
-            mCurrentOffset = -1;
+            connectionState.mCurrentOffset = -1;
         }
     }
 
@@ -184,42 +197,44 @@
         return false;
     }
 
-    private void seekTo(long offset) throws IOException {
-        teardownConnection();
+    private void seekTo(long offset, ConnectionState connectionState) throws IOException {
+        teardownConnection(connectionState);
 
         try {
             int response;
             int redirectCount = 0;
 
-            URL url = mURL;
+            URL url = connectionState.mURL;
 
             // do not use any proxy for localhost (127.0.0.1)
             boolean noProxy = isLocalHost(url);
 
             while (true) {
                 if (noProxy) {
-                    mConnection = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);
+                    connectionState.mConnection =
+                            (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
                 } else {
-                    mConnection = (HttpURLConnection)url.openConnection();
+                    connectionState.mConnection = (HttpURLConnection) url.openConnection();
                 }
-                mConnection.setConnectTimeout(CONNECT_TIMEOUT_MS);
+                connectionState.mConnection.setConnectTimeout(CONNECT_TIMEOUT_MS);
 
                 // handle redirects ourselves if we do not allow cross-domain redirect
-                mConnection.setInstanceFollowRedirects(mAllowCrossDomainRedirect);
+                connectionState.mConnection.setInstanceFollowRedirects(
+                        connectionState.mAllowCrossDomainRedirect);
 
-                if (mHeaders != null) {
-                    for (Map.Entry<String, String> entry : mHeaders.entrySet()) {
-                        mConnection.setRequestProperty(
+                if (connectionState.mHeaders != null) {
+                    for (Map.Entry<String, String> entry : connectionState.mHeaders.entrySet()) {
+                        connectionState.mConnection.setRequestProperty(
                                 entry.getKey(), entry.getValue());
                     }
                 }
 
                 if (offset > 0) {
-                    mConnection.setRequestProperty(
+                    connectionState.mConnection.setRequestProperty(
                             "Range", "bytes=" + offset + "-");
                 }
 
-                response = mConnection.getResponseCode();
+                response = connectionState.mConnection.getResponseCode();
                 if (response != HttpURLConnection.HTTP_MULT_CHOICE &&
                         response != HttpURLConnection.HTTP_MOVED_PERM &&
                         response != HttpURLConnection.HTTP_MOVED_TEMP &&
@@ -233,7 +248,7 @@
                     throw new NoRouteToHostException("Too many redirects: " + redirectCount);
                 }
 
-                String method = mConnection.getRequestMethod();
+                String method = connectionState.mConnection.getRequestMethod();
                 if (response == HTTP_TEMP_REDIRECT &&
                         !method.equals("GET") && !method.equals("HEAD")) {
                     // "If the 307 status code is received in response to a
@@ -241,34 +256,35 @@
                     // automatically redirect the request"
                     throw new NoRouteToHostException("Invalid redirect");
                 }
-                String location = mConnection.getHeaderField("Location");
+                String location = connectionState.mConnection.getHeaderField("Location");
                 if (location == null) {
                     throw new NoRouteToHostException("Invalid redirect");
                 }
-                url = new URL(mURL /* TRICKY: don't use url! */, location);
+                url = new URL(connectionState.mURL /* TRICKY: don't use url! */, location);
                 if (!url.getProtocol().equals("https") &&
                         !url.getProtocol().equals("http")) {
                     throw new NoRouteToHostException("Unsupported protocol redirect");
                 }
-                boolean sameProtocol = mURL.getProtocol().equals(url.getProtocol());
-                if (!mAllowCrossProtocolRedirect && !sameProtocol) {
+                boolean sameProtocol =
+                        connectionState.mURL.getProtocol().equals(url.getProtocol());
+                if (!connectionState.mAllowCrossProtocolRedirect && !sameProtocol) {
                     throw new NoRouteToHostException("Cross-protocol redirects are disallowed");
                 }
-                boolean sameHost = mURL.getHost().equals(url.getHost());
-                if (!mAllowCrossDomainRedirect && !sameHost) {
+                boolean sameHost = connectionState.mURL.getHost().equals(url.getHost());
+                if (!connectionState.mAllowCrossDomainRedirect && !sameHost) {
                     throw new NoRouteToHostException("Cross-domain redirects are disallowed");
                 }
 
                 if (response != HTTP_TEMP_REDIRECT) {
                     // update effective URL, unless it is a Temporary Redirect
-                    mURL = url;
+                    connectionState.mURL = url;
                 }
             }
 
-            if (mAllowCrossDomainRedirect) {
+            if (connectionState.mAllowCrossDomainRedirect) {
                 // remember the current, potentially redirected URL if redirects
                 // were handled by HttpURLConnection
-                mURL = mConnection.getURL();
+                connectionState.mURL = connectionState.mConnection.getURL();
             }
 
             if (response == HttpURLConnection.HTTP_PARTIAL) {
@@ -276,10 +292,9 @@
                 // because what we want is not just the length of the range
                 // returned but the size of the full content if available.
 
-                String contentRange =
-                    mConnection.getHeaderField("Content-Range");
+                String contentRange = connectionState.mConnection.getHeaderField("Content-Range");
 
-                mTotalSize = -1;
+                connectionState.mTotalSize = -1;
                 if (contentRange != null) {
                     // format is "bytes xxx-yyy/zzz
                     // where "zzz" is the total number of bytes of the
@@ -291,7 +306,7 @@
                             contentRange.substring(lastSlashPos + 1);
 
                         try {
-                            mTotalSize = Long.parseLong(total);
+                            connectionState.mTotalSize = Long.parseLong(total);
                         } catch (NumberFormatException e) {
                         }
                     }
@@ -299,7 +314,7 @@
             } else if (response != HttpURLConnection.HTTP_OK) {
                 throw new IOException();
             } else {
-                mTotalSize = mConnection.getContentLength();
+                connectionState.mTotalSize = connectionState.mConnection.getContentLength();
             }
 
             if (offset > 0 && response != HttpURLConnection.HTTP_PARTIAL) {
@@ -308,14 +323,14 @@
                 throw new ProtocolException();
             }
 
-            mInputStream =
-                new BufferedInputStream(mConnection.getInputStream());
+            connectionState.mInputStream =
+                new BufferedInputStream(connectionState.mConnection.getInputStream());
 
-            mCurrentOffset = offset;
+            connectionState.mCurrentOffset = offset;
         } catch (IOException e) {
-            mTotalSize = -1;
-            teardownConnection();
-            mCurrentOffset = -1;
+            connectionState.mTotalSize = -1;
+            teardownConnection(connectionState);
+            connectionState.mCurrentOffset = -1;
 
             throw e;
         }
@@ -324,10 +339,14 @@
     @Override
     @UnsupportedAppUsage
     public int readAt(long offset, int size) {
-        return native_readAt(offset, size);
+        ConnectionState connectionState = mConnectionStateHolder.get();
+        if (connectionState != null) {
+            return native_readAt(offset, size, connectionState);
+        }
+        return -1;
     }
 
-    private int readAt(long offset, byte[] data, int size) {
+    private int readAt(long offset, byte[] data, int size, ConnectionState connectionState) {
         StrictMode.ThreadPolicy policy =
             new StrictMode.ThreadPolicy.Builder().permitAll().build();
 
@@ -335,12 +354,12 @@
 
         try {
             synchronized(this) {
-                if (offset != mCurrentOffset) {
-                    seekTo(offset);
+                if (offset != connectionState.mCurrentOffset) {
+                    seekTo(offset, connectionState);
                 }
             }
 
-            int n = mInputStream.read(data, 0, size);
+            int n = connectionState.mInputStream.read(data, 0, size);
 
             if (n == -1) {
                 // InputStream signals EOS using a -1 result, our semantics
@@ -348,7 +367,7 @@
                 n = 0;
             }
 
-            mCurrentOffset += n;
+            connectionState.mCurrentOffset += n;
 
             if (VERBOSE) {
                 Log.d(TAG, "readAt " + offset + " / " + size + " => " + n);
@@ -380,35 +399,47 @@
 
     @Override
     public synchronized long getSize() {
-        if (mConnection == null) {
-            try {
-                seekTo(0);
-            } catch (IOException e) {
-                return -1;
+        ConnectionState connectionState = mConnectionStateHolder.get();
+        if (connectionState != null) {
+            if (connectionState.mConnection == null) {
+                try {
+                    seekTo(0, connectionState);
+                } catch (IOException e) {
+                    return -1;
+                }
             }
+            return connectionState.mTotalSize;
         }
 
-        return mTotalSize;
+        return -1;
     }
 
     @Override
     @UnsupportedAppUsage
     public synchronized String getMIMEType() {
-        if (mConnection == null) {
-            try {
-                seekTo(0);
-            } catch (IOException e) {
-                return "application/octet-stream";
+        ConnectionState connectionState = mConnectionStateHolder.get();
+        if (connectionState != null) {
+            if (connectionState.mConnection == null) {
+                try {
+                    seekTo(0, connectionState);
+                } catch (IOException e) {
+                    return "application/octet-stream";
+                }
             }
+            return connectionState.mConnection.getContentType();
         }
 
-        return mConnection.getContentType();
+        return null;
     }
 
     @Override
     @UnsupportedAppUsage
     public String getUri() {
-        return mURL.toString();
+        ConnectionState connectionState = mConnectionStateHolder.get();
+        if (connectionState != null) {
+            return connectionState.mURL.toString();
+        }
+        return null;
     }
 
     @Override
@@ -421,7 +452,7 @@
     private native final void native_finalize();
 
     private native final IBinder native_getIMemory();
-    private native final int native_readAt(long offset, int size);
+    private native int native_readAt(long offset, int size, ConnectionState connectionState);
 
     static {
         System.loadLibrary("media_jni");
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index cfcc294..02348476 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -22,10 +22,10 @@
 import android.media.session.ICallback;
 import android.media.session.IOnMediaKeyListener;
 import android.media.session.IOnVolumeKeyLongPressListener;
+import android.media.session.ISession;
 import android.media.session.ISession2TokensListener;
 import android.media.session.MediaSession;
 import android.media.session.SessionCallbackLink;
-import android.media.session.SessionLink;
 import android.os.Bundle;
 import android.view.KeyEvent;
 
@@ -34,7 +34,7 @@
  * @hide
  */
 interface ISessionManager {
-    SessionLink createSession(String packageName, in SessionCallbackLink sessionCb, String tag,
+    ISession createSession(String packageName, in SessionCallbackLink sessionCb, String tag,
             in Bundle sessionInfo, int userId);
     void notifySession2Created(in Session2Token sessionToken);
     List<MediaSession.Token> getSessions(in ComponentName compName, int userId);
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 4fc436c..cbc6c9d 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -36,6 +36,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.service.media.MediaBrowserService;
 import android.text.TextUtils;
@@ -163,11 +164,11 @@
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
         try {
             SessionCallbackLink cbLink = new SessionCallbackLink(context);
-            SessionLink sessionLink = manager.createSession(cbLink, tag, sessionInfo);
-            mImpl = new MediaSessionEngine(context, sessionLink, cbLink);
+            ISession binder = manager.createSession(cbLink, tag, sessionInfo);
+            mImpl = new MediaSessionEngine(context, binder, cbLink);
             mMaxBitmapSize = context.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
-        } catch (RuntimeException e) {
+        } catch (RemoteException e) {
             throw new RuntimeException("Remote error creating session.", e);
         }
     }
diff --git a/media/java/android/media/session/MediaSessionEngine.java b/media/java/android/media/session/MediaSessionEngine.java
index 266bf32..7c5243a 100644
--- a/media/java/android/media/session/MediaSessionEngine.java
+++ b/media/java/android/media/session/MediaSessionEngine.java
@@ -25,6 +25,7 @@
 import android.media.AudioAttributes;
 import android.media.MediaDescription;
 import android.media.MediaMetadata;
+import android.media.MediaParceledListSlice;
 import android.media.Rating;
 import android.media.VolumeProvider;
 import android.media.session.MediaSessionManager.RemoteUserInfo;
@@ -34,6 +35,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.service.media.MediaBrowserService;
 import android.text.TextUtils;
@@ -55,7 +57,7 @@
 
     private final MediaSession.Token mSessionToken;
     private final MediaController mController;
-    private final SessionLink mSessionLink;
+    private final ISession mBinder;
 
     private CallbackMessageHandler mCallbackHandler;
     private VolumeProvider mVolumeProvider;
@@ -70,14 +72,14 @@
      * finished with the session.
      *
      * @param context The context to use to create the session.
-     * @param sessionLink A session link for the binder of MediaSessionRecord
+     * @param binder A session binder
      */
-    public MediaSessionEngine(@NonNull Context context, @NonNull SessionLink sessionLink,
-            @NonNull SessionCallbackLink cbLink) {
-        mSessionLink = sessionLink;
+    public MediaSessionEngine(@NonNull Context context, @NonNull ISession binder,
+            @NonNull SessionCallbackLink cbLink) throws RemoteException {
+        mBinder = binder;
 
         cbLink.setSessionEngine(this);
-        mSessionToken = new MediaSession.Token(mSessionLink.getController());
+        mSessionToken = new MediaSession.Token(mBinder.getController());
         mController = new MediaController(context, mSessionToken);
     }
 
@@ -134,8 +136,8 @@
      */
     public void setSessionActivity(@Nullable PendingIntent pi) {
         try {
-            mSessionLink.setLaunchPendingIntent(pi);
-        } catch (RuntimeException e) {
+            mBinder.setLaunchPendingIntent(pi);
+        } catch (RemoteException e) {
             Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e);
         }
     }
@@ -150,8 +152,8 @@
      */
     public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
         try {
-            mSessionLink.setMediaButtonReceiver(mbr);
-        } catch (RuntimeException e) {
+            mBinder.setMediaButtonReceiver(mbr);
+        } catch (RemoteException e) {
             Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
         }
     }
@@ -163,8 +165,8 @@
      */
     public void setFlags(int flags) {
         try {
-            mSessionLink.setFlags(flags);
-        } catch (RuntimeException e) {
+            mBinder.setFlags(flags);
+        } catch (RemoteException e) {
             Log.wtf(TAG, "Failure in setFlags.", e);
         }
     }
@@ -185,8 +187,8 @@
             throw new IllegalArgumentException("Attributes cannot be null for local playback.");
         }
         try {
-            mSessionLink.setPlaybackToLocal(attributes);
-        } catch (RuntimeException e) {
+            mBinder.setPlaybackToLocal(attributes);
+        } catch (RemoteException e) {
             Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
         }
     }
@@ -217,10 +219,10 @@
         });
 
         try {
-            mSessionLink.setPlaybackToRemote(volumeProvider.getVolumeControl(),
+            mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(),
                     volumeProvider.getMaxVolume());
-            mSessionLink.setCurrentVolume(volumeProvider.getCurrentVolume());
-        } catch (RuntimeException e) {
+            mBinder.setCurrentVolume(volumeProvider.getCurrentVolume());
+        } catch (RemoteException e) {
             Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
         }
     }
@@ -238,9 +240,9 @@
             return;
         }
         try {
-            mSessionLink.setActive(active);
+            mBinder.setActive(active);
             mActive = active;
-        } catch (RuntimeException e) {
+        } catch (RemoteException e) {
             Log.wtf(TAG, "Failure in setActive.", e);
         }
     }
@@ -267,8 +269,8 @@
             throw new IllegalArgumentException("event cannot be null or empty");
         }
         try {
-            mSessionLink.sendEvent(event, extras);
-        } catch (RuntimeException e) {
+            mBinder.sendEvent(event, extras);
+        } catch (RemoteException e) {
             Log.wtf(TAG, "Error sending event", e);
         }
     }
@@ -280,8 +282,8 @@
      */
     public void close() {
         try {
-            mSessionLink.destroySession();
-        } catch (RuntimeException e) {
+            mBinder.destroySession();
+        } catch (RemoteException e) {
             Log.wtf(TAG, "Error releasing session: ", e);
         }
     }
@@ -316,8 +318,8 @@
     public void setPlaybackState(@Nullable PlaybackState state) {
         mPlaybackState = state;
         try {
-            mSessionLink.setPlaybackState(state);
-        } catch (RuntimeException e) {
+            mBinder.setPlaybackState(state);
+        } catch (RemoteException e) {
             Log.wtf(TAG, "Dead object in setPlaybackState.", e);
         }
     }
@@ -344,8 +346,8 @@
         String metadataDescription = "size=" + fields + ", description=" + description;
 
         try {
-            mSessionLink.setMetadata(metadata, duration, metadataDescription);
-        } catch (RuntimeException e) {
+            mBinder.setMetadata(metadata, duration, metadataDescription);
+        } catch (RemoteException e) {
             Log.wtf(TAG, "Dead object in setPlaybackState.", e);
         }
     }
@@ -363,8 +365,8 @@
      */
     public void setQueue(@Nullable List<MediaSession.QueueItem> queue) {
         try {
-            mSessionLink.setQueue(queue);
-        } catch (RuntimeException e) {
+            mBinder.setQueue(queue == null ? null : new MediaParceledListSlice(queue));
+        } catch (RemoteException e) {
             Log.wtf("Dead object in setQueue.", e);
         }
     }
@@ -378,8 +380,8 @@
      */
     public void setQueueTitle(@Nullable CharSequence title) {
         try {
-            mSessionLink.setQueueTitle(title);
-        } catch (RuntimeException e) {
+            mBinder.setQueueTitle(title);
+        } catch (RemoteException e) {
             Log.wtf("Dead object in setQueueTitle.", e);
         }
     }
@@ -399,8 +401,8 @@
      */
     public void setRatingType(int type) {
         try {
-            mSessionLink.setRatingType(type);
-        } catch (RuntimeException e) {
+            mBinder.setRatingType(type);
+        } catch (RemoteException e) {
             Log.e(TAG, "Error in setRatingType.", e);
         }
     }
@@ -414,8 +416,8 @@
      */
     public void setExtras(@Nullable Bundle extras) {
         try {
-            mSessionLink.setExtras(extras);
-        } catch (RuntimeException e) {
+            mBinder.setExtras(extras);
+        } catch (RemoteException e) {
             Log.wtf("Dead object in setExtras.", e);
         }
     }
@@ -464,8 +466,8 @@
             }
         }
         try {
-            mSessionLink.setCurrentVolume(provider.getCurrentVolume());
-        } catch (RuntimeException e) {
+            mBinder.setCurrentVolume(provider.getCurrentVolume());
+        } catch (RemoteException e) {
             Log.e(TAG, "Error in notifyVolumeChanged", e);
         }
     }
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index f530442..7ca5c93 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -107,7 +107,7 @@
      * @hide
      */
     @NonNull
-    public SessionLink createSession(@NonNull SessionCallbackLink cbStub, @NonNull String tag,
+    public ISession createSession(@NonNull SessionCallbackLink cbStub, @NonNull String tag,
             @Nullable Bundle sessionInfo) {
         try {
             return mService.createSession(mContext.getPackageName(), cbStub, tag, sessionInfo,
diff --git a/media/java/android/media/session/SessionLink.aidl b/media/java/android/media/session/SessionLink.aidl
deleted file mode 100644
index c3be23e..0000000
--- a/media/java/android/media/session/SessionLink.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.session;
-
-parcelable SessionLink;
diff --git a/media/java/android/media/session/SessionLink.java b/media/java/android/media/session/SessionLink.java
deleted file mode 100644
index a47c262..0000000
--- a/media/java/android/media/session/SessionLink.java
+++ /dev/null
@@ -1,450 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.session;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.PendingIntent;
-import android.media.AudioAttributes;
-import android.media.MediaMetadata;
-import android.media.MediaParceledListSlice;
-import android.media.session.MediaSession.QueueItem;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.RemoteException;
-
-import java.util.List;
-
-/**
- * Handles incoming commands from {@link MediaSession}.
- * @hide
- */
-public final class SessionLink implements Parcelable {
-    public static final @android.annotation.NonNull Parcelable.Creator<SessionLink> CREATOR =
-            new Parcelable.Creator<SessionLink>() {
-                @Override
-                public SessionLink createFromParcel(Parcel in) {
-                    return new SessionLink(in.readStrongBinder());
-                }
-
-                @Override
-                public SessionLink[] newArray(int size) {
-                    return new SessionLink[size];
-                }
-            };
-
-    final SessionStub mSessionStub;
-    final ISession mISession;
-
-    /**
-     * Constructor for stub (Callee)
-     */
-    public SessionLink(@NonNull SessionStub sessionStub) {
-        mSessionStub = sessionStub;
-        mISession = new StubProxy();
-    }
-
-    /**
-     * Constructor for interface (Caller)
-     */
-    public SessionLink(IBinder binder) {
-        mSessionStub = null;
-        mISession = ISession.Stub.asInterface(binder);
-    }
-
-    /**
-     * Tell system that the session sends an event to all the connected controllers.
-     *
-     * @param event the name of the event
-     * @param extras the extras included with the event
-     */
-    void sendEvent(@NonNull String event, @Nullable Bundle extras) {
-        try {
-            mISession.sendEvent(event, extras);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Gets the controller binder from the system.
-     */
-    @NonNull
-    ISessionController getController() {
-        try {
-            return mISession.getController();
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session sets the flags.
-     *
-     * @param flags the new session flags
-     */
-    void setFlags(int flags) {
-        try {
-            mISession.setFlags(flags);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session is (in)active.
-     *
-     * @param active the new activeness state
-     */
-    void setActive(boolean active) {
-        try {
-            mISession.setActive(active);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session sets the media button receiver.
-     *
-     * @param mbr the pending intent for media button receiver
-     */
-    void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
-        try {
-            mISession.setMediaButtonReceiver(mbr);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session sets the pending intent for launching UI.
-     *
-     * @param pi the pending intent for launching UI
-     */
-    void setLaunchPendingIntent(@Nullable PendingIntent pi) {
-        try {
-            mISession.setLaunchPendingIntent(pi);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session is destroyed.
-     */
-    void destroySession() {
-        try {
-            mISession.destroySession();
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session sets the new metadata.
-     *
-     * @param metadata the new metadata
-     * @param duration the duration of the media in milliseconds
-     * @param metadataDescription the description of the metadata
-     */
-    void setMetadata(@Nullable MediaMetadata metadata, long duration,
-            @Nullable String metadataDescription) {
-        try {
-            mISession.setMetadata(metadata, duration, metadataDescription);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session sets the new playback state.
-     *
-     * @param state the new playback state
-     */
-    void setPlaybackState(@Nullable PlaybackState state) {
-        try {
-            mISession.setPlaybackState(state);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session sets the new queue.
-     *
-     * @param queue the new queue
-     */
-    void setQueue(@Nullable List<QueueItem> queue) {
-        try {
-            mISession.setQueue(queue == null ? null : new MediaParceledListSlice(queue));
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session sets the new queue title.
-     *
-     * @param title the new queue title
-     */
-    void setQueueTitle(@Nullable CharSequence title) {
-        try {
-            mISession.setQueueTitle(title);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session sets the new extras.
-     *
-     * @param extras the new extras
-     */
-    void setExtras(@Nullable Bundle extras) {
-        try {
-            mISession.setExtras(extras);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session sets the new rating type of the current media.
-     *
-     * @param type the rating type.
-     */
-    void setRatingType(int type) {
-        try {
-            mISession.setRatingType(type);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session represents a local playback.
-     *
-     * @param attributes the audio attributes of the local playback.
-     */
-    void setPlaybackToLocal(@NonNull AudioAttributes attributes) {
-        try {
-            mISession.setPlaybackToLocal(attributes);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session represents a remote playback.
-     *
-     * @param control the volume control type
-     * @param max the max volume
-     */
-    void setPlaybackToRemote(int control, int max) {
-        try {
-            mISession.setPlaybackToRemote(control, max);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Tell system that the session sets the new current volume.
-     *
-     * @param currentVolume the new current volume
-     */
-    void setCurrentVolume(int currentVolume) {
-        try {
-            mISession.setCurrentVolume(currentVolume);
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /** Gets the binder */
-    @NonNull
-    public IBinder getBinder() {
-        return mISession.asBinder();
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeStrongBinder(mISession.asBinder());
-    }
-
-    /**
-     * Class for Stub implementation
-     */
-    public abstract static class SessionStub {
-        /** Stub method for ISession.sendEvent */
-        public void sendEvent(@NonNull String event, @Nullable Bundle data) {
-        }
-
-        /** Stub method for ISession.getController */
-        @NonNull
-        public ISessionController getController() {
-            return null;
-        }
-
-        /** Stub method for ISession.setFlags */
-        public void setFlags(int flags) {
-        }
-
-        /** Stub method for ISession.setActive */
-        public void setActive(boolean active) {
-        }
-
-        /** Stub method for ISession.setMediaButtonReceiver */
-        public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
-        }
-
-        /** Stub method for ISession.setLaunchPendingIntent */
-        public void setLaunchPendingIntent(@Nullable PendingIntent pi) {
-        }
-
-        /** Stub method for ISession.destroySession */
-        public void destroySession() {
-        }
-
-        /** Stub method for ISession.setMetadata */
-        public void setMetadata(@Nullable MediaMetadata metadata, long duration,
-                @Nullable String metadataDescription) {
-        }
-
-        /** Stub method for ISession.setPlaybackState */
-        public void setPlaybackState(@Nullable PlaybackState state) {
-        }
-
-        /** Stub method for ISession.setQueue */
-        public void setQueue(@Nullable List<QueueItem> queue) {
-        }
-
-        /** Stub method for ISession.setQueueTitle */
-        public void setQueueTitle(@Nullable CharSequence title) {
-        }
-
-        /** Stub method for ISession.setExtras */
-        public void setExtras(@Nullable Bundle extras) {
-        }
-
-        /** Stub method for ISession.setRatingType */
-        public void setRatingType(int type) {
-        }
-
-        /** Stub method for ISession.setPlaybackToLocal */
-        public void setPlaybackToLocal(@NonNull AudioAttributes attributes) {
-        }
-
-        /** Stub method for ISession.setPlaybackToRemote */
-        public void setPlaybackToRemote(int control, int max) {
-        }
-
-        /** Stub method for ISession.setCurrentVolume */
-        public void setCurrentVolume(int currentVolume) {
-        }
-    }
-
-    private class StubProxy extends ISession.Stub {
-        @Override
-        public void sendEvent(String event, Bundle data) {
-            mSessionStub.sendEvent(event, data);
-        }
-
-        @Override
-        public ISessionController getController() {
-            return mSessionStub.getController();
-        }
-
-        @Override
-        public void setFlags(int flags) {
-            mSessionStub.setFlags(flags);
-        }
-
-        @Override
-        public void setActive(boolean active) {
-            mSessionStub.setActive(active);
-        }
-
-        @Override
-        public void setMediaButtonReceiver(PendingIntent mbr) {
-            mSessionStub.setMediaButtonReceiver(mbr);
-        }
-
-        @Override
-        public void setLaunchPendingIntent(PendingIntent pi) {
-            mSessionStub.setLaunchPendingIntent(pi);
-        }
-
-        @Override
-        public void destroySession() {
-            mSessionStub.destroySession();
-        }
-
-        @Override
-        public void setMetadata(MediaMetadata metadata, long duration, String metadataDescription) {
-            mSessionStub.setMetadata(metadata, duration, metadataDescription);
-        }
-
-        @Override
-        public void setPlaybackState(PlaybackState state) {
-            mSessionStub.setPlaybackState(state);
-        }
-
-        @Override
-        public void setQueue(MediaParceledListSlice queue) {
-            mSessionStub.setQueue(queue == null ? null : queue.getList());
-        }
-
-        @Override
-        public void setQueueTitle(CharSequence title) {
-            mSessionStub.setQueueTitle(title);
-        }
-
-        @Override
-        public void setExtras(Bundle extras) {
-            mSessionStub.setExtras(extras);
-        }
-
-        @Override
-        public void setRatingType(int type) {
-            mSessionStub.setRatingType(type);
-        }
-
-        @Override
-        public void setPlaybackToLocal(AudioAttributes attributes) {
-            mSessionStub.setPlaybackToLocal(attributes);
-        }
-
-        @Override
-        public void setPlaybackToRemote(int control, int max) {
-            mSessionStub.setPlaybackToRemote(control, max);
-        }
-
-        @Override
-        public void setCurrentVolume(int currentVolume) {
-            mSessionStub.setCurrentVolume(currentVolume);
-        }
-    }
-}
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 474b671..dc2d177 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -54,6 +54,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.IntStream;
 
@@ -248,7 +249,7 @@
     public MtpDatabase(Context context, String volumeName,
             String[] subDirectories) {
         native_setup();
-        mContext = context;
+        mContext = Objects.requireNonNull(context);
         mMediaProvider = context.getContentResolver()
                 .acquireContentProviderClient(MediaStore.AUTHORITY);
         mVolumeName = volumeName;
@@ -294,6 +295,10 @@
         }
     }
 
+    public Context getContext() {
+        return mContext;
+    }
+
     @Override
     public void close() {
         mManager.close();
diff --git a/media/java/android/mtp/MtpServer.java b/media/java/android/mtp/MtpServer.java
index a555d37..f0ab9a3 100644
--- a/media/java/android/mtp/MtpServer.java
+++ b/media/java/android/mtp/MtpServer.java
@@ -18,7 +18,12 @@
 
 import com.android.internal.util.Preconditions;
 
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.ByteStringUtils;
+
 import java.io.FileDescriptor;
+import java.util.Random;
 
 /**
  * Java wrapper for MTP/PTP support as USB responder.
@@ -29,6 +34,12 @@
     private long mNativeContext; // accessed by native methods
     private final MtpDatabase mDatabase;
     private final Runnable mOnTerminate;
+    private final Context mContext;
+
+// It requires "exactly 32 characters, including any leading 0s" in MTP spec
+// (5.1.1.14 Serial Number)
+    private static final int sID_LEN_BYTES = 16;
+    private static final int sID_LEN_STR = (sID_LEN_BYTES * 2);
 
     static {
         System.loadLibrary("media_jni");
@@ -41,10 +52,41 @@
             Runnable onTerminate,
             String deviceInfoManufacturer,
             String deviceInfoModel,
-            String deviceInfoDeviceVersion,
-            String deviceInfoSerialNumber) {
+            String deviceInfoDeviceVersion) {
         mDatabase = Preconditions.checkNotNull(database);
         mOnTerminate = Preconditions.checkNotNull(onTerminate);
+        mContext = mDatabase.getContext();
+
+        final String strID_PREFS_NAME = "mtp-cfg";
+        final String strID_PREFS_KEY = "mtp-id";
+        String strRandomId = null;
+        String deviceInfoSerialNumber;
+
+        SharedPreferences sharedPref =
+                mContext.getSharedPreferences(strID_PREFS_NAME, Context.MODE_PRIVATE);
+        if (sharedPref.contains(strID_PREFS_KEY)) {
+            strRandomId = sharedPref.getString(strID_PREFS_KEY, null);
+
+            // Check for format consistence (regenerate upon corruption)
+            if (strRandomId.length() != sID_LEN_STR) {
+                strRandomId = null;
+            } else {
+                // Only accept hex digit
+                for (int ii = 0; ii < strRandomId.length(); ii++)
+                    if (Character.digit(strRandomId.charAt(ii), 16) == -1) {
+                        strRandomId = null;
+                        break;
+                    }
+            }
+        }
+
+        if (strRandomId == null) {
+            strRandomId = getRandId();
+            sharedPref.edit().putString(strID_PREFS_KEY, strRandomId).apply();
+        }
+
+        deviceInfoSerialNumber = strRandomId;
+
         native_setup(
                 database,
                 controlFd,
@@ -56,6 +98,14 @@
         database.setServer(this);
     }
 
+    private String getRandId() {
+        Random randomVal = new Random();
+        byte[] randomBytes = new byte[sID_LEN_BYTES];
+
+        randomVal.nextBytes(randomBytes);
+        return ByteStringUtils.toHexString(randomBytes);
+    }
+
     public void start() {
         Thread thread = new Thread(this, "MtpServer");
         thread.start();
diff --git a/media/jni/android_media_MediaHTTPConnection.cpp b/media/jni/android_media_MediaHTTPConnection.cpp
index 365e045..d28c15c 100644
--- a/media/jni/android_media_MediaHTTPConnection.cpp
+++ b/media/jni/android_media_MediaHTTPConnection.cpp
@@ -109,7 +109,8 @@
     gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J");
     CHECK(gFields.context != NULL);
 
-    gFields.readAtMethodID = env->GetMethodID(clazz.get(), "readAt", "(J[BI)I");
+    gFields.readAtMethodID = env->GetMethodID(
+            clazz.get(), "readAt", "(J[BILandroid/media/MediaHTTPConnection$ConnectionState;)I");
 }
 
 static void android_media_MediaHTTPConnection_native_setup(
@@ -132,7 +133,7 @@
 }
 
 static jint android_media_MediaHTTPConnection_native_readAt(
-        JNIEnv *env, jobject thiz, jlong offset, jint size) {
+        JNIEnv *env, jobject thiz, jlong offset, jint size, jobject connectionState) {
     sp<JMediaHTTPConnection> conn = getObject(env, thiz);
     if (size > JMediaHTTPConnection::kBufferSize) {
         size = JMediaHTTPConnection::kBufferSize;
@@ -141,7 +142,7 @@
     jbyteArray byteArrayObj = conn->getByteArrayObj();
 
     jint n = env->CallIntMethod(
-            thiz, gFields.readAtMethodID, offset, byteArrayObj, size);
+            thiz, gFields.readAtMethodID, offset, byteArrayObj, size, connectionState);
 
     if (n > 0) {
         env->GetByteArrayRegion(
@@ -158,7 +159,7 @@
     { "native_getIMemory", "()Landroid/os/IBinder;",
       (void *)android_media_MediaHTTPConnection_native_getIMemory },
 
-    { "native_readAt", "(JI)I",
+    { "native_readAt", "(JILandroid/media/MediaHTTPConnection$ConnectionState;)I",
       (void *)android_media_MediaHTTPConnection_native_readAt },
 
     { "native_init", "()V",
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index b238ae4..9d91487 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -1615,12 +1615,12 @@
                 return;
             }
             // See if the data sub is registered for PS services on cell.
-            final NetworkRegistrationInfo nrs = dataSs.getNetworkRegistrationInfo(
+            final NetworkRegistrationInfo nri = dataSs.getNetworkRegistrationInfo(
                     NetworkRegistrationInfo.DOMAIN_PS,
                     AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
             latencyBroadcast.putExtra(
                     NetworkMonitorUtils.EXTRA_CELL_ID,
-                    nrs == null ? null : nrs.getCellIdentity());
+                    nri == null ? null : nri.getCellIdentity());
             latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE);
         } else {
             return;
diff --git a/packages/SystemUI/res/layout/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml
index 1e8cd5a..83557f2 100644
--- a/packages/SystemUI/res/layout/biometric_dialog.xml
+++ b/packages/SystemUI/res/layout/biometric_dialog.xml
@@ -39,152 +39,158 @@
             android:layout_height="0dp"
             android:layout_weight="1" />
 
-        <LinearLayout
+        <ScrollView
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <!-- This is not a Space since Spaces cannot be clicked. The width of this changes depending
-             on horizontal/portrait orientation -->
-            <View
-                android:id="@+id/left_space"
-                android:layout_weight="1"
-                android:layout_width="0dp"
-                android:layout_height="match_parent"/>
+            android:layout_height="wrap_content">
 
             <LinearLayout
-                android:id="@+id/dialog"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:orientation="vertical"
-                android:background="@drawable/biometric_dialog_bg"
-                android:layout_marginBottom="@dimen/biometric_dialog_border_padding"
-                android:layout_marginLeft="@dimen/biometric_dialog_border_padding"
-                android:layout_marginRight="@dimen/biometric_dialog_border_padding">
+                android:orientation="horizontal">
 
-                <TextView
-                    android:id="@+id/title"
-                    android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginEnd="24dp"
-                    android:layout_marginStart="24dp"
-                    android:layout_marginTop="24dp"
-                    android:gravity="@integer/biometric_dialog_text_gravity"
-                    android:textSize="20sp"
-                    android:maxLines="1"
-                    android:singleLine="true"
-                    android:ellipsize="marquee"
-                    android:marqueeRepeatLimit="marquee_forever"
-                    android:textColor="?android:attr/textColorPrimary"/>
+                <!-- This is not a Space since Spaces cannot be clicked. The width of this changes
+                depending on horizontal/portrait orientation -->
+                <View
+                    android:id="@+id/left_space"
+                    android:layout_weight="1"
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent"/>
 
-                <TextView
-                    android:id="@+id/subtitle"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginTop="8dp"
-                    android:layout_marginStart="24dp"
-                    android:layout_marginEnd="24dp"
-                    android:gravity="@integer/biometric_dialog_text_gravity"
-                    android:textSize="16sp"
-                    android:maxLines="1"
-                    android:singleLine="true"
-                    android:ellipsize="marquee"
-                    android:marqueeRepeatLimit="marquee_forever"
-                    android:textColor="?android:attr/textColorPrimary"/>
+                    <LinearLayout
+                        android:id="@+id/dialog"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="vertical"
+                        android:background="@drawable/biometric_dialog_bg"
+                        android:layout_marginBottom="@dimen/biometric_dialog_border_padding"
+                        android:layout_marginLeft="@dimen/biometric_dialog_border_padding"
+                        android:layout_marginRight="@dimen/biometric_dialog_border_padding">
 
-                <TextView
-                    android:id="@+id/description"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginEnd="24dp"
-                    android:layout_marginStart="24dp"
-                    android:gravity="@integer/biometric_dialog_text_gravity"
-                    android:paddingTop="8dp"
-                    android:textSize="16sp"
-                    android:maxLines="4"
-                    android:textColor="?android:attr/textColorPrimary"/>
+                        <TextView
+                            android:id="@+id/title"
+                            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_marginEnd="24dp"
+                            android:layout_marginStart="24dp"
+                            android:layout_marginTop="24dp"
+                            android:gravity="@integer/biometric_dialog_text_gravity"
+                            android:textSize="20sp"
+                            android:maxLines="1"
+                            android:singleLine="true"
+                            android:ellipsize="marquee"
+                            android:marqueeRepeatLimit="marquee_forever"
+                            android:textColor="?android:attr/textColorPrimary"/>
 
-                <ImageView
-                    android:id="@+id/biometric_icon"
-                    android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
-                    android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
-                    android:layout_gravity="center_horizontal"
-                    android:layout_marginTop="48dp"
-                    android:scaleType="fitXY" />
+                        <TextView
+                            android:id="@+id/subtitle"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="8dp"
+                            android:layout_marginStart="24dp"
+                            android:layout_marginEnd="24dp"
+                            android:gravity="@integer/biometric_dialog_text_gravity"
+                            android:textSize="16sp"
+                            android:maxLines="1"
+                            android:singleLine="true"
+                            android:ellipsize="marquee"
+                            android:marqueeRepeatLimit="marquee_forever"
+                            android:textColor="?android:attr/textColorPrimary"/>
 
-                <TextView
-                    android:id="@+id/error"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginEnd="24dp"
-                    android:layout_marginStart="24dp"
-                    android:paddingTop="16dp"
-                    android:paddingBottom="24dp"
-                    android:textSize="12sp"
-                    android:gravity="center_horizontal"
-                    android:accessibilityLiveRegion="polite"
-                    android:contentDescription="@string/accessibility_biometric_dialog_help_area"
-                    android:textColor="?android:attr/textColorSecondary"/>
+                        <TextView
+                            android:id="@+id/description"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_marginEnd="24dp"
+                            android:layout_marginStart="24dp"
+                            android:gravity="@integer/biometric_dialog_text_gravity"
+                            android:paddingTop="8dp"
+                            android:textSize="16sp"
+                            android:maxLines="4"
+                            android:textColor="?android:attr/textColorPrimary"/>
 
-                <LinearLayout
-                    android:layout_width="match_parent"
-                    android:layout_height="72dip"
-                    android:paddingTop="24dp"
-                    android:layout_gravity="center_vertical"
-                    style="?android:attr/buttonBarStyle"
-                    android:orientation="horizontal"
-                    android:measureWithLargestChild="true">
-                    <Space android:id="@+id/leftSpacer"
-                        android:layout_width="12dp"
-                        android:layout_height="match_parent"
-                        android:visibility="visible" />
-                    <!-- Negative Button -->
-                    <Button android:id="@+id/button2"
-                        android:layout_width="wrap_content"
-                        android:layout_height="match_parent"
-                        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-                        android:gravity="center"
-                        android:maxLines="2" />
-                    <Space android:id="@+id/middleSpacer"
-                        android:layout_width="0dp"
-                        android:layout_height="match_parent"
-                        android:layout_weight="1"
-                        android:visibility="visible" />
-                    <!-- Positive Button -->
-                    <Button android:id="@+id/button1"
-                        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_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"
-                        android:visibility="visible" />
-                </LinearLayout>
+                        <ImageView
+                            android:id="@+id/biometric_icon"
+                            android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
+                            android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
+                            android:layout_gravity="center_horizontal"
+                            android:layout_marginTop="48dp"
+                            android:scaleType="fitXY" />
+
+                        <TextView
+                            android:id="@+id/error"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_marginEnd="24dp"
+                            android:layout_marginStart="24dp"
+                            android:paddingTop="16dp"
+                            android:paddingBottom="24dp"
+                            android:textSize="12sp"
+                            android:gravity="center_horizontal"
+                            android:accessibilityLiveRegion="polite"
+                            android:contentDescription="@string/accessibility_biometric_dialog_help_area"
+                            android:textColor="?android:attr/textColorSecondary"/>
+
+                        <LinearLayout
+                            android:layout_width="match_parent"
+                            android:layout_height="72dip"
+                            android:paddingTop="24dp"
+                            android:layout_gravity="center_vertical"
+                            style="?android:attr/buttonBarStyle"
+                            android:orientation="horizontal"
+                            android:measureWithLargestChild="true">
+                            <Space android:id="@+id/leftSpacer"
+                                android:layout_width="12dp"
+                                android:layout_height="match_parent"
+                                android:visibility="visible" />
+                            <!-- Negative Button -->
+                            <Button android:id="@+id/button2"
+                                android:layout_width="wrap_content"
+                                android:layout_height="match_parent"
+                                style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+                                android:gravity="center"
+                                android:maxLines="2" />
+                            <Space android:id="@+id/middleSpacer"
+                                android:layout_width="0dp"
+                                android:layout_height="match_parent"
+                                android:layout_weight="1"
+                                android:visibility="visible" />
+                            <!-- Positive Button -->
+                            <Button android:id="@+id/button1"
+                                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_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"
+                                android:visibility="visible" />
+                        </LinearLayout>
+                    </LinearLayout>
+
+                <!-- This is not a Space since Spaces cannot be clicked. The width of this changes
+                depending on horizontal/portrait orientation -->
+                <View
+                    android:id="@+id/right_space"
+                    android:layout_weight="1"
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent" />
+
             </LinearLayout>
 
-            <!-- This is not a Space since Spaces cannot be clicked. The width of this changes depending
-             on horizontal/portrait orientation -->
-            <View
-                android:id="@+id/right_space"
-                android:layout_weight="1"
-                android:layout_width="0dp"
-                android:layout_height="match_parent" />
-
-        </LinearLayout>
+        </ScrollView>
 
     </LinearLayout>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index d40fa66..ce65b5a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -60,8 +60,8 @@
      */
     private ViewGroup mBigClockContainer;
     /**
-     * Status area (date and other stuff) shown below the clock. Plugin can decide whether
-     * or not to show it below the alternate clock.
+     * Status area (date and other stuff) shown below the clock. Plugin can decide whether or not to
+     * show it below the alternate clock.
      */
     private View mKeyguardStatusArea;
     /**
@@ -75,24 +75,19 @@
     private boolean mSupportsDarkText;
     private int[] mColorPalette;
 
+    /**
+     * Track the state of the status bar to know when to hide the big_clock_container.
+     */
+    private int mStatusBarState;
+
     private final StatusBarStateController.StateListener mStateListener =
             new StatusBarStateController.StateListener() {
                 @Override
                 public void onStateChanged(int newState) {
-                    if (mBigClockContainer == null) {
-                        return;
-                    }
-                    if (newState == StatusBarState.SHADE) {
-                        if (mBigClockContainer.getVisibility() == View.VISIBLE) {
-                            mBigClockContainer.setVisibility(View.INVISIBLE);
-                        }
-                    } else {
-                        if (mBigClockContainer.getVisibility() == View.INVISIBLE) {
-                            mBigClockContainer.setVisibility(View.VISIBLE);
-                        }
-                    }
+                    mStatusBarState = newState;
+                    updateBigClockVisibility();
                 }
-    };
+            };
 
     private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
 
@@ -139,7 +134,9 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         Dependency.get(ClockManager.class).addOnClockChangedListener(mClockChangedListener);
-        Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
+        StatusBarStateController stateController = Dependency.get(StatusBarStateController.class);
+        stateController.addCallback(mStateListener);
+        mStateListener.onStateChanged(stateController.getState());
         SysuiColorExtractor colorExtractor = Dependency.get(SysuiColorExtractor.class);
         colorExtractor.addOnColorsChangedListener(mColorsListener);
         updateColors(colorExtractor);
@@ -151,7 +148,7 @@
         Dependency.get(ClockManager.class).removeOnClockChangedListener(mClockChangedListener);
         Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
         Dependency.get(SysuiColorExtractor.class)
-            .removeOnColorsChangedListener(mColorsListener);
+                .removeOnColorsChangedListener(mColorsListener);
         setClockPlugin(null);
     }
 
@@ -164,7 +161,7 @@
             }
             if (mBigClockContainer != null) {
                 mBigClockContainer.removeAllViews();
-                mBigClockContainer.setVisibility(View.GONE);
+                updateBigClockVisibility();
             }
             mClockPlugin = null;
         }
@@ -184,7 +181,7 @@
         View bigClockView = plugin.getBigClockView();
         if (bigClockView != null && mBigClockContainer != null) {
             mBigClockContainer.addView(bigClockView);
-            mBigClockContainer.setVisibility(View.VISIBLE);
+            updateBigClockVisibility();
         }
         // Hide default clock.
         if (!plugin.shouldShowStatusArea()) {
@@ -208,12 +205,10 @@
             View bigClockView = mClockPlugin.getBigClockView();
             if (bigClockView != null) {
                 container.addView(bigClockView);
-                if (container.getVisibility() == View.GONE) {
-                    container.setVisibility(View.VISIBLE);
-                }
             }
         }
         mBigClockContainer = container;
+        updateBigClockVisibility();
     }
 
     /**
@@ -254,6 +249,7 @@
 
     /**
      * Set the amount (ratio) that the device has transitioned to doze.
+     *
      * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
      */
     public void setDarkAmount(float darkAmount) {
@@ -307,9 +303,24 @@
         }
     }
 
+    private void updateBigClockVisibility() {
+        if (mBigClockContainer == null) {
+            return;
+        }
+        final boolean inDisplayState = mStatusBarState == StatusBarState.KEYGUARD
+                || mStatusBarState == StatusBarState.SHADE_LOCKED;
+        final int visibility =
+                inDisplayState && mBigClockContainer.getChildCount() != 0 ? View.VISIBLE
+                        : View.GONE;
+        if (mBigClockContainer.getVisibility() != visibility) {
+            mBigClockContainer.setVisibility(visibility);
+        }
+    }
+
     /**
-     * Sets if the keyguard slice is showing a center-aligned header. We need a smaller clock
-     * in these cases.
+     * Sets if the keyguard slice is showing a center-aligned header. We need a smaller clock in
+     * these
+     * cases.
      */
     public void setKeyguardShowingHeader(boolean hasHeader) {
         if (mShowingHeader == hasHeader || hasCustomClock()) {
@@ -327,12 +338,12 @@
                 mClockView.getPaddingRight(), paddingBottom);
     }
 
-    @VisibleForTesting (otherwise = VisibleForTesting.NONE)
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
     ClockManager.ClockChangedListener getClockChangedListener() {
         return mClockChangedListener;
     }
 
-    @VisibleForTesting (otherwise = VisibleForTesting.NONE)
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
     StatusBarStateController.StateListener getStateListener() {
         return mStateListener;
     }
@@ -352,7 +363,8 @@
 
     /**
      * Special layout transition that scales the clock view as its bounds change, to make it look
-     * like the text is shrinking.
+     * like
+     * the text is shrinking.
      */
     private class ClockBoundsTransition extends ChangeBounds {
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
index 016b8fe..4028109 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
@@ -191,32 +191,41 @@
         mCurrentDialogArgs = args;
         final int type = args.argi1;
 
+        // Create a new dialog but do not replace the current one yet.
+        BiometricDialogView newDialog;
         if (type == BiometricAuthenticator.TYPE_FINGERPRINT) {
-            mCurrentDialog = new FingerprintDialogView(mContext, mCallback);
+            newDialog = new FingerprintDialogView(mContext, mCallback);
         } else if (type == BiometricAuthenticator.TYPE_FACE) {
-            mCurrentDialog = new FaceDialogView(mContext, mCallback);
+            newDialog = new FaceDialogView(mContext, mCallback);
         } else {
             Log.e(TAG, "Unsupported type: " + type);
-        }
-
-        if (savedState != null) {
-            mCurrentDialog.restoreState(savedState);
-        }
-
-        if (DEBUG) Log.d(TAG, "handleShowDialog, isAnimatingAway: "
-                + mCurrentDialog.isAnimatingAway() + " type: " + type);
-
-        if (mCurrentDialog.isAnimatingAway()) {
-            mCurrentDialog.forceRemove();
-        } else if (mDialogShowing) {
-            Log.w(TAG, "Dialog already showing");
             return;
         }
+
+        if (DEBUG) Log.d(TAG, "handleShowDialog, "
+                + " savedState: " + savedState
+                + " mCurrentDialog: " + mCurrentDialog
+                + " newDialog: " + newDialog
+                + " type: " + type);
+
+        if (savedState != null) {
+            // SavedState is only non-null if it's from onConfigurationChanged. Restore the state
+            // even though it may be removed / re-created again
+            newDialog.restoreState(savedState);
+        } else if (mCurrentDialog != null && mDialogShowing) {
+            // If somehow we're asked to show a dialog, the old one doesn't need to be animated
+            // away. This can happen if the app cancels and re-starts auth during configuration
+            // change. This is ugly because we also have to do things on onConfigurationChanged
+            // here.
+            mCurrentDialog.forceRemove();
+        }
+
         mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
-        mCurrentDialog.setBundle((Bundle)args.arg1);
-        mCurrentDialog.setRequireConfirmation((boolean) args.arg3);
-        mCurrentDialog.setUserId(args.argi2);
-        mCurrentDialog.setSkipIntro(skipAnimation);
+        newDialog.setBundle((Bundle) args.arg1);
+        newDialog.setRequireConfirmation((boolean) args.arg3);
+        newDialog.setUserId(args.argi2);
+        newDialog.setSkipIntro(skipAnimation);
+        mCurrentDialog = newDialog;
         mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams());
         mDialogShowing = true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 0adbefe..f07887e 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -92,8 +92,6 @@
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.ExtensionController.Extension;
 import com.android.systemui.util.EmergencyDialerConstants;
 import com.android.systemui.util.leak.RotationUtils;
 import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator;
@@ -161,9 +159,8 @@
     private final EmergencyAffordanceManager mEmergencyAffordanceManager;
     private final ScreenshotHelper mScreenshotHelper;
     private final ScreenRecordHelper mScreenRecordHelper;
-
-    private final Extension<GlobalActionsPanelPlugin> mPanelExtension;
-    private ActivityStarter mActivityStarter;
+    private final ActivityStarter mActivityStarter;
+    private GlobalActionsPanelPlugin mPanelPlugin;
 
     /**
      * @param context everything needs a context :(
@@ -209,10 +206,6 @@
 
         Dependency.get(ConfigurationController.class).addCallback(this);
 
-        mPanelExtension = Dependency.get(ExtensionController.class)
-            .newExtension(GlobalActionsPanelPlugin.class)
-            .withPlugin(GlobalActionsPanelPlugin.class)
-            .build();
         mActivityStarter = Dependency.get(ActivityStarter.class);
     }
 
@@ -221,9 +214,11 @@
      *
      * @param keyguardShowing True if keyguard is showing
      */
-    public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
+    public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
+            GlobalActionsPanelPlugin panelPlugin) {
         mKeyguardShowing = keyguardShowing;
         mDeviceProvisioned = isDeviceProvisioned;
+        mPanelPlugin = panelPlugin;
         if (mDialog != null) {
             mDialog.dismiss();
             mDialog = null;
@@ -400,8 +395,8 @@
         mAdapter = new MyAdapter();
 
         GlobalActionsPanelPlugin.PanelViewController panelViewController =
-                mPanelExtension.get() != null
-                        ? mPanelExtension.get().onPanelShown(
+                mPanelPlugin != null
+                        ? mPanelPlugin.onPanelShown(
                                 new GlobalActionsPanelPlugin.Callbacks() {
                                     @Override
                                     public void dismissGlobalActionsMenu() {
@@ -1504,12 +1499,12 @@
         private final Context mContext;
         private final MyAdapter mAdapter;
         private MultiListLayout mGlobalActionsLayout;
-        private final Drawable mBackgroundDrawable;
+        private Drawable mBackgroundDrawable;
         private final ColorExtractor mColorExtractor;
         private final GlobalActionsPanelPlugin.PanelViewController mPanelController;
         private boolean mKeyguardShowing;
         private boolean mShowing;
-        private final float mScrimAlpha;
+        private float mScrimAlpha;
 
         ActionsDialog(Context context, MyAdapter adapter,
                 GlobalActionsPanelPlugin.PanelViewController plugin) {
@@ -1536,49 +1531,37 @@
                     | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                     | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
             window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
-
-            initializeLayout();
-
             setTitle(R.string.global_actions);
 
             mPanelController = plugin;
-            View panelView = initializePanel();
-            if (panelView == null) {
-                mBackgroundDrawable = new GradientDrawable(context);
-                mScrimAlpha = ScrimController.GRADIENT_SCRIM_ALPHA;
-            } else {
-                mBackgroundDrawable = context.getDrawable(
-                        com.android.systemui.R.drawable.global_action_panel_scrim);
-                mScrimAlpha = 1f;
-                addContentView(
-                        panelView,
-                        new ViewGroup.LayoutParams(
-                                ViewGroup.LayoutParams.MATCH_PARENT,
-                                ViewGroup.LayoutParams.MATCH_PARENT));
-            }
-            window.setBackgroundDrawable(mBackgroundDrawable);
+            initializeLayout();
         }
 
-        private View initializePanel() {
-            if (isPanelEnabled(mContext) && mPanelController != null) {
-                View panelView = mPanelController.getPanelContent();
-                if (panelView != null) {
-                    FrameLayout panelContainer = new FrameLayout(mContext);
-                    FrameLayout.LayoutParams panelParams =
-                            new FrameLayout.LayoutParams(
-                                    FrameLayout.LayoutParams.MATCH_PARENT,
-                                    FrameLayout.LayoutParams.WRAP_CONTENT);
-                    panelContainer.addView(panelView, panelParams);
-                    return panelContainer;
-                }
+        private boolean initializePanel() {
+            if (!isPanelEnabled(mContext) || mPanelController == null) {
+                return false;
             }
-            return null;
+            View panelView = mPanelController.getPanelContent();
+            if (panelView == null) {
+                return false;
+            }
+            FrameLayout panelContainer = new FrameLayout(mContext);
+            FrameLayout.LayoutParams panelParams =
+                    new FrameLayout.LayoutParams(
+                            FrameLayout.LayoutParams.MATCH_PARENT,
+                            FrameLayout.LayoutParams.WRAP_CONTENT);
+            panelContainer.addView(panelView, panelParams);
+            addContentView(
+                    panelContainer,
+                    new ViewGroup.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT));
+            return true;
         }
 
         private void initializeLayout() {
             setContentView(getGlobalActionsLayoutId(mContext));
-            mGlobalActionsLayout = (MultiListLayout)
-                    findViewById(com.android.systemui.R.id.global_actions_view);
+            mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view);
             mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss());
             mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() {
                 @Override
@@ -1591,8 +1574,18 @@
             });
             mGlobalActionsLayout.setRotationListener(this::onRotate);
             mGlobalActionsLayout.setAdapter(mAdapter);
-            mGlobalActionsLayout.setSnapToEdge(isPanelEnabled(mContext)
-                    && mPanelController != null);
+
+            boolean panelEnabled = initializePanel();
+            if (!panelEnabled) {
+                mBackgroundDrawable = new GradientDrawable(mContext);
+                mScrimAlpha = ScrimController.GRADIENT_SCRIM_ALPHA;
+            } else {
+                mBackgroundDrawable = mContext.getDrawable(
+                        com.android.systemui.R.drawable.global_action_panel_scrim);
+                mScrimAlpha = 1f;
+            }
+            mGlobalActionsLayout.setSnapToEdge(panelEnabled);
+            getWindow().setBackgroundDrawable(mBackgroundDrawable);
         }
 
         private int getGlobalActionsLayoutId(Context context) {
@@ -1748,7 +1741,8 @@
      */
     private static boolean isPanelEnabled(Context context) {
         return FeatureFlagUtils.isEnabled(
-                context, FeatureFlagUtils.GLOBAL_ACTIONS_PANEL_ENABLED);    }
+                context, FeatureFlagUtils.GLOBAL_ACTIONS_PANEL_ENABLED);
+    }
 
     /**
      * Determines whether the Global Actions menu should use a separated view for emergency actions.
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 19a7cea..4cf58b7 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -37,8 +37,10 @@
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.GlobalActions;
+import com.android.systemui.plugins.GlobalActionsPanelPlugin;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 
 public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks {
@@ -48,6 +50,7 @@
     private final Context mContext;
     private final KeyguardMonitor mKeyguardMonitor;
     private final DeviceProvisionedController mDeviceProvisionedController;
+    private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension;
     private GlobalActionsDialog mGlobalActions;
     private boolean mDisabled;
 
@@ -56,6 +59,10 @@
         mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
         SysUiServiceProvider.getComponent(context, CommandQueue.class).addCallback(this);
+        mPanelExtension = Dependency.get(ExtensionController.class)
+                .newExtension(GlobalActionsPanelPlugin.class)
+                .withPlugin(GlobalActionsPanelPlugin.class)
+                .build();
     }
 
     @Override
@@ -74,7 +81,8 @@
             mGlobalActions = new GlobalActionsDialog(mContext, manager);
         }
         mGlobalActions.showDialog(mKeyguardMonitor.isShowing(),
-                mDeviceProvisionedController.isDeviceProvisioned());
+                mDeviceProvisionedController.isDeviceProvisioned(),
+                mPanelExtension.get());
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index b0d1106..29505a2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -101,6 +101,8 @@
         // AND the plugin returns a view for the big clock
         ClockPlugin plugin = mock(ClockPlugin.class);
         when(plugin.getBigClockView()).thenReturn(mBigClock);
+        // AND in the keyguard state
+        mStateListener.onStateChanged(StatusBarState.KEYGUARD);
         // WHEN the plugin is connected
         mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
         // THEN the big clock container is visible and it is the parent of the
@@ -166,6 +168,8 @@
         ClockPlugin plugin = mock(ClockPlugin.class);
         TextClock pluginView = new TextClock(getContext());
         when(plugin.getBigClockView()).thenReturn(pluginView);
+        // AND in the keyguard state
+        mStateListener.onStateChanged(StatusBarState.KEYGUARD);
         // WHEN the plugin is connected and then disconnected
         mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
         mKeyguardClockSwitch.getClockChangedListener().onClockChanged(null);
@@ -245,21 +249,25 @@
     }
 
     @Test
-    public void onStateChanged_InvisibleInShade() {
+    public void onStateChanged_GoneInShade() {
         // GIVEN that the big clock container is visible
         mBigClockContainer.setVisibility(View.VISIBLE);
         mKeyguardClockSwitch.setBigClockContainer(mBigClockContainer);
         // WHEN transitioned to SHADE state
         mStateListener.onStateChanged(StatusBarState.SHADE);
-        // THEN the container is invisible.
-        assertThat(mBigClockContainer.getVisibility()).isEqualTo(View.INVISIBLE);
+        // THEN the container is gone.
+        assertThat(mBigClockContainer.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
     public void onStateChanged_VisibleInKeyguard() {
-        // GIVEN that the big clock container is invisible
-        mBigClockContainer.setVisibility(View.INVISIBLE);
+        // GIVEN that the big clock container is gone
+        mBigClockContainer.setVisibility(View.GONE);
         mKeyguardClockSwitch.setBigClockContainer(mBigClockContainer);
+        // AND GIVEN that a plugin is active.
+        ClockPlugin plugin = mock(ClockPlugin.class);
+        when(plugin.getBigClockView()).thenReturn(mBigClock);
+        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
         // WHEN transitioned to KEYGUARD state
         mStateListener.onStateChanged(StatusBarState.KEYGUARD);
         // THEN the container is visible.
@@ -274,6 +282,8 @@
         ClockPlugin plugin = mock(ClockPlugin.class);
         when(plugin.getBigClockView()).thenReturn(mBigClock);
         mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
+        // AND in the keyguard state
+        mStateListener.onStateChanged(StatusBarState.KEYGUARD);
         // WHEN the container is associated with the clock switch
         mKeyguardClockSwitch.setBigClockContainer(mBigClockContainer);
         // THEN the container remains visible.
@@ -281,20 +291,6 @@
     }
 
     @Test
-    public void setBigClockContainer_invisible() {
-        // GIVEN that the big clock container is invisible
-        mBigClockContainer.setVisibility(View.INVISIBLE);
-        // AND GIVEN that a plugin is active.
-        ClockPlugin plugin = mock(ClockPlugin.class);
-        when(plugin.getBigClockView()).thenReturn(mBigClock);
-        mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
-        // WHEN the container is associated with the clock switch
-        mKeyguardClockSwitch.setBigClockContainer(mBigClockContainer);
-        // THEN the container remains invisible.
-        assertThat(mBigClockContainer.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
     public void setBigClockContainer_gone() {
         // GIVEN that the big clock container is gone
         mBigClockContainer.setVisibility(View.GONE);
@@ -302,6 +298,8 @@
         ClockPlugin plugin = mock(ClockPlugin.class);
         when(plugin.getBigClockView()).thenReturn(mBigClock);
         mKeyguardClockSwitch.getClockChangedListener().onClockChanged(plugin);
+        // AND in the keyguard state
+        mStateListener.onStateChanged(StatusBarState.KEYGUARD);
         // WHEN the container is associated with the clock switch
         mKeyguardClockSwitch.setBigClockContainer(mBigClockContainer);
         // THEN the container is made visible.
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index 65e31f3..1e3f20e 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -809,13 +809,9 @@
 
         // Announce the end of the gesture recognition.
         sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
+        // Don't announce the end of a the touch interaction if users didn't lift their fingers.
         if (interactionEnd) {
-            // Announce the end of a the touch interaction.
             sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
-        } else {
-            // If gesture detection is end, but user doesn't release the finger, announce the
-            // transition to exploration state.
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
         }
 
         mExitGestureDetectionModeDelayed.cancel();
@@ -1151,10 +1147,7 @@
         public void run() {
             // Announce the end of gesture recognition.
             sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
-            // Clearing puts is in touch exploration state with a finger already
-            // down, so announce the transition to exploration state.
             clear();
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
         }
     }
 
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index c2a0611..7fab2b96 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -501,6 +501,8 @@
         }
 
         if (broadcast) {
+            // needs to be sent to everyone because we don't know which user may have changed
+            // LOCATION_MODE state.
             mContext.sendBroadcastAsUser(
                     new Intent(LocationManager.MODE_CHANGED_ACTION),
                     UserHandle.ALL);
@@ -1212,6 +1214,13 @@
                             "-" + mName,
                             mCurrentUserId);
                 }
+
+                // needs to be sent to all users because whether or not a provider is enabled for
+                // a given user is complicated... we broadcast to everyone and let them figure it
+                // out via isProviderEnabled()
+                Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION);
+                intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName);
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
             }
 
             if (useable == mUseable) {
@@ -1232,10 +1241,6 @@
             }
 
             updateProviderUseableLocked(this);
-
-            mContext.sendBroadcastAsUser(
-                    new Intent(LocationManager.PROVIDERS_CHANGED_ACTION),
-                    UserHandle.ALL);
         }
 
         @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 7da848c..4e0380d 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -59,6 +59,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemService;
 
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
 /**
@@ -103,6 +104,7 @@
 
     @Override
     public void onStart() {
+        publishBinderService(Context.ATTENTION_SERVICE, new BinderService());
         publishLocalService(AttentionManagerInternal.class, new LocalService());
     }
 
@@ -329,17 +331,15 @@
         return null;
     }
 
-    private void dumpInternal(PrintWriter pw) {
-        if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
-        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
-        ipw.println("Attention Manager Service (dumpsys attention)\n");
+    private void dumpInternal(IndentingPrintWriter ipw) {
+        ipw.println("Attention Manager Service (dumpsys attention) state:\n");
 
         ipw.printPair("context", mContext);
-        pw.println();
+        ipw.println();
         synchronized (mLock) {
             int size = mUserStates.size();
             ipw.print("Number user states: ");
-            pw.println(size);
+            ipw.println(size);
             if (size > 0) {
                 ipw.increaseIndent();
                 for (int i = 0; i < size; i++) {
@@ -591,4 +591,15 @@
             }
         }
     }
+
+    private final class BinderService extends Binder {
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) {
+                return;
+            }
+
+            dumpInternal(new IndentingPrintWriter(pw, "  "));
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index ddd416e..f313e1d 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -57,6 +57,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -68,6 +69,7 @@
 import android.util.StatsLog;
 
 import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.server.SystemService;
 
@@ -85,22 +87,137 @@
 
     private static final String TAG = "BiometricService";
 
+    private static final int MSG_ON_TASK_STACK_CHANGED = 1;
+    private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
+    private static final int MSG_ON_AUTHENTICATION_FAILED = 3;
+    private static final int MSG_ON_ERROR = 4;
+    private static final int MSG_ON_ACQUIRED = 5;
+    private static final int MSG_ON_DISMISSED = 6;
+    private static final int MSG_ON_TRY_AGAIN_PRESSED = 7;
+    private static final int MSG_ON_READY_FOR_AUTHENTICATION = 8;
+    private static final int MSG_AUTHENTICATE = 9;
+    private static final int MSG_CANCEL_AUTHENTICATION = 10;
+
     private static final int[] FEATURE_ID = {
         TYPE_FINGERPRINT,
         TYPE_IRIS,
         TYPE_FACE
     };
 
+    /**
+     * 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. Only
+     * passive modalities such as Face or Iris should have this state. Note that for passive
+     * modalities, the HAL enters the idle state after onAuthenticated(false) which differs from
+     * fingerprint.
+     */
+    private static final int STATE_AUTH_PAUSED = 3;
+    /**
+     * Authentication is successful, but we're waiting for the user to press "confirm" button.
+     */
+    private static final int STATE_AUTH_PENDING_CONFIRM = 5;
+
+    private 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;
+        final boolean mRequireConfirmation;
+
+        // 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;
+
+        // Timestamp when hardware authentication occurred
+        private long mAuthenticatedTimeMs;
+
+        AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId,
+                int userId, IBiometricServiceReceiver receiver, String opPackageName,
+                Bundle bundle, int callingUid, int callingPid, int callingUserId,
+                int modality, boolean requireConfirmation) {
+            mModalitiesWaiting = modalities;
+            mToken = token;
+            mSessionId = sessionId;
+            mUserId = userId;
+            mClientReceiver = receiver;
+            mOpPackageName = opPackageName;
+            mBundle = bundle;
+            mCallingUid = callingUid;
+            mCallingPid = callingPid;
+            mCallingUserId = callingUserId;
+            mModality = modality;
+            mRequireConfirmation = requireConfirmation;
+        }
+
+        boolean isCrypto() {
+            return mSessionId != 0;
+        }
+
+        boolean containsCookie(int cookie) {
+            if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) {
+                return true;
+            }
+            if (mModalitiesMatched != null && mModalitiesMatched.containsValue(cookie)) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private final class BiometricTaskStackListener extends TaskStackListener {
+        @Override
+        public void onTaskStackChanged() {
+            mHandler.sendEmptyMessage(MSG_ON_TASK_STACK_CHANGED);
+        }
+    }
+
     private final AppOpsManager mAppOps;
-    private final Handler mHandler;
     private final boolean mHasFeatureFingerprint;
     private final boolean mHasFeatureIris;
     private final boolean mHasFeatureFace;
     private final SettingObserver mSettingObserver;
     private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
+    private final BiometricTaskStackListener mTaskStackListener = new BiometricTaskStackListener();
+    private final Random mRandom = new Random();
 
     private IFingerprintService mFingerprintService;
     private IFaceService mFaceService;
+    private IActivityTaskManager mActivityTaskManager;
+    private IStatusBarService mStatusBarService;
 
     // Get and cache the available authenticator (manager) classes. Used since aidl doesn't support
     // polymorphism :/
@@ -113,6 +230,108 @@
     // should be safe.
     private int mCurrentModality;
 
+    // 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;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ON_TASK_STACK_CHANGED: {
+                    handleTaskStackChanged();
+                    break;
+                }
+
+                case MSG_ON_AUTHENTICATION_SUCCEEDED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleAuthenticationSucceeded(
+                            (boolean) args.arg1 /* requireConfirmation */,
+                            (byte[]) args.arg2 /* token */);
+                    args.recycle();
+                    break;
+                }
+
+                case MSG_ON_AUTHENTICATION_FAILED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleAuthenticationFailed(
+                            args.argi1 /* cookie */,
+                            (boolean) args.arg1 /* requireConfirmation */);
+                    args.recycle();
+                    break;
+                }
+
+                case MSG_ON_ERROR: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleOnError(
+                            args.argi1 /* cookie */,
+                            args.argi2 /* error */,
+                            (String) args.arg1 /* message */);
+                    args.recycle();
+                    break;
+                }
+
+                case MSG_ON_ACQUIRED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleOnAcquired(
+                            args.argi1 /* acquiredInfo */,
+                            (String) args.arg1 /* message */);
+                    args.recycle();
+                    break;
+                }
+
+                case MSG_ON_DISMISSED: {
+                    handleOnDismissed(msg.arg1);
+                    break;
+                }
+
+                case MSG_ON_TRY_AGAIN_PRESSED: {
+                    handleOnTryAgainPressed();
+                    break;
+                }
+
+                case MSG_ON_READY_FOR_AUTHENTICATION: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleOnReadyForAuthentication(
+                            args.argi1 /* cookie */,
+                            (boolean) args.arg1 /* requireConfirmation */,
+                            args.argi2 /* userId */);
+                    args.recycle();
+                    break;
+                }
+
+                case MSG_AUTHENTICATE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleAuthenticate(
+                            (IBinder) args.arg1 /* token */,
+                            (long) args.arg2 /* sessionId */,
+                            args.argi1 /* userid */,
+                            (IBiometricServiceReceiver) args.arg3 /* receiver */,
+                            (String) args.arg4 /* opPackageName */,
+                            (Bundle) args.arg5 /* bundle */,
+                            args.argi2 /* callingUid */,
+                            args.argi3 /* callingPid */,
+                            args.argi4 /* callingUserId */);
+                    args.recycle();
+                    break;
+                }
+
+                case MSG_CANCEL_AUTHENTICATION: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleCancelAuthentication(
+                            (IBinder) args.arg1 /* token */,
+                            (String) args.arg2 /* opPackageName */);
+                    args.recycle();
+                    break;
+                }
+
+                default:
+                    break;
+            }
+        }
+    };
+
     private final class Authenticator {
         int mType;
         BiometricAuthenticator mAuthenticator;
@@ -251,142 +470,62 @@
         }
     }
 
+    // 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 {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = requireConfirmation;
+            args.arg2 = token;
+            mHandler.obtainMessage(MSG_ON_AUTHENTICATION_SUCCEEDED, args).sendToTarget();
+        }
+
+        @Override
+        public void onAuthenticationFailed(int cookie, boolean requireConfirmation)
+                throws RemoteException {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = cookie;
+            args.arg1 = requireConfirmation;
+            mHandler.obtainMessage(MSG_ON_AUTHENTICATION_FAILED, args).sendToTarget();
+        }
+
+        @Override
+        public void onError(int cookie, int error, String message) throws RemoteException {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = cookie;
+            args.argi2 = error;
+            args.arg1 = message;
+            mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget();
+        }
+
+        @Override
+        public void onAcquired(int acquiredInfo, String message) throws RemoteException {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = acquiredInfo;
+            args.arg1 = message;
+            mHandler.obtainMessage(MSG_ON_ACQUIRED, args).sendToTarget();
+        }
+
+        @Override
+        public void onDialogDismissed(int reason) throws RemoteException {
+            mHandler.obtainMessage(MSG_ON_DISMISSED, reason, 0 /* arg2 */).sendToTarget();
+        }
+
+        @Override
+        public void onTryAgainPressed() {
+            mHandler.sendEmptyMessage(MSG_ON_TRY_AGAIN_PRESSED);
+        }
+    };
+
+
     /**
      * This is just a pass-through service that wraps Fingerprint, Iris, Face services. This service
      * should not carry any state. The reality is we need to keep a tiny amount of state so that
      * cancelAuthentication() can go to the right place.
      */
     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. Only
-         * passive modalities such as Face or Iris should have this state. Note that for passive
-         * modalities, the HAL enters the idle state after onAuthenticated(false) which differs from
-         * fingerprint.
-         */
-        private static final int STATE_AUTH_PAUSED = 3;
-        /**
-         * 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;
-            final boolean mRequireConfirmation;
-
-            // 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;
-
-            // Timestamp when hardware authentication occurred
-            private long mAuthenticatedTimeMs;
-
-            AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId,
-                    int userId, IBiometricServiceReceiver receiver, String opPackageName,
-                    Bundle bundle, int callingUid, int callingPid, int callingUserId,
-                    int modality, boolean requireConfirmation) {
-                mModalitiesWaiting = modalities;
-                mToken = token;
-                mSessionId = sessionId;
-                mUserId = userId;
-                mClientReceiver = receiver;
-                mOpPackageName = opPackageName;
-                mBundle = bundle;
-                mCallingUid = callingUid;
-                mCallingPid = callingPid;
-                mCallingUserId = callingUserId;
-                mModality = modality;
-                mRequireConfirmation = requireConfirmation;
-            }
-
-            boolean isCrypto() {
-                return mSessionId != 0;
-            }
-
-            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)) {
-                            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();
-
         // TODO(b/123378871): Remove when moved.
         // When BiometricPrompt#setAllowDeviceCredentials is set to true, we need to store the
         // client (app) receiver. BiometricService internally launches CDCA which invokes
@@ -395,331 +534,15 @@
         // to this receiver.
         private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver;
 
-        // 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 {
-                    // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
-                    // after user dismissed/canceled dialog).
-                    if (mCurrentAuthSession == null) {
-                        Slog.e(TAG, "onAuthenticationSucceeded(): Auth session is null");
-                        return;
-                    }
-
-                    if (!requireConfirmation) {
-                        mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
-                        KeyStore.getInstance().addAuthToken(token);
-                        mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
-                        mCurrentAuthSession.mState = STATE_AUTH_IDLE;
-                        mCurrentAuthSession = null;
-                    } else {
-                        mCurrentAuthSession.mAuthenticatedTimeMs = System.currentTimeMillis();
-                        // 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(true);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Remote exception", e);
-                }
-            }
-
-            @Override
-            public void onAuthenticationFailed(int cookie, boolean requireConfirmation)
-                    throws RemoteException {
-                try {
-                    // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
-                    // after user dismissed/canceled dialog).
-                    if (mCurrentAuthSession == null) {
-                        Slog.e(TAG, "onAuthenticationFailed(): Auth session is null");
-                        return;
-                    }
-
-                    mStatusBarService.onBiometricAuthenticated(false);
-
-                    // TODO: This logic will need to be updated if BP is multi-modal
-                    if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
-                        // Pause authentication. onBiometricAuthenticated(false) causes the
-                        // dialog to show a "try again" button for passive modalities.
-                        mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
-                    }
-
-                    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);
-                            if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
-                                    mActivityTaskManager.unregisterTaskStackListener(
-                                            mTaskStackListener);
-                                    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 {
-                                        if (mCurrentAuthSession != null) {
-                                            mActivityTaskManager.unregisterTaskStackListener(
-                                                    mTaskStackListener);
-                                            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) {
-                            // 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 {
-                // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
-                // after user dismissed/canceled dialog).
-                if (mCurrentAuthSession == null) {
-                    Slog.e(TAG, "onAcquired(): Auth session is null");
-                    return;
-                }
-
-                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 (mCurrentAuthSession == null) {
-                    Slog.e(TAG, "onDialogDismissed: " + reason + ", auth session null");
-                    return;
-                }
-
-                logDialogDismissed(reason);
-
-                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);
-                });
-            }
-
-            private void logDialogDismissed(int reason) {
-                if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) {
-                    // Explicit auth, authentication confirmed.
-                    // Latency in this case is authenticated -> confirmed. <Biometric>Service
-                    // should have the first half (first acquired -> authenticated).
-                    final long latency = System.currentTimeMillis()
-                            - mCurrentAuthSession.mAuthenticatedTimeMs;
-
-                    if (LoggableMonitor.DEBUG) {
-                        Slog.v(LoggableMonitor.TAG, "Confirmed! Modality: " + statsModality()
-                                + ", User: " + mCurrentAuthSession.mUserId
-                                + ", IsCrypto: " + mCurrentAuthSession.isCrypto()
-                                + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
-                                + ", RequireConfirmation: "
-                                    + mCurrentAuthSession.mRequireConfirmation
-                                + ", State: " + StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED
-                                + ", Latency: " + latency);
-                    }
-
-                    StatsLog.write(StatsLog.BIOMETRIC_AUTHENTICATED,
-                            statsModality(),
-                            mCurrentAuthSession.mUserId,
-                            mCurrentAuthSession.isCrypto(),
-                            BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
-                            mCurrentAuthSession.mRequireConfirmation,
-                            StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
-                            latency);
-                } else {
-                    int error = reason == BiometricPrompt.DISMISSED_REASON_NEGATIVE
-                            ? BiometricConstants.BIOMETRIC_ERROR_NEGATIVE_BUTTON
-                            : reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL
-                                    ? BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED
-                                    : 0;
-                    if (LoggableMonitor.DEBUG) {
-                        Slog.v(LoggableMonitor.TAG, "Dismissed! Modality: " + statsModality()
-                                + ", User: " + mCurrentAuthSession.mUserId
-                                + ", IsCrypto: " + mCurrentAuthSession.isCrypto()
-                                + ", Action: " + BiometricsProtoEnums.ACTION_AUTHENTICATE
-                                + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
-                                + ", Error: " + error);
-                    }
-                    // Auth canceled
-                    StatsLog.write(StatsLog.BIOMETRIC_ERROR_OCCURRED,
-                            statsModality(),
-                            mCurrentAuthSession.mUserId,
-                            mCurrentAuthSession.isCrypto(),
-                            BiometricsProtoEnums.ACTION_AUTHENTICATE,
-                            BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
-                            error,
-                            0 /* vendorCode */);
-                }
-            }
-
-            private int statsModality() {
-                int modality = 0;
-                if (mCurrentAuthSession == null) {
-                    return BiometricsProtoEnums.MODALITY_UNKNOWN;
-                }
-                if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FINGERPRINT)
-                        != 0) {
-                    modality |= BiometricsProtoEnums.MODALITY_FINGERPRINT;
-                }
-                if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_IRIS) != 0) {
-                    modality |= BiometricsProtoEnums.MODALITY_IRIS;
-                }
-                if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FACE) != 0) {
-                    modality |= BiometricsProtoEnums.MODALITY_FACE;
-                }
-                return modality;
-            }
-        };
-
         @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 continuing = 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 (!continuing) {
-                        mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
-                                mInternalReceiver, modality, requireConfirmation, userId);
-                        mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
-                    }
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Remote exception", e);
-                }
-            }
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = cookie;
+            args.arg1 = requireConfirmation;
+            args.argi2 = userId;
+            mHandler.obtainMessage(MSG_ON_READY_FOR_AUTHENTICATION, args).sendToTarget();
         }
 
         @Override // Binder call
@@ -754,25 +577,23 @@
                 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;
+                    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) {
@@ -792,7 +613,8 @@
                             KeyguardManager.class);
                     if (!kgm.isDeviceSecure()) {
                         try {
-                            receiver.onError(BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL,
+                            receiver.onError(
+                                    BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL,
                                     getContext().getString(
                                             R.string.biometric_error_device_not_secured));
                         } catch (RemoteException e) {
@@ -811,160 +633,63 @@
                 return;
             }
 
-            mHandler.post(() -> {
-                final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
-                final int modality = result.first;
-                final int error = result.second;
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = token;
+            args.arg2 = sessionId;
+            args.argi1 = userId;
+            args.arg3 = receiver;
+            args.arg4 = opPackageName;
+            args.arg5 = bundle;
+            args.argi2 = callingUid;
+            args.argi3 = callingPid;
+            args.argi4 = callingUserId;
 
-                // Check for errors, notify callback, and return
-                if (error != BiometricConstants.BIOMETRIC_SUCCESS) {
-                    try {
-                        final String hardwareUnavailable =
-                                getContext().getString(R.string.biometric_error_hw_unavailable);
-                        switch (error) {
-                            case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
-                                receiver.onError(error, hardwareUnavailable);
-                                break;
-                            case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
-                                receiver.onError(error, hardwareUnavailable);
-                                break;
-                            case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
-                                receiver.onError(error,
-                                        getErrorString(modality, error, 0 /* vendorCode */));
-                                break;
-                            default:
-                                Slog.e(TAG, "Unhandled error");
-                                break;
-                        }
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "Unable to send error", e);
-                    }
-                    return;
-                }
-
-                mCurrentModality = modality;
-
-                // Start preparing for authentication. Authentication starts when
-                // all modalities requested have invoked onReadyForAuthentication.
-                authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
-                        callingUid, callingPid, callingUserId, modality);
-            });
+            mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget();
         }
 
         @Override // Binder call
         public void onConfirmDeviceCredentialSuccess() {
             checkInternalPermission();
-            if (mConfirmDeviceCredentialReceiver == null) {
-                Slog.w(TAG, "onCDCASuccess null!");
-                return;
-            }
-            try {
-                mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded();
-            } catch (RemoteException e) {
-                Slog.e(TAG, "RemoteException", e);
-            }
-            mConfirmDeviceCredentialReceiver = null;
+            mHandler.post(() -> {
+                if (mConfirmDeviceCredentialReceiver == null) {
+                    Slog.w(TAG, "onCDCASuccess null!");
+                    return;
+                }
+                try {
+                    mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "RemoteException", e);
+                }
+                mConfirmDeviceCredentialReceiver = null;
+            });
         }
 
         @Override // Binder call
         public void onConfirmDeviceCredentialError(int error, String message) {
             checkInternalPermission();
-            if (mConfirmDeviceCredentialReceiver == null) {
-                Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message);
-                return;
-            }
-            try {
-                mConfirmDeviceCredentialReceiver.onError(error, message);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "RemoteException", e);
-            }
-            mConfirmDeviceCredentialReceiver = null;
-        }
-
-        /**
-         * 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 {
-                boolean requireConfirmation = bundle.getBoolean(
-                        BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
-                if ((modality & TYPE_FACE) != 0) {
-                    // Check if the user has forced confirmation to be required in Settings.
-                    requireConfirmation = requireConfirmation
-                            || mSettingObserver.getFaceAlwaysRequireConfirmation();
+            mHandler.post(() -> {
+                if (mConfirmDeviceCredentialReceiver == null) {
+                    Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message);
+                    return;
                 }
-                // 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, requireConfirmation);
-                mPendingAuthSession.mState = STATE_AUTH_CALLED;
-                // No polymorphism :(
-                if ((modality & TYPE_FINGERPRINT) != 0) {
-                    mFingerprintService.prepareForAuthentication(token, sessionId, userId,
-                            mInternalReceiver, opPackageName, cookie,
-                            callingUid, callingPid, callingUserId);
+                try {
+                    mConfirmDeviceCredentialReceiver.onError(error, message);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "RemoteException", e);
                 }
-                if ((modality & TYPE_IRIS) != 0) {
-                    Slog.w(TAG, "Iris unsupported");
-                }
-                if ((modality & TYPE_FACE) != 0) {
-                    mFaceService.prepareForAuthentication(requireConfirmation,
-                            token, sessionId, userId, mInternalReceiver, opPackageName,
-                            cookie, callingUid, callingPid, callingUserId);
-                }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Unable to start authentication", e);
-            }
+                mConfirmDeviceCredentialReceiver = null;
+            });
         }
 
         @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;
-            }
 
-            // 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)
-                        );
-
-                        mCurrentAuthSession.mState = STATE_AUTH_IDLE;
-                        mCurrentAuthSession = null;
-                        mStatusBarService.hideBiometricDialog();
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "Remote exception", e);
-                    }
-                });
-            } else {
-                cancelInternal(token, opPackageName, true /* fromClient */);
-            }
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = token;
+            args.arg2 = opPackageName;
+            mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION, args).sendToTarget();
         }
 
         @Override // Binder call
@@ -1027,31 +752,6 @@
                 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) {
@@ -1088,7 +788,6 @@
         super(context);
 
         mAppOps = context.getSystemService(AppOpsManager.class);
-        mHandler = new Handler(Looper.getMainLooper());
         mEnabledOnKeyguardCallbacks = new ArrayList<>();
         mSettingObserver = new SettingObserver(mHandler);
 
@@ -1123,6 +822,10 @@
                     ServiceManager.getService(Context.FACE_SERVICE));
         }
 
+        mActivityTaskManager = ActivityTaskManager.getService();
+        mStatusBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+
         // Cache the authenticators
         for (int i = 0; i < FEATURE_ID.length; i++) {
             if (hasFeature(FEATURE_ID[i])) {
@@ -1259,4 +962,491 @@
                 return false;
         }
     }
+
+    private void logDialogDismissed(int reason) {
+        if (reason == BiometricPrompt.DISMISSED_REASON_POSITIVE) {
+            // Explicit auth, authentication confirmed.
+            // Latency in this case is authenticated -> confirmed. <Biometric>Service
+            // should have the first half (first acquired -> authenticated).
+            final long latency = System.currentTimeMillis()
+                    - mCurrentAuthSession.mAuthenticatedTimeMs;
+
+            if (LoggableMonitor.DEBUG) {
+                Slog.v(LoggableMonitor.TAG, "Confirmed! Modality: " + statsModality()
+                        + ", User: " + mCurrentAuthSession.mUserId
+                        + ", IsCrypto: " + mCurrentAuthSession.isCrypto()
+                        + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
+                        + ", RequireConfirmation: "
+                        + mCurrentAuthSession.mRequireConfirmation
+                        + ", State: " + StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED
+                        + ", Latency: " + latency);
+            }
+
+            StatsLog.write(StatsLog.BIOMETRIC_AUTHENTICATED,
+                    statsModality(),
+                    mCurrentAuthSession.mUserId,
+                    mCurrentAuthSession.isCrypto(),
+                    BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
+                    mCurrentAuthSession.mRequireConfirmation,
+                    StatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED,
+                    latency);
+        } else {
+            int error = reason == BiometricPrompt.DISMISSED_REASON_NEGATIVE
+                    ? BiometricConstants.BIOMETRIC_ERROR_NEGATIVE_BUTTON
+                    : reason == BiometricPrompt.DISMISSED_REASON_USER_CANCEL
+                            ? BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED
+                            : 0;
+            if (LoggableMonitor.DEBUG) {
+                Slog.v(LoggableMonitor.TAG, "Dismissed! Modality: " + statsModality()
+                        + ", User: " + mCurrentAuthSession.mUserId
+                        + ", IsCrypto: " + mCurrentAuthSession.isCrypto()
+                        + ", Action: " + BiometricsProtoEnums.ACTION_AUTHENTICATE
+                        + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
+                        + ", Error: " + error);
+            }
+            // Auth canceled
+            StatsLog.write(StatsLog.BIOMETRIC_ERROR_OCCURRED,
+                    statsModality(),
+                    mCurrentAuthSession.mUserId,
+                    mCurrentAuthSession.isCrypto(),
+                    BiometricsProtoEnums.ACTION_AUTHENTICATE,
+                    BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
+                    error,
+                    0 /* vendorCode */);
+        }
+    }
+
+    private int statsModality() {
+        int modality = 0;
+        if (mCurrentAuthSession == null) {
+            return BiometricsProtoEnums.MODALITY_UNKNOWN;
+        }
+        if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FINGERPRINT)
+                != 0) {
+            modality |= BiometricsProtoEnums.MODALITY_FINGERPRINT;
+        }
+        if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_IRIS) != 0) {
+            modality |= BiometricsProtoEnums.MODALITY_IRIS;
+        }
+        if ((mCurrentAuthSession.mModality & BiometricAuthenticator.TYPE_FACE) != 0) {
+            modality |= BiometricsProtoEnums.MODALITY_FACE;
+        }
+        return modality;
+    }
+
+    private void handleTaskStackChanged() {
+        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)) {
+                    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 void handleAuthenticationSucceeded(boolean requireConfirmation, byte[] token) {
+
+        try {
+            // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
+            // after user dismissed/canceled dialog).
+            if (mCurrentAuthSession == null) {
+                Slog.e(TAG, "onAuthenticationSucceeded(): Auth session is null");
+                return;
+            }
+
+            if (!requireConfirmation) {
+                mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+                KeyStore.getInstance().addAuthToken(token);
+                mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
+                mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                mCurrentAuthSession = null;
+            } else {
+                mCurrentAuthSession.mAuthenticatedTimeMs = System.currentTimeMillis();
+                // 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(true);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+        }
+    }
+
+    private void handleAuthenticationFailed(int cookie, boolean requireConfirmation) {
+        try {
+            // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
+            // after user dismissed/canceled dialog).
+            if (mCurrentAuthSession == null) {
+                Slog.e(TAG, "onAuthenticationFailed(): Auth session is null");
+                return;
+            }
+
+            mStatusBarService.onBiometricAuthenticated(false);
+
+            // TODO: This logic will need to be updated if BP is multi-modal
+            if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
+                // Pause authentication. onBiometricAuthenticated(false) causes the
+                // dialog to show a "try again" button for passive modalities.
+                mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
+            }
+
+            mCurrentAuthSession.mClientReceiver.onAuthenticationFailed();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+        }
+    }
+
+    private void handleOnError(int cookie, int error, String message) {
+        Slog.d(TAG, "Error: " + error + " cookie: " + cookie);
+        // Errors can either be from the current auth session or the pending auth session.
+        // 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);
+                    if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
+                        mActivityTaskManager.unregisterTaskStackListener(
+                                mTaskStackListener);
+                        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 {
+                                if (mCurrentAuthSession != null) {
+                                    mActivityTaskManager.unregisterTaskStackListener(
+                                            mTaskStackListener);
+                                    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) {
+                    // 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);
+        }
+    }
+
+    private void handleOnAcquired(int acquiredInfo, String message) {
+        // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
+        // after user dismissed/canceled dialog).
+        if (mCurrentAuthSession == null) {
+            Slog.e(TAG, "onAcquired(): Auth session is null");
+            return;
+        }
+
+        if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
+            try {
+                mStatusBarService.onBiometricHelp(message);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception", e);
+            }
+        }
+    }
+
+    private void handleOnDismissed(int reason) {
+        if (mCurrentAuthSession == null) {
+            Slog.e(TAG, "onDialogDismissed: " + reason + ", auth session null");
+            return;
+        }
+
+        logDialogDismissed(reason);
+
+        try {
+            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;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception", e);
+        }
+    }
+
+    private void handleOnTryAgainPressed() {
+        Slog.d(TAG, "onTryAgainPressed");
+        // No need to check permission, since it can only be invoked by SystemUI
+        // (or system server itself).
+        authenticateInternal(mCurrentAuthSession.mToken,
+                mCurrentAuthSession.mSessionId,
+                mCurrentAuthSession.mUserId,
+                mCurrentAuthSession.mClientReceiver,
+                mCurrentAuthSession.mOpPackageName,
+                mCurrentAuthSession.mBundle,
+                mCurrentAuthSession.mCallingUid,
+                mCurrentAuthSession.mCallingPid,
+                mCurrentAuthSession.mCallingUserId,
+                mCurrentAuthSession.mModality);
+    }
+
+    private void handleOnReadyForAuthentication(int cookie, boolean requireConfirmation,
+            int userId) {
+        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 continuing = 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 (!continuing) {
+                    mStatusBarService.showBiometricDialog(mCurrentAuthSession.mBundle,
+                            mInternalReceiver, modality, requireConfirmation, userId);
+                    mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception", e);
+            }
+        }
+    }
+
+    private void handleAuthenticate(IBinder token, long sessionId, int userId,
+            IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
+            int callingUid, int callingPid, int callingUserId) {
+
+        mHandler.post(() -> {
+            final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
+            final int modality = result.first;
+            final int error = result.second;
+
+            // Check for errors, notify callback, and return
+            if (error != BiometricConstants.BIOMETRIC_SUCCESS) {
+                try {
+                    final String hardwareUnavailable =
+                            getContext().getString(R.string.biometric_error_hw_unavailable);
+                    switch (error) {
+                        case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
+                            receiver.onError(error, hardwareUnavailable);
+                            break;
+                        case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
+                            receiver.onError(error, hardwareUnavailable);
+                            break;
+                        case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
+                            receiver.onError(error,
+                                    getErrorString(modality, error, 0 /* vendorCode */));
+                            break;
+                        default:
+                            Slog.e(TAG, "Unhandled error");
+                            break;
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to send error", e);
+                }
+                return;
+            }
+
+            mCurrentModality = modality;
+
+            // Start preparing for authentication. Authentication starts when
+            // all modalities requested have invoked onReadyForAuthentication.
+            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 {
+            boolean requireConfirmation = bundle.getBoolean(
+                    BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
+            if ((modality & TYPE_FACE) != 0) {
+                // Check if the user has forced confirmation to be required in Settings.
+                requireConfirmation = requireConfirmation
+                        || mSettingObserver.getFaceAlwaysRequireConfirmation();
+            }
+            // 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, requireConfirmation);
+            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(requireConfirmation,
+                        token, sessionId, userId, mInternalReceiver, opPackageName,
+                        cookie, callingUid, callingPid, callingUserId);
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to start authentication", e);
+        }
+    }
+
+    private void handleCancelAuthentication(IBinder token, String opPackageName) {
+        if (token == null || opPackageName == null) {
+            Slog.e(TAG, "Unable to cancel, one or more null arguments");
+            return;
+        }
+
+        // We need to check the current authenticators state. If we're pending confirm
+        // or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client,
+        // since we won't be getting an onError from the driver.
+        if (mCurrentAuthSession != null && mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
+            try {
+                // Send error to client
+                mCurrentAuthSession.mClientReceiver.onError(
+                        BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+                        getContext().getString(
+                                com.android.internal.R.string.biometric_error_user_canceled)
+                );
+
+                mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                mCurrentAuthSession = null;
+                mStatusBarService.hideBiometricDialog();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception", e);
+            }
+        } else {
+            cancelInternal(token, opPackageName, true /* fromClient */);
+        }
+    }
+
+
+    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");
+            }
+        });
+    }
+
 }
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index ebb9e06..3e48445 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -1223,6 +1223,9 @@
                     BiometricsProtoEnums.ISSUE_UNKNOWN_TEMPLATE_ENROLLED_HAL);
         } else {
             clearEnumerateState();
+            if (mPendingClient != null) {
+                startClient(mPendingClient, false /* initiatedByClient */);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 192ad6d..7b691b4 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -28,6 +28,7 @@
 import android.media.Rating;
 import android.media.VolumeProvider;
 import android.media.session.ControllerCallbackLink;
+import android.media.session.ISession;
 import android.media.session.ISessionController;
 import android.media.session.MediaController;
 import android.media.session.MediaController.PlaybackInfo;
@@ -35,7 +36,6 @@
 import android.media.session.MediaSession.QueueItem;
 import android.media.session.PlaybackState;
 import android.media.session.SessionCallbackLink;
-import android.media.session.SessionLink;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -45,6 +45,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.util.Log;
@@ -81,7 +82,7 @@
     private final Bundle mSessionInfo;
     private final ControllerStub mController;
     private final MediaSession.Token mSessionToken;
-    private final SessionLink mSession;
+    private final SessionStub mSession;
     private final SessionCb mSessionCb;
     private final MediaSessionService.ServiceImpl mService;
     private final Context mContext;
@@ -133,7 +134,7 @@
         mSessionInfo = sessionInfo;
         mController = new ControllerStub();
         mSessionToken = new MediaSession.Token(mController);
-        mSession = new SessionLink(new SessionStub());
+        mSession = new SessionStub();
         mSessionCb = new SessionCb(cb);
         mService = service;
         mContext = mService.getContext();
@@ -144,11 +145,11 @@
     }
 
     /**
-     * Get the session link for the {@link MediaSession}.
+     * Get the session binder for the {@link MediaSession}.
      *
-     * @return The session link apps talk to.
+     * @return The session binder apps talk to.
      */
-    public SessionLink getSessionBinder() {
+    public ISession getSessionBinder() {
         return mSession;
     }
 
@@ -818,9 +819,9 @@
         }
     };
 
-    private final class SessionStub extends SessionLink.SessionStub {
+    private final class SessionStub extends ISession.Stub {
         @Override
-        public void destroySession() {
+        public void destroySession() throws RemoteException {
             final long token = Binder.clearCallingIdentity();
             try {
                 mService.destroySession(MediaSessionRecord.this);
@@ -830,18 +831,18 @@
         }
 
         @Override
-        public void sendEvent(String event, Bundle data) {
+        public void sendEvent(String event, Bundle data) throws RemoteException {
             mHandler.post(MessageHandler.MSG_SEND_EVENT, event,
                     data == null ? null : new Bundle(data));
         }
 
         @Override
-        public ISessionController getController() {
+        public ISessionController getController() throws RemoteException {
             return mController;
         }
 
         @Override
-        public void setActive(boolean active) {
+        public void setActive(boolean active) throws RemoteException {
             mIsActive = active;
             final long token = Binder.clearCallingIdentity();
             try {
@@ -853,7 +854,7 @@
         }
 
         @Override
-        public void setFlags(int flags) {
+        public void setFlags(int flags) throws RemoteException {
             if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
                 int pid = Binder.getCallingPid();
                 int uid = Binder.getCallingUid();
@@ -872,7 +873,7 @@
         }
 
         @Override
-        public void setMediaButtonReceiver(PendingIntent pi) {
+        public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException {
             mMediaButtonReceiver = pi;
             final long token = Binder.clearCallingIdentity();
             try {
@@ -883,12 +884,13 @@
         }
 
         @Override
-        public void setLaunchPendingIntent(PendingIntent pi) {
+        public void setLaunchPendingIntent(PendingIntent pi) throws RemoteException {
             mLaunchIntent = pi;
         }
 
         @Override
-        public void setMetadata(MediaMetadata metadata, long duration, String metadataDescription) {
+        public void setMetadata(MediaMetadata metadata, long duration, String metadataDescription)
+                throws RemoteException {
             synchronized (mLock) {
                 MediaMetadata temp = metadata == null ? null : new MediaMetadata.Builder(metadata)
                         .build();
@@ -906,7 +908,7 @@
         }
 
         @Override
-        public void setPlaybackState(PlaybackState state) {
+        public void setPlaybackState(PlaybackState state) throws RemoteException {
             int oldState = mPlaybackState == null
                     ? PlaybackState.STATE_NONE : mPlaybackState.getState();
             int newState = state == null
@@ -924,21 +926,21 @@
         }
 
         @Override
-        public void setQueue(List<QueueItem> queue) {
+        public void setQueue(MediaParceledListSlice queue) throws RemoteException {
             synchronized (mLock) {
-                mQueue = queue;
+                mQueue = queue == null ? null : (List<QueueItem>) queue.getList();
             }
             mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
         }
 
         @Override
-        public void setQueueTitle(CharSequence title) {
+        public void setQueueTitle(CharSequence title) throws RemoteException {
             mQueueTitle = title;
             mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE);
         }
 
         @Override
-        public void setExtras(Bundle extras) {
+        public void setExtras(Bundle extras) throws RemoteException {
             synchronized (mLock) {
                 mExtras = extras == null ? null : new Bundle(extras);
             }
@@ -946,18 +948,18 @@
         }
 
         @Override
-        public void setRatingType(int type) {
+        public void setRatingType(int type) throws RemoteException {
             mRatingType = type;
         }
 
         @Override
-        public void setCurrentVolume(int volume) {
+        public void setCurrentVolume(int volume) throws RemoteException {
             mCurrentVolume = volume;
             mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
         }
 
         @Override
-        public void setPlaybackToLocal(AudioAttributes attributes) {
+        public void setPlaybackToLocal(AudioAttributes attributes) throws RemoteException {
             boolean typeChanged;
             synchronized (mLock) {
                 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
@@ -980,7 +982,7 @@
         }
 
         @Override
-        public void setPlaybackToRemote(int control, int max) {
+        public void setPlaybackToRemote(int control, int max) throws RemoteException {
             boolean typeChanged;
             synchronized (mLock) {
                 typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
index 94f289f..7c8dc74 100644
--- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
@@ -49,12 +49,12 @@
 import android.media.session.ICallback;
 import android.media.session.IOnMediaKeyListener;
 import android.media.session.IOnVolumeKeyLongPressListener;
+import android.media.session.ISession;
 import android.media.session.ISession2TokensListener;
 import android.media.session.ISessionManager;
 import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
 import android.media.session.SessionCallbackLink;
-import android.media.session.SessionLink;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -1001,7 +1001,7 @@
         private boolean mVoiceButtonHandled = false;
 
         @Override
-        public SessionLink createSession(String packageName, SessionCallbackLink cb, String tag,
+        public ISession createSession(String packageName, SessionCallbackLink cb, String tag,
                 Bundle sessionInfo, int userId) throws RemoteException {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5810636..e7c9a08 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -104,7 +104,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
 import android.app.AppOpsManager;
 import android.app.IUiModeManager;
@@ -5114,6 +5113,7 @@
             awakenDreams();
         }
 
+        // Start dock.
         Intent dock = createHomeDockIntent();
         if (dock != null) {
             try {
@@ -5126,21 +5126,9 @@
             }
         }
 
-        Intent intent;
-
-        if (fromHomeKey) {
-            intent = new Intent(mHomeIntent);
-            intent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, fromHomeKey);
-        } else {
-            intent = mHomeIntent;
-        }
-        final Bundle bundle = getLaunchDisplayIdBundle(displayId);
-        startActivityAsUser(intent, bundle, UserHandle.CURRENT);
-    }
-
-    private @Nullable Bundle getLaunchDisplayIdBundle(int displayId) {
-        return (displayId == INVALID_DISPLAY) ? null
-                : ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
+        // Start home.
+        mActivityTaskManagerInternal.startHomeOnDisplay(mCurrentUserId, "startDockOrHome",
+                displayId, fromHomeKey);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 5a20959..b262a00 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -352,6 +352,19 @@
     /** @return The intent used to launch the home activity. */
     public abstract Intent getHomeIntent();
     public abstract boolean startHomeActivity(int userId, String reason);
+    /**
+     * This starts home activity on displays that can have system decorations based on displayId -
+     * Default display always use primary home component.
+     * For Secondary displays, the home activity must have category SECONDARY_HOME and then resolves
+     * according to the priorities listed below.
+     *  - If default home is not set, always use the secondary home defined in the config.
+     *  - Use currently selected primary home activity.
+     *  - Use the activity in the same package as currently selected primary home activity.
+     *    If there are multiple activities matched, use first one.
+     *  - Use the secondary home defined in the config.
+     */
+    public abstract boolean startHomeOnDisplay(int userId, String reason, int displayId,
+            boolean fromHomeKey);
     /** Start home activities on all displays that support system decorations. */
     public abstract boolean startHomeOnAllDisplays(int userId, String reason);
     /** @return true if the given process is the factory test process. */
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a1dbbab..118eb5b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6491,6 +6491,13 @@
         }
 
         @Override
+        public boolean startHomeOnDisplay(int userId, String reason, int displayId,
+                boolean fromHomeKey) {
+            return mRootActivityContainer.startHomeOnDisplay(userId, reason, displayId,
+                    fromHomeKey);
+        }
+
+        @Override
         public boolean startHomeOnAllDisplays(int userId, String reason) {
             synchronized (mGlobalLock) {
                 return mRootActivityContainer.startHomeOnAllDisplays(userId, reason);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index bcfbefe..b00cafd 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3284,7 +3284,11 @@
     }
 
     private void updateImeParent() {
-        final SurfaceControl newParent = computeImeParent();
+        // Force attaching IME to the display when magnifying, or it would be magnified with
+        // target app together.
+        final boolean shouldAttachToDisplay = (mMagnificationSpec != null);
+        final SurfaceControl newParent =
+                shouldAttachToDisplay ? mWindowingLayer : computeImeParent();
         if (newParent != null) {
             mPendingTransaction.reparent(mImeWindowsContainers.mSurfaceControl, newParent);
             scheduleAnimation();
@@ -4699,6 +4703,8 @@
         } else {
             mMagnificationSpec = null;
         }
+        // Re-parent IME's SurfaceControl when MagnificationSpec changed.
+        updateImeParent();
 
         applyMagnificationSpec(getPendingTransaction(), spec);
         getPendingTransaction().apply();
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index f964b57..24cf7f1 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -114,6 +114,7 @@
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.AppTimeTracker;
 import com.android.server.am.UserState;
+import com.android.server.policy.WindowManagerPolicy;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -345,6 +346,10 @@
         }
     }
 
+    boolean startHomeOnDisplay(int userId, String reason, int displayId) {
+        return startHomeOnDisplay(userId, reason, displayId, false /*fromHomeKey*/);
+    }
+
     /**
      * This starts home activity on displays that can have system decorations based on displayId -
      * Default display always use primary home component.
@@ -356,7 +361,12 @@
      *    If there are multiple activities matched, use first one.
      *  - Use the secondary home defined in the config.
      */
-    boolean startHomeOnDisplay(int userId, String reason, int displayId) {
+    boolean startHomeOnDisplay(int userId, String reason, int displayId, boolean fromHomeKey) {
+        // Fallback to top focused display if the displayId is invalid.
+        if (displayId == INVALID_DISPLAY) {
+            displayId = getTopDisplayFocusedStack().mDisplayId;
+        }
+
         Intent homeIntent;
         ActivityInfo aInfo;
         if (displayId == DEFAULT_DISPLAY) {
@@ -380,6 +390,10 @@
         // Updates the home component of the intent.
         homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
         homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
+        // Updates the extra information of the intent.
+        if (fromHomeKey) {
+            homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true);
+        }
         // Update the reason for ANR debugging to verify if the user activity is the one that
         // actually launched.
         final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId(
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index bdb76c2..bd4e542 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -83,7 +83,12 @@
     }
 
     public void systemReady() {
-        mGestureDetector = new GestureDetector(mContext, new FlingGestureDetector(), mHandler);
+        // GestureDetector records statistics about gesture classification events to inform gesture
+        // usage trends. SystemGesturesPointerEventListener creates a lot of noise in these
+        // statistics because it passes every touch event though a GestureDetector. By creating an
+        // anonymous subclass of GestureDetector, these statistics will be recorded with a unique
+        // source name that can be filtered.
+        mGestureDetector = new GestureDetector(mContext, new FlingGestureDetector(), mHandler) {};
     }
 
     @Override
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 2b00363..5f9ef3c 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2315,9 +2315,9 @@
             "undelivered_sms_message_expiration_time";
 
     /**
-     * Specifies a carrier-defined {@link CallRedirectionService} which Telecom will bind
-     * to for outgoing calls.  An empty string indicates that no carrier-defined
-     * {@link CallRedirectionService} is specified.
+     * Specifies a carrier-defined {@link android.telecom.CallRedirectionService} which Telecom
+     * will bind to for outgoing calls.  An empty string indicates that no carrier-defined
+     * {@link android.telecom.CallRedirectionService} is specified.
      * @hide
      */
     public static final String KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING =
@@ -2875,6 +2875,7 @@
         sDefaults.putString(KEY_CARRIER_NAME_STRING, "");
         sDefaults.putString(KEY_SIM_COUNTRY_ISO_OVERRIDE_STRING, "");
         sDefaults.putString(KEY_CARRIER_CALL_SCREENING_APP_STRING, "");
+        sDefaults.putString(KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING, null);
         sDefaults.putBoolean(KEY_CDMA_HOME_REGISTERED_PLMN_NAME_OVERRIDE_BOOL, false);
         sDefaults.putString(KEY_CDMA_HOME_REGISTERED_PLMN_NAME_STRING, "");
         sDefaults.putBoolean(KEY_SUPPORT_DIRECT_FDN_DIALING_BOOL, false);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f7ab921..99032bc 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -58,6 +58,8 @@
 import android.os.WorkSource;
 import android.provider.Settings.SettingNotFoundException;
 import android.service.carrier.CarrierIdentifier;
+import android.telecom.CallScreeningService;
+import android.telecom.InCallService;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -617,7 +619,13 @@
      * <p class="note">
      * Retrieve with
      * {@link android.content.Intent#getStringExtra(String)}.
+     * <p>
+     *
+     * @deprecated Companion apps for wearable devices should use the {@link InCallService} API
+     * to retrieve the phone number for calls instead.  Apps performing call screening should use
+     * the {@link CallScreeningService} API instead.
      */
+    @Deprecated
     public static final String EXTRA_INCOMING_NUMBER = "incoming_number";
 
     /**
@@ -9857,8 +9865,11 @@
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      *
+     *
+     * @deprecated
      * @hide
      */
+    @Deprecated
     @TestApi
     public void setCarrierTestOverride(String mccmnc, String imsi, String iccid, String gid1,
             String gid2, String plmn, String spn) {
@@ -9866,7 +9877,35 @@
             ITelephony telephony = getITelephony();
             if (telephony != null) {
                 telephony.setCarrierTestOverride(
-                        getSubId(), mccmnc, imsi, iccid, gid1, gid2, plmn, spn);
+                        getSubId(), mccmnc, imsi, iccid, gid1, gid2, plmn, spn,
+                        null, null);
+            }
+        } catch (RemoteException ex) {
+            // This could happen if binder process crashes.
+        }
+    }
+
+    /**
+     * A test API to override carrier information including mccmnc, imsi, iccid, gid1, gid2,
+     * plmn, spn, apn and carrier priviledge. This would be handy for, eg, forcing a particular
+     * carrier id, carrier's config (also any country or carrier overlays) to be loaded when using
+     * a test SIM with a call box.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     *
+     * @hide
+     */
+    @TestApi
+    public void setCarrierTestOverride(String mccmnc, String imsi, String iccid, String gid1,
+                                       String gid2, String plmn, String spn,
+                                       String carrierPriviledgeRules, String apn) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.setCarrierTestOverride(
+                        getSubId(), mccmnc, imsi, iccid, gid1, gid2, plmn, spn,
+                        carrierPriviledgeRules, apn);
             }
         } catch (RemoteException ex) {
             // This could happen if binder process crashes.
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index e651783..875bd78 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -285,6 +285,8 @@
      * {@link #ACTION_DELETE_SUBSCRIPTION_PRIVILEGED}, and
      * {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing the ID of the targeted subscription.
      *
+     * <p>Expected type of the extra data: int
+     *
      * @hide
      */
     @SystemApi
@@ -295,6 +297,8 @@
      * Key for an extra set on {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED} providing a boolean
      * value of whether to enable or disable the targeted subscription.
      *
+     * <p>Expected type of the extra data: boolean
+     *
      * @hide
      */
     @SystemApi
@@ -305,6 +309,8 @@
      * Key for an extra set on {@link #ACTION_RENAME_SUBSCRIPTION_PRIVILEGED} providing a new
      * nickname for the targeted subscription.
      *
+     * <p>Expected type of the extra data: String
+     *
      * @hide
      */
     @SystemApi
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 62b92fd..c2c31cc 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1683,7 +1683,7 @@
      * (also any country or carrier overlays) to be loaded when using a test SIM with a call box.
      */
     void setCarrierTestOverride(int subId, String mccmnc, String imsi, String iccid, String gid1,
-            String gid2, String plmn, String spn);
+            String gid2, String plmn, String spn, String carrierPrivilegeRules, String apn);
 
     /**
      * A test API to return installed carrier id list version.
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 7a505a2..950c6f8 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -139,6 +139,8 @@
     private X509Certificate[] mClientCertificateChain;
     private int mEapMethod = Eap.NONE;
     private int mPhase2Method = Phase2.NONE;
+    private boolean mIsAppInstalledDeviceKeyAndCert = false;
+    private boolean mIsAppInstalledCaCert = false;
 
     private static final String TAG = "WifiEnterpriseConfig";
 
@@ -181,6 +183,8 @@
         }
         mEapMethod = source.mEapMethod;
         mPhase2Method = source.mPhase2Method;
+        mIsAppInstalledDeviceKeyAndCert = source.mIsAppInstalledDeviceKeyAndCert;
+        mIsAppInstalledCaCert = source.mIsAppInstalledCaCert;
     }
 
     /**
@@ -224,6 +228,8 @@
         ParcelUtil.writeCertificates(dest, mCaCerts);
         ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
         ParcelUtil.writeCertificates(dest, mClientCertificateChain);
+        dest.writeBoolean(mIsAppInstalledDeviceKeyAndCert);
+        dest.writeBoolean(mIsAppInstalledCaCert);
     }
 
     public static final @android.annotation.NonNull Creator<WifiEnterpriseConfig> CREATOR =
@@ -243,6 +249,8 @@
                     enterpriseConfig.mCaCerts = ParcelUtil.readCertificates(in);
                     enterpriseConfig.mClientPrivateKey = ParcelUtil.readPrivateKey(in);
                     enterpriseConfig.mClientCertificateChain = ParcelUtil.readCertificates(in);
+                    enterpriseConfig.mIsAppInstalledDeviceKeyAndCert = in.readBoolean();
+                    enterpriseConfig.mIsAppInstalledCaCert = in.readBoolean();
                     return enterpriseConfig;
                 }
 
@@ -652,8 +660,10 @@
     public void setCaCertificate(@Nullable X509Certificate cert) {
         if (cert != null) {
             if (cert.getBasicConstraints() >= 0) {
+                mIsAppInstalledCaCert = true;
                 mCaCerts = new X509Certificate[] {cert};
             } else {
+                mCaCerts = null;
                 throw new IllegalArgumentException("Not a CA certificate");
             }
         } else {
@@ -694,10 +704,12 @@
                 if (certs[i].getBasicConstraints() >= 0) {
                     newCerts[i] = certs[i];
                 } else {
+                    mCaCerts = null;
                     throw new IllegalArgumentException("Not a CA certificate");
                 }
             }
             mCaCerts = newCerts;
+            mIsAppInstalledCaCert = true;
         } else {
             mCaCerts = null;
         }
@@ -853,6 +865,7 @@
 
         mClientPrivateKey = privateKey;
         mClientCertificateChain = newCerts;
+        mIsAppInstalledDeviceKeyAndCert = true;
     }
 
     /**
@@ -1147,4 +1160,30 @@
         }
         return true;
     }
+
+    /**
+     * Check if certificate was installed by an app, or manually (not by an app). If true,
+     * certificate and keys will be removed from key storage when this network is removed. If not,
+     * then certificates and keys remain persistent until the user manually removes them.
+     *
+     * @return true if certificate was installed by an app, false if certificate was installed
+     * manually by the user.
+     * @hide
+     */
+    public boolean isAppInstalledDeviceKeyAndCert() {
+        return mIsAppInstalledDeviceKeyAndCert;
+    }
+
+    /**
+     * Check if CA certificate was installed by an app, or manually (not by an app). If true,
+     * CA certificate will be removed from key storage when this network is removed. If not,
+     * then certificates and keys remain persistent until the user manually removes them.
+     *
+     * @return true if CA certificate was installed by an app, false if CA certificate was installed
+     * manually by the user.
+     * @hide
+     */
+    public boolean isAppInstalledCaCert() {
+        return mIsAppInstalledCaCert;
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index 6ec64ff..beed666 100644
--- a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -423,4 +423,68 @@
         mEnterpriseConfig.setPassword(password);
         assertFalse(mEnterpriseConfig.toString().contains(password));
     }
+
+    /** Verifies that certificate ownership flag is set correctly */
+    @Test
+    public void testIsAppInstalledDeviceKeyAndCert() {
+        // First make sure that app didn't install anything
+        assertFalse(mEnterpriseConfig.isAppInstalledDeviceKeyAndCert());
+        assertFalse(mEnterpriseConfig.isAppInstalledCaCert());
+
+        // Then app loads keys via the enterprise config API
+        PrivateKey clientKey = FakeKeys.RSA_KEY1;
+        X509Certificate cert0 = FakeKeys.CLIENT_CERT;
+        X509Certificate cert1 = FakeKeys.CA_CERT1;
+        X509Certificate[] clientChain = new X509Certificate[] {cert0, cert1};
+        mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+        X509Certificate[] result = mEnterpriseConfig.getClientCertificateChain();
+        assertEquals(result.length, 2);
+        assertTrue(result[0] == cert0 && result[1] == cert1);
+        assertTrue(mEnterpriseConfig.getClientCertificate() == cert0);
+
+        // Make sure it is the owner now
+        assertTrue(mEnterpriseConfig.isAppInstalledDeviceKeyAndCert());
+        assertFalse(mEnterpriseConfig.isAppInstalledCaCert());
+    }
+
+    /** Verifies that certificate ownership flag is set correctly */
+    @Test
+    public void testIsAppInstalledCaCert() {
+        // First make sure that app didn't install anything
+        assertFalse(mEnterpriseConfig.isAppInstalledDeviceKeyAndCert());
+        assertFalse(mEnterpriseConfig.isAppInstalledCaCert());
+
+        // Then app loads CA cert via the enterprise config API
+        X509Certificate cert = FakeKeys.CA_CERT1;
+        mEnterpriseConfig.setCaCertificate(cert);
+        X509Certificate result = mEnterpriseConfig.getCaCertificate();
+        assertTrue(result == cert);
+
+        // Make sure it is the owner now
+        assertFalse(mEnterpriseConfig.isAppInstalledDeviceKeyAndCert());
+        assertTrue(mEnterpriseConfig.isAppInstalledCaCert());
+    }
+
+    /** Verifies that certificate ownership flag is set correctly */
+    @Test
+    public void testIsAppInstalledCaCerts() {
+        // First make sure that app didn't install anything
+        assertFalse(mEnterpriseConfig.isAppInstalledDeviceKeyAndCert());
+        assertFalse(mEnterpriseConfig.isAppInstalledCaCert());
+
+        // Then app loads CA cert via the enterprise config API
+        X509Certificate cert0 = FakeKeys.CA_CERT0;
+        X509Certificate cert1 = FakeKeys.CA_CERT1;
+        X509Certificate[] cert = new X509Certificate[] {cert0, cert1};
+
+        mEnterpriseConfig.setCaCertificates(cert);
+        X509Certificate[] result = mEnterpriseConfig.getCaCertificates();
+        assertEquals(result.length, 2);
+        assertTrue(result[0] == cert0 && result[1] == cert1);
+//        assertTrue(mEnterpriseConfig.getClientCertificate() == cert0);
+
+        // Make sure it is the owner now
+        assertFalse(mEnterpriseConfig.isAppInstalledDeviceKeyAndCert());
+        assertTrue(mEnterpriseConfig.isAppInstalledCaCert());
+    }
 }