Merge "Correct SurfaceView background visibility."
diff --git a/Android.bp b/Android.bp
index a5cc89c..afdcadd 100644
--- a/Android.bp
+++ b/Android.bp
@@ -271,6 +271,7 @@
         "core/java/android/os/IThermalService.aidl",
         "core/java/android/os/IUpdateLock.aidl",
         "core/java/android/os/IUserManager.aidl",
+        ":libvibrator_aidl",
         "core/java/android/os/IVibratorService.aidl",
         "core/java/android/os/storage/IStorageManager.aidl",
         "core/java/android/os/storage/IStorageEventListener.aidl",
@@ -561,6 +562,7 @@
         "telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl",
         "telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl",
         "telephony/java/android/telephony/ICellInfoCallback.aidl",
+        "telephony/java/android/telephony/IFinancialSmsCallback.aidl",
         "telephony/java/android/telephony/INetworkService.aidl",
         "telephony/java/android/telephony/INetworkServiceCallback.aidl",
         "telephony/java/com/android/ims/internal/IImsCallSession.aidl",
@@ -726,7 +728,7 @@
         "ext",
     ],
 
-    jarjar_rules: ":framework-hidl-jarjar",
+    jarjar_rules: "jarjar_rules_hidl.txt",
 
     static_libs: [
         "apex_aidl_interface-java",
@@ -738,6 +740,15 @@
         "android.hardware.cas-V1.0-java",
         "android.hardware.contexthub-V1.0-java",
         "android.hardware.health-V1.0-java-constants",
+        "android.hardware.radio-V1.0-java",
+        "android.hardware.radio-V1.1-java",
+        "android.hardware.radio-V1.2-java",
+        "android.hardware.radio-V1.3-java",
+        "android.hardware.radio-V1.4-java",
+        "android.hardware.radio.config-V1.0-java",
+        "android.hardware.radio.config-V1.1-java",
+        "android.hardware.radio.config-V1.2-java",
+        "android.hardware.radio.deprecated-V1.0-java",
         "android.hardware.thermal-V1.0-java-constants",
         "android.hardware.thermal-V1.0-java",
         "android.hardware.thermal-V1.1-java",
@@ -746,15 +757,12 @@
         "android.hardware.usb-V1.0-java-constants",
         "android.hardware.usb-V1.1-java-constants",
         "android.hardware.usb-V1.2-java-constants",
+        "android.hardware.usb.gadget-V1.0-java",
         "android.hardware.vibrator-V1.0-java",
         "android.hardware.vibrator-V1.1-java",
         "android.hardware.vibrator-V1.2-java",
         "android.hardware.vibrator-V1.3-java",
         "android.hardware.wifi-V1.0-java-constants",
-        "android.hardware.radio-V1.0-java",
-        "android.hardware.radio-V1.3-java",
-        "android.hardware.radio-V1.4-java",
-        "android.hardware.usb.gadget-V1.0-java",
         "networkstack-aidl-interfaces-java",
         "netd_aidl_interface-java",
         "devicepolicyprotosnano",
@@ -789,8 +797,11 @@
 }
 
 filegroup {
-    name: "framework-hidl-jarjar",
-    srcs: ["jarjar_rules_hidl.txt"],
+    name: "libvibrator_aidl",
+    srcs: [
+        "core/java/android/os/IExternalVibrationController.aidl",
+        "core/java/android/os/IExternalVibratorService.aidl",
+    ],
 }
 
 java_library {
@@ -817,19 +828,6 @@
 }
 
 // A temporary build target that is conditionally included on the bootclasspath if
-// org.apache.http.legacy library has been removed and which provides support for
-// maintaining backwards compatibility for APKs that target pre-P and depend on
-// org.apache.http.legacy classes. This is used iff REMOVE_OAHL_FROM_BCP=true is
-// specified on the build command line.
-java_library {
-    name: "framework-oahl-backward-compatibility",
-    installable: true,
-    srcs: [
-        "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java",
-    ],
-}
-
-// A temporary build target that is conditionally included on the bootclasspath if
 // android.test.base library has been removed and which provides support for
 // maintaining backwards compatibility for APKs that target pre-P and depend on
 // android.test.base classes. This is used iff REMOVE_ATB_FROM_BCP=true is
@@ -910,6 +908,31 @@
     api_dir: "aidl/networkstack",
 }
 
+filegroup {
+    name: "framework-networkstack-shared-srcs",
+    srcs: [
+        // TODO: remove these annotations as soon as we can use andoid.support.annotations.*
+        "core/java/android/annotation/NonNull.java",
+        "core/java/android/annotation/Nullable.java",
+        "core/java/android/annotation/IntDef.java",
+        "core/java/android/annotation/IntRange.java",
+        "core/java/android/annotation/UnsupportedAppUsage.java",
+        "core/java/android/net/DhcpResults.java",
+        "core/java/android/util/LocalLog.java",
+        "core/java/com/android/internal/annotations/VisibleForTesting.java",
+        "core/java/com/android/internal/util/HexDump.java",
+        "core/java/com/android/internal/util/IndentingPrintWriter.java",
+        "core/java/com/android/internal/util/IState.java",
+        "core/java/com/android/internal/util/MessageUtils.java",
+        "core/java/com/android/internal/util/Preconditions.java",
+        "core/java/com/android/internal/util/RingBufferIndices.java",
+        "core/java/com/android/internal/util/State.java",
+        "core/java/com/android/internal/util/StateMachine.java",
+        "core/java/com/android/internal/util/WakeupMessage.java",
+        "core/java/android/net/shared/*.java",
+    ]
+}
+
 // Build ext.jar
 // ============================================================
 java_library {
diff --git a/api/current.txt b/api/current.txt
index bb6dbeb..2692916 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5101,7 +5101,7 @@
   }
 
   public class KeyguardManager {
-    method public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence);
+    method @Deprecated public android.content.Intent createConfirmDeviceCredentialIntent(CharSequence, CharSequence);
     method @Deprecated @RequiresPermission(android.Manifest.permission.DISABLE_KEYGUARD) public void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult);
     method @Deprecated public boolean inKeyguardRestrictedInputMode();
     method public boolean isDeviceLocked();
@@ -12512,6 +12512,7 @@
     method public int getInt(int);
     method public long getLong(int);
     method public android.net.Uri getNotificationUri();
+    method public default java.util.List<android.net.Uri> getNotificationUris();
     method public int getPosition();
     method public short getShort(int);
     method public String getString(int);
@@ -12535,6 +12536,7 @@
     method public android.os.Bundle respond(android.os.Bundle);
     method public void setExtras(android.os.Bundle);
     method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
+    method public default void setNotificationUris(@NonNull android.content.ContentResolver, @NonNull java.util.List<android.net.Uri>);
     method public void unregisterContentObserver(android.database.ContentObserver);
     method public void unregisterDataSetObserver(android.database.DataSetObserver);
     field public static final int FIELD_TYPE_BLOB = 4; // 0x4
@@ -13947,8 +13949,6 @@
   @AnyThread public abstract class ColorSpace {
     method @NonNull public static android.graphics.ColorSpace adapt(@NonNull android.graphics.ColorSpace, @NonNull @Size(min=2, max=3) float[]);
     method @NonNull public static android.graphics.ColorSpace adapt(@NonNull android.graphics.ColorSpace, @NonNull @Size(min=2, max=3) float[], @NonNull android.graphics.ColorSpace.Adaptation);
-    method @NonNull @Size(3) public static float[] cctToIlluminantdXyz(@IntRange(from=1) int);
-    method @NonNull @Size(9) public static float[] chromaticAdaptation(@NonNull android.graphics.ColorSpace.Adaptation, @NonNull @Size(min=2, max=3) float[], @NonNull @Size(min=2, max=3) float[]);
     method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace);
     method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace, @NonNull android.graphics.ColorSpace.RenderIntent);
     method @NonNull public static android.graphics.ColorSpace.Connector connect(@NonNull android.graphics.ColorSpace);
@@ -16498,6 +16498,7 @@
     ctor public BiometricPrompt.Builder(android.content.Context);
     method public android.hardware.biometrics.BiometricPrompt build();
     method public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
+    method public android.hardware.biometrics.BiometricPrompt.Builder setEnableFallback(boolean);
     method public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
     method public android.hardware.biometrics.BiometricPrompt.Builder setRequireConfirmation(boolean);
     method public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
@@ -23522,6 +23523,8 @@
     method public int setBufferSizeInFrames(int);
     method public int setLoopPoints(int, int, int);
     method public int setNotificationMarkerPosition(int);
+    method public void setOffloadDelayPadding(int, int);
+    method public void setOffloadEndOfStream();
     method public int setPlaybackHeadPosition(int);
     method public void setPlaybackParams(@NonNull android.media.PlaybackParams);
     method public void setPlaybackPositionUpdateListener(android.media.AudioTrack.OnPlaybackPositionUpdateListener);
@@ -34405,8 +34408,12 @@
   }
 
   public abstract class FileObserver {
-    ctor public FileObserver(String);
-    ctor public FileObserver(String, int);
+    ctor @Deprecated public FileObserver(String);
+    ctor public FileObserver(@NonNull java.io.File);
+    ctor public FileObserver(@NonNull java.util.List<java.io.File>);
+    ctor @Deprecated public FileObserver(String, int);
+    ctor public FileObserver(@NonNull java.io.File, int);
+    ctor public FileObserver(@NonNull java.util.List<java.io.File>, int);
     method protected void finalize();
     method public abstract void onEvent(int, @Nullable String);
     method public void startWatching();
@@ -38642,6 +38649,7 @@
     field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_SETTINGS";
     field public static final String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS";
     field public static final String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS";
+    field public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_QR_CODE = "android.settings.PROCESS_WIFI_EASY_CONNECT_QR_CODE";
     field public static final String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS";
     field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE";
@@ -38675,6 +38683,7 @@
     field public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
     field public static final String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
     field public static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
+    field public static final String EXTRA_QR_CODE = "android.provider.extra.QR_CODE";
     field public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
     field public static final String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = "android.intent.category.USAGE_ACCESS_CONFIG";
     field public static final String METADATA_USAGE_ACCESS_REASON = "android.settings.metadata.USAGE_ACCESS_REASON";
@@ -41405,9 +41414,9 @@
     method public int getUser();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
+    field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
     field public static final String KEY_IMPORTANCE = "key_importance";
-    field public static final String KEY_SMART_ACTIONS = "key_smart_actions";
-    field public static final String KEY_SMART_REPLIES = "key_smart_replies";
+    field public static final String KEY_TEXT_REPLIES = "key_text_replies";
     field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
   }
 
@@ -44662,12 +44671,14 @@
 
   public final class SmsManager {
     method public String createAppSpecificSmsToken(android.app.PendingIntent);
+    method @Nullable public String createAppSpecificSmsTokenWithPackageInfo(@Nullable String, @NonNull android.app.PendingIntent);
     method public java.util.ArrayList<java.lang.String> divideMessage(String);
     method public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
     method public android.os.Bundle getCarrierConfigValues();
     method public static android.telephony.SmsManager getDefault();
     method public static int getDefaultSmsSubscriptionId();
     method public static android.telephony.SmsManager getSmsManagerForSubscriptionId(int);
+    method @RequiresPermission(android.Manifest.permission.SMS_FINANCIAL_TRANSACTIONS) public void getSmsMessagesForFinancialApp(android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.SmsManager.FinancialSmsCallback);
     method public int getSubscriptionId();
     method public void injectSmsPdu(byte[], String, android.app.PendingIntent);
     method public void sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent);
@@ -44730,6 +44741,11 @@
     field public static final int STATUS_ON_ICC_UNSENT = 7; // 0x7
   }
 
+  public abstract static class SmsManager.FinancialSmsCallback {
+    ctor public SmsManager.FinancialSmsCallback();
+    method public abstract void onFinancialSmsMessages(android.database.CursorWindow);
+  }
+
   public class SmsMessage {
     method public static int[] calculateLength(CharSequence, boolean);
     method public static int[] calculateLength(String, boolean);
@@ -48568,6 +48584,7 @@
     method public String getName();
     method @Deprecated public int getOrientation();
     method @Deprecated public int getPixelFormat();
+    method @Nullable public android.graphics.ColorSpace getPreferredWideGamutColorSpace();
     method public long getPresentationDeadlineNanos();
     method public void getRealMetrics(android.util.DisplayMetrics);
     method public void getRealSize(android.graphics.Point);
@@ -55931,10 +55948,10 @@
     method @NonNull public android.widget.Magnifier.Builder setCornerRadius(@Px @FloatRange(from=0) float);
     method @NonNull public android.widget.Magnifier.Builder setDefaultSourceToMagnifierOffset(@Px int, @Px int);
     method @NonNull public android.widget.Magnifier.Builder setElevation(@Px @FloatRange(from=0) float);
+    method @NonNull public android.widget.Magnifier.Builder setInitialZoom(@FloatRange(from=0.0f) float);
     method @NonNull public android.widget.Magnifier.Builder setOverlay(@Nullable android.graphics.drawable.Drawable);
     method @NonNull public android.widget.Magnifier.Builder setSize(@Px @IntRange(from=0) int, @Px @IntRange(from=0) int);
     method @NonNull public android.widget.Magnifier.Builder setSourceBounds(int, int, int, int);
-    method @NonNull public android.widget.Magnifier.Builder setZoom(@FloatRange(from=0.0f) float);
   }
 
   public class MediaController extends android.widget.FrameLayout {
diff --git a/api/system-current.txt b/api/system-current.txt
index 9e3e800..66e3642 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -508,11 +508,13 @@
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException;
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException;
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long);
+    method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException;
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setBroadcastSubscriber(android.app.PendingIntent, long, long) throws android.app.StatsManager.StatsUnavailableException;
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent);
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent);
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException;
     field public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
+    field public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
     field public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
     field public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
     field public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
@@ -1265,6 +1267,7 @@
     field public static final String CONTEXTHUB_SERVICE = "contexthub";
     field public static final String EUICC_CARD_SERVICE = "euicc_card";
     field public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
+    field public static final String NETD_SERVICE = "netd";
     field public static final String NETWORK_SCORE_SERVICE = "network_score";
     field public static final String OEM_LOCK_SERVICE = "oem_lock";
     field public static final String PERMISSION_SERVICE = "permission";
@@ -1303,13 +1306,13 @@
     field public static final String ACTION_MANAGE_PERMISSION_APPS = "android.intent.action.MANAGE_PERMISSION_APPS";
     field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_SPECIAL_APP_ACCESSES = "android.intent.action.MANAGE_SPECIAL_APP_ACCESSES";
     field public static final String ACTION_MASTER_CLEAR_NOTIFICATION = "android.intent.action.MASTER_CLEAR_NOTIFICATION";
-    field public static final String ACTION_PACKAGE_ROLLBACK_EXECUTED = "android.intent.action.PACKAGE_ROLLBACK_EXECUTED";
     field public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED";
     field public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
     field public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
     field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_REVIEW_APP_PERMISSION_USAGE = "android.intent.action.REVIEW_APP_PERMISSION_USAGE";
     field public static final String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
     field public static final String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE";
+    field public static final String ACTION_ROLLBACK_COMMITTED = "android.intent.action.ROLLBACK_COMMITTED";
     field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
     field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
     field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
@@ -1695,18 +1698,19 @@
 
   public final class RollbackInfo implements android.os.Parcelable {
     method public int describeContents();
+    method public java.util.List<android.content.rollback.PackageRollbackInfo> getPackages();
     method public int getRollbackId();
+    method public int getSessionId();
+    method public boolean isStaged();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.rollback.RollbackInfo> CREATOR;
-    field public final android.content.rollback.PackageRollbackInfo targetPackage;
   }
 
   public final class RollbackManager {
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void executeRollback(@NonNull android.content.rollback.RollbackInfo, @NonNull android.content.IntentSender);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void commitRollback(int, @NonNull android.content.IntentSender);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void expireRollbackForPackage(@NonNull String);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @Nullable public android.content.rollback.RollbackInfo getAvailableRollback(@NonNull String);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<java.lang.String> getPackagesWithAvailableRollbacks();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyExecutedRollbacks();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public java.util.List<android.content.rollback.RollbackInfo> getAvailableRollbacks();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) @NonNull public java.util.List<android.content.rollback.RollbackInfo> getRecentlyCommittedRollbacks();
     method @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS) public void reloadPersistedData();
   }
 
@@ -4101,25 +4105,10 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void removeAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException;
   }
 
-  public final class IpSecTransform implements java.lang.AutoCloseable {
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "android.permission.PACKET_KEEPALIVE_OFFLOAD"}) public void startNattKeepalive(@NonNull android.net.IpSecTransform.NattKeepaliveCallback, int, @NonNull android.os.Handler) throws java.io.IOException;
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "android.permission.PACKET_KEEPALIVE_OFFLOAD"}) public void stopNattKeepalive();
-  }
-
   public static class IpSecTransform.Builder {
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecTransform buildTunnelModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
   }
 
-  public static class IpSecTransform.NattKeepaliveCallback {
-    ctor public IpSecTransform.NattKeepaliveCallback();
-    method public void onError(int);
-    method public void onStarted();
-    method public void onStopped();
-    field public static final int ERROR_HARDWARE_ERROR = 3; // 0x3
-    field public static final int ERROR_HARDWARE_UNSUPPORTED = 2; // 0x2
-    field public static final int ERROR_INVALID_NETWORK = 1; // 0x1
-  }
-
   public class LinkAddress implements android.os.Parcelable {
     ctor public LinkAddress(java.net.InetAddress, int, int, int);
     ctor public LinkAddress(java.net.InetAddress, int);
@@ -4249,6 +4238,24 @@
     field public final android.net.RssiCurve rssiCurve;
   }
 
+  public final class StaticIpConfiguration implements android.os.Parcelable {
+    ctor public StaticIpConfiguration();
+    ctor public StaticIpConfiguration(android.net.StaticIpConfiguration);
+    method public void addDnsServer(java.net.InetAddress);
+    method public void clear();
+    method public int describeContents();
+    method public java.util.List<java.net.InetAddress> getDnsServers();
+    method public String getDomains();
+    method public java.net.InetAddress getGateway();
+    method public android.net.LinkAddress getIpAddress();
+    method public java.util.List<android.net.RouteInfo> getRoutes(String);
+    method public void setDomains(String);
+    method public void setGateway(java.net.InetAddress);
+    method public void setIpAddress(android.net.LinkAddress);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
+  }
+
   public class TrafficStats {
     method public static void setThreadStatsTagApp();
     method public static void setThreadStatsTagBackup();
@@ -4274,6 +4281,47 @@
 
 }
 
+package android.net.apf {
+
+  public class ApfCapabilities {
+    ctor public ApfCapabilities(int, int, int);
+    method public boolean hasDataAccess();
+    field public final int apfPacketFormat;
+    field public final int apfVersionSupported;
+    field public final int maximumApfProgramSize;
+  }
+
+}
+
+package android.net.captiveportal {
+
+  public final class CaptivePortalProbeResult {
+    ctor public CaptivePortalProbeResult(int);
+    ctor public CaptivePortalProbeResult(int, String, String);
+    ctor public CaptivePortalProbeResult(int, String, String, android.net.captiveportal.CaptivePortalProbeSpec);
+    method public boolean isFailed();
+    method public boolean isPortal();
+    method public boolean isSuccessful();
+    field public static final android.net.captiveportal.CaptivePortalProbeResult FAILED;
+    field public static final int FAILED_CODE = 599; // 0x257
+    field public static final int PORTAL_CODE = 302; // 0x12e
+    field public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS;
+    field public static final int SUCCESS_CODE = 204; // 0xcc
+    field public final String detectUrl;
+    field @Nullable public final android.net.captiveportal.CaptivePortalProbeSpec probeSpec;
+    field public final String redirectUrl;
+  }
+
+  public abstract class CaptivePortalProbeSpec {
+    method public String getEncodedSpec();
+    method public abstract android.net.captiveportal.CaptivePortalProbeResult getResult(int, @Nullable String);
+    method public java.net.URL getUrl();
+    method public static java.util.Collection<android.net.captiveportal.CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(String);
+    method @Nullable public static android.net.captiveportal.CaptivePortalProbeSpec parseSpecOrNull(@Nullable String);
+  }
+
+}
+
 package android.net.metrics {
 
   public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
@@ -5042,6 +5090,7 @@
 package android.os {
 
   public class BatteryManager {
+    method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setChargingStateUpdateDelayMillis(int);
     field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS";
     field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
   }
@@ -5059,6 +5108,36 @@
     method public Object onTransactStarted(android.os.IBinder, int);
   }
 
+  public class BugreportManager {
+    method @RequiresPermission(android.Manifest.permission.DUMP) public void cancelBugreport();
+    method @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback);
+  }
+
+  public abstract static class BugreportManager.BugreportCallback {
+    ctor public BugreportManager.BugreportCallback();
+    method public void onError(int);
+    method public void onFinished();
+    method public void onProgress(float);
+    field public static final int BUGREPORT_ERROR_INVALID_INPUT = 1; // 0x1
+    field public static final int BUGREPORT_ERROR_RUNTIME = 2; // 0x2
+    field public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 4; // 0x4
+    field public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = 3; // 0x3
+  }
+
+  public final class BugreportParams {
+    ctor public BugreportParams(@android.os.BugreportParams.BugreportMode int);
+    method public int getMode();
+    field public static final int BUGREPORT_MODE_FULL = 0; // 0x0
+    field public static final int BUGREPORT_MODE_INTERACTIVE = 1; // 0x1
+    field public static final int BUGREPORT_MODE_REMOTE = 2; // 0x2
+    field public static final int BUGREPORT_MODE_TELEPHONY = 4; // 0x4
+    field public static final int BUGREPORT_MODE_WEAR = 3; // 0x3
+    field public static final int BUGREPORT_MODE_WIFI = 5; // 0x5
+  }
+
+  @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef(prefix={"BUGREPORT_MODE_"}, value={android.os.BugreportParams.BUGREPORT_MODE_FULL, android.os.BugreportParams.BUGREPORT_MODE_INTERACTIVE, android.os.BugreportParams.BUGREPORT_MODE_REMOTE, android.os.BugreportParams.BUGREPORT_MODE_WEAR, android.os.BugreportParams.BUGREPORT_MODE_TELEPHONY, android.os.BugreportParams.BUGREPORT_MODE_WIFI}) public static @interface BugreportParams.BugreportMode {
+  }
+
   public static class Build.VERSION {
     field public static final String PREVIEW_SDK_FINGERPRINT;
   }
@@ -5533,7 +5612,6 @@
     method @Deprecated public final void attachBaseContext(android.content.Context);
     method @Deprecated public final android.os.IBinder onBind(android.content.Intent);
     method @Deprecated public abstract java.util.List<android.content.pm.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(@NonNull String);
-    method @Deprecated public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String);
     field @Deprecated public static final String SERVICE_INTERFACE = "android.permissionpresenterservice.RuntimePermissionPresenterService";
   }
 
@@ -8244,6 +8322,16 @@
     field public final java.util.HashMap<java.lang.String,android.os.Bundle> mParticipants;
   }
 
+  public class ImsException extends java.lang.Exception {
+    ctor public ImsException(@Nullable String);
+    ctor public ImsException(@Nullable String, int);
+    ctor public ImsException(@Nullable String, int, Throwable);
+    method public int getCode();
+    field public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1; // 0x1
+    field public static final int CODE_ERROR_UNSPECIFIED = 0; // 0x0
+    field public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2; // 0x2
+  }
+
   public final class ImsExternalCallState implements android.os.Parcelable {
     ctor public ImsExternalCallState(String, android.net.Uri, android.net.Uri, boolean, int, int, boolean);
     method public int describeContents();
@@ -8261,7 +8349,7 @@
   }
 
   public class ImsMmTelManager {
-    method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(android.content.Context, int);
+    method public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiModeSetting();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiRoamingModeSetting();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAdvancedCallingSettingEnabled();
@@ -8270,8 +8358,8 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiRoamingSettingEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVoWiFiSettingEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isVtSettingEnabled();
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAdvancedCallingSetting(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRttCapabilitySetting(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoWiFiModeSetting(int);
@@ -8706,13 +8794,13 @@
   }
 
   public class ProvisioningManager {
-    method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(android.content.Context, int);
+    method public static android.telephony.ims.ProvisioningManager createForSubscriptionId(int);
     method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
+    method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
     method @WorkerThread @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getProvisioningStringValue(int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
+    method @WorkerThread @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
diff --git a/api/test-current.txt b/api/test-current.txt
index 0f2ba12..049e002 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6,6 +6,7 @@
     field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
     field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
     field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
+    field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
     field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
     field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
     field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
@@ -866,6 +867,24 @@
     field public static final int RTN_UNREACHABLE = 7; // 0x7
   }
 
+  public final class StaticIpConfiguration implements android.os.Parcelable {
+    ctor public StaticIpConfiguration();
+    ctor public StaticIpConfiguration(android.net.StaticIpConfiguration);
+    method public void addDnsServer(java.net.InetAddress);
+    method public void clear();
+    method public int describeContents();
+    method public java.util.List<java.net.InetAddress> getDnsServers();
+    method public String getDomains();
+    method public java.net.InetAddress getGateway();
+    method public android.net.LinkAddress getIpAddress();
+    method public java.util.List<android.net.RouteInfo> getRoutes(String);
+    method public void setDomains(String);
+    method public void setGateway(java.net.InetAddress);
+    method public void setIpAddress(android.net.LinkAddress);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
+  }
+
   public class TrafficStats {
     method public static long getLoopbackRxBytes();
     method public static long getLoopbackRxPackets();
@@ -875,6 +894,47 @@
 
 }
 
+package android.net.apf {
+
+  public class ApfCapabilities {
+    ctor public ApfCapabilities(int, int, int);
+    method public boolean hasDataAccess();
+    field public final int apfPacketFormat;
+    field public final int apfVersionSupported;
+    field public final int maximumApfProgramSize;
+  }
+
+}
+
+package android.net.captiveportal {
+
+  public final class CaptivePortalProbeResult {
+    ctor public CaptivePortalProbeResult(int);
+    ctor public CaptivePortalProbeResult(int, String, String);
+    ctor public CaptivePortalProbeResult(int, String, String, android.net.captiveportal.CaptivePortalProbeSpec);
+    method public boolean isFailed();
+    method public boolean isPortal();
+    method public boolean isSuccessful();
+    field public static final android.net.captiveportal.CaptivePortalProbeResult FAILED;
+    field public static final int FAILED_CODE = 599; // 0x257
+    field public static final int PORTAL_CODE = 302; // 0x12e
+    field public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS;
+    field public static final int SUCCESS_CODE = 204; // 0xcc
+    field public final String detectUrl;
+    field @Nullable public final android.net.captiveportal.CaptivePortalProbeSpec probeSpec;
+    field public final String redirectUrl;
+  }
+
+  public abstract class CaptivePortalProbeSpec {
+    method public String getEncodedSpec();
+    method public abstract android.net.captiveportal.CaptivePortalProbeResult getResult(int, @Nullable String);
+    method public java.net.URL getUrl();
+    method public static java.util.Collection<android.net.captiveportal.CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(String);
+    method @Nullable public static android.net.captiveportal.CaptivePortalProbeSpec parseSpecOrNull(@Nullable String);
+  }
+
+}
+
 package android.net.metrics {
 
   public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
@@ -1053,6 +1113,10 @@
     method public static java.io.File getStorageDirectory();
   }
 
+  public class FileUtils {
+    method public static boolean contains(java.io.File, java.io.File);
+  }
+
   public abstract class HwBinder implements android.os.IHwBinder {
     ctor public HwBinder();
     method public static final void configureRpcThreadpool(long, boolean);
@@ -1218,6 +1282,10 @@
     method public boolean hasSingleFileDescriptor();
   }
 
+  public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable {
+    method public static java.io.File getFile(java.io.FileDescriptor) throws java.io.IOException;
+  }
+
   public final class PowerManager {
     method @RequiresPermission("android.permission.POWER_SAVER") public int getPowerSaveMode();
     method @RequiresPermission("android.permission.POWER_SAVER") public boolean setDynamicPowerSavings(boolean, int);
@@ -1485,17 +1553,37 @@
 
 package android.provider {
 
+  public static final class CalendarContract.Calendars implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.SyncColumns {
+    field public static final String[] SYNC_WRITABLE_COLUMNS;
+  }
+
+  public static final class CalendarContract.Events implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.EventsColumns android.provider.CalendarContract.SyncColumns {
+    field public static final String[] SYNC_WRITABLE_COLUMNS;
+  }
+
+  public final class ContactsContract {
+    field public static final String HIDDEN_COLUMN_PREFIX = "x_";
+  }
+
   public static final class ContactsContract.CommonDataKinds.Phone implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
     field public static final android.net.Uri ENTERPRISE_CONTENT_URI;
   }
 
+  public static final class ContactsContract.PinnedPositions {
+    field public static final String UNDEMOTE_METHOD = "undemote";
+  }
+
   public static final class ContactsContract.RawContactsEntity implements android.provider.BaseColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.RawContactsColumns {
     field public static final android.net.Uri CORP_CONTENT_URI;
   }
 
   public final class MediaStore {
-    method @RequiresPermission("android.permission.CLEAR_APP_USER_DATA") public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
-    method @RequiresPermission("android.permission.CLEAR_APP_USER_DATA") public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
+    method @NonNull public static java.io.File getVolumePath(@NonNull String) throws java.io.FileNotFoundException;
+    method @NonNull public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
+    field public static final String SCAN_FILE_CALL = "scan_file";
+    field public static final String SCAN_VOLUME_CALL = "scan_volume";
   }
 
   public final class Settings {
@@ -1555,6 +1643,10 @@
     field public static final String SMS_CARRIER_PROVISION_ACTION = "android.provider.Telephony.SMS_CARRIER_PROVISION";
   }
 
+  public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
+    field public static final String _DATA = "_data";
+  }
+
 }
 
 package android.security {
@@ -1829,10 +1921,15 @@
   }
 
   public class TelephonyManager {
+    method public int checkCarrierPrivilegesForPackage(String);
     method public int getCarrierIdListVersion();
     method public boolean isRttSupported();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
     method public void setCarrierTestOverride(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
+    field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
     field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff
   }
 
diff --git a/cmds/statsd/OWNERS b/cmds/statsd/OWNERS
index 1315750..deebd4e 100644
--- a/cmds/statsd/OWNERS
+++ b/cmds/statsd/OWNERS
@@ -1,6 +1,7 @@
 bookatz@google.com
 cjyu@google.com
 dwchen@google.com
+gaillard@google.com
 jinyithu@google.com
 joeo@google.com
 kwekua@google.com
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 9c320d3..b26c713 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -989,6 +989,25 @@
     return Status::ok();
 }
 
+Status StatsService::setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender,
+                                                      const String16& packageName,
+                                                      vector<int64_t>* output) {
+    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+
+    IPCThreadState* ipc = IPCThreadState::self();
+    mConfigManager->SetActiveConfigsChangedReceiver(ipc->getCallingUid(), intentSender);
+    //TODO: Return the list of configs that are already active
+    return Status::ok();
+}
+
+Status StatsService::removeActiveConfigsChangedOperation(const String16& packageName) {
+    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+
+    IPCThreadState* ipc = IPCThreadState::self();
+    mConfigManager->RemoveActiveConfigsChangedReceiver(ipc->getCallingUid());
+    return Status::ok();
+}
+
 Status StatsService::removeConfiguration(int64_t key, const String16& packageName) {
     ENFORCE_DUMP_AND_USAGE_STATS(packageName);
 
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index fe0504f..cdff50f 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -132,6 +132,17 @@
                                             const String16& packageName) override;
 
     /**
+     * Binder call to let clients register the active configs changed operation.
+     */
+    virtual Status setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender,
+                                                    const String16& packageName,
+                                                    vector<int64_t>* output) override;
+
+    /**
+     * Binder call to remove the active configs changed operation for the specified package..
+     */
+    virtual Status removeActiveConfigsChangedOperation(const String16& packageName) override;
+    /**
      * Binder call to allow clients to remove the specified configuration.
      */
     virtual Status removeConfiguration(int64_t key,
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index d6b4737f..8e56bef 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -28,6 +28,7 @@
 import "frameworks/base/core/proto/android/bluetooth/hci/enums.proto";
 import "frameworks/base/core/proto/android/bluetooth/hfp/enums.proto";
 import "frameworks/base/core/proto/android/debug/enums.proto";
+import "frameworks/base/core/proto/android/hardware/biometrics/enums.proto";
 import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
 import "frameworks/base/core/proto/android/os/enums.proto";
 import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
@@ -146,9 +147,9 @@
         VibratorStateChanged vibrator_state_changed = 84;
         DeferredJobStatsReported deferred_job_stats_reported = 85;
         ThermalThrottlingStateChanged thermal_throttling = 86;
-        FingerprintAcquired fingerprint_acquired = 87;
-        FingerprintAuthenticated fingerprint_authenticated = 88;
-        FingerprintErrorOccurred fingerprint_error_occurred = 89;
+        BiometricAcquired biometric_acquired = 87;
+        BiometricAuthenticated biometric_authenticated = 88;
+        BiometricErrorOccurred biometric_error_occurred = 89;
         Notification notification = 90;
         BatteryHealthSnapshot battery_health_snapshot = 91;
         SlowIo slow_io = 92;
@@ -163,7 +164,7 @@
         // Consider removing this if it becomes a problem
         ServiceStateChanged service_state_changed = 99;
         ServiceLaunchReported service_launch_reported = 100;
-        PhenotypeFlagStateChanged phenotype_flag_state_changed = 101;
+        FlagFlipUpdateOccurred flag_flip_update_occurred = 101;
         BinaryPushStateChanged binary_push_state_changed = 102;
         DevicePolicyEvent device_policy_event = 103;
         DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104;
@@ -209,10 +210,13 @@
         AdbConnectionChanged adb_connection_changed = 144;
         SpeechDspStatReported speech_dsp_stat_reported = 145;
         UsbContaminantReported usb_contaminant_reported = 146;
+        WatchdogRollbackOccurred watchdog_rollback_occurred = 147;
+        BiometricHalDeathReported biometric_hal_death_reported = 148;
+        BubbleUIChanged bubble_ui_changed = 149;
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10043
+    // Next: 10048
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000;
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -245,7 +249,7 @@
         CategorySize category_size = 10028;
         ProcStats proc_stats = 10029;
         BatteryVoltage battery_voltage = 10030;
-        NumFingerprints num_fingerprints = 10031;
+        NumBiometricsEnrolled num_fingerprints_enrolled = 10031;
         DiskIo disk_io = 10032;
         PowerProfile power_profile = 10033;
         ProcStatsPkgProc proc_stats_pkg_proc = 10034;
@@ -260,6 +264,9 @@
         BatteryLevel battery_level = 10043;
         BuildInformation build_information = 10044;
         BatteryCycleCount battery_cycle_count = 10045;
+        DebugElapsedClock debug_elapsed_clock = 10046;
+        DebugFailingElapsedClock debug_failing_elapsed_clock = 10047;
+        NumBiometricsEnrolled num_faces_enrolled = 10048;
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
@@ -1483,6 +1490,25 @@
     optional android.bluetooth.hci.StatusEnum reason_code = 9;
 }
 
+/**
+ * Logs when a module is rolled back by Watchdog.
+ *
+ * Logged from: Rollback Manager
+ */
+message WatchdogRollbackOccurred {
+    enum RollbackType {
+        UNKNOWN = 0;
+        ROLLBACK_INITIATE = 1;
+        ROLLBACK_SUCCESS = 2;
+        ROLLBACK_FAILURE = 3;
+    }
+    optional RollbackType rollback_type = 1;
+
+    optional string package_name = 2;
+
+    optional int32 package_version_code = 3;
+}
+
 
 /**
  * Logs when something is plugged into or removed from the USB-C connector.
@@ -2351,58 +2377,95 @@
 }
 
 /**
- * Logs when a fingerprint acquire event occurs.
+ * Logs when a biometric acquire event occurs.
  *
  * Logged from:
- *   frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+ *   frameworks/base/services/core/java/com/android/server/biometrics
  */
-message FingerprintAcquired {
-    // The associated user. Eg: 0 for owners, 10+ for others.
-    // Defined in android/os/UserHandle.java
-    optional int32 user = 1;
-    // If this acquire is for a crypto fingerprint.
-    // e.g. Secure purchases, unlock password storage.
-    optional bool is_crypto = 2;
+message BiometricAcquired {
+    // Biometric modality that was acquired.
+    optional android.hardware.biometrics.ModalityEnum modality = 1;
+    // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java.
+    optional int32 user = 2;
+    // If this acquire is for a crypto operation. e.g. Secure purchases, unlock password storage.
+    optional bool is_crypto = 3;
+    // Action that the device is performing. Acquired messages are only expected for enroll and
+    // authenticate. Other actions may indicate an error.
+    optional android.hardware.biometrics.ActionEnum action = 4;
+    // The client that this acquisition was received for.
+    optional android.hardware.biometrics.ClientEnum client = 5;
+    // Acquired constants, e.g. ACQUIRED_GOOD. See constants defined by <Biometric>Manager.
+    optional int32 acquire_info = 6;
+    // Vendor-specific acquire info. Valid only if acquire_info == ACQUIRED_VENDOR.
+    optional int32 acquire_info_vendor = 7;
 }
 
 /**
- * Logs when a fingerprint authentication event occurs.
+ * Logs when a biometric authentication event occurs.
  *
  * Logged from:
- *   frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+ *   frameworks/base/services/core/java/com/android/server/biometrics
  */
-message FingerprintAuthenticated {
-    // The associated user. Eg: 0 for owners, 10+ for others.
-    // Defined in android/os/UserHandle.java
-    optional int32 user = 1;
-    // If this authentication is for a crypto fingerprint.
-    // e.g. Secure purchases, unlock password storage.
-    optional bool is_crypto = 2;
-    // Whether or not this authentication was successful.
-    optional bool is_authenticated = 3;
-}
+message BiometricAuthenticated {
+    // Biometric modality that was used.
+    optional android.hardware.biometrics.ModalityEnum modality = 1;
+    // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java
+    optional int32 user = 2;
+    // If this authentication is for a crypto operation. e.g. Secure purchases, unlock password
+    // storage.
+    optional bool is_crypto = 3;
+    // The client that this acquisition was received for.
+    optional android.hardware.biometrics.ClientEnum client = 4;
 
-/**
- * Logs when a fingerprint error occurs.
- *
- * Logged from:
- *   frameworks/base/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
- */
-message FingerprintErrorOccurred {
-    // The associated user. Eg: 0 for owners, 10+ for others.
-    // Defined in android/os/UserHandle.java
-    optional int32 user = 1;
-    // If this error is for a crypto fingerprint.
-    // e.g. Secure purchases, unlock password storage.
-    optional bool is_crypto = 2;
-
-    enum Error {
+    enum State {
         UNKNOWN = 0;
-        LOCKOUT = 1;
-        PERMANENT_LOCKOUT = 2;
+        REJECTED = 1;
+        PENDING_CONFIRMATION = 2;
+        CONFIRMED = 3;
     }
-    // The type of error.
-    optional Error error = 3;
+
+    // State of the current auth attempt.
+    optional State state = 5;
+    // Time it took to authenticate. For BiometricPrompt where setRequireConfirmation(false) is
+    // specified and supported by the biometric modality, this is from the first ACQUIRED_GOOD to
+    // AUTHENTICATED. for setRequireConfirmation(true), this is from PENDING_CONFIRMATION to
+    // CONFIRMED.
+    optional int64 latency_millis = 6;
+}
+
+/**
+ * Logs when a biometric error occurs.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/biometrics
+ */
+message BiometricErrorOccurred {
+    // Biometric modality that was used.
+    optional android.hardware.biometrics.ModalityEnum modality = 1;
+    // The associated user. Eg: 0 for owners, 10+ for others. Defined in android/os/UserHandle.java
+    optional int32 user = 2;
+    // If this error is for a crypto operation. e.g. Secure purchases, unlock password storage.
+    optional bool is_crypto = 3;
+    // Action that the device is performing.
+    optional android.hardware.biometrics.ActionEnum action = 4;
+    // The client that this acquisition was received for.
+    optional android.hardware.biometrics.ClientEnum client = 5;
+    // Error constants. See constants defined by <Biometric>Manager. Enums won't work since errors
+    // are unique to modality.
+    optional int32 error_info = 6;
+    // Vendor-specific error info. Valid only if acquire_info == ACQUIRED_VENDOR. These are defined
+    // by the vendor and not specified by the HIDL interface.
+    optional int32 error_info_vendor = 7;
+}
+
+/**
+ * Logs when a biometric HAL has crashed.
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/biometrics
+ */
+message BiometricHalDeathReported {
+    // Biometric modality.
+    optional android.hardware.biometrics.ModalityEnum modality = 1;
 }
 
 message Notification {
@@ -2476,18 +2539,14 @@
 }
 
 /*
- * Logs when a flag flip state changes.
- * Logged in P/h.
+ * Logs when a flag flip update occurrs. Used for mainline modules that update via flag flips.
  */
-message PhenotypeFlagStateChanged {
-    // Mendel configuration name.
-    optional string mendel_config_name = 1;
-    // State
-    enum State {
-        STATE_UNKNOWN = 0;
-        COMMITTED = 1;
-    }
-    optional State state = 2;
+message FlagFlipUpdateOccurred {
+    // If the event is from a flag config package, specify the package name.
+    optional string flag_flip_package_name = 1;
+
+    // The order id of the package
+    optional int64 order_id = 2;
 }
 
 /*
@@ -3349,14 +3408,14 @@
 /**
  * Pulls the number of fingerprints for each user.
  *
- * Pulled from StatsCompanionService, which queries FingerprintManager.
+ * Pulled from StatsCompanionService, which queries <Biometric>Manager.
  */
-message NumFingerprints {
+message NumBiometricsEnrolled {
     // The associated user. Eg: 0 for owners, 10+ for others.
     // Defined in android/os/UserHandle.java
     optional int32 user = 1;
     // Number of fingerprints registered to that user.
-    optional int32 num_fingerprints = 2;
+    optional int32 num_enrolled = 2;
 }
 
 message AggStats {
@@ -4557,3 +4616,86 @@
     optional string id = 1;
     optional android.service.usb.ContaminantPresenceStatus status = 2;
 }
+
+/**
+ * This atom is for debugging purpose.
+ */
+message DebugElapsedClock {
+    // Monotically increasing value for each pull.
+    optional int64 pull_count = 1;
+    // Time from System.elapsedRealtime.
+    optional int64 elapsed_clock_millis = 2;
+    // Time from System.elapsedRealtime.
+    optional int64 same_elapsed_clock_millis = 3;
+    // Diff between current elapsed time and elapsed time from previous pull.
+    optional int64 elapsed_clock_diff_millis = 4;
+
+    enum Type {
+      TYPE_UNKNOWN = 0;
+      ALWAYS_PRESENT = 1;
+      PRESENT_ON_ODD_PULLS = 2;
+    }
+    // Type of behavior for the pulled data.
+    optional Type type = 5;
+}
+
+/**
+ * This atom is for debugging purpose.
+ */
+message DebugFailingElapsedClock {
+    // Monotically increasing value for each pull.
+    optional int64 pull_count = 1;
+    // Time from System.elapsedRealtime.
+    optional int64 elapsed_clock_millis = 2;
+    // Time from System.elapsedRealtime.
+    optional int64 same_elapsed_clock_millis = 3;
+    // Diff between current elapsed time and elapsed time from previous pull.
+    optional int64 elapsed_clock_diff_millis = 4;
+}
+
+/** Logs System UI bubbles event changed.
+ *
+ * Logged from:
+ *     frameworks/base/packages/SystemUI/src/com/android/systemui/bubbles
+ */
+message BubbleUIChanged {
+
+    // The app package that is posting the bubble.
+    optional string package_name = 1;
+
+    // The notification channel that is posting the bubble.
+    optional string notification_channel = 2;
+
+    // The notification id associated with the posted bubble.
+    optional int32 notification_id = 3;
+
+    // The position of the bubble within the bubble stack.
+    optional int32 position = 4;
+
+    // The total number of bubbles within the bubble stack.
+    optional int32 total_number = 5;
+
+    // User interactions with the bubble.
+    enum Action {
+        UNKNOWN = 0;
+        POSTED = 1;
+        UPDATED = 2;
+        EXPANDED = 3;
+        COLLAPSED = 4;
+        DISMISSED = 5;
+        STACK_DISMISSED = 6;
+        STACK_MOVED = 7;
+        HEADER_GO_TO_APP = 8;
+        HEADER_GO_TO_SETTINGS = 9;
+        PERMISSION_OPT_IN = 10;
+        PERMISSION_OPT_OUT = 11;
+        PERMISSION_IGNORED = 12;
+        SWIPE_LEFT = 13;
+        SWIPE_RIGHT = 14;
+    }
+    optional Action action = 6;
+
+    // Normalized screen position of the bubble stack. The range is between 0 and 1.
+    optional float normalized_x_position = 7;
+    optional float normalized_y_position = 8;
+}
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 5fea90b..aa22333 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -128,6 +128,17 @@
     mConfigReceivers.erase(key);
 }
 
+void ConfigManager::SetActiveConfigsChangedReceiver(const int uid,
+                                                    const sp<IBinder>& intentSender) {
+    lock_guard<mutex> lock(mMutex);
+    mActiveConfigsChangedReceivers[uid] = intentSender;
+}
+
+void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) {
+    lock_guard<mutex> lock(mMutex);
+    mActiveConfigsChangedReceivers.erase(uid);
+}
+
 void ConfigManager::RemoveConfig(const ConfigKey& key) {
     vector<sp<ConfigListener>> broadcastList;
     {
@@ -181,6 +192,11 @@
                 mConfigReceivers.erase(*it);
         }
 
+        auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid);
+        if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) {
+            mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver);
+        }
+
         mConfigs.erase(uidIt);
 
         for (const sp<ConfigListener>& listener : mListeners) {
@@ -213,6 +229,7 @@
         }
 
         mConfigReceivers.clear();
+        mActiveConfigsChangedReceivers.clear();
         for (const sp<ConfigListener>& listener : mListeners) {
             broadcastList.push_back(listener);
         }
@@ -250,6 +267,17 @@
     }
 }
 
+const sp<android::IBinder> ConfigManager::GetActiveConfigsChangedReceiver(const int uid) const {
+    lock_guard<mutex> lock(mMutex);
+
+    auto it = mActiveConfigsChangedReceivers.find(uid);
+    if (it == mActiveConfigsChangedReceivers.end()) {
+        return nullptr;
+    } else {
+        return it->second;
+    }
+}
+
 void ConfigManager::Dump(FILE* out) {
     lock_guard<mutex> lock(mMutex);
 
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index 122e669..c064a51 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -82,6 +82,23 @@
     void RemoveConfigReceiver(const ConfigKey& key);
 
     /**
+     * Sets the broadcast receiver that is notified whenever the list of active configs
+     * changes for this uid.
+     */
+    void SetActiveConfigsChangedReceiver(const int uid, const sp<IBinder>& intentSender);
+
+    /**
+     * Returns the broadcast receiver for active configs changed for this uid.
+     */
+
+    const sp<IBinder> GetActiveConfigsChangedReceiver(const int uid) const;
+
+    /**
+     * Erase any active configs changed broadcast receiver associated with this uid.
+     */
+    void RemoveActiveConfigsChangedReceiver(const int uid);
+
+    /**
      * A configuration was removed.
      *
      * Reports this to listeners.
@@ -130,6 +147,12 @@
     std::map<ConfigKey, sp<android::IBinder>> mConfigReceivers;
 
     /**
+     * Each uid can be subscribed by up to one receiver to notify that the list of active configs
+     * for this uid has changed. The receiver is specified as IBinder from PendingIntent.
+     */
+     std::map<int, sp<android::IBinder>> mActiveConfigsChangedReceivers;
+
+    /**
      * The ConfigListeners that will be told about changes.
      */
     std::vector<sp<ConfigListener>> mListeners;
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 7e56bee..ba7bcc4 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -175,8 +175,8 @@
         {android::util::CATEGORY_SIZE,
          {.puller = new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}},
         // Number of fingerprints registered to each user.
-        {android::util::NUM_FINGERPRINTS,
-         {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS)}},
+        {android::util::NUM_FINGERPRINTS_ENROLLED,
+         {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS_ENROLLED)}},
         // ProcStats.
         {android::util::PROC_STATS,
          {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS)}},
@@ -209,6 +209,14 @@
         {android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER,
          {.puller = new StatsCompanionServicePuller(
                   android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}},
+        // DebugElapsedClock.
+        {android::util::DEBUG_ELAPSED_CLOCK,
+         {.additiveFields = {1, 2, 3, 4},
+          .puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}},
+        // DebugFailingElapsedClock.
+        {android::util::DEBUG_FAILING_ELAPSED_CLOCK,
+         {.additiveFields = {1, 2, 3, 4},
+          .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}},
         // BuildInformation.
         {android::util::BUILD_INFORMATION,
          {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}},
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index ee2fe8f..8e7a58b 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -901,7 +901,6 @@
 Landroid/os/ParcelableParcel;->getParcel()Landroid/os/Parcel;
 Landroid/os/ParcelFileDescriptor;-><init>(Ljava/io/FileDescriptor;)V
 Landroid/os/ParcelFileDescriptor;->fromData([BLjava/lang/String;)Landroid/os/ParcelFileDescriptor;
-Landroid/os/ParcelFileDescriptor;->getFile(Ljava/io/FileDescriptor;)Ljava/io/File;
 Landroid/os/ParcelFileDescriptor;->seekTo(J)J
 Landroid/os/PerformanceCollector;-><init>()V
 Landroid/os/PerformanceCollector;->beginSnapshot(Ljava/lang/String;)V
diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java
index b556033..da45054 100644
--- a/core/java/android/app/AppOpsManagerInternal.java
+++ b/core/java/android/app/AppOpsManagerInternal.java
@@ -78,7 +78,7 @@
     /**
      * Sets the app-ops mode for a certain app-op and uid.
      *
-     * <p>Similar as {@link AppOpsManager#setMode} but does not require the package manager to be
+     * <p>Similar as {@link AppOpsManager#setUidMode} but does not require the package manager to be
      * working. Hence this can be used very early during boot.
      *
      * <p>Only for internal callers. Does <u>not</u> verify that package name belongs to uid.
@@ -88,4 +88,12 @@
      * @param mode The new mode to set.
      */
     public abstract void setUidMode(int code, int uid, int mode);
+
+    /**
+     * Set all {@link #setMode (package) modes} for this uid to the default value.
+     *
+     * @param code The app-op
+     * @param uid The uid
+     */
+    public abstract void setAllPkgModesToDefault(int code, int uid);
 }
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 181acce..f522d71 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -29,6 +29,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.hardware.biometrics.BiometricPrompt;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -87,6 +88,12 @@
             "android.app.action.CONFIRM_FRP_CREDENTIAL";
 
     /**
+     * @hide
+     */
+    public static final String EXTRA_BIOMETRIC_PROMPT_BUNDLE =
+            "android.app.extra.BIOMETRIC_PROMPT_BUNDLE";
+
+    /**
      * A CharSequence dialog title to show to the user when used with a
      * {@link #ACTION_CONFIRM_DEVICE_CREDENTIAL}.
      * @hide
@@ -101,12 +108,6 @@
     public static final String EXTRA_DESCRIPTION = "android.app.extra.DESCRIPTION";
 
     /**
-     * A boolean value to forward to {@link android.hardware.biometrics.BiometricPrompt}.
-     * @hide
-     */
-    public static final String EXTRA_USE_IMPLICIT = "android.app.extra.USE_IMPLICIT";
-
-    /**
      * A CharSequence description to show to the user on the alternate button when used with
      * {@link #ACTION_CONFIRM_FRP_CREDENTIAL}.
      * @hide
@@ -124,44 +125,23 @@
     public static final int RESULT_ALTERNATE = 1;
 
     /**
-     * Get an intent to prompt the user to confirm credentials (pin, pattern or password)
-     * for the current user of the device. The caller is expected to launch this activity using
-     * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
+     * @deprecated see {@link BiometricPrompt.Builder#setEnableFallback(boolean)}
+     *
+     * Get an intent to prompt the user to confirm credentials (pin, pattern, password or biometrics
+     * if enrolled) for the current user of the device. The caller is expected to launch this
+     * activity using {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
      * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
      *
-     * @param title Title to be shown on the dialog.
-     * @param description Description to be shown on the dialog.
      * @return the intent for launching the activity or null if no password is required.
      **/
+    @Deprecated
     @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
-    public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description) {
-        return createConfirmDeviceCredentialIntent(title, description, false /* useImplicit */);
-    }
-
-    /**
-     * Get an intent to prompt the user to confirm credentials (pin, pattern or password)
-     * for the current user of the device. The caller is expected to launch this activity using
-     * {@link android.app.Activity#startActivityForResult(Intent, int)} and check for
-     * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
-     *
-     * @param title Title to be shown on the dialog.
-     * @param description Description to be shown on the dialog.
-     * @param useImplicit If useImplicit is set to true, ConfirmDeviceCredentials will invoke
-     *      {@link android.hardware.biometrics.BiometricPrompt} with
-     *      {@link android.hardware.biometrics.BiometricPrompt.Builder#setRequireConfirmation(
-     *      boolean)} set to false.
-     * @return the intent for launching the activity or null if no password is required.
-     * @hide
-     */
-    public Intent createConfirmDeviceCredentialIntent(CharSequence title, CharSequence description,
-            boolean useImplicit) {
-        if (!isDeviceSecure()) {
-            return null;
-        }
+    public Intent createConfirmDeviceCredentialIntent(CharSequence title,
+            CharSequence description) {
+        if (!isDeviceSecure()) return null;
         Intent intent = new Intent(ACTION_CONFIRM_DEVICE_CREDENTIAL);
         intent.putExtra(EXTRA_TITLE, title);
         intent.putExtra(EXTRA_DESCRIPTION, description);
-        intent.putExtra(EXTRA_USE_IMPLICIT, useImplicit);
 
         // explicitly set the package for security
         intent.setPackage(getSettingsPackageForIntent(intent));
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index 9dcd122..9b66c92 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -73,6 +73,11 @@
      */
     public static final String EXTRA_STATS_DIMENSIONS_VALUE =
             "android.app.extra.STATS_DIMENSIONS_VALUE";
+    /**
+     * Long array extra of the active configs for the uid that added those configs.
+     */
+    public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS =
+            "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
 
     /**
      * Broadcast Action: Statsd has started.
@@ -274,6 +279,43 @@
         }
     }
 
+    /**
+     * Registers the operation that is called whenever there is a change in which configs are
+     * active. This must be called each time statsd starts. This operation allows
+     * statsd to inform clients that they should pull data of the configs that are currently
+     * active. The activeConfigsChangedOperation should set periodic alarms to pull data of configs
+     * that are active and stop pulling data of configs that are no longer active.
+     *
+     * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
+     *                      associated with the given subscriberId. May be null, in which case
+     *                      it removes any associated pending intent for this client.
+     * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service
+     */
+    @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS })
+    public void setActiveConfigsChangedOperation(@Nullable PendingIntent pendingIntent)
+            throws StatsUnavailableException {
+        synchronized (this) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (pendingIntent == null) {
+                    service.removeActiveConfigsChangedOperation(mContext.getOpPackageName());
+                } else {
+                    // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
+                    IBinder intentSender = pendingIntent.getTarget().asBinder();
+                    service.setActiveConfigsChangedOperation(intentSender,
+                            mContext.getOpPackageName());
+                }
+
+            } catch (RemoteException e) {
+                Slog.e(TAG,
+                        "Failed to connect to statsd when registering active configs listener.");
+                throw new StatsUnavailableException("could not connect", e);
+            } catch (SecurityException e) {
+                throw new StatsUnavailableException(e.getMessage(), e);
+            }
+        }
+    }
+
     // TODO: Temporary for backwards compatibility. Remove.
     /**
      * @deprecated Use {@link #setFetchReportsOperation(PendingIntent, long)}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 2dc225a..ee13164 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -102,6 +102,7 @@
 import android.net.IEthernetManager;
 import android.net.IIpMemoryStore;
 import android.net.IIpSecService;
+import android.net.INetd;
 import android.net.INetworkPolicyManager;
 import android.net.IpMemoryStore;
 import android.net.IpSecManager;
@@ -328,6 +329,14 @@
                 return new ConnectivityManager(context, service);
             }});
 
+        registerService(Context.NETD_SERVICE, INetd.class, new StaticServiceFetcher<INetd>() {
+            @Override
+            public INetd createService() throws ServiceNotFoundException {
+                return INetd.Stub.asInterface(
+                        ServiceManager.getServiceOrThrow(Context.NETD_SERVICE));
+            }
+        });
+
         registerService(Context.NETWORK_STACK_SERVICE, NetworkStack.class,
                 new StaticServiceFetcher<NetworkStack>() {
                     @Override
diff --git a/core/java/android/app/role/IRoleManager.aidl b/core/java/android/app/role/IRoleManager.aidl
index 2964fbc..cf62e8d 100644
--- a/core/java/android/app/role/IRoleManager.aidl
+++ b/core/java/android/app/role/IRoleManager.aidl
@@ -18,6 +18,8 @@
 
 import android.app.role.IOnRoleHoldersChangedListener;
 import android.app.role.IRoleManagerCallback;
+import android.os.Bundle;
+import android.telephony.IFinancialSmsCallback;
 
 /**
  * @hide
@@ -52,4 +54,8 @@
     List<String> getHeldRolesFromController(in String packageName);
 
     String getDefaultSmsPackage(int userId);
+    /**
+     * Get filtered SMS messages for financial app.
+     */
+    void getSmsMessagesForFinancialApp(in String callingPkg, in Bundle params, in IFinancialSmsCallback callback);
 }
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 171c2f5..c4bf1eb 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -434,7 +434,7 @@
      * {@inheritDoc}
      */
     @Override
-    public int getConnectionState(BluetoothDevice device) {
+    public @BtProfileState int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
         try {
             mServiceLock.readLock().lock();
@@ -689,7 +689,7 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
+    public @Nullable BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
         try {
             mServiceLock.readLock().lock();
diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java
index 78560d2..2cb7b2d 100644
--- a/core/java/android/bluetooth/BluetoothCodecStatus.java
+++ b/core/java/android/bluetooth/BluetoothCodecStatus.java
@@ -16,6 +16,7 @@
 
 package android.bluetooth;
 
+import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -42,7 +43,7 @@
     public static final String EXTRA_CODEC_STATUS =
             "android.bluetooth.codec.extra.CODEC_STATUS";
 
-    private final BluetoothCodecConfig mCodecConfig;
+    private final @Nullable BluetoothCodecConfig mCodecConfig;
     private final BluetoothCodecConfig[] mCodecsLocalCapabilities;
     private final BluetoothCodecConfig[] mCodecsSelectableCapabilities;
 
@@ -140,7 +141,7 @@
      * @return the current codec configuration
      */
     @UnsupportedAppUsage
-    public BluetoothCodecConfig getCodecConfig() {
+    public @Nullable BluetoothCodecConfig getCodecConfig() {
         return mCodecConfig;
     }
 
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index b8670db..ef77596 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -18,11 +18,14 @@
 package android.bluetooth;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Build;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -60,6 +63,16 @@
     /** The profile is in disconnecting state */
     int STATE_DISCONNECTING = 3;
 
+    /** @hide */
+    @IntDef({
+            STATE_DISCONNECTED,
+            STATE_CONNECTING,
+            STATE_CONNECTED,
+            STATE_DISCONNECTING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BtProfileState {}
+
     /**
      * Headset and Handsfree profile
      */
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 6704a45..47a4a2d 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -87,6 +87,7 @@
  * <p>For more information about using a ContentResolver with content providers, read the
  * <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a>
  * developer guide.</p>
+ * </div>
  */
 public abstract class ContentResolver implements ContentInterface {
     /**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 280f1ac..87f9e46 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3637,6 +3637,16 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.net.INetd} for communicating with the network stack
+     * @hide
+     * @see #getSystemService(String)
+     * @hide
+     */
+    @SystemApi
+    public static final String NETD_SERVICE = "netd";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link NetworkStack} for communicating with the network stack
      * @hide
      * @see #getSystemService(String)
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9e7aaf6..22f73db 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2375,8 +2375,7 @@
     public static final String ACTION_PACKAGE_ENABLE_ROLLBACK =
             "android.intent.action.PACKAGE_ENABLE_ROLLBACK";
     /**
-     * Broadcast Action: An existing version of an application package has been
-     * rolled back to a previous version.
+     * Broadcast Action: A rollback has been committed.
      *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
@@ -2385,8 +2384,8 @@
      */
     @SystemApi
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_PACKAGE_ROLLBACK_EXECUTED =
-            "android.intent.action.PACKAGE_ROLLBACK_EXECUTED";
+    public static final String ACTION_ROLLBACK_COMMITTED =
+            "android.intent.action.ROLLBACK_COMMITTED";
     /**
      * @hide
      * Broadcast Action: Ask system services if there is any reason to
diff --git a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
index 2faf3ad..4f4c34b 100644
--- a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
+++ b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
@@ -27,5 +27,4 @@
  */
 oneway interface IRuntimePermissionPresenter {
     void getAppPermissions(String packageName, in RemoteCallback callback);
-    void revokeRuntimePermission(String packageName, String permissionName);
 }
diff --git a/core/java/android/content/rollback/IRollbackManager.aidl b/core/java/android/content/rollback/IRollbackManager.aidl
index 420bcb6..226d76a 100644
--- a/core/java/android/content/rollback/IRollbackManager.aidl
+++ b/core/java/android/content/rollback/IRollbackManager.aidl
@@ -17,20 +17,16 @@
 package android.content.rollback;
 
 import android.content.pm.ParceledListSlice;
-import android.content.pm.StringParceledListSlice;
 import android.content.rollback.RollbackInfo;
 import android.content.IntentSender;
 
 /** {@hide} */
 interface IRollbackManager {
 
-    RollbackInfo getAvailableRollback(String packageName);
-
-    StringParceledListSlice getPackagesWithAvailableRollbacks();
-
+    ParceledListSlice getAvailableRollbacks();
     ParceledListSlice getRecentlyExecutedRollbacks();
 
-    void executeRollback(in RollbackInfo rollback, String callerPackageName,
+    void commitRollback(int rollbackId, String callerPackageName,
             in IntentSender statusReceiver);
 
     // Exposed for use from the system server only. Callback from the package
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index 0803a7c..812f995 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -17,9 +17,12 @@
 package android.content.rollback;
 
 import android.annotation.SystemApi;
+import android.content.pm.PackageInstaller;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.List;
+
 /**
  * Information about a set of packages that can be, or already have been
  * rolled back together.
@@ -34,25 +37,17 @@
      */
     private final int mRollbackId;
 
-    /**
-     * The package that needs to be rolled back.
-     */
-    public final PackageRollbackInfo targetPackage;
-
-    // TODO: Add a list of additional packages rolled back due to atomic
-    // install dependencies when rollback of atomic installs is supported.
-    // TODO: Add a flag to indicate if reboot is required, when rollback of
-    // staged installs is supported.
+    private final List<PackageRollbackInfo> mPackages;
 
     /** @hide */
-    public RollbackInfo(int rollbackId, PackageRollbackInfo targetPackage) {
+    public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages) {
         this.mRollbackId = rollbackId;
-        this.targetPackage = targetPackage;
+        this.mPackages = packages;
     }
 
     private RollbackInfo(Parcel in) {
         mRollbackId = in.readInt();
-        targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
+        mPackages = in.createTypedArrayList(PackageRollbackInfo.CREATOR);
     }
 
     /**
@@ -62,6 +57,31 @@
         return mRollbackId;
     }
 
+    /**
+     * Returns the list of package that are rolled back.
+     */
+    public List<PackageRollbackInfo> getPackages() {
+        return mPackages;
+    }
+
+    /**
+     * Returns true if this rollback requires reboot to take effect after
+     * being committed.
+     */
+    public boolean isStaged() {
+        // TODO: Support rollback of staged installs.
+        return false;
+    }
+
+    /**
+     * Returns the session ID for the committed rollback for staged rollbacks.
+     * Only applicable for rollbacks that have been committed.
+     */
+    public int getSessionId() {
+        // TODO: Support rollback of staged installs.
+        return PackageInstaller.SessionInfo.INVALID_ID;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -70,7 +90,7 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mRollbackId);
-        targetPackage.writeToParcel(out, flags);
+        out.writeTypedList(mPackages);
     }
 
     public static final Parcelable.Creator<RollbackInfo> CREATOR =
diff --git a/core/java/android/content/rollback/RollbackManager.java b/core/java/android/content/rollback/RollbackManager.java
index c1c0bc1..f8abcfc 100644
--- a/core/java/android/content/rollback/RollbackManager.java
+++ b/core/java/android/content/rollback/RollbackManager.java
@@ -17,7 +17,6 @@
 package android.content.rollback;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -50,55 +49,26 @@
     }
 
     /**
-     * Returns the rollback currently available to be executed for the given
-     * package.
-     * <p>
-     * The returned RollbackInfo describes what packages would be rolled back,
-     * including package version codes before and after rollback. The rollback
-     * can be initiated using {@link #executeRollback(RollbackInfo,IntentSender)}.
-     * <p>
-     * TODO: What if there is no package installed on device for packageName?
+     * Returns a list of all currently available rollbacks.
      *
-     * @param packageName name of the package to get the availble RollbackInfo for.
-     * @return the rollback available for the package, or null if no rollback
-     *         is available for the package.
      * @throws SecurityException if the caller does not have the
      *            MANAGE_ROLLBACKS permission.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
-    public @Nullable RollbackInfo getAvailableRollback(@NonNull String packageName) {
+    public List<RollbackInfo> getAvailableRollbacks() {
         try {
-            return mBinder.getAvailableRollback(packageName);
+            return mBinder.getAvailableRollbacks().getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Gets the names of packages that are available for rollback.
-     * Call {@link #getAvailableRollback(String)} to get more information
-     * about the rollback available for a particular package.
-     *
-     * @return the names of packages that are available for rollback.
-     * @throws SecurityException if the caller does not have the
-     *            MANAGE_ROLLBACKS permission.
-     */
-    @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
-    public @NonNull List<String> getPackagesWithAvailableRollbacks() {
-        try {
-            return mBinder.getPackagesWithAvailableRollbacks().getList();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-
-    /**
-     * Gets the list of all recently executed rollbacks.
+     * Gets the list of all recently committed rollbacks.
      * This is for the purposes of preventing re-install of a bad version of a
-     * package.
+     * package and monitoring the status of a staged rollback.
      * <p>
-     * Returns an empty list if there are no recently executed rollbacks.
+     * Returns an empty list if there are no recently committed rollbacks.
      * <p>
      * To avoid having to keep around complete rollback history forever on a
      * device, the returned list of rollbacks is only guaranteed to include
@@ -107,12 +77,12 @@
      * (without the possibility of rollback) to a higher version code than was
      * rolled back from.
      *
-     * @return the recently executed rollbacks
+     * @return the recently committed rollbacks
      * @throws SecurityException if the caller does not have the
      *            MANAGE_ROLLBACKS permission.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
-    public @NonNull List<RollbackInfo> getRecentlyExecutedRollbacks() {
+    public @NonNull List<RollbackInfo> getRecentlyCommittedRollbacks() {
         try {
             return mBinder.getRecentlyExecutedRollbacks().getList();
         } catch (RemoteException e) {
@@ -121,28 +91,26 @@
     }
 
     /**
-     * Execute the given rollback, rolling back all versions of the packages
-     * to the last good versions previously installed on the device as
-     * specified in the given rollback object. The rollback will fail if any
-     * of the installed packages or available rollbacks are inconsistent with
-     * the versions specified in the given rollback object, which can happen
-     * if a package has been updated or a rollback expired since the rollback
-     * object was retrieved from {@link #getAvailableRollback(String)}.
+     * Commit the rollback with given id, rolling back all versions of the
+     * packages to the last good versions previously installed on the device
+     * as specified in the corresponding RollbackInfo object. The
+     * rollback will fail if any of the installed packages or available
+     * rollbacks are inconsistent with the versions specified in the given
+     * rollback object, which can happen if a package has been updated or a
+     * rollback expired since the rollback object was retrieved from
+     * {@link #getAvailableRollbacks()}.
      * <p>
      * TODO: Specify the returns status codes.
-     * TODO: What happens in case reboot is required for the rollback to take
-     * effect for staged installs?
      *
-     * @param rollback to execute
+     * @param rollbackId ID of the rollback to commit
      * @param statusReceiver where to deliver the results
      * @throws SecurityException if the caller does not have the
      *            MANAGE_ROLLBACKS permission.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
-    public void executeRollback(@NonNull RollbackInfo rollback,
-            @NonNull IntentSender statusReceiver) {
+    public void commitRollback(int rollbackId, @NonNull IntentSender statusReceiver) {
         try {
-            mBinder.executeRollback(rollback, mCallerPackageName, statusReceiver);
+            mBinder.commitRollback(rollbackId, mCallerPackageName, statusReceiver);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index 8bcaa45..b44458a 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -16,17 +16,20 @@
 
 package android.database;
 
+import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.UserHandle;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.lang.ref.WeakReference;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 
@@ -72,6 +75,7 @@
 
     @UnsupportedAppUsage
     private Uri mNotifyUri;
+    private List<Uri> mNotifyUris;
 
     private final Object mSelfObserverLock = new Object();
     private ContentObserver mSelfObserver;
@@ -155,7 +159,11 @@
     @Override
     public boolean requery() {
         if (mSelfObserver != null && mSelfObserverRegistered == false) {
-            mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
+            final int size = mNotifyUris.size();
+            for (int i = 0; i < size; ++i) {
+                final Uri notifyUri = mNotifyUris.get(i);
+                mContentResolver.registerContentObserver(notifyUri, true, mSelfObserver);
+            }
             mSelfObserverRegistered = true;
         }
         mDataSetObservable.notifyChanged();
@@ -384,8 +392,12 @@
     protected void onChange(boolean selfChange) {
         synchronized (mSelfObserverLock) {
             mContentObservable.dispatchChange(selfChange, null);
-            if (mNotifyUri != null && selfChange) {
-                mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
+            if (mNotifyUris != null && selfChange) {
+                final int size = mNotifyUris.size();
+                for (int i = 0; i < size; ++i) {
+                    final Uri notifyUri = mNotifyUris.get(i);
+                    mContentResolver.notifyChange(notifyUri, mSelfObserver);
+                }
             }
         }
     }
@@ -399,19 +411,33 @@
      */
     @Override
     public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
-        setNotificationUri(cr, notifyUri, cr.getUserId());
+        setNotificationUris(cr, Arrays.asList(notifyUri));
+    }
+
+    @Override
+    public void setNotificationUris(@NonNull ContentResolver cr, @NonNull List<Uri> notifyUris) {
+        Preconditions.checkNotNull(cr);
+        Preconditions.checkNotNull(notifyUris);
+
+        setNotificationUris(cr, notifyUris, cr.getUserId());
     }
 
     /** @hide - set the notification uri but with an observer for a particular user's view */
-    public void setNotificationUri(ContentResolver cr, Uri notifyUri, int userHandle) {
+    public void setNotificationUris(ContentResolver cr, List<Uri> notifyUris, int userHandle) {
         synchronized (mSelfObserverLock) {
-            mNotifyUri = notifyUri;
+            mNotifyUris = notifyUris;
+            mNotifyUri = mNotifyUris.get(0);
             mContentResolver = cr;
             if (mSelfObserver != null) {
                 mContentResolver.unregisterContentObserver(mSelfObserver);
             }
             mSelfObserver = new SelfContentObserver(this);
-            mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver, userHandle);
+            final int size = mNotifyUris.size();
+            for (int i = 0; i < size; ++i) {
+                final Uri notifyUri = mNotifyUris.get(i);
+                mContentResolver.registerContentObserver(
+                        notifyUri, true, mSelfObserver, userHandle);
+            }
             mSelfObserverRegistered = true;
         }
     }
@@ -424,6 +450,13 @@
     }
 
     @Override
+    public List<Uri> getNotificationUris() {
+        synchronized (mSelfObserverLock) {
+            return mNotifyUris;
+        }
+    }
+
+    @Override
     public boolean getWantsAllOnMoveCalls() {
         return false;
     }
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index 5a4280e..1379138 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -16,11 +16,14 @@
 
 package android.database;
 
+import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.net.Uri;
 import android.os.Bundle;
 
 import java.io.Closeable;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * This interface provides random read-write access to the result set returned
@@ -421,7 +424,10 @@
     /**
      * Register to watch a content URI for changes. This can be the URI of a specific data row (for 
      * example, "content://my_provider_type/23"), or a a generic URI for a content type.
-     * 
+     *
+     * <p>Calling this overrides any previous call to
+     * {@link #setNotificationUris(ContentResolver, List)}.
+     *
      * @param cr The content resolver from the caller's context. The listener attached to 
      * this resolver will be notified.
      * @param uri The content URI to watch.
@@ -429,6 +435,24 @@
     void setNotificationUri(ContentResolver cr, Uri uri);
 
     /**
+     * Similar to {@link #setNotificationUri(ContentResolver, Uri)}, except this version allows
+     * to watch multiple content URIs for changes.
+     *
+     * <p>If this is not implemented, this is equivalent to calling
+     * {@link #setNotificationUri(ContentResolver, Uri)} with the first URI in {@code uris}.
+     *
+     * <p>Calling this overrides any previous call to
+     * {@link #setNotificationUri(ContentResolver, Uri)}.
+     *
+     * @param cr The content resolver from the caller's context. The listener attached to
+     * this resolver will be notified.
+     * @param uris The content URIs to watch.
+     */
+    default void setNotificationUris(@NonNull ContentResolver cr, @NonNull List<Uri> uris) {
+        setNotificationUri(cr, uris.get(0));
+    }
+
+    /**
      * Return the URI at which notifications of changes in this Cursor's data
      * will be delivered, as previously set by {@link #setNotificationUri}.
      * @return Returns a URI that can be used with
@@ -439,6 +463,22 @@
     Uri getNotificationUri();
 
     /**
+     * Return the URIs at which notifications of changes in this Cursor's data
+     * will be delivered, as previously set by {@link #setNotificationUris}.
+     *
+     * <p>If this is not implemented, this is equivalent to calling {@link #getNotificationUri()}.
+     *
+     * @return Returns URIs that can be used with
+     * {@link ContentResolver#registerContentObserver(android.net.Uri, boolean, ContentObserver)
+     * ContentResolver.registerContentObserver} to find out about changes to this Cursor's
+     * data. May be null if no notification URI has been set.
+     */
+    default List<Uri> getNotificationUris() {
+        final Uri notifyUri = getNotificationUri();
+        return notifyUri == null ? null : Arrays.asList(notifyUri);
+    }
+
+    /**
      * onMove() will only be called across processes if this method returns true.
      * @return whether all cursor movement should result in a call to onMove().
      */
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
index 0d27dfb..c9cafaf 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -21,6 +21,8 @@
 import android.net.Uri;
 import android.os.Bundle;
 
+import java.util.List;
+
 /**
  * Wrapper class for Cursor that delegates all calls to the actual cursor object.  The primary
  * use for this class is to extend a cursor while overriding only a subset of its methods.
@@ -241,11 +243,21 @@
     }
 
     @Override
+    public void setNotificationUris(ContentResolver cr, List<Uri> uris) {
+        mCursor.setNotificationUris(cr, uris);
+    }
+
+    @Override
     public Uri getNotificationUri() {
         return mCursor.getNotificationUri();
     }
 
     @Override
+    public List<Uri> getNotificationUris() {
+        return mCursor.getNotificationUris();
+    }
+
+    @Override
     public void unregisterContentObserver(ContentObserver observer) {
         mCursor.unregisterContentObserver(observer);
     }
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index 9d37d99..209afb8 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -252,12 +252,55 @@
     public static final int FACE_ACQUIRED_TOO_SIMILAR = 15;
 
     /**
+     * The magnitude of the pan angle of the user’s face with respect to the sensor’s
+     * capture plane is too high.
+     *
+     * The pan angle is defined as the angle swept out by the user’s face turning
+     * their neck left and right. The pan angle would be zero if the user faced the
+     * camera directly.
+     *
+     * The user should be informed to look more directly at the camera.
+     */
+    public static final int FACE_ACQUIRED_PAN_TOO_EXTREME = 16;
+
+    /**
+     * The magnitude of the tilt angle of the user’s face with respect to the sensor’s
+     * capture plane is too high.
+     *
+     * The tilt angle is defined as the angle swept out by the user’s face looking up
+     * and down. The pan angle would be zero if the user faced the camera directly.
+     *
+     * The user should be informed to look more directly at the camera.
+     */
+    public static final int FACE_ACQUIRED_TILT_TOO_EXTREME = 17;
+
+    /**
+     * The magnitude of the roll angle of the user’s face with respect to the sensor’s
+     * capture plane is too high.
+     *
+     * The roll angle is defined as the angle swept out by the user’s face tilting their head
+     * towards their shoulders to the left and right. The pan angle would be zero if the user
+     * faced the camera directly.
+     *
+     * The user should be informed to look more directly at the camera.
+     */
+    public static final int FACE_ACQUIRED_ROLL_TOO_EXTREME = 18;
+
+    /**
+     * The user’s face has been obscured by some object.
+     *
+     * The user should be informed to remove any objects from the line of sight from
+     * the sensor to the user’s face.
+     */
+    public static final int FACE_ACQUIRED_FACE_OBSCURED = 19;
+
+    /**
      * Hardware vendors may extend this list if there are conditions that do not fall under one of
      * the above categories. Vendors are responsible for providing error strings for these errors.
      *
      * @hide
      */
-    public static final int FACE_ACQUIRED_VENDOR = 16;
+    public static final int FACE_ACQUIRED_VENDOR = 20;
 
     /**
      * @hide
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 8aac1bf..5afe1a9 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -171,5 +171,39 @@
             Slog.w(TAG, "resetTimeout(): Service not connected");
         }
     }
+
+    /**
+     * TODO(b/123378871): Remove when moved.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void onConfirmDeviceCredentialSuccess() {
+        if (mService != null) {
+            try {
+                mService.onConfirmDeviceCredentialSuccess();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Slog.w(TAG, "onConfirmDeviceCredentialSuccess(): Service not connected");
+        }
+    }
+
+    /**
+     * TODO(b/123378871): Remove when moved.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void onConfirmDeviceCredentialError(int error, String message) {
+        if (mService != null) {
+            try {
+                mService.onConfirmDeviceCredentialError(error, message);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Slog.w(TAG, "onConfirmDeviceCredentialError(): Service not connected");
+        }
+    }
 }
 
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index c69b68e4..ec62aba 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -77,6 +77,10 @@
      * @hide
      */
     public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation";
+    /**
+     * @hide
+     */
+    public static final String KEY_ENABLE_FALLBACK = "enable_fallback";
 
     /**
      * Error/help message will show for this amount of time.
@@ -242,6 +246,18 @@
         }
 
         /**
+         * The user will first be prompted to authenticate with biometrics, but also given the
+         * option to authenticate with their device PIN, pattern, or password.
+         * @param enable When true, the prompt will fall back to ask for the user's device
+         *               credentials (PIN, pattern, or password).
+         * @return
+         */
+        public Builder setEnableFallback(boolean enable) {
+            mBundle.putBoolean(KEY_ENABLE_FALLBACK, enable);
+            return this;
+        }
+
+        /**
          * Creates a {@link BiometricPrompt}.
          * @return a {@link BiometricPrompt}
          * @throws IllegalArgumentException if any of the required fields are not set.
@@ -250,11 +266,15 @@
             final CharSequence title = mBundle.getCharSequence(KEY_TITLE);
             final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
             final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE);
+            final boolean enableFallback = mBundle.getBoolean(KEY_ENABLE_FALLBACK);
 
             if (TextUtils.isEmpty(title) && !useDefaultTitle) {
                 throw new IllegalArgumentException("Title must be set and non-empty");
-            } else if (TextUtils.isEmpty(negative)) {
+            } else if (TextUtils.isEmpty(negative) && !enableFallback) {
                 throw new IllegalArgumentException("Negative text must be set and non-empty");
+            } else if (!TextUtils.isEmpty(negative) && enableFallback) {
+                throw new IllegalArgumentException("Can't have both negative button behavior"
+                        + " and fallback enabled");
             }
             return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo);
         }
@@ -514,6 +534,9 @@
         if (callback == null) {
             throw new IllegalArgumentException("Must supply a callback");
         }
+        if (mBundle.getBoolean(KEY_ENABLE_FALLBACK)) {
+            throw new IllegalArgumentException("Fallback not supported with crypto");
+        }
         authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId());
     }
 
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index de828f2..e4336d1 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -51,4 +51,12 @@
 
     // Reset the timeout when user authenticates with strong auth (e.g. PIN, pattern or password)
     void resetTimeout(in byte [] token);
+
+    // TODO(b/123378871): Remove when moved.
+    // CDCA needs to send results to BiometricService if it was invoked using BiometricPrompt's
+    // setEnableFallback method, since there's no way for us to intercept onActivityResult.
+    // CDCA is launched from BiometricService (startActivityAsUser) instead of *ForResult.
+    void onConfirmDeviceCredentialSuccess();
+    // TODO(b/123378871): Remove when moved.
+    void onConfirmDeviceCredentialError(int error, String message);
 }
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 7e45441..f3ebd7f 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
+import android.graphics.ColorSpace;
 import android.graphics.Point;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.media.projection.IMediaProjection;
@@ -80,12 +81,20 @@
             new ArrayList<DisplayListenerDelegate>();
 
     private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>();
+    private final ColorSpace mWideColorSpace;
     private int[] mDisplayIdCache;
 
     private int mWifiDisplayScanNestCount;
 
     private DisplayManagerGlobal(IDisplayManager dm) {
         mDm = dm;
+        try {
+            mWideColorSpace =
+                    ColorSpace.get(
+                            ColorSpace.Named.values()[mDm.getPreferredWideGamutColorSpaceId()]);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -495,6 +504,17 @@
     }
 
     /**
+     * Gets the preferred wide gamut color space for all displays.
+     * The wide gamut color space is returned from composition pipeline
+     * based on hardware capability.
+     *
+     * @hide
+     */
+    public ColorSpace getPreferredWideGamutColorSpace() {
+        return mWideColorSpace;
+    }
+
+    /**
      * Sets the global brightness configuration for a given user.
      *
      * @hide
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index aae8afb..5ea8bd6 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -116,4 +116,9 @@
 
     // Get the minimum brightness curve.
     Curve getMinimumBrightnessCurve();
+
+    // Gets the id of the preferred wide gamut color space for all displays.
+    // The wide gamut color space is returned from composition pipeline
+    // based on hardware capability.
+    int getPreferredWideGamutColorSpaceId();
 }
diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java
index b5d8226..6c291c2 100644
--- a/core/java/android/net/DhcpResults.java
+++ b/core/java/android/net/DhcpResults.java
@@ -17,24 +17,39 @@
 package android.net;
 
 import android.annotation.UnsupportedAppUsage;
-import android.net.NetworkUtils;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
 
 import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
  * A simple object for retrieving the results of a DHCP request.
  * Optimized (attempted) for that jni interface
- * TODO - remove when DhcpInfo is deprecated.  Move the remaining api to LinkProperties.
+ * TODO: remove this class and replace with other existing constructs
  * @hide
  */
-public class DhcpResults extends StaticIpConfiguration {
+public final class DhcpResults implements Parcelable {
     private static final String TAG = "DhcpResults";
 
     @UnsupportedAppUsage
+    public LinkAddress ipAddress;
+
+    @UnsupportedAppUsage
+    public InetAddress gateway;
+
+    @UnsupportedAppUsage
+    public final ArrayList<InetAddress> dnsServers = new ArrayList<>();
+
+    @UnsupportedAppUsage
+    public String domains;
+
+    @UnsupportedAppUsage
     public Inet4Address serverAddress;
 
     /** Vendor specific information (from RFC 2132). */
@@ -48,23 +63,36 @@
     @UnsupportedAppUsage
     public int mtu;
 
-    @UnsupportedAppUsage
     public DhcpResults() {
         super();
     }
 
-    @UnsupportedAppUsage
+    /**
+     * Create a {@link StaticIpConfiguration} based on the DhcpResults.
+     */
+    public StaticIpConfiguration toStaticIpConfiguration() {
+        final StaticIpConfiguration s = new StaticIpConfiguration();
+        // All these except dnsServers are immutable, so no need to make copies.
+        s.ipAddress = ipAddress;
+        s.gateway = gateway;
+        s.dnsServers.addAll(dnsServers);
+        s.domains = domains;
+        return s;
+    }
+
     public DhcpResults(StaticIpConfiguration source) {
-        super(source);
+        if (source != null) {
+            ipAddress = source.ipAddress;
+            gateway = source.gateway;
+            dnsServers.addAll(source.dnsServers);
+            domains = source.domains;
+        }
     }
 
     /** copy constructor */
-    @UnsupportedAppUsage
     public DhcpResults(DhcpResults source) {
-        super(source);
-
+        this(source == null ? null : source.toStaticIpConfiguration());
         if (source != null) {
-            // All these are immutable, so no need to make copies.
             serverAddress = source.serverAddress;
             vendorInfo = source.vendorInfo;
             leaseDuration = source.leaseDuration;
@@ -73,6 +101,14 @@
     }
 
     /**
+     * @see StaticIpConfiguration#getRoutes(String)
+     * @hide
+     */
+    public List<RouteInfo> getRoutes(String iface) {
+        return toStaticIpConfiguration().getRoutes(iface);
+    }
+
+    /**
      * Test if this DHCP lease includes vendor hint that network link is
      * metered, and sensitive to heavy data transfers.
      */
@@ -85,7 +121,11 @@
     }
 
     public void clear() {
-        super.clear();
+        ipAddress = null;
+        gateway = null;
+        dnsServers.clear();
+        domains = null;
+        serverAddress = null;
         vendorInfo = null;
         leaseDuration = 0;
         mtu = 0;
@@ -111,20 +151,20 @@
 
         DhcpResults target = (DhcpResults)obj;
 
-        return super.equals((StaticIpConfiguration) obj) &&
-                Objects.equals(serverAddress, target.serverAddress) &&
-                Objects.equals(vendorInfo, target.vendorInfo) &&
-                leaseDuration == target.leaseDuration &&
-                mtu == target.mtu;
+        return toStaticIpConfiguration().equals(target.toStaticIpConfiguration())
+                && Objects.equals(serverAddress, target.serverAddress)
+                && Objects.equals(vendorInfo, target.vendorInfo)
+                && leaseDuration == target.leaseDuration
+                && mtu == target.mtu;
     }
 
-    /** Implement the Parcelable interface */
+    /**
+     * Implement the Parcelable interface
+     */
     public static final Creator<DhcpResults> CREATOR =
         new Creator<DhcpResults>() {
             public DhcpResults createFromParcel(Parcel in) {
-                DhcpResults dhcpResults = new DhcpResults();
-                readFromParcel(dhcpResults, in);
-                return dhcpResults;
+                return readFromParcel(in);
             }
 
             public DhcpResults[] newArray(int size) {
@@ -134,19 +174,26 @@
 
     /** Implement the Parcelable interface */
     public void writeToParcel(Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
+        toStaticIpConfiguration().writeToParcel(dest, flags);
         dest.writeInt(leaseDuration);
         dest.writeInt(mtu);
         NetworkUtils.parcelInetAddress(dest, serverAddress, flags);
         dest.writeString(vendorInfo);
     }
 
-    private static void readFromParcel(DhcpResults dhcpResults, Parcel in) {
-        StaticIpConfiguration.readFromParcel(dhcpResults, in);
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private static DhcpResults readFromParcel(Parcel in) {
+        final StaticIpConfiguration s = StaticIpConfiguration.CREATOR.createFromParcel(in);
+        final DhcpResults dhcpResults = new DhcpResults(s);
         dhcpResults.leaseDuration = in.readInt();
         dhcpResults.mtu = in.readInt();
         dhcpResults.serverAddress = (Inet4Address) NetworkUtils.unparcelInetAddress(in);
         dhcpResults.vendorInfo = in.readString();
+        return dhcpResults;
     }
 
     // Utils for jni population - false on success
@@ -184,25 +231,70 @@
         return false;
     }
 
-    public boolean setServerAddress(String addrString) {
-        try {
-            serverAddress = (Inet4Address) NetworkUtils.numericToInetAddress(addrString);
-        } catch (IllegalArgumentException|ClassCastException e) {
-            Log.e(TAG, "setServerAddress failed with addrString " + addrString);
-            return true;
-        }
-        return false;
+    public LinkAddress getIpAddress() {
+        return ipAddress;
+    }
+
+    public void setIpAddress(LinkAddress ipAddress) {
+        this.ipAddress = ipAddress;
+    }
+
+    public InetAddress getGateway() {
+        return gateway;
+    }
+
+    public void setGateway(InetAddress gateway) {
+        this.gateway = gateway;
+    }
+
+    public List<InetAddress> getDnsServers() {
+        return dnsServers;
+    }
+
+    /**
+     * Add a DNS server to this configuration.
+     */
+    public void addDnsServer(InetAddress server) {
+        dnsServers.add(server);
+    }
+
+    public String getDomains() {
+        return domains;
+    }
+
+    public void setDomains(String domains) {
+        this.domains = domains;
+    }
+
+    public Inet4Address getServerAddress() {
+        return serverAddress;
+    }
+
+    public void setServerAddress(Inet4Address addr) {
+        serverAddress = addr;
+    }
+
+    public int getLeaseDuration() {
+        return leaseDuration;
     }
 
     public void setLeaseDuration(int duration) {
         leaseDuration = duration;
     }
 
+    public String getVendorInfo() {
+        return vendorInfo;
+    }
+
     public void setVendorInfo(String info) {
         vendorInfo = info;
     }
 
-    public void setDomains(String newDomains) {
-        domains = newDomains;
+    public int getMtu() {
+        return mtu;
+    }
+
+    public void setMtu(int mtu) {
+        this.mtu = mtu;
     }
 }
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index 23c8aa3..e519fdf 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -257,7 +257,6 @@
      *
      * @hide
      */
-    @SystemApi
     public static class NattKeepaliveCallback {
         /** The specified {@code Network} is not connected. */
         public static final int ERROR_INVALID_NETWORK = 1;
@@ -288,7 +287,6 @@
      *
      * @hide
      */
-    @SystemApi
     @RequiresPermission(anyOf = {
             android.Manifest.permission.MANAGE_IPSEC_TUNNELS,
             android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD
@@ -331,7 +329,6 @@
      *
      * @hide
      */
-    @SystemApi
     @RequiresPermission(anyOf = {
             android.Manifest.permission.MANAGE_IPSEC_TUNNELS,
             android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index c996d01..39db4fe 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -21,8 +21,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.UnsupportedAppUsage;
+import android.net.shared.Inet4AddressUtils;
 import android.os.Build;
 import android.os.Parcel;
+import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
 import android.util.Pair;
@@ -39,8 +41,6 @@
 import java.util.Locale;
 import java.util.TreeSet;
 
-import android.system.ErrnoException;
-
 /**
  * Native methods for managing network interfaces.
  *
@@ -177,119 +177,37 @@
             FileDescriptor fd) throws IOException;
 
     /**
-     * @see #intToInet4AddressHTL(int)
-     * @deprecated Use either {@link #intToInet4AddressHTH(int)}
-     *             or {@link #intToInet4AddressHTL(int)}
+     * @see Inet4AddressUtils#intToInet4AddressHTL(int)
+     * @deprecated Use either {@link Inet4AddressUtils#intToInet4AddressHTH(int)}
+     *             or {@link Inet4AddressUtils#intToInet4AddressHTL(int)}
      */
     @Deprecated
     @UnsupportedAppUsage
     public static InetAddress intToInetAddress(int hostAddress) {
-        return intToInet4AddressHTL(hostAddress);
+        return Inet4AddressUtils.intToInet4AddressHTL(hostAddress);
     }
 
     /**
-     * Convert a IPv4 address from an integer to an InetAddress (0x04030201 -> 1.2.3.4)
-     *
-     * <p>This method uses the higher-order int bytes as the lower-order IPv4 address bytes,
-     * which is an unusual convention. Consider {@link #intToInet4AddressHTH(int)} instead.
-     * @param hostAddress an int coding for an IPv4 address, where higher-order int byte is
-     *                    lower-order IPv4 address byte
-     */
-    public static Inet4Address intToInet4AddressHTL(int hostAddress) {
-        return intToInet4AddressHTH(Integer.reverseBytes(hostAddress));
-    }
-
-    /**
-     * Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4)
-     * @param hostAddress an int coding for an IPv4 address
-     */
-    public static Inet4Address intToInet4AddressHTH(int hostAddress) {
-        byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)),
-                (byte) (0xff & (hostAddress >> 16)),
-                (byte) (0xff & (hostAddress >> 8)),
-                (byte) (0xff & hostAddress) };
-
-        try {
-            return (Inet4Address) InetAddress.getByAddress(addressBytes);
-        } catch (UnknownHostException e) {
-            throw new AssertionError();
-        }
-    }
-
-    /**
-     * @see #inet4AddressToIntHTL(Inet4Address)
-     * @deprecated Use either {@link #inet4AddressToIntHTH(Inet4Address)}
-     *             or {@link #inet4AddressToIntHTL(Inet4Address)}
+     * @see Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address)
+     * @deprecated Use either {@link Inet4AddressUtils#inet4AddressToIntHTH(Inet4Address)}
+     *             or {@link Inet4AddressUtils#inet4AddressToIntHTL(Inet4Address)}
      */
     @Deprecated
     public static int inetAddressToInt(Inet4Address inetAddr)
             throws IllegalArgumentException {
-        return inet4AddressToIntHTL(inetAddr);
+        return Inet4AddressUtils.inet4AddressToIntHTL(inetAddr);
     }
 
     /**
-     * Convert an IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x01020304)
-     *
-     * <p>This conversion can help order IP addresses: considering the ordering
-     * 192.0.2.1 < 192.0.2.2 < ..., resulting ints will follow that ordering if read as unsigned
-     * integers with {@link Integer#toUnsignedLong}.
-     * @param inetAddr is an InetAddress corresponding to the IPv4 address
-     * @return the IP address as integer
-     */
-    public static int inet4AddressToIntHTH(Inet4Address inetAddr)
-            throws IllegalArgumentException {
-        byte [] addr = inetAddr.getAddress();
-        return ((addr[0] & 0xff) << 24) | ((addr[1] & 0xff) << 16)
-                | ((addr[2] & 0xff) << 8) | (addr[3] & 0xff);
-    }
-
-    /**
-     * Convert a IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x04030201)
-     *
-     * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes,
-     * which is an unusual convention. Consider {@link #inet4AddressToIntHTH(Inet4Address)} instead.
-     * @param inetAddr is an InetAddress corresponding to the IPv4 address
-     * @return the IP address as integer
-     */
-    public static int inet4AddressToIntHTL(Inet4Address inetAddr) {
-        return Integer.reverseBytes(inet4AddressToIntHTH(inetAddr));
-    }
-
-    /**
-     * @see #prefixLengthToV4NetmaskIntHTL(int)
-     * @deprecated Use either {@link #prefixLengthToV4NetmaskIntHTH(int)}
-     *             or {@link #prefixLengthToV4NetmaskIntHTL(int)}
+     * @see Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int)
+     * @deprecated Use either {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTH(int)}
+     *             or {@link Inet4AddressUtils#prefixLengthToV4NetmaskIntHTL(int)}
      */
     @Deprecated
     @UnsupportedAppUsage
     public static int prefixLengthToNetmaskInt(int prefixLength)
             throws IllegalArgumentException {
-        return prefixLengthToV4NetmaskIntHTL(prefixLength);
-    }
-
-    /**
-     * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0xffff8000)
-     * @return the IPv4 netmask as an integer
-     */
-    public static int prefixLengthToV4NetmaskIntHTH(int prefixLength)
-            throws IllegalArgumentException {
-        if (prefixLength < 0 || prefixLength > 32) {
-            throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)");
-        }
-        // (int)a << b is equivalent to a << (b & 0x1f): can't shift by 32 (-1 << 32 == -1)
-        return prefixLength == 0 ? 0 : 0xffffffff << (32 - prefixLength);
-    }
-
-    /**
-     * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0x0080ffff).
-     *
-     * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes,
-     * which is an unusual convention. Consider {@link #prefixLengthToV4NetmaskIntHTH(int)} instead.
-     * @return the IPv4 netmask as an integer
-     */
-    public static int prefixLengthToV4NetmaskIntHTL(int prefixLength)
-            throws IllegalArgumentException {
-        return Integer.reverseBytes(prefixLengthToV4NetmaskIntHTH(prefixLength));
+        return Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL(prefixLength);
     }
 
     /**
@@ -307,17 +225,13 @@
      * @return the network prefix length
      * @throws IllegalArgumentException the specified netmask was not contiguous.
      * @hide
+     * @deprecated use {@link Inet4AddressUtils#netmaskToPrefixLength(Inet4Address)}
      */
     @UnsupportedAppUsage
+    @Deprecated
     public static int netmaskToPrefixLength(Inet4Address netmask) {
-        // inetAddressToInt returns an int in *network* byte order.
-        int i = Integer.reverseBytes(inetAddressToInt(netmask));
-        int prefixLength = Integer.bitCount(i);
-        int trailingZeros = Integer.numberOfTrailingZeros(i);
-        if (trailingZeros != 32 - prefixLength) {
-            throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i));
-        }
-        return prefixLength;
+        // This is only here because some apps seem to be using it (@UnsupportedAppUsage).
+        return Inet4AddressUtils.netmaskToPrefixLength(netmask);
     }
 
 
@@ -408,16 +322,8 @@
      */
     @UnsupportedAppUsage
     public static int getImplicitNetmask(Inet4Address address) {
-        int firstByte = address.getAddress()[0] & 0xff;  // Convert to an unsigned value.
-        if (firstByte < 128) {
-            return 8;
-        } else if (firstByte < 192) {
-            return 16;
-        } else if (firstByte < 224) {
-            return 24;
-        } else {
-            return 32;  // Will likely not end well for other reasons.
-        }
+        // Only here because it seems to be used by apps
+        return Inet4AddressUtils.getImplicitNetmask(address);
     }
 
     /**
@@ -445,28 +351,6 @@
     }
 
     /**
-     * Get a prefix mask as Inet4Address for a given prefix length.
-     *
-     * <p>For example 20 -> 255.255.240.0
-     */
-    public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength)
-            throws IllegalArgumentException {
-        return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength));
-    }
-
-    /**
-     * Get the broadcast address for a given prefix.
-     *
-     * <p>For example 192.168.0.1/24 -> 192.168.0.255
-     */
-    public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength)
-            throws IllegalArgumentException {
-        final int intBroadcastAddr = inet4AddressToIntHTH(addr)
-                | ~prefixLengthToV4NetmaskIntHTH(prefixLength);
-        return intToInet4AddressHTH(intBroadcastAddr);
-    }
-
-    /**
      * Check if IP address type is consistent between two InetAddress.
      * @return true if both are the same type.  False otherwise.
      */
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index 3aa56b9..25bae3c 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -16,10 +16,11 @@
 
 package android.net;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
-import android.net.LinkAddress;
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
 
 import java.net.InetAddress;
 import java.util.ArrayList;
@@ -46,17 +47,22 @@
  *
  * @hide
  */
-public class StaticIpConfiguration implements Parcelable {
+@SystemApi
+@TestApi
+public final class StaticIpConfiguration implements Parcelable {
+    /** @hide */
     @UnsupportedAppUsage
     public LinkAddress ipAddress;
+    /** @hide */
     @UnsupportedAppUsage
     public InetAddress gateway;
+    /** @hide */
     @UnsupportedAppUsage
     public final ArrayList<InetAddress> dnsServers;
+    /** @hide */
     @UnsupportedAppUsage
     public String domains;
 
-    @UnsupportedAppUsage
     public StaticIpConfiguration() {
         dnsServers = new ArrayList<InetAddress>();
     }
@@ -79,6 +85,41 @@
         domains = null;
     }
 
+    public LinkAddress getIpAddress() {
+        return ipAddress;
+    }
+
+    public void setIpAddress(LinkAddress ipAddress) {
+        this.ipAddress = ipAddress;
+    }
+
+    public InetAddress getGateway() {
+        return gateway;
+    }
+
+    public void setGateway(InetAddress gateway) {
+        this.gateway = gateway;
+    }
+
+    public List<InetAddress> getDnsServers() {
+        return dnsServers;
+    }
+
+    public String getDomains() {
+        return domains;
+    }
+
+    public void setDomains(String newDomains) {
+        domains = newDomains;
+    }
+
+    /**
+     * Add a DNS server to this configuration.
+     */
+    public void addDnsServer(InetAddress server) {
+        dnsServers.add(server);
+    }
+
     /**
      * Returns the network routes specified by this object. Will typically include a
      * directly-connected route for the IP address's local subnet and a default route. If the
@@ -86,7 +127,6 @@
      * route to the gateway as well. This configuration is arguably invalid, but it used to work
      * in K and earlier, and other OSes appear to accept it.
      */
-    @UnsupportedAppUsage
     public List<RouteInfo> getRoutes(String iface) {
         List<RouteInfo> routes = new ArrayList<RouteInfo>(3);
         if (ipAddress != null) {
@@ -107,6 +147,7 @@
      * contained in the LinkProperties will not be a complete picture of the link's configuration,
      * because any configuration information that is obtained dynamically by the network (e.g.,
      * IPv6 configuration) will not be included.
+     * @hide
      */
     public LinkProperties toLinkProperties(String iface) {
         LinkProperties lp = new LinkProperties();
@@ -124,6 +165,7 @@
         return lp;
     }
 
+    @Override
     public String toString() {
         StringBuffer str = new StringBuffer();
 
@@ -143,6 +185,7 @@
         return str.toString();
     }
 
+    @Override
     public int hashCode() {
         int result = 13;
         result = 47 * result + (ipAddress == null ? 0 : ipAddress.hashCode());
@@ -168,12 +211,10 @@
     }
 
     /** Implement the Parcelable interface */
-    public static Creator<StaticIpConfiguration> CREATOR =
+    public static final Creator<StaticIpConfiguration> CREATOR =
         new Creator<StaticIpConfiguration>() {
             public StaticIpConfiguration createFromParcel(Parcel in) {
-                StaticIpConfiguration s = new StaticIpConfiguration();
-                readFromParcel(s, in);
-                return s;
+                return readFromParcel(in);
             }
 
             public StaticIpConfiguration[] newArray(int size) {
@@ -182,11 +223,13 @@
         };
 
     /** Implement the Parcelable interface */
+    @Override
     public int describeContents() {
         return 0;
     }
 
     /** Implement the Parcelable interface */
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeParcelable(ipAddress, flags);
         NetworkUtils.parcelInetAddress(dest, gateway, flags);
@@ -197,7 +240,9 @@
         dest.writeString(domains);
     }
 
-    protected static void readFromParcel(StaticIpConfiguration s, Parcel in) {
+    /** @hide */
+    public static StaticIpConfiguration readFromParcel(Parcel in) {
+        final StaticIpConfiguration s = new StaticIpConfiguration();
         s.ipAddress = in.readParcelable(null);
         s.gateway = NetworkUtils.unparcelInetAddress(in);
         s.dnsServers.clear();
@@ -206,5 +251,6 @@
             s.dnsServers.add(NetworkUtils.unparcelInetAddress(in));
         }
         s.domains = in.readString();
+        return s;
     }
 }
diff --git a/core/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java
index f28cdc9..73cf94b 100644
--- a/core/java/android/net/apf/ApfCapabilities.java
+++ b/core/java/android/net/apf/ApfCapabilities.java
@@ -16,11 +16,16 @@
 
 package android.net.apf;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+
 /**
  * APF program support capabilities.
  *
  * @hide
  */
+@SystemApi
+@TestApi
 public class ApfCapabilities {
     /**
      * Version of APF instruction set supported for packet filtering. 0 indicates no support for
diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
index 1634694..7432687 100644
--- a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
+++ b/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
@@ -17,11 +17,15 @@
 package android.net.captiveportal;
 
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 
 /**
  * Result of calling isCaptivePortal().
  * @hide
  */
+@SystemApi
+@TestApi
 public final class CaptivePortalProbeResult {
     public static final int SUCCESS_CODE = 204;
     public static final int FAILED_CODE = 599;
diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
index 57a926a..7ad4ecf 100644
--- a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
+++ b/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
@@ -21,21 +21,26 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.text.ParseException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
 
 /** @hide */
+@SystemApi
+@TestApi
 public abstract class CaptivePortalProbeSpec {
-    public static final String HTTP_LOCATION_HEADER_NAME = "Location";
-
     private static final String TAG = CaptivePortalProbeSpec.class.getSimpleName();
     private static final String REGEX_SEPARATOR = "@@/@@";
     private static final String SPEC_SEPARATOR = "@@,@@";
@@ -55,7 +60,9 @@
      * @throws MalformedURLException The URL has invalid format for {@link URL#URL(String)}.
      * @throws ParseException The string is empty, does not match the above format, or a regular
      * expression is invalid for {@link Pattern#compile(String)}.
+     * @hide
      */
+    @VisibleForTesting
     @NonNull
     public static CaptivePortalProbeSpec parseSpec(String spec) throws ParseException,
             MalformedURLException {
@@ -113,7 +120,8 @@
      * <p>Each spec is separated by @@,@@ and follows the format for {@link #parseSpec(String)}.
      * <p>This method does not throw but ignores any entry that could not be parsed.
      */
-    public static CaptivePortalProbeSpec[] parseCaptivePortalProbeSpecs(String settingsVal) {
+    public static Collection<CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(
+            String settingsVal) {
         List<CaptivePortalProbeSpec> specs = new ArrayList<>();
         if (settingsVal != null) {
             for (String spec : TextUtils.split(settingsVal, SPEC_SEPARATOR)) {
@@ -128,7 +136,7 @@
         if (specs.isEmpty()) {
             Log.e(TAG, String.format("could not create any validation spec from %s", settingsVal));
         }
-        return specs.toArray(new CaptivePortalProbeSpec[specs.size()]);
+        return specs;
     }
 
     /**
diff --git a/core/java/android/net/shared/Inet4AddressUtils.java b/core/java/android/net/shared/Inet4AddressUtils.java
new file mode 100644
index 0000000..bec0c84
--- /dev/null
+++ b/core/java/android/net/shared/Inet4AddressUtils.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Collection of utilities to work with IPv4 addresses.
+ * @hide
+ */
+public class Inet4AddressUtils {
+
+    /**
+     * Convert a IPv4 address from an integer to an InetAddress (0x04030201 -> 1.2.3.4)
+     *
+     * <p>This method uses the higher-order int bytes as the lower-order IPv4 address bytes,
+     * which is an unusual convention. Consider {@link #intToInet4AddressHTH(int)} instead.
+     * @param hostAddress an int coding for an IPv4 address, where higher-order int byte is
+     *                    lower-order IPv4 address byte
+     */
+    public static Inet4Address intToInet4AddressHTL(int hostAddress) {
+        return intToInet4AddressHTH(Integer.reverseBytes(hostAddress));
+    }
+
+    /**
+     * Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4)
+     * @param hostAddress an int coding for an IPv4 address
+     */
+    public static Inet4Address intToInet4AddressHTH(int hostAddress) {
+        byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)),
+                (byte) (0xff & (hostAddress >> 16)),
+                (byte) (0xff & (hostAddress >> 8)),
+                (byte) (0xff & hostAddress) };
+
+        try {
+            return (Inet4Address) InetAddress.getByAddress(addressBytes);
+        } catch (UnknownHostException e) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Convert an IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x01020304)
+     *
+     * <p>This conversion can help order IP addresses: considering the ordering
+     * 192.0.2.1 < 192.0.2.2 < ..., resulting ints will follow that ordering if read as unsigned
+     * integers with {@link Integer#toUnsignedLong}.
+     * @param inetAddr is an InetAddress corresponding to the IPv4 address
+     * @return the IP address as integer
+     */
+    public static int inet4AddressToIntHTH(Inet4Address inetAddr)
+            throws IllegalArgumentException {
+        byte [] addr = inetAddr.getAddress();
+        return ((addr[0] & 0xff) << 24) | ((addr[1] & 0xff) << 16)
+                | ((addr[2] & 0xff) << 8) | (addr[3] & 0xff);
+    }
+
+    /**
+     * Convert a IPv4 address from an InetAddress to an integer (1.2.3.4 -> 0x04030201)
+     *
+     * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes,
+     * which is an unusual convention. Consider {@link #inet4AddressToIntHTH(Inet4Address)} instead.
+     * @param inetAddr is an InetAddress corresponding to the IPv4 address
+     * @return the IP address as integer
+     */
+    public static int inet4AddressToIntHTL(Inet4Address inetAddr) {
+        return Integer.reverseBytes(inet4AddressToIntHTH(inetAddr));
+    }
+
+    /**
+     * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0xffff8000)
+     * @return the IPv4 netmask as an integer
+     */
+    public static int prefixLengthToV4NetmaskIntHTH(int prefixLength)
+            throws IllegalArgumentException {
+        if (prefixLength < 0 || prefixLength > 32) {
+            throw new IllegalArgumentException("Invalid prefix length (0 <= prefix <= 32)");
+        }
+        // (int)a << b is equivalent to a << (b & 0x1f): can't shift by 32 (-1 << 32 == -1)
+        return prefixLength == 0 ? 0 : 0xffffffff << (32 - prefixLength);
+    }
+
+    /**
+     * Convert a network prefix length to an IPv4 netmask integer (prefixLength 17 -> 0x0080ffff).
+     *
+     * <p>This method stores the higher-order IPv4 address bytes in the lower-order int bytes,
+     * which is an unusual convention. Consider {@link #prefixLengthToV4NetmaskIntHTH(int)} instead.
+     * @return the IPv4 netmask as an integer
+     */
+    public static int prefixLengthToV4NetmaskIntHTL(int prefixLength)
+            throws IllegalArgumentException {
+        return Integer.reverseBytes(prefixLengthToV4NetmaskIntHTH(prefixLength));
+    }
+
+    /**
+     * Convert an IPv4 netmask to a prefix length, checking that the netmask is contiguous.
+     * @param netmask as a {@code Inet4Address}.
+     * @return the network prefix length
+     * @throws IllegalArgumentException the specified netmask was not contiguous.
+     * @hide
+     */
+    public static int netmaskToPrefixLength(Inet4Address netmask) {
+        // inetAddressToInt returns an int in *network* byte order.
+        int i = inet4AddressToIntHTH(netmask);
+        int prefixLength = Integer.bitCount(i);
+        int trailingZeros = Integer.numberOfTrailingZeros(i);
+        if (trailingZeros != 32 - prefixLength) {
+            throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i));
+        }
+        return prefixLength;
+    }
+
+    /**
+     * Returns the implicit netmask of an IPv4 address, as was the custom before 1993.
+     */
+    public static int getImplicitNetmask(Inet4Address address) {
+        int firstByte = address.getAddress()[0] & 0xff;  // Convert to an unsigned value.
+        if (firstByte < 128) {
+            return 8;
+        } else if (firstByte < 192) {
+            return 16;
+        } else if (firstByte < 224) {
+            return 24;
+        } else {
+            return 32;  // Will likely not end well for other reasons.
+        }
+    }
+
+    /**
+     * Get the broadcast address for a given prefix.
+     *
+     * <p>For example 192.168.0.1/24 -> 192.168.0.255
+     */
+    public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength)
+            throws IllegalArgumentException {
+        final int intBroadcastAddr = inet4AddressToIntHTH(addr)
+                | ~prefixLengthToV4NetmaskIntHTH(prefixLength);
+        return intToInet4AddressHTH(intBroadcastAddr);
+    }
+
+    /**
+     * Get a prefix mask as Inet4Address for a given prefix length.
+     *
+     * <p>For example 20 -> 255.255.240.0
+     */
+    public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength)
+            throws IllegalArgumentException {
+        return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength));
+    }
+}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 954071a..3fc5e41 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.Manifest.permission;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
@@ -369,4 +371,26 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Sets the delay for reporting battery state as charging after device is plugged in.
+     * This allows machine-learning or heuristics to delay the reporting and the corresponding
+     * broadcast, based on battery level, charging rate, and/or other parameters.
+     *
+     * @param delayMillis the delay in milliseconds, negative value to reset.
+     *
+     * @return True if the delay was set successfully.
+     *
+     * @see ACTION_CHARGING
+     * @hide
+     */
+    @RequiresPermission(permission.POWER_SAVER)
+    @SystemApi
+    public boolean setChargingStateUpdateDelayMillis(int delayMillis) {
+        try {
+            return mBatteryStats.setChargingStateUpdateDelayMillis(delayMillis);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index eae1aa5..1919fcc 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -834,10 +834,10 @@
          * also be bumped.
          */
         static final String[] USER_ACTIVITY_TYPES = {
-            "other", "button", "touch", "accessibility"
+            "other", "button", "touch", "accessibility", "attention"
         };
 
-        public static final int NUM_USER_ACTIVITY_TYPES = 4;
+        public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length;
 
         public abstract void noteUserActivityLocked(int type);
         public abstract boolean hasUserActivity();
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index d463b44..3a5b8a8 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -16,10 +16,12 @@
 
 package android.os;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.os.IBinder.DeathRecipient;
@@ -27,14 +29,14 @@
 import java.io.FileDescriptor;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
 
 /**
  * Class that provides a privileged API to capture and consume bugreports.
  *
  * @hide
  */
-// TODO: Expose API when the implementation is more complete.
-// @SystemApi
+@SystemApi
 @SystemService(Context.BUGREPORT_SERVICE)
 public class BugreportManager {
     private final Context mContext;
@@ -47,47 +49,46 @@
     }
 
     /**
-     * An interface describing the listener for bugreport progress and status.
+     * An interface describing the callback for bugreport progress and status.
      */
-    public interface BugreportListener {
+    public abstract static class BugreportCallback {
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
+                BUGREPORT_ERROR_INVALID_INPUT,
+                BUGREPORT_ERROR_RUNTIME,
+                BUGREPORT_ERROR_USER_DENIED_CONSENT,
+                BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT
+        })
+
+        /** Possible error codes taking a bugreport can encounter */
+        public @interface BugreportErrorCode {}
+
+        /** The input options were invalid */
+        public static final int BUGREPORT_ERROR_INVALID_INPUT =
+                IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
+
+        /** A runtime error occured */
+        public static final int BUGREPORT_ERROR_RUNTIME =
+                IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
+
+        /** User denied consent to share the bugreport */
+        public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT =
+                IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT;
+
+        /** The request to get user consent timed out. */
+        public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT =
+                IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT;
+
         /**
          * Called when there is a progress update.
          * @param progress the progress in [0.0, 100.0]
          */
-        void onProgress(float progress);
-
-        @Retention(RetentionPolicy.SOURCE)
-        @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
-                BUGREPORT_ERROR_INVALID_INPUT,
-                BUGREPORT_ERROR_RUNTIME
-        })
-
-        /** Possible error codes taking a bugreport can encounter */
-        @interface BugreportErrorCode {}
-
-        /** The input options were invalid */
-        int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
-
-        /** A runtime error occured */
-        int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
-
-        /** User denied consent to share the bugreport */
-        int BUGREPORT_ERROR_USER_DENIED_CONSENT =
-                IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT;
-
-        /** The request to get user consent timed out. */
-        int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT =
-                IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT;
+        public void onProgress(float progress) {}
 
         /**
          * Called when taking bugreport resulted in an error.
          *
-         * @param errorCode the error that occurred. Possible values are
-         *     {@code BUGREPORT_ERROR_INVALID_INPUT},
-         *     {@code BUGREPORT_ERROR_RUNTIME},
-         *     {@code BUGREPORT_ERROR_USER_DENIED_CONSENT},
-         *     {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT}.
-         *
          * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not
          * consent to sharing the bugreport with the calling app.
          *
@@ -95,19 +96,19 @@
          * out, but the bugreport could be available in the internal directory of dumpstate for
          * manual retrieval.
          */
-        void onError(@BugreportErrorCode int errorCode);
+        public void onError(@BugreportErrorCode int errorCode) {}
 
         /**
          * Called when taking bugreport finishes successfully.
          */
-        void onFinished();
+        public void onFinished() {}
     }
 
     /**
      * Starts a bugreport.
      *
      * <p>This starts a bugreport in the background. However the call itself can take several
-     * seconds to return in the worst case. {@code listener} will receive progress and status
+     * seconds to return in the worst case. {@code callback} will receive progress and status
      * updates.
      *
      * <p>The bugreport artifacts will be copied over to the given file descriptors only if the
@@ -118,19 +119,23 @@
      * @param screenshotFd file to write the screenshot, if necessary. This should be opened
      *     in write-only, append mode.
      * @param params options that specify what kind of a bugreport should be taken
-     * @param listener callback for progress and status updates
+     * @param callback callback for progress and status updates
      */
     @RequiresPermission(android.Manifest.permission.DUMP)
-    public void startBugreport(@NonNull FileDescriptor bugreportFd,
-            @Nullable FileDescriptor screenshotFd,
-            @NonNull BugreportParams params, @NonNull BugreportListener listener) {
+    public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd,
+            @Nullable ParcelFileDescriptor screenshotFd,
+            @NonNull BugreportParams params,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull BugreportCallback callback) {
         // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary.
-        DumpstateListener dsListener = new DumpstateListener(listener);
-
+        DumpstateListener dsListener = new DumpstateListener(executor, callback);
         try {
             // Note: mBinder can get callingUid from the binder transaction.
             mBinder.startBugreport(-1 /* callingUid */,
-                    mContext.getOpPackageName(), bugreportFd, screenshotFd,
+                    mContext.getOpPackageName(),
+                    (bugreportFd != null ? bugreportFd.getFileDescriptor() : new FileDescriptor()),
+                    (screenshotFd != null
+                            ? screenshotFd.getFileDescriptor() : new FileDescriptor()),
                     params.getMode(), dsListener);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -151,10 +156,12 @@
 
     private final class DumpstateListener extends IDumpstateListener.Stub
             implements DeathRecipient {
-        private final BugreportListener mListener;
+        private final Executor mExecutor;
+        private final BugreportCallback mCallback;
 
-        DumpstateListener(@Nullable BugreportListener listener) {
-            mListener = listener;
+        DumpstateListener(Executor executor, @Nullable BugreportCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
         }
 
         @Override
@@ -164,19 +171,37 @@
 
         @Override
         public void onProgress(int progress) throws RemoteException {
-            mListener.onProgress(progress);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> {
+                    mCallback.onProgress(progress);
+                });
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
 
         @Override
         public void onError(int errorCode) throws RemoteException {
-            mListener.onError(errorCode);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> {
+                    mCallback.onError(errorCode);
+                });
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
 
         @Override
         public void onFinished() throws RemoteException {
+            final long identity = Binder.clearCallingIdentity();
             try {
-                mListener.onFinished();
+                mExecutor.execute(() -> {
+                    mCallback.onFinished();
+                });
             } finally {
+                Binder.restoreCallingIdentity(identity);
                 // The bugreport has finished. Let's shutdown the service to minimize its footprint.
                 cancelBugreport();
             }
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 4e696ae..3871375 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.annotation.IntDef;
+import android.annotation.SystemApi;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -26,8 +27,7 @@
  *
  * @hide
  */
-// TODO: Expose API when the implementation is more complete.
-// @SystemApi
+@SystemApi
 public final class BugreportParams {
     private final int mMode;
 
diff --git a/core/java/android/os/ExternalVibration.aidl b/core/java/android/os/ExternalVibration.aidl
new file mode 100644
index 0000000..2629a40
--- /dev/null
+++ b/core/java/android/os/ExternalVibration.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+parcelable ExternalVibration cpp_header "vibrator/ExternalVibration.h";
diff --git a/core/java/android/os/ExternalVibration.java b/core/java/android/os/ExternalVibration.java
new file mode 100644
index 0000000..69ab1d9
--- /dev/null
+++ b/core/java/android/os/ExternalVibration.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+import android.media.AudioAttributes;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * An ExternalVibration represents an on-going vibration being controlled by something other than
+ * the core vibrator service.
+ *
+ * @hide
+ */
+public class ExternalVibration implements Parcelable {
+    private static final String TAG = "ExternalVibration";
+    private int mUid;
+    @NonNull
+    private String mPkg;
+    @NonNull
+    private AudioAttributes mAttrs;
+    @NonNull
+    private IExternalVibrationController mController;
+    // A token used to maintain equality comparisons when passing objects across process
+    // boundaries.
+    @NonNull
+    private IBinder mToken;
+
+    public ExternalVibration(int uid, @NonNull String pkg, @NonNull AudioAttributes attrs,
+            @NonNull IExternalVibrationController controller) {
+        mUid = uid;
+        mPkg = Preconditions.checkNotNull(pkg);
+        mAttrs = Preconditions.checkNotNull(attrs);
+        mController = Preconditions.checkNotNull(controller);
+        mToken = new Binder();
+    }
+
+    private ExternalVibration(Parcel in) {
+        mUid = in.readInt();
+        mPkg = in.readString();
+        mAttrs = readAudioAttributes(in);
+        mController = IExternalVibrationController.Stub.asInterface(in.readStrongBinder());
+        mToken = in.readStrongBinder();
+    }
+
+    private AudioAttributes readAudioAttributes(Parcel in) {
+        int usage = in.readInt();
+        int contentType = in.readInt();
+        int capturePreset = in.readInt();
+        int flags = in.readInt();
+        AudioAttributes.Builder builder = new AudioAttributes.Builder();
+        return builder.setUsage(usage)
+                .setContentType(contentType)
+                .setCapturePreset(capturePreset)
+                .setFlags(flags)
+                .build();
+    }
+
+    public int getUid() {
+        return mUid;
+    }
+
+    public String getPackage() {
+        return mPkg;
+    }
+
+    public AudioAttributes getAudioAttributes() {
+        return mAttrs;
+    }
+
+    /**
+     * Mutes the external vibration if it's playing and unmuted.
+     *
+     * @return whether the muting operation was successful
+     */
+    public boolean mute() {
+        try {
+            mController.mute();
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to mute vibration stream: " + this, e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Unmutes the external vibration if it's playing and muted.
+     *
+     * @return whether the unmuting operation was successful
+     */
+    public boolean unmute() {
+        try {
+            mController.unmute();
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to unmute vibration stream: " + this, e);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || !(o instanceof ExternalVibration)) {
+            return false;
+        }
+        ExternalVibration other = (ExternalVibration) o;
+        return mToken.equals(other.mToken);
+    }
+
+    @Override
+    public String toString() {
+        return "ExternalVibration{"
+            + "uid=" + mUid + ", "
+            + "pkg=" + mPkg + ", "
+            + "attrs=" + mAttrs + ", "
+            + "controller=" + mController
+            + "token=" + mController
+            + "}";
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mUid);
+        out.writeString(mPkg);
+        writeAudioAttributes(mAttrs, out, flags);
+        out.writeParcelable(mAttrs, flags);
+        out.writeStrongBinder(mController.asBinder());
+        out.writeStrongBinder(mToken);
+    }
+
+    private static void writeAudioAttributes(AudioAttributes attrs, Parcel out, int flags) {
+        out.writeInt(attrs.getUsage());
+        out.writeInt(attrs.getContentType());
+        out.writeInt(attrs.getCapturePreset());
+        out.writeInt(attrs.getAllFlags());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<ExternalVibration> CREATOR =
+            new Parcelable.Creator<ExternalVibration>() {
+                @Override
+                public ExternalVibration createFromParcel(Parcel in) {
+                    return new ExternalVibration(in);
+                }
+
+                @Override
+                public ExternalVibration[] newArray(int size) {
+                    return new ExternalVibration[size];
+                }
+            };
+}
diff --git a/core/java/android/os/FileObserver.java b/core/java/android/os/FileObserver.java
index dd85e15..da03895 100644
--- a/core/java/android/os/FileObserver.java
+++ b/core/java/android/os/FileObserver.java
@@ -16,11 +16,15 @@
 
 package android.os;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.Log;
 
+import java.io.File;
 import java.lang.ref.WeakReference;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * Monitors files (using <a href="http://en.wikipedia.org/wiki/Inotify">inotify</a>)
@@ -28,7 +32,7 @@
  * the device (including this one).  FileObserver is an abstract class;
  * subclasses must implement the event handler {@link #onEvent(int, String)}.
  *
- * <p>Each FileObserver instance monitors a single file or directory.
+ * <p>Each FileObserver instance can monitor multiple files or directories.
  * If a directory is monitored, events will be triggered for all files and
  * subdirectories inside the monitored directory.</p>
  *
@@ -86,21 +90,32 @@
             observe(m_fd);
         }
 
-        public int startWatching(String path, int mask, FileObserver observer) {
-            int wfd = startWatching(m_fd, path, mask);
+        public int[] startWatching(List<File> files, int mask, FileObserver observer) {
+            final int count = files.size();
+            final String[] paths = new String[count];
+            for (int i = 0; i < count; ++i) {
+                paths[i] = files.get(i).getAbsolutePath();
+            }
+            final int[] wfds = new int[count];
+            Arrays.fill(wfds, -1);
 
-            Integer i = new Integer(wfd);
-            if (wfd >= 0) {
-                synchronized (m_observers) {
-                    m_observers.put(i, new WeakReference(observer));
+            startWatching(m_fd, paths, mask, wfds);
+
+            final WeakReference<FileObserver> fileObserverWeakReference =
+                    new WeakReference<>(observer);
+            synchronized (m_observers) {
+                for (int wfd : wfds) {
+                    if (wfd >= 0) {
+                        m_observers.put(wfd, fileObserverWeakReference);
+                    }
                 }
             }
 
-            return i;
+            return wfds;
         }
 
-        public void stopWatching(int descriptor) {
-            stopWatching(m_fd, descriptor);
+        public void stopWatching(int[] descriptors) {
+            stopWatching(m_fd, descriptors);
         }
 
         public void onEvent(int wfd, int mask, String path) {
@@ -129,8 +144,8 @@
 
         private native int init();
         private native void observe(int fd);
-        private native int startWatching(int fd, String path, int mask);
-        private native void stopWatching(int fd, int wfd);
+        private native void startWatching(int fd, String[] paths, int mask, int[] wfds);
+        private native void stopWatching(int fd, int[] wfds);
     }
 
     private static ObserverThread s_observerThread;
@@ -141,15 +156,34 @@
     }
 
     // instance
-    private String m_path;
-    private Integer m_descriptor;
-    private int m_mask;
+    private final List<File> mFiles;
+    private int[] mDescriptors;
+    private final int mMask;
 
     /**
      * Equivalent to FileObserver(path, FileObserver.ALL_EVENTS).
+     *
+     * @deprecated use {@link #FileObserver(File)} instead.
      */
+    @Deprecated
     public FileObserver(String path) {
-        this(path, ALL_EVENTS);
+        this(new File(path));
+    }
+
+    /**
+     * Equivalent to FileObserver(file, FileObserver.ALL_EVENTS).
+     */
+    public FileObserver(@NonNull File file) {
+        this(Arrays.asList(file));
+    }
+
+    /**
+     * Equivalent to FileObserver(paths, FileObserver.ALL_EVENTS).
+     *
+     * @param files The files or directories to monitor
+     */
+    public FileObserver(@NonNull List<File> files) {
+        this(files, ALL_EVENTS);
     }
 
     /**
@@ -159,11 +193,36 @@
      *
      * @param path The file or directory to monitor
      * @param mask The event or events (added together) to watch for
+     *
+     * @deprecated use {@link #FileObserver(File, int)} instead.
      */
+    @Deprecated
     public FileObserver(String path, int mask) {
-        m_path = path;
-        m_mask = mask;
-        m_descriptor = -1;
+        this(new File(path), mask);
+    }
+
+    /**
+     * Create a new file observer for a certain file or directory.
+     * Monitoring does not start on creation!  You must call
+     * {@link #startWatching()} before you will receive events.
+     *
+     * @param file The file or directory to monitor
+     * @param mask The event or events (added together) to watch for
+     */
+    public FileObserver(@NonNull File file, int mask) {
+        this(Arrays.asList(file), mask);
+    }
+
+    /**
+     * Version of {@link #FileObserver(File, int)} that allows callers to monitor
+     * multiple files or directories.
+     *
+     * @param files The files or directories to monitor
+     * @param mask The event or events (added together) to watch for
+     */
+    public FileObserver(@NonNull List<File> files, int mask) {
+        mFiles = files;
+        mMask = mask;
     }
 
     protected void finalize() {
@@ -176,8 +235,8 @@
      * If monitoring is already started, this call has no effect.
      */
     public void startWatching() {
-        if (m_descriptor < 0) {
-            m_descriptor = s_observerThread.startWatching(m_path, m_mask, this);
+        if (mDescriptors == null) {
+            mDescriptors = s_observerThread.startWatching(mFiles, mMask, this);
         }
     }
 
@@ -187,9 +246,9 @@
      * monitoring is already stopped, this call has no effect.
      */
     public void stopWatching() {
-        if (m_descriptor >= 0) {
-            s_observerThread.stopWatching(m_descriptor);
-            m_descriptor = -1;
+        if (mDescriptors != null) {
+            s_observerThread.stopWatching(mDescriptors);
+            mDescriptors = null;
         }
     }
 
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 51c3c4c..629289b 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -39,6 +39,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.content.ContentResolver;
 import android.provider.DocumentsContract.Document;
 import android.system.ErrnoException;
@@ -852,6 +853,7 @@
      *
      * @hide
      */
+    @TestApi
     public static boolean contains(File dir, File file) {
         if (dir == null || file == null) return false;
         return contains(dir.getAbsolutePath(), file.getAbsolutePath());
diff --git a/core/java/android/os/IExternalVibrationController.aidl b/core/java/android/os/IExternalVibrationController.aidl
new file mode 100644
index 0000000..56da8c7
--- /dev/null
+++ b/core/java/android/os/IExternalVibrationController.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * {@hide}
+ */
+
+interface IExternalVibrationController {
+    /**
+     * A method to ask a currently playing vibration to mute (i.e. not vibrate).
+     *
+     * This method is only valid from the time that
+     * {@link IExternalVibratorService#onExternalVibrationStart} returns until
+     * {@link IExternalVibratorService#onExternalVibrationStop} returns.
+     *
+     * @return whether the mute operation was successful
+     */
+    boolean mute();
+
+    /**
+     * A method to ask a currently playing vibration to unmute (i.e. start vibrating).
+     *
+     * This method is only valid from the time that
+     * {@link IExternalVibratorService#onExternalVibrationStart} returns until
+     * {@link IExternalVibratorService#onExternalVibrationStop} returns.
+     *
+     * @return whether the unmute operation was successful
+     */
+    boolean unmute();
+}
diff --git a/core/java/android/os/IExternalVibratorService.aidl b/core/java/android/os/IExternalVibratorService.aidl
new file mode 100644
index 0000000..666171f
--- /dev/null
+++ b/core/java/android/os/IExternalVibratorService.aidl
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.ExternalVibration;
+
+/**
+ * The communication channel by which an external system that wants to control the system
+ * vibrator can notify the vibrator subsystem.
+ *
+ * Some vibrators can be driven via multiple paths (e.g. as an audio channel) in addition to
+ * the usual interface, but we typically only want one vibration at a time playing because they
+ * don't mix well. In order to synchronize the two places where vibration might be controlled,
+ * we provide this interface so the vibrator subsystem has a chance to:
+ *
+ * 1) Decide whether the current vibration should play based on the current system policy.
+ * 2) Stop any currently on-going vibrations.
+ * {@hide}
+ */
+interface IExternalVibratorService {
+    const int SCALE_MUTE = -100;
+    const int SCALE_VERY_LOW = -2;
+    const int SCALE_LOW = -1;
+    const int SCALE_NONE = 0;
+    const int SCALE_HIGH = 1;
+    const int SCALE_VERY_HIGH = 2;
+
+    /**
+     * A method called by the external system to start a vibration.
+     *
+     * If this returns {@code SCALE_MUTE}, then the vibration should <em>not</em> play. If this
+     * returns any other scale level, then any currently playing vibration controlled by the
+     * requesting system must be muted and this vibration can begin playback.
+     *
+     * Note that the IExternalVibratorService implementation will not call mute on any currently
+     * playing external vibrations in order to avoid re-entrancy with the system on the other side.
+     *
+     * @param vibration An ExternalVibration
+     *
+     * @return {@code SCALE_MUTE} if the external vibration should not play, and any other scale
+     *         level if it should.
+     */
+    int onExternalVibrationStart(in ExternalVibration vib);
+
+    /**
+     * A method called by the external system when a vibration no longer wants to play.
+     */
+    void onExternalVibrationStop(in ExternalVibration vib);
+}
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 74d434c..93d6f4c 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -119,6 +119,23 @@
     void removeDataFetchOperation(long configKey, in String packageName);
 
     /**
+     * Registers the given pending intent for this packagename. This intent is invoked when the
+     * active status of any of the configs sent by this package changes and will contain a list of
+     * config ids that are currently active. It also returns the list of configs that are currently
+     * active. There can be at most one active configs changed listener per package.
+     *
+     * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+     */
+    long[] setActiveConfigsChangedOperation(in IBinder intentSender, in String packageName);
+
+    /**
+     * Removes the active configs changed operation for the specified package name.
+     *
+     * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+     */
+    void removeActiveConfigsChangedOperation(in String packageName);
+
+    /**
      * Removes the configuration with the matching config key. No-op if this config key does not
      * exist.
      *
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 630bd2e..d68eeed 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -31,6 +31,7 @@
 import static android.system.OsConstants.S_ISREG;
 import static android.system.OsConstants.S_IWOTH;
 
+import android.annotation.TestApi;
 import android.content.BroadcastReceiver;
 import android.content.ContentProvider;
 import android.os.MessageQueue.OnFileDescriptorEventListener;
@@ -580,6 +581,7 @@
      *
      * @hide
      */
+    @TestApi
     public static File getFile(FileDescriptor fd) throws IOException {
         try {
             final String path = Os.readlink("/proc/self/fd/" + fd.getInt$());
diff --git a/core/java/android/os/ParcelUuid.java b/core/java/android/os/ParcelUuid.java
index 2c68ddd..5b45ac2 100644
--- a/core/java/android/os/ParcelUuid.java
+++ b/core/java/android/os/ParcelUuid.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.annotation.UnsupportedAppUsage;
+
 import java.util.UUID;
 
 /**
@@ -109,6 +111,7 @@
 
    public static final Parcelable.Creator<ParcelUuid> CREATOR =
                new Parcelable.Creator<ParcelUuid>() {
+        @UnsupportedAppUsage
         public ParcelUuid createFromParcel(Parcel source) {
             long mostSigBits = source.readLong();
             long leastSigBits = source.readLong();
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 4ce760f..7f4254e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -336,6 +336,13 @@
     public static final int USER_ACTIVITY_EVENT_ACCESSIBILITY = 3;
 
     /**
+     * User activity event type: {@link android.service.attention.AttentionService} taking action
+     * on behalf of user.
+     * @hide
+     */
+    public static final int USER_ACTIVITY_EVENT_ATTENTION = 4;
+
+    /**
      * User activity flag: If already dimmed, extend the dim timeout
      * but do not brighten.  This flag is useful for keeping the screen on
      * a little longer without causing a visible change such as when
diff --git a/core/java/android/os/SELinux.java b/core/java/android/os/SELinux.java
index 94441ca..a96618a 100644
--- a/core/java/android/os/SELinux.java
+++ b/core/java/android/os/SELinux.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.UnsupportedAppUsage;
 import android.util.Slog;
 
 import java.io.File;
@@ -69,6 +70,7 @@
      * @param path the pathname of the file object.
      * @return a security context given as a String.
      */
+    @UnsupportedAppUsage
     public static final native String getFileContext(String path);
 
     /**
diff --git a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
index 8d568c8..33795f8 100644
--- a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
+++ b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
@@ -78,15 +78,6 @@
     public abstract List<RuntimePermissionPresentationInfo> onGetAppPermissions(
             @NonNull String packageName);
 
-    /**
-     * Revokes the permission {@code permissionName} for app {@code packageName}
-     *
-     * @param packageName The package for which to revoke
-     * @param permissionName The permission to revoke
-     */
-    public abstract void onRevokeRuntimePermission(@NonNull String packageName,
-            @NonNull String permissionName);
-
     @Override
     public final IBinder onBind(Intent intent) {
         return new IRuntimePermissionPresenter.Stub() {
@@ -99,17 +90,6 @@
                         obtainMessage(RuntimePermissionPresenterService::getAppPermissions,
                                 RuntimePermissionPresenterService.this, packageName, callback));
             }
-
-            @Override
-            public void revokeRuntimePermission(String packageName, String permissionName) {
-                checkNotNull(packageName, "packageName");
-                checkNotNull(permissionName, "permissionName");
-
-                mHandler.sendMessage(
-                        obtainMessage(RuntimePermissionPresenterService::onRevokeRuntimePermission,
-                                RuntimePermissionPresenterService.this, packageName,
-                                permissionName));
-            }
         };
     }
 
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index 8bd75d7..8a52f1f 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.Activity;
 import android.app.AlarmManager;
@@ -805,6 +806,7 @@
          *
          * @hide
          */
+        @TestApi
         public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
             ACCOUNT_NAME,
             ACCOUNT_TYPE,
@@ -1832,6 +1834,7 @@
          *
          * @hide
          */
+        @TestApi
         public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
             _SYNC_ID,
             DIRTY,
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 25554b9..81e1eb9 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -126,6 +126,7 @@
      * Prefix for column names that are not visible to client apps.
      * @hide
      */
+    @TestApi
     public static final String HIDDEN_COLUMN_PREFIX = "x_";
 
     /**
@@ -8444,6 +8445,7 @@
          * nothing will be done.
          * @hide
          */
+        @TestApi
         public static final String UNDEMOTE_METHOD = "undemote";
 
         /**
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index f5c442f..887175a 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -104,6 +104,11 @@
      */
     public static final String VOLUME_EXTERNAL = "external";
 
+    /** {@hide} */ @TestApi
+    public static final String SCAN_FILE_CALL = "scan_file";
+    /** {@hide} */ @TestApi
+    public static final String SCAN_VOLUME_CALL = "scan_volume";
+
     /**
      * The method name used by the media scanner and mtp to tell the media provider to
      * rescan and reclassify that have become unhidden because of renaming folders or
@@ -2992,6 +2997,7 @@
      *
      * @hide
      */
+    @TestApi
     public static @NonNull File getVolumePath(@NonNull String volumeName)
             throws FileNotFoundException {
         if (TextUtils.isEmpty(volumeName)) {
@@ -3022,6 +3028,7 @@
      *
      * @hide
      */
+    @TestApi
     public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
             throws FileNotFoundException {
         if (TextUtils.isEmpty(volumeName)) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5222fc3..f04c752 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -486,6 +486,37 @@
             "android.settings.WIFI_IP_SETTINGS";
 
     /**
+     * Activity Action: Show setting page to process an Easy Connect (Wi-Fi DPP) QR code and start
+     * configuration. This intent should be used when you want to use this device to take on the
+     * configurator role for an IoT/other device. When provided with a valid DPP URI string Settings
+     * will open a wifi selection screen for the user to indicate which network they would like
+     * to configure the device specified in the DPP URI string for and carry them through the rest
+     * of the flow for provisioning the device.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you safeguard
+     * against this by checking WifiManager.isEasyConnectSupported();
+     * <p>
+     * Input:
+     * The following keys in the bundle with their associated value.
+     * <ul>
+     *     <li>"qrCode": Standard Easy Connect (Wi-Fi DPP) URI bootstrapping information as a
+     *     string.</li>
+     * </ul>
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_QR_CODE =
+            "android.settings.PROCESS_WIFI_EASY_CONNECT_QR_CODE";
+
+    /**
+     * An extra to put in the bundle for {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_QR_CODE} intents.
+     * It must contain properly formatted Easy Connect (Wi-Fi DPP) URI bootstrapping information for
+     * the process to proceed.
+     */
+    public static final String EXTRA_QR_CODE = "android.provider.extra.QR_CODE";
+
+    /**
      * Activity Action: Show settings to allow configuration of data and view data usage.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -8786,6 +8817,7 @@
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_CHANGER);
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
+            CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD);
             if (!InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
                 CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
                 CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
@@ -11722,6 +11754,7 @@
          * entity_list_default                      (String[])
          * entity_list_not_editable                 (String[])
          * entity_list_editable                     (String[])
+         * lang_id_threshold_override               (float)
          * </pre>
          *
          * <p>
@@ -14280,6 +14313,17 @@
          */
         public static final String APPOP_HISTORY_PARAMETERS =
                 "appop_history_parameters";
+
+        /**
+         * Delay for sending ACTION_CHARGING after device is plugged in.
+         * This is used as an override for constants defined in BatteryStatsImpl for
+         * ease of experimentation.
+         *
+         * @see com.android.internal.os.BatteryStatsImpl.Constants.KEY_BATTERY_CHARGED_DELAY_MS
+         * @hide
+         */
+        public static final String BATTERY_CHARGING_STATE_UPDATE_DELAY =
+                "battery_charging_state_update_delay";
     }
 
     /**
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index 140336e..dce5d56 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -289,6 +290,7 @@
          * Path to the media content file. Internal only field.
          * @hide
          */
+        @TestApi
         public static final String _DATA = "_data";
 
         // Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index b6788f5..93189b3 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -63,15 +63,17 @@
 
     /**
      * Data type: ArrayList of {@link android.app.Notification.Action}.
-     * Used to suggest extra actions for a notification.
+     * Used to suggest contextual actions for a notification.
+     *
+     * @see Notification.Action.Builder#setContextual(boolean)
      */
-    public static final String KEY_SMART_ACTIONS = "key_smart_actions";
+    public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
 
     /**
      * Data type: ArrayList of {@link CharSequence}.
      * Used to suggest smart replies for a notification.
      */
-    public static final String KEY_SMART_REPLIES = "key_smart_replies";
+    public static final String KEY_TEXT_REPLIES = "key_text_replies";
 
     /**
      * Data type: int, one of importance values e.g.
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 4052ed7..db9351b 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -52,7 +52,7 @@
         DEFAULT_FLAGS.put("settings_mobile_network_v2", "true");
         DEFAULT_FLAGS.put("settings_network_and_internet_v2", "false");
         DEFAULT_FLAGS.put("settings_seamless_transfer", "false");
-        DEFAULT_FLAGS.put("settings_slice_injection", "false");
+        DEFAULT_FLAGS.put("settings_slice_injection", "true");
         DEFAULT_FLAGS.put("settings_systemui_theme", "true");
         DEFAULT_FLAGS.put("settings_wifi_dpp", "true");
         DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "true");
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index f58efc9..e3a6bd7 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -26,6 +26,7 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.ColorSpace;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -953,6 +954,24 @@
     }
 
     /**
+     * Returns the preferred wide color space of the Display.
+     * The returned wide gamut color space is based on hardware capability and
+     * is preferred by the composition pipeline.
+     * Returns null if the display doesn't support wide color gamut.
+     * {@link Display#isWideColorGamut()}.
+     */
+    @Nullable
+    public ColorSpace getPreferredWideGamutColorSpace() {
+        synchronized (this) {
+            updateDisplayInfoLocked();
+            if (mDisplayInfo.isWideColorGamut()) {
+                return mGlobal.getPreferredWideGamutColorSpace();
+            }
+            return null;
+        }
+    }
+
+    /**
      * Gets the supported color modes of this device.
      * @hide
      */
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 863b717..4032a6b 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -46,10 +46,9 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.Process;
-import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.Surface.OutOfResourcesException;
 
@@ -60,6 +59,7 @@
 import libcore.util.NativeAllocationRegistry;
 
 import java.io.Closeable;
+import java.nio.ByteBuffer;
 
 /**
  * Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is
@@ -75,7 +75,7 @@
     private static final String TAG = "SurfaceControl";
 
     private static native long nativeCreate(SurfaceSession session, String name,
-            int w, int h, int format, int flags, long parentObject, int windowType, int ownerUid)
+            int w, int h, int format, int flags, long parentObject, Parcel metadata)
             throws OutOfResourcesException;
     private static native long nativeReadFromParcel(Parcel in);
     private static native long nativeCopyFromSurfaceControl(long nativeObject);
@@ -182,6 +182,7 @@
     private static native void nativeTransferTouchFocus(long transactionObj, IBinder fromToken,
             IBinder toToken);
     private static native boolean nativeGetProtectedContentSupport();
+    private static native void nativeSetMetadata(long transactionObj, int key, Parcel data);
 
     private final CloseGuard mCloseGuard = CloseGuard.get();
     private String mName;
@@ -413,6 +414,24 @@
     }
 
     /**
+     * owner UID.
+     * @hide
+     */
+    public static final int METADATA_OWNER_UID = 1;
+
+    /**
+     * Window type as per {@link WindowManager.LayoutParams}.
+     * @hide
+     */
+    public static final int METADATA_WINDOW_TYPE = 2;
+
+    /**
+     * Task id to allow association between surfaces and task.
+     * @hide
+     */
+    public static final int METADATA_TASK_ID = 3;
+
+    /**
      * Builder class for {@link SurfaceControl} objects.
      */
     public static class Builder {
@@ -423,8 +442,7 @@
         private int mFormat = PixelFormat.OPAQUE;
         private String mName;
         private SurfaceControl mParent;
-        private int mWindowType = -1;
-        private int mOwnerUid = -1;
+        private SparseIntArray mMetadata;
 
         /**
          * Begin building a SurfaceControl with a given {@link SurfaceSession}.
@@ -455,8 +473,8 @@
                 throw new IllegalArgumentException(
                         "Only buffer layers can set a valid buffer size.");
             }
-            return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat,
-                    mFlags, mParent, mWindowType, mOwnerUid);
+            return new SurfaceControl(
+                    mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata);
         }
 
         /**
@@ -581,23 +599,17 @@
         }
 
         /**
-         * Set surface metadata.
+         * Sets a metadata int.
          *
-         * Currently these are window-types as per {@link WindowManager.LayoutParams} and
-         * owner UIDs. Child surfaces inherit their parents
-         * metadata so only the WindowManager needs to set this on root Surfaces.
-         *
-         * @param windowType A window-type
-         * @param ownerUid UID of the window owner.
+         * @param key metadata key
+         * @param data associated data
          * @hide
          */
-        public Builder setMetadata(int windowType, int ownerUid) {
-            if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) {
-                throw new UnsupportedOperationException(
-                        "It only makes sense to set Surface metadata from the WindowManager");
+        public Builder setMetadata(int key, int data) {
+            if (mMetadata == null) {
+                mMetadata = new SparseIntArray();
             }
-            mWindowType = windowType;
-            mOwnerUid = ownerUid;
+            mMetadata.put(key, data);
             return this;
         }
 
@@ -682,13 +694,12 @@
      * @param h The surface initial height.
      * @param flags The surface creation flags.  Should always include {@link #HIDDEN}
      * in the creation flags.
-     * @param windowType The type of the window as specified in WindowManager.java.
-     * @param ownerUid A unique per-app ID.
+     * @param metadata Initial metadata.
      *
      * @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
      */
     private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
-            SurfaceControl parent, int windowType, int ownerUid)
+            SurfaceControl parent, SparseIntArray metadata)
                     throws OutOfResourcesException, IllegalArgumentException {
         if (name == null) {
             throw new IllegalArgumentException("name must not be null");
@@ -706,8 +717,21 @@
         mName = name;
         mWidth = w;
         mHeight = h;
-        mNativeObject = nativeCreate(session, name, w, h, format, flags,
-            parent != null ? parent.mNativeObject : 0, windowType, ownerUid);
+        Parcel metaParcel = Parcel.obtain();
+        try {
+            if (metadata != null && metadata.size() > 0) {
+                metaParcel.writeInt(metadata.size());
+                for (int i = 0; i < metadata.size(); ++i) {
+                    metaParcel.writeInt(metadata.keyAt(i));
+                    metaParcel.writeByteArray(
+                            ByteBuffer.allocate(4).putInt(metadata.valueAt(i)).array());
+                }
+            }
+            mNativeObject = nativeCreate(session, name, w, h, format, flags,
+                    parent != null ? parent.mNativeObject : 0, metaParcel);
+        } finally {
+            metaParcel.recycle();
+        }
         if (mNativeObject == 0) {
             throw new OutOfResourcesException(
                     "Couldn't allocate SurfaceControl native object");
@@ -2326,6 +2350,30 @@
         }
 
         /**
+         * Sets an arbitrary piece of metadata on the surface. This is a helper for int data.
+         * @hide
+         */
+        public Transaction setMetadata(int key, int data) {
+            Parcel parcel = Parcel.obtain();
+            parcel.writeInt(data);
+            try {
+                setMetadata(key, parcel);
+            } finally {
+                parcel.recycle();
+            }
+            return this;
+        }
+
+        /**
+         * Sets an arbitrary piece of metadata on the surface.
+         * @hide
+         */
+        public Transaction setMetadata(int key, Parcel data) {
+            nativeSetMetadata(mNativeObject, key, data);
+            return this;
+        }
+
+        /**
          * Merge the other transaction into this transaction, clearing the
          * other transaction as if it had been applied.
          *
diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java
index ce680ec..7f928f7 100644
--- a/core/java/android/view/textclassifier/TextClassificationConstants.java
+++ b/core/java/android/view/textclassifier/TextClassificationConstants.java
@@ -46,6 +46,7 @@
  * entity_list_default                      (String[])
  * entity_list_not_editable                 (String[])
  * entity_list_editable                     (String[])
+ * lang_id_threshold_override               (float)
  * </pre>
  *
  * <p>
@@ -94,6 +95,8 @@
             "in_app_conversation_action_types_default";
     private static final String NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT =
             "notification_conversation_action_types_default";
+    private static final String LANG_ID_THRESHOLD_OVERRIDE =
+            "lang_id_threshold_override";
 
     private static final boolean LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
     private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT = true;
@@ -106,8 +109,8 @@
     private static final int CLASSIFY_TEXT_MAX_RANGE_LENGTH_DEFAULT = 10 * 1000;
     private static final int GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT = 100 * 1000;
     private static final int GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT = 100;
-    private static final String ENTITY_LIST_DELIMITER = ":";
-    private static final String ENTITY_LIST_DEFAULT_VALUE = new StringJoiner(ENTITY_LIST_DELIMITER)
+    private static final String STRING_LIST_DELIMITER = ":";
+    private static final String ENTITY_LIST_DEFAULT_VALUE = new StringJoiner(STRING_LIST_DELIMITER)
             .add(TextClassifier.TYPE_ADDRESS)
             .add(TextClassifier.TYPE_EMAIL)
             .add(TextClassifier.TYPE_PHONE)
@@ -116,7 +119,7 @@
             .add(TextClassifier.TYPE_DATE_TIME)
             .add(TextClassifier.TYPE_FLIGHT_NUMBER).toString();
     private static final String CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES =
-            new StringJoiner(ENTITY_LIST_DELIMITER)
+            new StringJoiner(STRING_LIST_DELIMITER)
                     .add(ConversationAction.TYPE_TEXT_REPLY)
                     .add(ConversationAction.TYPE_CREATE_REMINDER)
                     .add(ConversationAction.TYPE_CALL_PHONE)
@@ -127,6 +130,13 @@
                     .add(ConversationAction.TYPE_VIEW_CALENDAR)
                     .add(ConversationAction.TYPE_VIEW_MAP)
                     .toString();
+    /**
+     * < 0  : Not set. Use value from LangId model.
+     * 0 - 1: Override value in LangId model.
+     * > 1  : Effectively turns off the foreign language detection. Scores should never be > 1.
+     * @see EntityConfidence
+     */
+    private static final float LANG_ID_THRESHOLD_OVERRIDE_DEFAULT = -1f;
 
     private final boolean mSystemTextClassifierEnabled;
     private final boolean mLocalTextClassifierEnabled;
@@ -144,6 +154,7 @@
     private final List<String> mEntityListEditable;
     private final List<String> mInAppConversationActionTypesDefault;
     private final List<String> mNotificationConversationActionTypesDefault;
+    private final float mLangIdThresholdOverride;
 
     private TextClassificationConstants(@Nullable String settings) {
         final KeyValueListParser parser = new KeyValueListParser(',');
@@ -186,21 +197,24 @@
         mGenerateLinksLogSampleRate = parser.getInt(
                 GENERATE_LINKS_LOG_SAMPLE_RATE,
                 GENERATE_LINKS_LOG_SAMPLE_RATE_DEFAULT);
-        mEntityListDefault = parseEntityList(parser.getString(
+        mEntityListDefault = parseStringList(parser.getString(
                 ENTITY_LIST_DEFAULT,
                 ENTITY_LIST_DEFAULT_VALUE));
-        mEntityListNotEditable = parseEntityList(parser.getString(
+        mEntityListNotEditable = parseStringList(parser.getString(
                 ENTITY_LIST_NOT_EDITABLE,
                 ENTITY_LIST_DEFAULT_VALUE));
-        mEntityListEditable = parseEntityList(parser.getString(
+        mEntityListEditable = parseStringList(parser.getString(
                 ENTITY_LIST_EDITABLE,
                 ENTITY_LIST_DEFAULT_VALUE));
-        mInAppConversationActionTypesDefault = parseEntityList(parser.getString(
+        mInAppConversationActionTypesDefault = parseStringList(parser.getString(
                 IN_APP_CONVERSATION_ACTION_TYPES_DEFAULT,
                 CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES));
-        mNotificationConversationActionTypesDefault = parseEntityList(parser.getString(
+        mNotificationConversationActionTypesDefault = parseStringList(parser.getString(
                 NOTIFICATION_CONVERSATION_ACTION_TYPES_DEFAULT,
                 CONVERSATION_ACTIONS_TYPES_DEFAULT_VALUES));
+        mLangIdThresholdOverride = parser.getFloat(
+                LANG_ID_THRESHOLD_OVERRIDE,
+                LANG_ID_THRESHOLD_OVERRIDE_DEFAULT);
     }
 
     /** Load from a settings string. */
@@ -272,8 +286,12 @@
         return mNotificationConversationActionTypesDefault;
     }
 
-    private static List<String> parseEntityList(String listStr) {
-        return Collections.unmodifiableList(Arrays.asList(listStr.split(ENTITY_LIST_DELIMITER)));
+    public float getLangIdThresholdOverride() {
+        return mLangIdThresholdOverride;
+    }
+
+    private static List<String> parseStringList(String listStr) {
+        return Collections.unmodifiableList(Arrays.asList(listStr.split(STRING_LIST_DELIMITER)));
     }
 
     void dump(IndentingPrintWriter pw) {
@@ -296,6 +314,7 @@
         pw.printPair("getInAppConversationActionTypes", mInAppConversationActionTypesDefault);
         pw.printPair("getNotificationConversationActionTypes",
                 mNotificationConversationActionTypesDefault);
+        pw.printPair("getLangIdThresholdOverride", mLangIdThresholdOverride);
         pw.decreaseIndent();
         pw.println();
     }
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index a5b7c62..7782079 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -567,8 +567,9 @@
             }
         }
 
-        // TODO: Make this configurable.
-        final float foreignTextThreshold = typeCount == 0 ? 0.5f : 0.7f;
+        final float foreignTextThreshold = mSettings.getLangIdThresholdOverride() >= 0
+                ? mSettings.getLangIdThresholdOverride()
+                : 0.5f /* TODO: Load this from the langId model. */;
         boolean isPrimaryAction = true;
         final ArrayList<Intent> sourceIntents = new ArrayList<>();
         for (LabeledIntent labeledIntent : IntentFactory.create(
@@ -602,6 +603,10 @@
     }
 
     private boolean isForeignText(String text, float threshold) {
+        if (threshold > 1) {
+            return false;
+        }
+
         // TODO: Revisit this algorithm.
         try {
             final LangIdModel.LanguageResult[] langResults = getLangIdImpl().detectLanguages(text);
diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java
index b1609fc..735c3eb 100644
--- a/core/java/android/view/textclassifier/TextLanguage.java
+++ b/core/java/android/view/textclassifier/TextLanguage.java
@@ -89,9 +89,10 @@
     /**
      * Returns the language locale at the specified index. Locales are ordered from high
      * confidence to low confidence.
+     * <p>
+     * See {@link #getLocaleHypothesisCount()} for the number of locales available.
      *
      * @throws IndexOutOfBoundsException if the specified index is out of range.
-     * @see #getLocaleHypothesisCount() for the number of locales available.
      */
     @NonNull
     public ULocale getLocale(int index) {
@@ -109,7 +110,8 @@
     }
 
     /**
-     * Returns a bundle containing non-structured extra information about this result.
+     * Returns a bundle containing non-structured extra information about this result. What is
+     * returned in the extras is specific to the {@link TextClassifier} implementation.
      *
      * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should prefer
      * to hold a reference to the returned bundle rather than frequently calling this method.
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index afe46701..5147306 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -359,7 +359,7 @@
     /**
      * @return the initial width of the content magnified and copied to the magnifier, in pixels
      * @see Magnifier.Builder#setSize(int, int)
-     * @see Magnifier.Builder#setZoom(float)
+     * @see Magnifier.Builder#setInitialZoom(float)
      */
     @Px
     public int getSourceWidth() {
@@ -369,7 +369,7 @@
     /**
      * @return the initial height of the content magnified and copied to the magnifier, in pixels
      * @see Magnifier.Builder#setSize(int, int)
-     * @see Magnifier.Builder#setZoom(float)
+     * @see Magnifier.Builder#setInitialZoom(float)
      */
     @Px
     public int getSourceHeight() {
@@ -394,7 +394,7 @@
      * If the zoom is x and the magnifier window size is (width, height), the original size
      * of the content being magnified will be (width / x, height / x).
      * @return the zoom applied to the content
-     * @see Magnifier.Builder#setZoom(float)
+     * @see Magnifier.Builder#setInitialZoom(float)
      */
     public float getZoom() {
         return mZoom;
@@ -1196,10 +1196,12 @@
          * (content_width * zoom, content_height * zoom), which will coincide with the size
          * of the magnifier. A zoom of 1 will translate to no magnification (the content will
          * be just copied to the magnifier with no scaling). The zoom defaults to 1.25.
+         * Note that the zoom can also be changed after the instance is built, using the
+         * {@link Magnifier#setZoom(float)} method.
          * @param zoom the zoom to be set
          */
         @NonNull
-        public Builder setZoom(@FloatRange(from = 0f) float zoom) {
+        public Builder setInitialZoom(@FloatRange(from = 0f) float zoom) {
             Preconditions.checkArgumentPositive(zoom, "Zoom should be positive");
             mZoom = zoom;
             return this;
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 42acb09..803462d 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -18,6 +18,10 @@
 
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -98,6 +102,20 @@
 
     private static final boolean DEBUG = false;
 
+
+    /**
+     * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
+     * {@link AppPredictionManager} will be queried for direct share targets.
+     */
+    // TODO(b/123089490): Replace with system flag
+    private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = false;
+    // TODO(b/123088566) Share these in a better way.
+    private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
+    private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
+    public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
+    private AppPredictor mAppPredictor;
+    private AppPredictor.Callback mAppPredictorCallback;
+
     /**
      * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
      * binding to every ChooserTargetService implementation.
@@ -309,6 +327,35 @@
         mChooserShownTime = System.currentTimeMillis();
         final long systemCost = mChooserShownTime - intentReceivedTime;
         MetricsLogger.histogram(null, "system_cost_for_smart_sharing", (int) systemCost);
+
+        if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+            final IntentFilter filter = getTargetIntentFilter();
+            Bundle extras = new Bundle();
+            extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
+            AppPredictionManager appPredictionManager =
+                    getSystemService(AppPredictionManager.class);
+            mAppPredictor = appPredictionManager.createAppPredictionSession(
+                new AppPredictionContext.Builder(this)
+                    .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
+                    .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
+                    .setExtras(extras)
+                    .build());
+            mAppPredictorCallback = resultList -> {
+                final List<DisplayResolveInfo> driList =
+                        getDisplayResolveInfos(mChooserListAdapter);
+                final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
+                        new ArrayList<>();
+                for (AppTarget appTarget : resultList) {
+                    shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
+                            appTarget.getShortcutInfo(),
+                            new ComponentName(
+                                appTarget.getPackageName(), appTarget.getClassName())));
+                }
+                sendShareShortcutInfoList(shareShortcutInfos, driList);
+            };
+            mAppPredictor.registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
+        }
+
         if (DEBUG) {
             Log.d(TAG, "System Time Cost is " + systemCost);
         }
@@ -339,6 +386,10 @@
         }
         unbindRemainingServices();
         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
+        if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+            mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
+            mAppPredictor.destroy();
+        }
     }
 
     @Override
@@ -606,15 +657,10 @@
         }
     }
 
-    private void queryDirectShareTargets(ChooserListAdapter adapter) {
-        final IntentFilter filter = getTargetIntentFilter();
-        if (filter == null) {
-            return;
-        }
-
+    private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
         // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
         // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
-        final List<DisplayResolveInfo> driList = new ArrayList<>();
+        List<DisplayResolveInfo> driList = new ArrayList<>();
         int targetsToQuery = 0;
         for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
             final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
@@ -634,42 +680,59 @@
                 break;
             }
         }
+        return driList;
+    }
+
+    private void queryDirectShareTargets(ChooserListAdapter adapter) {
+        if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+            mAppPredictor.requestPredictionUpdate();
+            return;
+        }
+        final IntentFilter filter = getTargetIntentFilter();
+        if (filter == null) {
+            return;
+        }
+        final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
 
         AsyncTask.execute(() -> {
             ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
             List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
-
-            // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
-            // for direct share targets. After ShareSheet is refactored we should use the
-            // ShareShortcutInfos directly.
-            boolean resultMessageSent = false;
-            for (int i = 0; i < driList.size(); i++) {
-                List<ChooserTarget> chooserTargets = new ArrayList<>();
-                for (int j = 0; j < resultList.size(); j++) {
-                    if (driList.get(i).getResolvedComponentName().equals(
-                            resultList.get(j).getTargetComponent())) {
-                        chooserTargets.add(convertToChooserTarget(resultList.get(j)));
-                    }
-                }
-                if (chooserTargets.isEmpty()) {
-                    continue;
-                }
-
-                final Message msg = Message.obtain();
-                msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
-                msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
-                mChooserHandler.sendMessage(msg);
-                resultMessageSent = true;
-            }
-
-            if (resultMessageSent) {
-                final Message msg = Message.obtain();
-                msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
-                mChooserHandler.sendMessage(msg);
-            }
+            sendShareShortcutInfoList(resultList, driList);
         });
     }
 
+    private void sendShareShortcutInfoList(
+                List<ShortcutManager.ShareShortcutInfo> resultList,
+                List<DisplayResolveInfo> driList) {
+        // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
+        // for direct share targets. After ShareSheet is refactored we should use the
+        // ShareShortcutInfos directly.
+        boolean resultMessageSent = false;
+        for (int i = 0; i < driList.size(); i++) {
+            List<ChooserTarget> chooserTargets = new ArrayList<>();
+            for (int j = 0; j < resultList.size(); j++) {
+                if (driList.get(i).getResolvedComponentName().equals(
+                            resultList.get(j).getTargetComponent())) {
+                    chooserTargets.add(convertToChooserTarget(resultList.get(j)));
+                }
+            }
+            if (chooserTargets.isEmpty()) {
+                continue;
+            }
+            final Message msg = Message.obtain();
+            msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
+            msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
+            mChooserHandler.sendMessage(msg);
+            resultMessageSent = true;
+        }
+
+        if (resultMessageSent) {
+            final Message msg = Message.obtain();
+            msg.what = SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
+            mChooserHandler.sendMessage(msg);
+        }
+    }
+
     private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut) {
         ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
         Bundle extras = new Bundle();
@@ -724,6 +787,7 @@
         // Do nothing. We'll send the voice stuff ourselves.
     }
 
+    // TODO(b/123377860) Send clicked ShortcutInfo to mAppPredictor
     void updateModelAndChooserCounts(TargetInfo info) {
         if (info != null) {
             final ResolveInfo ri = info.getResolveInfo();
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 514ff76..d7514d1 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -154,4 +154,7 @@
     oneway void noteBluetoothControllerActivity(in BluetoothActivityEnergyInfo info);
     oneway void noteModemControllerActivity(in ModemActivityInfo info);
     oneway void noteWifiControllerActivity(in WifiActivityEnergyInfo info);
+
+    /** {@hide} */
+    boolean setChargingStateUpdateDelayMillis(int delay);
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 534361e..c6afee2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -13395,11 +13395,22 @@
             mResolver.registerContentObserver(
                     Settings.Global.getUriFor(Settings.Global.BATTERY_STATS_CONSTANTS),
                     false /* notifyForDescendants */, this);
+            mResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY),
+                    false /* notifyForDescendants */, this);
             updateConstants();
         }
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
+            if (uri.equals(
+                    Settings.Global.getUriFor(
+                            Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY))) {
+                synchronized (BatteryStatsImpl.this) {
+                    updateBatteryChargedDelayMsLocked();
+                }
+                return;
+            }
             updateConstants();
         }
 
@@ -13443,12 +13454,21 @@
                                 DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB
                                 : DEFAULT_MAX_HISTORY_BUFFER_KB)
                         * 1024;
-                BATTERY_CHARGED_DELAY_MS = mParser.getInt(
-                        KEY_BATTERY_CHARGED_DELAY_MS,
-                        DEFAULT_BATTERY_CHARGED_DELAY_MS);
+                updateBatteryChargedDelayMsLocked();
             }
         }
 
+        private void updateBatteryChargedDelayMsLocked() {
+            // a negative value indicates that we should ignore this override
+            final int delay = Settings.Global.getInt(mResolver,
+                    Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
+                    -1);
+
+            BATTERY_CHARGED_DELAY_MS = delay >= 0 ? delay : mParser.getInt(
+                    KEY_BATTERY_CHARGED_DELAY_MS,
+                    DEFAULT_BATTERY_CHARGED_DELAY_MS);
+        }
+
         private void updateTrackCpuTimesByProcStateLocked(boolean wasEnabled, boolean isEnabled) {
             TRACK_CPU_TIMES_BY_PROC_STATE = isEnabled;
             if (isEnabled && !wasEnabled) {
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 6a28059..943c726 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -160,4 +160,9 @@
     void onBiometricError(String error);
     // Used to hide the biometric dialog when the AuthenticationClient is stopped
     void hideBiometricDialog();
+
+    /**
+     * Notifies System UI that the display is ready to show system decorations.
+     */
+    void onDisplayReady(int displayId);
 }
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index e5ad1f4..7398e95 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.util;
 
+import android.annotation.UnsupportedAppUsage;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -1354,6 +1355,7 @@
      * Add a new state to the state machine, parent will be null
      * @param state to add
      */
+    @UnsupportedAppUsage
     public final void addState(State state) {
         mSmHandler.addState(state, null);
     }
@@ -1372,6 +1374,7 @@
      *
      * @param initialState is the state which will receive the first message.
      */
+    @UnsupportedAppUsage
     public final void setInitialState(State initialState) {
         mSmHandler.setInitialState(initialState);
     }
@@ -1410,6 +1413,7 @@
      *
      * @param destState will be the state that receives the next message.
      */
+    @UnsupportedAppUsage
     public final void transitionTo(IState destState) {
         mSmHandler.transitionTo(destState);
     }
@@ -2053,6 +2057,7 @@
     /**
      * Start the state machine.
      */
+    @UnsupportedAppUsage
     public void start() {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index be12700..0466aaa 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -275,6 +275,7 @@
         "libselinux",
         "libandroidicu",
         "libmedia",
+        "libmedia_helper",
         "libmediametrics",
         "libmeminfo",
         "libaudioclient",
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index dd7633a..cc22ff0 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -29,6 +29,7 @@
 #include "SkBlurDrawLooper.h"
 #include "SkColorFilter.h"
 #include "SkFont.h"
+#include "SkFontMetrics.h"
 #include "SkFontTypes.h"
 #include "SkMaskFilter.h"
 #include "SkPath.h"
@@ -668,12 +669,12 @@
     }
 
     static jint getHinting(jlong paintHandle) {
-        return (SkFontHinting)reinterpret_cast<Paint*>(paintHandle)->getHinting()
+        return (SkFontHinting)reinterpret_cast<Paint*>(paintHandle)->getSkFont().getHinting()
                 == kNo_SkFontHinting ? 0 : 1;
     }
 
     static void setHinting(jlong paintHandle, jint mode) {
-        reinterpret_cast<Paint*>(paintHandle)->setHinting(
+        reinterpret_cast<Paint*>(paintHandle)->getSkFont().setHinting(
                 mode == 0 ? kNo_SkFontHinting : kNormal_SkFontHinting);
     }
 
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 1065738..a0d99ec 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -23,6 +23,7 @@
 #include "core_jni_helpers.h"
 
 #include <utils/Log.h>
+#include <media/AudioParameter.h>
 #include <media/AudioSystem.h>
 #include <media/AudioTrack.h>
 
@@ -1311,6 +1312,33 @@
 }
 
 // ----------------------------------------------------------------------------
+static void android_media_AudioTrack_set_delay_padding(JNIEnv *env,  jobject thiz,
+        jint delayInFrames, jint paddingInFrames) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                "AudioTrack not initialized");
+        return;
+    }
+    AudioParameter param = AudioParameter();
+    param.addInt(String8(AUDIO_OFFLOAD_CODEC_DELAY_SAMPLES), (int) delayInFrames);
+    param.addInt(String8(AUDIO_OFFLOAD_CODEC_PADDING_SAMPLES), (int) paddingInFrames);
+    lpTrack->setParameters(param.toString());
+}
+
+static void android_media_AudioTrack_set_eos(JNIEnv *env,  jobject thiz) {
+    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+    if (lpTrack == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                          "AudioTrack not initialized");
+        return;
+    }
+    AudioParameter param = AudioParameter();
+    param.addInt(String8("EOS"), 1);
+    lpTrack->setParameters(param.toString());
+}
+
+// ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
 static const JNINativeMethod gMethods[] = {
     // name,              signature,     funcPtr
@@ -1385,6 +1413,8 @@
                                         (void *)android_media_AudioTrack_get_volume_shaper_state},
     {"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation},
     {"native_getPortId", "()I", (void *)android_media_AudioTrack_get_port_id},
+    {"native_set_delay_padding", "(II)V", (void *)android_media_AudioTrack_set_delay_padding},
+    {"native_set_eos",        "()V",    (void *)android_media_AudioTrack_set_eos},
 };
 
 
diff --git a/core/jni/android_util_FileObserver.cpp b/core/jni/android_util_FileObserver.cpp
index 6f975b2..d25192a 100644
--- a/core/jni/android_util_FileObserver.cpp
+++ b/core/jni/android_util_FileObserver.cpp
@@ -16,6 +16,8 @@
 */
 
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "jni.h"
 #include "utils/Log.h"
 #include "utils/misc.h"
@@ -98,31 +100,45 @@
 #endif
 }
 
-static jint android_os_fileobserver_startWatching(JNIEnv* env, jobject object, jint fd, jstring pathString, jint mask)
+static void android_os_fileobserver_startWatching(JNIEnv* env, jobject object, jint fd,
+                                                       jobjectArray pathStrings, jint mask,
+                                                       jintArray wfdArray)
 {
-    int res = -1;
+    ScopedIntArrayRW wfds(env, wfdArray);
+    if (wfds.get() == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", "Failed to get ScopedIntArrayRW");
+    }
 
 #if defined(__linux__)
 
     if (fd >= 0)
     {
-        const char* path = env->GetStringUTFChars(pathString, NULL);
+        size_t count = wfds.size();
+        for (jsize i = 0; i < count; ++i) {
+            jstring pathString = (jstring) env->GetObjectArrayElement(pathStrings, i);
 
-        res = inotify_add_watch(fd, path, mask);
+            ScopedUtfChars path(env, pathString);
 
-        env->ReleaseStringUTFChars(pathString, path);
+            wfds[i] = inotify_add_watch(fd, path.c_str(), mask);
+        }
     }
 
 #endif
-
-    return res;
 }
 
-static void android_os_fileobserver_stopWatching(JNIEnv* env, jobject object, jint fd, jint wfd)
+static void android_os_fileobserver_stopWatching(JNIEnv* env, jobject object,
+                                                 jint fd, jintArray wfdArray)
 {
 #if defined(__linux__)
 
-    inotify_rm_watch((int)fd, (uint32_t)wfd);
+    ScopedIntArrayRO wfds(env, wfdArray);
+    if (wfds.get() == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException", "Failed to get ScopedIntArrayRO");
+    }
+    size_t count = wfds.size();
+    for (size_t i = 0; i < count; ++i) {
+        inotify_rm_watch((int)fd, (uint32_t)wfds[i]);
+    }
 
 #endif
 }
@@ -131,8 +147,8 @@
      /* name, signature, funcPtr */
     { "init", "()I", (void*)android_os_fileobserver_init },
     { "observe", "(I)V", (void*)android_os_fileobserver_observe },
-    { "startWatching", "(ILjava/lang/String;I)I", (void*)android_os_fileobserver_startWatching },
-    { "stopWatching", "(II)V", (void*)android_os_fileobserver_stopWatching }
+    { "startWatching", "(I[Ljava/lang/String;I[I)V", (void*)android_os_fileobserver_startWatching },
+    { "stopWatching", "(I[I)V", (void*)android_os_fileobserver_stopWatching }
 
 };
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 69877c7..f1b259e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -124,7 +124,7 @@
 
 static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
         jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject,
-        jint windowType, jint ownerUid) {
+        jobject metadataParcel) {
     ScopedUtfChars name(env, nameStr);
     sp<SurfaceComposerClient> client;
     if (sessionObj != NULL) {
@@ -134,8 +134,18 @@
     }
     SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject);
     sp<SurfaceControl> surface;
+    LayerMetadata metadata;
+    Parcel* parcel = parcelForJavaObject(env, metadataParcel);
+    if (parcel && !parcel->objectsCount()) {
+        status_t err = metadata.readFromParcel(parcel);
+        if (err != NO_ERROR) {
+          jniThrowException(env, "java/lang/IllegalArgumentException",
+                            "Metadata parcel has wrong format");
+        }
+    }
+
     status_t err = client->createSurfaceChecked(
-            String8(name.c_str()), w, h, format, &surface, flags, parent, windowType, ownerUid);
+            String8(name.c_str()), w, h, format, &surface, flags, parent, std::move(metadata));
     if (err == NAME_NOT_FOUND) {
         jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
         return 0;
@@ -377,6 +387,28 @@
     transaction->transferTouchFocus(fromToken, toToken);
 }
 
+static void nativeSetMetadata(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject, jint id, jobject parcelObj) {
+    Parcel* parcel = parcelForJavaObject(env, parcelObj);
+    if (!parcel) {
+        jniThrowNullPointerException(env, "attribute data");
+        return;
+    }
+    if (parcel->objectsCount()) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "Tried to marshall a Parcel that contained Binder objects.");
+        return;
+    }
+
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    std::vector<uint8_t> byteData(parcel->dataSize());
+    memcpy(byteData.data(), parcel->data(), parcel->dataSize());
+
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    transaction->setMetadata(ctrl, id, std::move(byteData));
+}
+
 static void nativeSetColor(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jfloatArray fColor) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -981,7 +1013,7 @@
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod sSurfaceControlMethods[] = {
-    {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJII)J",
+    {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J",
             (void*)nativeCreate },
     {"nativeReadFromParcel", "(Landroid/os/Parcel;)J",
             (void*)nativeReadFromParcel },
@@ -1099,6 +1131,8 @@
             (void*)nativeSetInputWindowInfo },
     {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)V",
             (void*)nativeTransferTouchFocus },
+    {"nativeSetMetadata", "(JILandroid/os/Parcel;)V",
+            (void*)nativeSetMetadata },
     {"nativeGetDisplayedContentSamplingAttributes",
             "(Landroid/os/IBinder;)Landroid/hardware/display/DisplayedContentSamplingAttributes;",
             (void*)nativeGetDisplayedContentSamplingAttributes },
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 8681d4b..6ee9606 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -821,7 +821,7 @@
 
 // Utility to close down the Zygote socket file descriptors while
 // the child is still running as root with Zygote's privileges.  Each
-// descriptor (if any) is closed via dup2(), replacing it with a valid
+// descriptor (if any) is closed via dup3(), replacing it with a valid
 // (open) descriptor to /dev/null.
 
 static void DetachDescriptors(JNIEnv* env,
@@ -829,15 +829,15 @@
                               fail_fn_t fail_fn) {
 
   if (fds_to_close.size() > 0) {
-    android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR));
+    android::base::unique_fd devnull_fd(open("/dev/null", O_RDWR | O_CLOEXEC));
     if (devnull_fd == -1) {
       fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
     }
 
     for (int fd : fds_to_close) {
       ALOGV("Switching descriptor %d to /dev/null", fd);
-      if (dup2(devnull_fd, fd) == -1) {
-        fail_fn(StringPrintf("Failed dup2() on descriptor %d: %s", fd, strerror(errno)));
+      if (dup3(devnull_fd, fd, O_CLOEXEC) == -1) {
+        fail_fn(StringPrintf("Failed dup3() on descriptor %d: %s", fd, strerror(errno)));
       }
     }
   }
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 4e48663..4b37f13 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -423,13 +423,13 @@
 }
 
 void FileDescriptorInfo::DetachSocket(fail_fn_t fail_fn) const {
-  const int dev_null_fd = open("/dev/null", O_RDWR);
+  const int dev_null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
   if (dev_null_fd < 0) {
     fail_fn(std::string("Failed to open /dev/null: ").append(strerror(errno)));
   }
 
-  if (dup2(dev_null_fd, fd) == -1) {
-    fail_fn(android::base::StringPrintf("Failed dup2 on socket descriptor %d: %s",
+  if (dup3(dev_null_fd, fd, O_CLOEXEC) == -1) {
+    fail_fn(android::base::StringPrintf("Failed dup3 on socket descriptor %d: %s",
                                         fd,
                                         strerror(errno)));
   }
diff --git a/core/proto/android/hardware/biometrics/enums.proto b/core/proto/android/hardware/biometrics/enums.proto
new file mode 100644
index 0000000..91f2acb
--- /dev/null
+++ b/core/proto/android/hardware/biometrics/enums.proto
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.hardware.biometrics;
+
+option java_outer_classname = "BiometricsProtoEnums";
+option java_multiple_files = true;
+
+// Logging constants for <Biometric>Service and BiometricService
+
+enum ModalityEnum {
+    MODALITY_UNKNOWN = 0;
+    MODALITY_FINGERPRINT = 1;   // 1 << 0
+    MODALITY_IRIS = 2;          // 1 << 1
+    MODALITY_FACE = 4;          // 1 << 2
+}
+
+enum ClientEnum {
+    CLIENT_UNKNOWN = 0;
+    CLIENT_KEYGUARD = 1;
+    CLIENT_BIOMETRIC_PROMPT = 2;
+    CLIENT_FINGERPRINT_MANAGER = 3; // Deprecated API before BiometricPrompt was introduced
+}
+
+enum ActionEnum {
+    ACTION_UNKNOWN = 0;
+    ACTION_ENROLL = 1;
+    ACTION_AUTHENTICATE = 2;
+    ACTION_ENUMERATE = 3;
+    ACTION_REMOVE = 4;
+}
\ No newline at end of file
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index f06165c..7e7942e 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -522,6 +522,8 @@
         optional SettingProto global_kill_switch = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto gnss_satellite_blacklist = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto gnss_hal_location_request_duration_millis = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        // Packages that are whitelisted for ignoring location settings (during emergencies)
+        optional SettingProto ignore_settings_package_whitelist = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Location location = 69;
 
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index aaf6c63..f3733fd 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -72,6 +72,9 @@
         // List of the accessibility services to which the user has granted
         // permission to put the device into touch exploration mode.
         optional SettingProto touch_exploration_granted_accessibility_services = 31;
+        // Settings for accessibility timeout
+        optional SettingProto non_interactive_ui_timeout_ms = 32 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto interactive_ui_timeout_ms = 33 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Accessibility accessibility = 2;
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4a54bd7..25baa92 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -43,7 +43,7 @@
     <protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" />
-    <protected-broadcast android:name="android.intent.action.PACKAGE_ROLLBACK_EXECUTED" />
+    <protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_RESTARTED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_FIRST_LAUNCH" />
@@ -3329,7 +3329,7 @@
     <permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @SystemApi Allows an application to clear user data.
+    <!-- @SystemApi @TestApi Allows an application to clear user data.
          <p>Not for use by third-party applications
          @hide
     -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 49f2c84..c05795d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2211,6 +2211,10 @@
          has expired, then assume the device is receiving insufficient current to charge
          effectively and terminate the dream.  Use -1 to disable this safety feature.  -->
     <integer name="config_dreamsBatteryLevelDrainCutoff">5</integer>
+    <!-- Limit of how long the device can remain unlocked due to attention checking.  -->
+    <integer name="config_attentionMaximumExtension">240000</integer> <!-- 4 minutes -->
+    <!-- How long we should wait until we give up on receiving an attention API callback.  -->
+    <integer name="config_attentionApiTimeout">2000</integer> <!-- 2 seconds -->
 
     <!-- ComponentName of a dream to show whenever the system would otherwise have
          gone to sleep.  When the PowerManager is asked to go to sleep, it will instead
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d556130..f79e22d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3554,4 +3554,8 @@
   <java-symbol type="bool" name="config_cbrs_supported" />
 
   <java-symbol type="bool" name="config_awareSettingAvailable" />
+
+  <!-- For Attention Service -->
+  <java-symbol type="integer" name="config_attentionMaximumExtension" />
+  <java-symbol type="integer" name="config_attentionApiTimeout" />
 </resources>
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 8e8b07a..9d04e63 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -53,7 +53,6 @@
     org.apache.http.legacy \
     android.test.base \
     android.test.mock \
-    framework-oahl-backward-compatibility \
     framework-atb-backward-compatibility \
 
 LOCAL_PACKAGE_NAME := FrameworksCoreTests
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index a15dbc8..bd7f852 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -131,6 +131,7 @@
                     Settings.Global.AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS,
                     Settings.Global.AUTOMATIC_POWER_SAVER_MODE,
                     Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED,
+                    Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
                     Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
                     Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
                     Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
index 9662182..32bafec 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
@@ -16,9 +16,7 @@
 
 package android.view.textclassifier;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -30,6 +28,8 @@
 @RunWith(AndroidJUnit4.class)
 public class TextClassificationConstantsTest {
 
+    private static final float EPSILON = 0.0001f;
+
     @Test
     public void testLoadFromString() {
         final String s = "local_textclassifier_enabled=true,"
@@ -42,26 +42,55 @@
                 + "suggest_selection_max_range_length=10,"
                 + "classify_text_max_range_length=11,"
                 + "generate_links_max_text_length=12,"
-                + "generate_links_log_sample_rate=13";
+                + "generate_links_log_sample_rate=13,"
+                + "entity_list_default=phone,"
+                + "entity_list_not_editable=address:flight,"
+                + "entity_list_editable=date:datetime,"
+                + "in_app_conversation_action_types_default=text_reply,"
+                + "notification_conversation_action_types_default=send_email:call_phone,"
+                + "lang_id_threshold_override=0.3";
         final TextClassificationConstants constants =
                 TextClassificationConstants.loadFromString(s);
-        assertTrue("local_textclassifier_enabled",
-                constants.isLocalTextClassifierEnabled());
-        assertTrue("system_textclassifier_enabled",
-                constants.isSystemTextClassifierEnabled());
-        assertTrue("model_dark_launch_enabled", constants.isModelDarkLaunchEnabled());
-        assertTrue("smart_selection_enabled", constants.isSmartSelectionEnabled());
-        assertTrue("smart_text_share_enabled", constants.isSmartTextShareEnabled());
-        assertTrue("smart_linkify_enabled", constants.isSmartLinkifyEnabled());
-        assertTrue("smart_select_animation_enabled", constants.isSmartSelectionAnimationEnabled());
-        assertEquals("suggest_selection_max_range_length",
-                10, constants.getSuggestSelectionMaxRangeLength());
-        assertEquals("classify_text_max_range_length",
-                11, constants.getClassifyTextMaxRangeLength());
-        assertEquals("generate_links_max_text_length",
-                12, constants.getGenerateLinksMaxTextLength());
-        assertEquals("generate_links_log_sample_rate",
-                13, constants.getGenerateLinksLogSampleRate());
+
+        assertWithMessage("local_textclassifier_enabled")
+                .that(constants.isLocalTextClassifierEnabled()).isTrue();
+        assertWithMessage("system_textclassifier_enabled")
+                .that(constants.isSystemTextClassifierEnabled()).isTrue();
+        assertWithMessage("model_dark_launch_enabled")
+                .that(constants.isModelDarkLaunchEnabled()).isTrue();
+        assertWithMessage("smart_selection_enabled")
+                .that(constants.isSmartSelectionEnabled()).isTrue();
+        assertWithMessage("smart_text_share_enabled")
+                .that(constants.isSmartTextShareEnabled()).isTrue();
+        assertWithMessage("smart_linkify_enabled")
+                .that(constants.isSmartLinkifyEnabled()).isTrue();
+        assertWithMessage("smart_select_animation_enabled")
+                .that(constants.isSmartSelectionAnimationEnabled()).isTrue();
+        assertWithMessage("suggest_selection_max_range_length")
+                .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(10);
+        assertWithMessage("classify_text_max_range_length")
+                .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(11);
+        assertWithMessage("generate_links_max_text_length")
+                .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(12);
+        assertWithMessage("generate_links_log_sample_rate")
+                .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(13);
+        assertWithMessage("entity_list_default")
+                .that(constants.getEntityListDefault())
+                .containsExactly("phone");
+        assertWithMessage("entity_list_not_editable")
+                .that(constants.getEntityListNotEditable())
+                .containsExactly("address", "flight");
+        assertWithMessage("entity_list_editable")
+                .that(constants.getEntityListEditable())
+                .containsExactly("date", "datetime");
+        assertWithMessage("in_app_conversation_action_types_default")
+                .that(constants.getInAppConversationActionTypes())
+                .containsExactly("text_reply");
+        assertWithMessage("notification_conversation_action_types_default")
+                .that(constants.getNotificationConversationActionTypes())
+                .containsExactly("send_email", "call_phone");
+        assertWithMessage("lang_id_threshold_override")
+                .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(0.3f);
     }
 
     @Test
@@ -76,42 +105,102 @@
                 + "suggest_selection_max_range_length=8,"
                 + "classify_text_max_range_length=7,"
                 + "generate_links_max_text_length=6,"
-                + "generate_links_log_sample_rate=5";
+                + "generate_links_log_sample_rate=5,"
+                + "entity_list_default=email:url,"
+                + "entity_list_not_editable=date,"
+                + "entity_list_editable=flight,"
+                + "in_app_conversation_action_types_default=view_map:track_flight,"
+                + "notification_conversation_action_types_default=share_location,"
+                + "lang_id_threshold_override=2";
         final TextClassificationConstants constants =
                 TextClassificationConstants.loadFromString(s);
-        assertFalse("local_textclassifier_enabled",
-                constants.isLocalTextClassifierEnabled());
-        assertFalse("system_textclassifier_enabled",
-                constants.isSystemTextClassifierEnabled());
-        assertFalse("model_dark_launch_enabled", constants.isModelDarkLaunchEnabled());
-        assertFalse("smart_selection_enabled", constants.isSmartSelectionEnabled());
-        assertFalse("smart_text_share_enabled", constants.isSmartTextShareEnabled());
-        assertFalse("smart_linkify_enabled", constants.isSmartLinkifyEnabled());
-        assertFalse("smart_select_animation_enabled",
-                constants.isSmartSelectionAnimationEnabled());
-        assertEquals("suggest_selection_max_range_length",
-                8, constants.getSuggestSelectionMaxRangeLength());
-        assertEquals("classify_text_max_range_length",
-                7, constants.getClassifyTextMaxRangeLength());
-        assertEquals("generate_links_max_text_length",
-                6, constants.getGenerateLinksMaxTextLength());
-        assertEquals("generate_links_log_sample_rate",
-                5, constants.getGenerateLinksLogSampleRate());
+
+        assertWithMessage("local_textclassifier_enabled")
+                .that(constants.isLocalTextClassifierEnabled()).isFalse();
+        assertWithMessage("system_textclassifier_enabled")
+                .that(constants.isSystemTextClassifierEnabled()).isFalse();
+        assertWithMessage("model_dark_launch_enabled")
+                .that(constants.isModelDarkLaunchEnabled()).isFalse();
+        assertWithMessage("smart_selection_enabled")
+                .that(constants.isSmartSelectionEnabled()).isFalse();
+        assertWithMessage("smart_text_share_enabled")
+                .that(constants.isSmartTextShareEnabled()).isFalse();
+        assertWithMessage("smart_linkify_enabled")
+                .that(constants.isSmartLinkifyEnabled()).isFalse();
+        assertWithMessage("smart_select_animation_enabled")
+                .that(constants.isSmartSelectionAnimationEnabled()).isFalse();
+        assertWithMessage("suggest_selection_max_range_length")
+                .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(8);
+        assertWithMessage("classify_text_max_range_length")
+                .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(7);
+        assertWithMessage("generate_links_max_text_length")
+                .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(6);
+        assertWithMessage("generate_links_log_sample_rate")
+                .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(5);
+        assertWithMessage("entity_list_default")
+                .that(constants.getEntityListDefault())
+                .containsExactly("email", "url");
+        assertWithMessage("entity_list_not_editable")
+                .that(constants.getEntityListNotEditable())
+                .containsExactly("date");
+        assertWithMessage("entity_list_editable")
+                .that(constants.getEntityListEditable())
+                .containsExactly("flight");
+        assertWithMessage("in_app_conversation_action_types_default")
+                .that(constants.getInAppConversationActionTypes())
+                .containsExactly("view_map", "track_flight");
+        assertWithMessage("notification_conversation_action_types_default")
+                .that(constants.getNotificationConversationActionTypes())
+                .containsExactly("share_location");
+        assertWithMessage("lang_id_threshold_override")
+                .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(2f);
     }
 
     @Test
-    public void testEntityListParsing() {
-        final TextClassificationConstants constants = TextClassificationConstants.loadFromString(
-                "entity_list_default=phone,"
-                        + "entity_list_not_editable=address:flight,"
-                        + "entity_list_editable=date:datetime");
-        assertEquals(1, constants.getEntityListDefault().size());
-        assertEquals("phone", constants.getEntityListDefault().get(0));
-        assertEquals(2, constants.getEntityListNotEditable().size());
-        assertEquals("address", constants.getEntityListNotEditable().get(0));
-        assertEquals("flight", constants.getEntityListNotEditable().get(1));
-        assertEquals(2, constants.getEntityListEditable().size());
-        assertEquals("date", constants.getEntityListEditable().get(0));
-        assertEquals("datetime", constants.getEntityListEditable().get(1));
+    public void testLoadFromString_defaultValues() {
+        final TextClassificationConstants constants =
+                TextClassificationConstants.loadFromString("");
+
+        assertWithMessage("local_textclassifier_enabled")
+                .that(constants.isLocalTextClassifierEnabled()).isTrue();
+        assertWithMessage("system_textclassifier_enabled")
+                .that(constants.isSystemTextClassifierEnabled()).isTrue();
+        assertWithMessage("model_dark_launch_enabled")
+                .that(constants.isModelDarkLaunchEnabled()).isFalse();
+        assertWithMessage("smart_selection_enabled")
+                .that(constants.isSmartSelectionEnabled()).isTrue();
+        assertWithMessage("smart_text_share_enabled")
+                .that(constants.isSmartTextShareEnabled()).isTrue();
+        assertWithMessage("smart_linkify_enabled")
+                .that(constants.isSmartLinkifyEnabled()).isTrue();
+        assertWithMessage("smart_select_animation_enabled")
+                .that(constants.isSmartSelectionAnimationEnabled()).isTrue();
+        assertWithMessage("suggest_selection_max_range_length")
+                .that(constants.getSuggestSelectionMaxRangeLength()).isEqualTo(10 * 1000);
+        assertWithMessage("classify_text_max_range_length")
+                .that(constants.getClassifyTextMaxRangeLength()).isEqualTo(10 * 1000);
+        assertWithMessage("generate_links_max_text_length")
+                .that(constants.getGenerateLinksMaxTextLength()).isEqualTo(100 * 1000);
+        assertWithMessage("generate_links_log_sample_rate")
+                .that(constants.getGenerateLinksLogSampleRate()).isEqualTo(100);
+        assertWithMessage("entity_list_default")
+                .that(constants.getEntityListDefault())
+                .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight");
+        assertWithMessage("entity_list_not_editable")
+                .that(constants.getEntityListNotEditable())
+                .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight");
+        assertWithMessage("entity_list_editable")
+                .that(constants.getEntityListEditable())
+                .containsExactly("address", "email", "url", "phone", "date", "datetime", "flight");
+        assertWithMessage("in_app_conversation_action_types_default")
+                .that(constants.getInAppConversationActionTypes())
+                .containsExactly("text_reply", "create_reminder", "call_phone", "open_url",
+                        "send_email", "send_sms", "track_flight", "view_calendar", "view_map");
+        assertWithMessage("notification_conversation_action_types_default")
+                .that(constants.getNotificationConversationActionTypes())
+                .containsExactly("text_reply", "create_reminder", "call_phone", "open_url",
+                        "send_email", "send_sms", "track_flight", "view_calendar", "view_map");
+        assertWithMessage("lang_id_threshold_override")
+                .that(constants.getLangIdThresholdOverride()).isWithin(EPSILON).of(-1f);
     }
 }
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 4755d45..c9e4694 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -1821,6 +1821,8 @@
      * @param cct The correlated color temperature, in Kelvin
      * @return Corresponding XYZ values
      * @throws IllegalArgumentException If cct is invalid
+     *
+     * @hide
      */
     @NonNull
     @Size(3)
@@ -1851,6 +1853,8 @@
      * @param srcWhitePoint The white point to adapt from
      * @param dstWhitePoint The white point to adapt to
      * @return A 3x3 matrix as a non-null array of 9 floats
+     *
+     * @hide
      */
     @NonNull
     @Size(9)
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index d2903f0..2f2d575 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -99,7 +99,7 @@
 
 ////////////////// Java flags compatibility //////////////////
 
-/*	Flags are tricky. Java has its own idea of the "paint" flags, but they don't really
+/*  Flags are tricky. Java has its own idea of the "paint" flags, but they don't really
     match up with skia anymore, so we have to do some shuffling in get/set flags()
 
 	3 flags apply to SkPaint (antialias, dither, filter -> enum)
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index b7f042b..f996d38 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -30,6 +30,7 @@
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothCodecConfig;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -3989,33 +3990,11 @@
     }
 
      /**
-     * Indicate A2DP source or sink connection state change.
-     * @param device Bluetooth device connected/disconnected
-     * @param state  new connection state (BluetoothProfile.STATE_xxx)
-     * @param profile profile for the A2DP device
-     * (either {@link android.bluetooth.BluetoothProfile.A2DP} or
-     * {@link android.bluetooth.BluetoothProfile.A2DP_SINK})
-     * @return a delay in ms that the caller should wait before broadcasting
-     * BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED intent.
-     * {@hide}
-     */
-    public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state,
-            int profile) {
-        final IAudioService service = getService();
-        int delay = 0;
-        try {
-            delay = service.setBluetoothA2dpDeviceConnectionState(device, state, profile);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        return delay;
-    }
-
-     /**
      * Indicate A2DP source or sink connection state change and eventually suppress
      * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent.
      * @param device Bluetooth device connected/disconnected
-     * @param state  new connection state (BluetoothProfile.STATE_xxx)
+     * @param state  new connection state, {@link BluetoothProfile#STATE_CONNECTED}
+     *     or {@link BluetoothProfile#STATE_DISCONNECTED}
      * @param profile profile for the A2DP device
      * @param a2dpVolume New volume for the connecting device. Does nothing if disconnecting.
      * (either {@link android.bluetooth.BluetoothProfile.A2DP} or
@@ -4027,8 +4006,8 @@
      * {@hide}
      */
     public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                BluetoothDevice device, int state, int profile,
-                boolean suppressNoisyIntent, int a2dpVolume) {
+            BluetoothDevice device, int state,
+            int profile, boolean suppressNoisyIntent, int a2dpVolume) {
         final IAudioService service = getService();
         int delay = 0;
         try {
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index af016d5..ffa3b24 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -39,6 +39,8 @@
  */
 public class AudioSystem
 {
+    private static final boolean DEBUG_VOLUME = true;
+
     private static final String TAG = "AudioSystem";
     /* These values must be kept in sync with system/audio.h */
     /*
@@ -879,6 +881,15 @@
         }
     }
 
+    /** Wrapper for native methods called from AudioService */
+    public static int setStreamVolumeIndexAS(int stream, int index, int device) {
+        if (DEBUG_VOLUME) {
+            Log.i(TAG, "setStreamVolumeIndex: " + STREAM_NAMES[stream]
+                    + " dev=" + Integer.toHexString(device) + " idx=" + index);
+        }
+        return setStreamVolumeIndex(stream, index, device);
+    }
+
     // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t
     public static final int SYNC_EVENT_NONE = 0;
     public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1;
@@ -906,7 +917,7 @@
     @UnsupportedAppUsage
     public static native int initStreamVolume(int stream, int indexMin, int indexMax);
     @UnsupportedAppUsage
-    public static native int setStreamVolumeIndex(int stream, int index, int device);
+    private static native int setStreamVolumeIndex(int stream, int index, int device);
     public static native int getStreamVolumeIndex(int stream, int device);
     public static native int setMasterVolume(float value);
     public static native float getMasterVolume();
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 5e77fdf..3440cde 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -996,6 +996,50 @@
     }
 
     /**
+     * Configures the delay and padding values for the current compressed stream playing
+     * in offload mode.
+     * This can only be used on a track successfully initialized with
+     * {@link AudioTrack.Builder#setOffloadedPlayback(boolean)}. The unit is frames, where a
+     * frame indicates the number of samples per channel, e.g. 100 frames for a stereo compressed
+     * stream corresponds to 200 decoded interleaved PCM samples.
+     * @param delayInFrames number of frames to be ignored at the beginning of the stream. A value
+     *     of 0 indicates no padding is to be applied.
+     * @param paddingInFrames number of frames to be ignored at the end of the stream. A value of 0
+     *     of 0 indicates no delay is to be applied.
+     */
+    public void setOffloadDelayPadding(int delayInFrames, int paddingInFrames) {
+        if (paddingInFrames < 0) {
+            throw new IllegalArgumentException("Illegal negative padding");
+        }
+        if (delayInFrames < 0) {
+            throw new IllegalArgumentException("Illegal negative delay");
+        }
+        if (!mOffloaded) {
+            throw new IllegalStateException("Illegal use of delay/padding on non-offloaded track");
+        }
+        if (mState == STATE_UNINITIALIZED) {
+            throw new IllegalStateException("Uninitialized track");
+        }
+        native_set_delay_padding(delayInFrames, paddingInFrames);
+    }
+
+    /**
+     * Declares that the last write() operation on this track provided the last buffer of this
+     * stream.
+     * After the end of stream, previously set padding and delay values are ignored.
+     * Use this method in the same thread as any write() operation.
+     */
+    public void setOffloadEndOfStream() {
+        if (!mOffloaded) {
+            throw new IllegalStateException("EOS not supported on non-offloaded track");
+        }
+        if (mState == STATE_UNINITIALIZED) {
+            throw new IllegalStateException("Uninitialized track");
+        }
+        native_set_eos();
+    }
+
+    /**
      * Returns whether direct playback of an audio format with the provided attributes is
      * currently supported on the system.
      * <p>Direct playback means that the audio stream is not resampled or downmixed
@@ -3457,6 +3501,9 @@
 
     private native int native_getPortId();
 
+    private native void native_set_delay_padding(int delayInFrames, int paddingInFrames);
+    private native void native_set_eos();
+
     //---------------------------------------------------------
     // Utility methods
     //------------------
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 14bdab9..f5aeca7 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -151,8 +151,6 @@
     void setWiredDeviceConnectionState(int type, int state, String address, String name,
             String caller);
 
-    int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile);
-
     void handleBluetoothA2dpDeviceConfigChange(in BluetoothDevice device);
 
     int handleBluetoothA2dpActiveDeviceChange(in BluetoothDevice device,
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 4eed12f..33e7d8e 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -335,7 +335,7 @@
     private final Uri mPlaylistsUri;
     @UnsupportedAppUsage
     private final Uri mFilesUri;
-    private final Uri mFilesUriNoNotify;
+    private final Uri mFilesFullUri;
     private final boolean mProcessPlaylists;
     private final boolean mProcessGenres;
     private int mMtpObjectHandle;
@@ -445,7 +445,11 @@
         mVideoUri = Video.Media.getContentUri(volumeName);
         mImagesUri = Images.Media.getContentUri(volumeName);
         mFilesUri = Files.getContentUri(volumeName);
-        mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
+
+        Uri filesFullUri = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
+        filesFullUri = MediaStore.setIncludePending(filesFullUri);
+        filesFullUri = MediaStore.setIncludeTrashed(filesFullUri);
+        mFilesFullUri = filesFullUri;
 
         if (!volumeName.equals("internal")) {
             // we only support playlists on external media
@@ -1625,7 +1629,7 @@
         try {
             where = Files.FileColumns.DATA + "=?";
             selectionArgs = new String[] { path };
-            c = mMediaProvider.query(mFilesUriNoNotify, FILES_PRESCAN_PROJECTION,
+            c = mMediaProvider.query(mFilesFullUri, FILES_PRESCAN_PROJECTION,
                     where, selectionArgs, null, null);
             if (c != null && c.moveToFirst()) {
                 long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index a45aa90..417a427 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -405,6 +405,11 @@
                 nativeFormat, consumerUsage);
         return;
     }
+
+    if (consumerUsage & GRALLOC_USAGE_PROTECTED) {
+        gbConsumer->setConsumerIsProtected(true);
+    }
+
     ctx->setBufferConsumer(bufferConsumer);
     bufferConsumer->setName(consumerName);
 
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 5fae9d5..3156732 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -47,32 +47,13 @@
 static bool getWideColorSupport(const sp<SurfaceControl>& surfaceControl) {
     sp<SurfaceComposerClient> client = surfaceControl->getClient();
     sp<IBinder> display(client->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
-
-    Vector<ui::ColorMode> colorModes;
-    status_t err = client->getDisplayColorModes(display, &colorModes);
+    bool isWideColorDisplay = false;
+    status_t err = client->isWideColorDisplay(display, &isWideColorDisplay);
     if (err) {
         ALOGE("unable to get wide color support");
         return false;
     }
-
-    bool wideColorBoardConfig =
-        getBool<ISurfaceFlingerConfigs,
-                &ISurfaceFlingerConfigs::hasWideColorDisplay>(false);
-
-    for (android::ui::ColorMode colorMode : colorModes) {
-        switch (colorMode) {
-            case ui::ColorMode::DISPLAY_P3:
-            case ui::ColorMode::ADOBE_RGB:
-            case ui::ColorMode::DCI_P3:
-                if (wideColorBoardConfig) {
-                    return true;
-                }
-                break;
-            default:
-                break;
-        }
-    }
-    return false;
+    return isWideColorDisplay;
 }
 
 static bool getHdrSupport(const sp<SurfaceControl>& surfaceControl) {
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 83084c5..7eaf04b 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -17,7 +17,6 @@
 package com.android.captiveportallogin;
 
 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
-import static android.net.captiveportal.CaptivePortalProbeSpec.HTTP_LOCATION_HEADER_NAME;
 
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -40,8 +39,6 @@
 import android.net.wifi.WifiInfo;
 import android.os.Build;
 import android.os.Bundle;
-import android.provider.Settings;
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -61,16 +58,17 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.lang.InterruptedException;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
 import java.util.Objects;
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -81,6 +79,7 @@
     private static final boolean VDBG = false;
 
     private static final int SOCKET_TIMEOUT_MS = 10000;
+    public static final String HTTP_LOCATION_HEADER_NAME = "Location";
 
     private enum Result {
         DISMISSED(MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_RESULT_DISMISSED),
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index f346b00..ce58d4e 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -230,10 +230,10 @@
         Bundle signals = new Bundle();
 
         if (!smartActions.isEmpty()) {
-            signals.putParcelableArrayList(Adjustment.KEY_SMART_ACTIONS, smartActions);
+            signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, smartActions);
         }
         if (!smartReplies.isEmpty()) {
-            signals.putCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES, smartReplies);
+            signals.putCharSequenceArrayList(Adjustment.KEY_TEXT_REPLIES, smartReplies);
         }
         if (mSettings.mNewInterruptionModel) {
             if (mNotificationCategorizer.shouldSilence(entry)) {
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index a2da0a0..9b0d896 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -21,6 +21,7 @@
     installable: true,
     srcs: [
         "src/**/*.java",
+        ":framework-networkstack-shared-srcs",
         ":services-networkstack-shared-srcs",
     ],
     static_libs: [
diff --git a/packages/NetworkStack/TEST_MAPPING b/packages/NetworkStack/TEST_MAPPING
new file mode 100644
index 0000000..55ba591
--- /dev/null
+++ b/packages/NetworkStack/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "NetworkStackTests"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
index 50c4dfc..08452bb 100644
--- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java
+++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
@@ -26,11 +26,6 @@
 import static android.system.OsConstants.IPPROTO_UDP;
 import static android.system.OsConstants.SOCK_RAW;
 
-import static com.android.internal.util.BitUtils.bytesToBEInt;
-import static com.android.internal.util.BitUtils.getUint16;
-import static com.android.internal.util.BitUtils.getUint32;
-import static com.android.internal.util.BitUtils.getUint8;
-import static com.android.internal.util.BitUtils.uint32;
 import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
 import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
 import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
@@ -1586,6 +1581,29 @@
     // TODO: move to android.net.NetworkUtils
     @VisibleForTesting
     public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) {
-        return bytesToBEInt(addrBytes) | (int) (uint32(-1) >>> prefixLength);
+        return bytesToBEInt(addrBytes) | (int) (Integer.toUnsignedLong(-1) >>> prefixLength);
+    }
+
+    private static int uint8(byte b) {
+        return b & 0xff;
+    }
+
+    private static int getUint16(ByteBuffer buffer, int position) {
+        return buffer.getShort(position) & 0xffff;
+    }
+
+    private static long getUint32(ByteBuffer buffer, int position) {
+        return Integer.toUnsignedLong(buffer.getInt(position));
+    }
+
+    private static int getUint8(ByteBuffer buffer, int position) {
+        return uint8(buffer.get(position));
+    }
+
+    private static int bytesToBEInt(byte[] bytes) {
+        return (uint8(bytes[0]) << 24)
+                + (uint8(bytes[1]) << 16)
+                + (uint8(bytes[2]) << 8)
+                + (uint8(bytes[3]));
     }
 }
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
index 04ac9a3..12eecc0 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
@@ -40,6 +40,8 @@
 import static android.system.OsConstants.SO_RCVBUF;
 import static android.system.OsConstants.SO_REUSEADDR;
 
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
+
 import android.content.Context;
 import android.net.DhcpResults;
 import android.net.NetworkUtils;
@@ -328,7 +330,7 @@
             Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
             Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
             Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0);
-            Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
+            Os.bind(mUdpSock, IPV4_ADDR_ANY, DhcpPacket.DHCP_CLIENT);
         } catch(SocketException|ErrnoException e) {
             Log.e(TAG, "Error creating UDP socket", e);
             return false;
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java b/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java
index 0d298de..0a15cd7 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java
@@ -16,12 +16,13 @@
 
 package android.net.dhcp;
 
-import static android.net.NetworkUtils.inet4AddressToIntHTH;
-import static android.net.NetworkUtils.intToInet4AddressHTH;
-import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH;
 import static android.net.dhcp.DhcpLease.EXPIRATION_NEVER;
 import static android.net.dhcp.DhcpLease.inet4AddrToString;
+import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
+import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
 
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
 import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_BITS;
 
 import static java.lang.Math.min;
@@ -201,7 +202,7 @@
 
     private static boolean isIpAddrOutsidePrefix(@NonNull IpPrefix prefix,
             @Nullable Inet4Address addr) {
-        return addr != null && !addr.equals(Inet4Address.ANY) && !prefix.contains(addr);
+        return addr != null && !addr.equals(IPV4_ADDR_ANY) && !prefix.contains(addr);
     }
 
     @Nullable
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
index ce8b7e7..d7ff98b1 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
@@ -1,10 +1,13 @@
 package android.net.dhcp;
 
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL;
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
+
 import android.annotation.Nullable;
 import android.net.DhcpResults;
 import android.net.LinkAddress;
-import android.net.NetworkUtils;
 import android.net.metrics.DhcpErrorEvent;
+import android.net.shared.Inet4AddressUtils;
 import android.os.Build;
 import android.os.SystemProperties;
 import android.system.OsConstants;
@@ -43,8 +46,8 @@
     public static final int MINIMUM_LEASE = 60;
     public static final int INFINITE_LEASE = (int) 0xffffffff;
 
-    public static final Inet4Address INADDR_ANY = (Inet4Address) Inet4Address.ANY;
-    public static final Inet4Address INADDR_BROADCAST = (Inet4Address) Inet4Address.ALL;
+    public static final Inet4Address INADDR_ANY = IPV4_ADDR_ANY;
+    public static final Inet4Address INADDR_BROADCAST = IPV4_ADDR_ALL;
     public static final byte[] ETHER_BROADCAST = new byte[] {
             (byte) 0xff, (byte) 0xff, (byte) 0xff,
             (byte) 0xff, (byte) 0xff, (byte) 0xff,
@@ -1212,9 +1215,9 @@
      */
     public DhcpResults toDhcpResults() {
         Inet4Address ipAddress = mYourIp;
-        if (ipAddress.equals(Inet4Address.ANY)) {
+        if (ipAddress.equals(IPV4_ADDR_ANY)) {
             ipAddress = mClientIp;
-            if (ipAddress.equals(Inet4Address.ANY)) {
+            if (ipAddress.equals(IPV4_ADDR_ANY)) {
                 return null;
             }
         }
@@ -1222,13 +1225,13 @@
         int prefixLength;
         if (mSubnetMask != null) {
             try {
-                prefixLength = NetworkUtils.netmaskToPrefixLength(mSubnetMask);
+                prefixLength = Inet4AddressUtils.netmaskToPrefixLength(mSubnetMask);
             } catch (IllegalArgumentException e) {
                 // Non-contiguous netmask.
                 return null;
             }
         } else {
-            prefixLength = NetworkUtils.getImplicitNetmask(ipAddress);
+            prefixLength = Inet4AddressUtils.getImplicitNetmask(ipAddress);
         }
 
         DhcpResults results = new DhcpResults();
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
index 7b112df..beabd3e 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
@@ -16,8 +16,6 @@
 
 package android.net.dhcp;
 
-import static android.net.NetworkUtils.getBroadcastAddress;
-import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
 import static android.net.TrafficStats.TAG_SYSTEM_DHCP_SERVER;
 import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
 import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
@@ -25,6 +23,8 @@
 import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
 import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
+import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.IPPROTO_UDP;
 import static android.system.OsConstants.SOCK_DGRAM;
@@ -33,6 +33,8 @@
 import static android.system.OsConstants.SO_REUSEADDR;
 
 import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ALL;
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
 import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
 
 import static java.lang.Integer.toUnsignedLong;
@@ -434,7 +436,7 @@
         if (!isEmpty(request.mRelayIp)) {
             return request.mRelayIp;
         } else if (broadcastFlag) {
-            return (Inet4Address) Inet4Address.ALL;
+            return IPV4_ADDR_ALL;
         } else if (!isEmpty(request.mClientIp)) {
             return request.mClientIp;
         } else {
@@ -517,7 +519,7 @@
                 request.mRelayIp, request.mClientMac, true /* broadcast */, message);
 
         final Inet4Address dst = isEmpty(request.mRelayIp)
-                ? (Inet4Address) Inet4Address.ALL
+                ? IPV4_ADDR_ALL
                 : request.mRelayIp;
         return transmitPacket(nakPacket, DhcpNakPacket.class.getSimpleName(), dst);
     }
@@ -598,7 +600,7 @@
     }
 
     private static boolean isEmpty(@Nullable Inet4Address address) {
-        return address == null || Inet4Address.ANY.equals(address);
+        return address == null || IPV4_ADDR_ANY.equals(address);
     }
 
     private class PacketListener extends DhcpPacketListener {
@@ -632,7 +634,7 @@
                 SocketUtils.bindSocketToInterface(mSocket, mIfName);
                 Os.setsockoptInt(mSocket, SOL_SOCKET, SO_REUSEADDR, 1);
                 Os.setsockoptInt(mSocket, SOL_SOCKET, SO_BROADCAST, 1);
-                Os.bind(mSocket, Inet4Address.ANY, DHCP_SERVER);
+                Os.bind(mSocket, IPV4_ADDR_ANY, DHCP_SERVER);
 
                 return mSocket;
             } catch (IOException | ErrnoException e) {
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
index 868f3be..31ce95b 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
@@ -16,8 +16,8 @@
 
 package android.net.dhcp;
 
-import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
-import static android.net.NetworkUtils.intToInet4AddressHTH;
+import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 
 import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
 import static com.android.server.util.NetworkStackConstants.IPV4_MAX_MTU;
@@ -29,7 +29,7 @@
 import android.annotation.Nullable;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
-import android.net.NetworkUtils;
+import android.net.shared.Inet4AddressUtils;
 import android.util.ArraySet;
 
 import java.net.Inet4Address;
@@ -164,7 +164,8 @@
      */
     @NonNull
     public Inet4Address getBroadcastAddress() {
-        return NetworkUtils.getBroadcastAddress(getServerInet4Addr(), serverAddr.getPrefixLength());
+        return Inet4AddressUtils.getBroadcastAddress(
+                getServerInet4Addr(), serverAddr.getPrefixLength());
     }
 
     /**
diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java
index ad7f85d..f20e0163 100644
--- a/packages/NetworkStack/src/android/net/ip/IpClient.java
+++ b/packages/NetworkStack/src/android/net/ip/IpClient.java
@@ -29,7 +29,6 @@
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
-import android.net.Network;
 import android.net.ProvisioningConfigurationParcelable;
 import android.net.ProxyInfo;
 import android.net.ProxyInfoParcelable;
@@ -1000,7 +999,9 @@
         // mDhcpResults is never shared with any other owner so we don't have
         // to worry about concurrent modification.
         if (mDhcpResults != null) {
-            for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
+            final List<RouteInfo> routes =
+                    mDhcpResults.toStaticIpConfiguration().getRoutes(mInterfaceName);
+            for (RouteInfo route : routes) {
                 newLp.addRoute(route);
             }
             addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
diff --git a/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
index eb993a4..2e6ff24 100644
--- a/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
+++ b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java
@@ -36,8 +36,6 @@
 import android.system.OsConstants;
 import android.util.Log;
 
-import com.android.internal.util.BitUtils;
-
 import libcore.io.IoUtils;
 
 import java.io.FileDescriptor;
@@ -186,7 +184,7 @@
 
             final int srcPortId = nlMsg.getHeader().nlmsg_pid;
             if (srcPortId !=  0) {
-                mLog.e("non-kernel source portId: " + BitUtils.uint32(srcPortId));
+                mLog.e("non-kernel source portId: " + Integer.toUnsignedLong(srcPortId));
                 break;
             }
 
diff --git a/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
index 761db68..76a0338 100644
--- a/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
+++ b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java
@@ -31,15 +31,15 @@
 import android.net.netlink.StructNdMsg;
 import android.net.util.InterfaceParams;
 import android.net.util.SharedLog;
+import android.os.ConditionVariable;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.SystemClock;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DumpUtils;
-import com.android.internal.util.DumpUtils.Dump;
 
 import java.io.PrintWriter;
 import java.net.Inet6Address;
@@ -215,15 +215,20 @@
     }
 
     public void dump(PrintWriter pw) {
-        DumpUtils.dumpAsync(
-                mIpNeighborMonitor.getHandler(),
-                new Dump() {
-                    @Override
-                    public void dump(PrintWriter pw, String prefix) {
-                        pw.println(describeWatchList("\n"));
-                    }
-                },
-                pw, "", 1000);
+        if (Looper.myLooper() == mIpNeighborMonitor.getHandler().getLooper()) {
+            pw.println(describeWatchList("\n"));
+            return;
+        }
+
+        final ConditionVariable cv = new ConditionVariable(false);
+        mIpNeighborMonitor.getHandler().post(() -> {
+            pw.println(describeWatchList("\n"));
+            cv.open();
+        });
+
+        if (!cv.block(1000)) {
+            pw.println("Timed out waiting for IpReachabilityMonitor dump");
+        }
     }
 
     private String describeWatchList() { return describeWatchList(" "); }
diff --git a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
new file mode 100644
index 0000000..6dcf0c0
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+/**
+ * Collection of utilities for the network stack.
+ */
+public class NetworkStackUtils {
+
+    /**
+     * @return True if the array is null or 0-length.
+     */
+    public static <T> boolean isEmpty(T[] array) {
+        return array == null || array.length == 0;
+    }
+}
diff --git a/packages/NetworkStack/src/com/android/server/NetworkObserver.java b/packages/NetworkStack/src/com/android/server/NetworkObserver.java
new file mode 100644
index 0000000..d3b40a6
--- /dev/null
+++ b/packages/NetworkStack/src/com/android/server/NetworkObserver.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.net.LinkAddress;
+
+/**
+ * Observer for network events, to use with {@link NetworkObserverRegistry}.
+ */
+public interface NetworkObserver {
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener#onInterfaceChanged(java.lang.String, boolean)
+     */
+    default void onInterfaceChanged(String ifName, boolean up) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener#onInterfaceRemoved(String)
+     */
+    default void onInterfaceRemoved(String ifName) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener
+     *          #onInterfaceAddressUpdated(String, String, int, int)
+     */
+    default void onInterfaceAddressUpdated(LinkAddress address, String ifName) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener
+     *          #onInterfaceAddressRemoved(String, String, int, int)
+     */
+    default void onInterfaceAddressRemoved(LinkAddress address, String ifName) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener#onInterfaceLinkStateChanged(String, boolean)
+     */
+    default void onInterfaceLinkStateChanged(String ifName, boolean up) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener#onInterfaceAdded(String)
+     */
+    default void onInterfaceAdded(String ifName) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener
+     *          #onInterfaceClassActivityChanged(boolean, int, long, int)
+     */
+    default void onInterfaceClassActivityChanged(
+            boolean isActive, int label, long timestamp, int uid) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener#onQuotaLimitReached(String, String)
+     */
+    default void onQuotaLimitReached(String alertName, String ifName) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener
+     *          #onInterfaceDnsServerInfo(String, long, String[])
+     */
+    default void onInterfaceDnsServerInfo(String ifName, long lifetime, String[] servers) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener
+     *          #onRouteChanged(boolean, String, String, String)
+     */
+    default void onRouteUpdated(String route, String gateway, String ifName) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener
+     *          #onRouteChanged(boolean, String, String, String)
+     */
+    default void onRouteRemoved(String route, String gateway, String ifName) {}
+}
diff --git a/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java b/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java
new file mode 100644
index 0000000..14e6c5f
--- /dev/null
+++ b/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server;
+
+import android.annotation.NonNull;
+import android.net.INetd;
+import android.net.INetdUnsolicitedEventListener;
+import android.net.LinkAddress;
+import android.os.Handler;
+import android.os.RemoteException;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A class for reporting network events to clients.
+ *
+ * Implements INetdUnsolicitedEventListener and registers with netd, and relays those events to
+ * all INetworkManagementEventObserver objects that have registered with it.
+ */
+public class NetworkObserverRegistry extends INetdUnsolicitedEventListener.Stub {
+
+    /**
+     * Constructs a new NetworkObserverRegistry.
+     *
+     * <p>Only one registry should be used per process since netd will silently ignore multiple
+     * registrations from the same process.
+     */
+    NetworkObserverRegistry() {}
+
+    /**
+     * Start listening for Netd events.
+     *
+     * <p>This should be called before allowing any observer to be registered.
+     */
+    void register(@NonNull INetd netd) throws RemoteException {
+        netd.registerUnsolicitedEventListener(this);
+    }
+
+    private final ConcurrentHashMap<NetworkObserver, Handler> mObservers =
+            new ConcurrentHashMap<>();
+
+    /**
+     * Registers the specified observer and start sending callbacks to it.
+     * This method may be called on any thread.
+     */
+    public void registerObserver(@NonNull NetworkObserver observer, @NonNull Handler handler) {
+        mObservers.put(observer, handler);
+    }
+
+    /**
+     * Unregisters the specified observer and stop sending callbacks to it.
+     * This method may be called on any thread.
+     */
+    public void unregisterObserver(@NonNull NetworkObserver observer) {
+        mObservers.remove(observer);
+    }
+
+    @FunctionalInterface
+    private interface NetworkObserverEventCallback {
+        void sendCallback(NetworkObserver o);
+    }
+
+    private void invokeForAllObservers(@NonNull final NetworkObserverEventCallback callback) {
+        // ConcurrentHashMap#entrySet is weakly consistent: observers that were in the map before
+        // creation will be processed, those added during traversal may or may not.
+        for (Map.Entry<NetworkObserver, Handler> entry : mObservers.entrySet()) {
+            final NetworkObserver observer = entry.getKey();
+            entry.getValue().post(() -> callback.sendCallback(observer));
+        }
+    }
+
+    @Override
+    public void onInterfaceClassActivityChanged(boolean isActive,
+            int label, long timestamp, int uid) {
+        invokeForAllObservers(o -> o.onInterfaceClassActivityChanged(
+                isActive, label, timestamp, uid));
+    }
+
+    /**
+     * Notify our observers of a limit reached.
+     */
+    @Override
+    public void onQuotaLimitReached(String alertName, String ifName) {
+        invokeForAllObservers(o -> o.onQuotaLimitReached(alertName, ifName));
+    }
+
+    @Override
+    public void onInterfaceDnsServerInfo(String ifName, long lifetime, String[] servers) {
+        invokeForAllObservers(o -> o.onInterfaceDnsServerInfo(ifName, lifetime, servers));
+    }
+
+    @Override
+    public void onInterfaceAddressUpdated(String addr, String ifName, int flags, int scope) {
+        final LinkAddress address = new LinkAddress(addr, flags, scope);
+        invokeForAllObservers(o -> o.onInterfaceAddressUpdated(address, ifName));
+    }
+
+    @Override
+    public void onInterfaceAddressRemoved(String addr,
+            String ifName, int flags, int scope) {
+        final LinkAddress address = new LinkAddress(addr, flags, scope);
+        invokeForAllObservers(o -> o.onInterfaceAddressRemoved(address, ifName));
+    }
+
+    @Override
+    public void onInterfaceAdded(String ifName) {
+        invokeForAllObservers(o -> o.onInterfaceAdded(ifName));
+    }
+
+    @Override
+    public void onInterfaceRemoved(String ifName) {
+        invokeForAllObservers(o -> o.onInterfaceRemoved(ifName));
+    }
+
+    @Override
+    public void onInterfaceChanged(String ifName, boolean up) {
+        invokeForAllObservers(o -> o.onInterfaceChanged(ifName, up));
+    }
+
+    @Override
+    public void onInterfaceLinkStateChanged(String ifName, boolean up) {
+        invokeForAllObservers(o -> o.onInterfaceLinkStateChanged(ifName, up));
+    }
+
+    @Override
+    public void onRouteChanged(boolean updated, String route, String gateway, String ifName) {
+        if (updated) {
+            invokeForAllObservers(o -> o.onRouteUpdated(route, gateway, ifName));
+        } else {
+            invokeForAllObservers(o -> o.onRouteRemoved(route, gateway, ifName));
+        }
+    }
+
+    @Override
+    public void onStrictCleartextDetected(int uid, String hex) {}
+}
diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
index 4080ddf..631ee45 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.ConnectivityManager;
+import android.net.INetd;
 import android.net.INetworkMonitor;
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkStackConnector;
@@ -65,6 +66,7 @@
  */
 public class NetworkStackService extends Service {
     private static final String TAG = NetworkStackService.class.getSimpleName();
+    private static NetworkStackConnector sConnector;
 
     /**
      * Create a binder connector for the system server to communicate with the network stack.
@@ -72,8 +74,11 @@
      * <p>On platforms where the network stack runs in the system server process, this method may
      * be called directly instead of obtaining the connector by binding to the service.
      */
-    public static IBinder makeConnector(Context context) {
-        return new NetworkStackConnector(context);
+    public static synchronized IBinder makeConnector(Context context) {
+        if (sConnector == null) {
+            sConnector = new NetworkStackConnector(context);
+        }
+        return sConnector;
     }
 
     @NonNull
@@ -85,6 +90,8 @@
     private static class NetworkStackConnector extends INetworkStackConnector.Stub {
         private static final int NUM_VALIDATION_LOG_LINES = 20;
         private final Context mContext;
+        private final INetd mNetd;
+        private final NetworkObserverRegistry mObserverRegistry;
         private final ConnectivityManager mCm;
         @GuardedBy("mIpClients")
         private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>();
@@ -106,7 +113,11 @@
 
         NetworkStackConnector(Context context) {
             mContext = context;
+            mNetd = (INetd) context.getSystemService(Context.NETD_SERVICE);
+            mObserverRegistry = new NetworkObserverRegistry();
             mCm = context.getSystemService(ConnectivityManager.class);
+
+            // TODO: call mObserverRegistry here after adding sepolicy changes
         }
 
         @NonNull
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 6b31b82..96eaa50 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -31,6 +31,7 @@
 import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
 import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
 import static android.net.metrics.ValidationProbeEvent.PROBE_PRIVDNS;
+import static android.net.util.NetworkStackUtils.isEmpty;
 
 import android.annotation.Nullable;
 import android.app.PendingIntent;
@@ -80,7 +81,6 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.RingBufferIndices;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -93,6 +93,7 @@
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -1146,7 +1147,10 @@
                 return null;
             }
 
-            return CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue);
+            final Collection<CaptivePortalProbeSpec> specs =
+                    CaptivePortalProbeSpec.parseCaptivePortalProbeSpecs(settingsValue);
+            final CaptivePortalProbeSpec[] specsArray = new CaptivePortalProbeSpec[specs.size()];
+            return specs.toArray(specsArray);
         } catch (Exception e) {
             // Don't let a misconfiguration bootloop the system.
             Log.e(TAG, "Error parsing configured fallback probe specs", e);
@@ -1169,7 +1173,7 @@
     }
 
     private CaptivePortalProbeSpec nextFallbackSpec() {
-        if (ArrayUtils.isEmpty(mCaptivePortalFallbackSpecs)) {
+        if (isEmpty(mCaptivePortalFallbackSpecs)) {
             return null;
         }
         // Randomly change spec without memory. Also randomize the first attempt.
diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
index eedaf30..804765e 100644
--- a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
+++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
@@ -16,6 +16,10 @@
 
 package com.android.server.util;
 
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
+
+import java.net.Inet4Address;
+
 /**
  * Network constants used by the network stack.
  */
@@ -79,6 +83,8 @@
     public static final int IPV4_SRC_ADDR_OFFSET = 12;
     public static final int IPV4_DST_ADDR_OFFSET = 16;
     public static final int IPV4_ADDR_LEN = 4;
+    public static final Inet4Address IPV4_ADDR_ALL = intToInet4AddressHTH(0xffffffff);
+    public static final Inet4Address IPV4_ADDR_ANY = intToInet4AddressHTH(0x0);
 
     /**
      * IPv6 constants.
diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
index 51d50d9..4abd77e 100644
--- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
+++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
@@ -21,6 +21,8 @@
 import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC;
 import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC;
 
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -55,7 +57,6 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class DhcpLeaseRepositoryTest {
-    private static final Inet4Address INET4_ANY = (Inet4Address) Inet4Address.ANY;
     private static final Inet4Address TEST_DEF_ROUTER = parseAddr4("192.168.42.247");
     private static final Inet4Address TEST_SERVER_ADDR = parseAddr4("192.168.42.241");
     private static final Inet4Address TEST_RESERVED_ADDR = parseAddr4("192.168.42.243");
@@ -108,7 +109,7 @@
             MacAddress newMac = MacAddress.fromBytes(hwAddrBytes);
             final String hostname = "host_" + i;
             final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, newMac,
-                    INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname);
+                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname);
 
             assertNotNull(lease);
             assertEquals(newMac, lease.getHwAddr());
@@ -130,7 +131,7 @@
 
         try {
             mRepo.getOffer(null, TEST_MAC_2,
-                    INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
             fail("Should be out of addresses");
         } catch (DhcpLeaseRepository.OutOfAddressesException e) {
             // Expected
@@ -181,11 +182,11 @@
     public void testGetOffer_StableAddress() throws Exception {
         for (final MacAddress macAddr : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) {
             final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr,
-                    INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
 
             // Same lease is offered twice
             final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr,
-                    INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
             assertEquals(lease, newLease);
         }
     }
@@ -196,7 +197,7 @@
         mRepo.updateParams(newPrefix, TEST_EXCL_SET, TEST_LEASE_TIME_MS);
 
         DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
         assertTrue(newPrefix.contains(lease.getNetAddr()));
     }
 
@@ -205,7 +206,7 @@
         requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1, TEST_HOSTNAME_1);
 
         DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
         assertEquals(TEST_INETADDR_1, offer.getNetAddr());
         assertEquals(TEST_HOSTNAME_1, offer.getHostname());
     }
@@ -213,12 +214,13 @@
     @Test
     public void testGetOffer_ClientIdHasExistingLease() throws Exception {
         final byte[] clientId = new byte[] { 1, 2 };
-        mRepo.requestLease(clientId, TEST_MAC_1, INET4_ANY /* clientAddr */,
-                INET4_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, TEST_HOSTNAME_1);
+        mRepo.requestLease(clientId, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */,
+                IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false,
+                TEST_HOSTNAME_1);
 
         // Different MAC, but same clientId
         DhcpLease offer = mRepo.getOffer(clientId, TEST_MAC_2,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
         assertEquals(TEST_INETADDR_1, offer.getNetAddr());
         assertEquals(TEST_HOSTNAME_1, offer.getHostname());
     }
@@ -227,12 +229,13 @@
     public void testGetOffer_DifferentClientId() throws Exception {
         final byte[] clientId1 = new byte[] { 1, 2 };
         final byte[] clientId2 = new byte[] { 3, 4 };
-        mRepo.requestLease(clientId1, TEST_MAC_1, INET4_ANY /* clientAddr */,
-                INET4_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false, TEST_HOSTNAME_1);
+        mRepo.requestLease(clientId1, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */,
+                IPV4_ADDR_ANY /* relayAddr */, TEST_INETADDR_1 /* reqAddr */, false,
+                TEST_HOSTNAME_1);
 
         // Same MAC, different client ID
         DhcpLease offer = mRepo.getOffer(clientId2, TEST_MAC_1,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
         // Obtains a different address
         assertNotEquals(TEST_INETADDR_1, offer.getNetAddr());
         assertEquals(HOSTNAME_NONE, offer.getHostname());
@@ -241,7 +244,7 @@
 
     @Test
     public void testGetOffer_RequestedAddress() throws Exception {
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */,
+        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */,
                 TEST_INETADDR_1 /* reqAddr */, TEST_HOSTNAME_1);
         assertEquals(TEST_INETADDR_1, offer.getNetAddr());
         assertEquals(TEST_HOSTNAME_1, offer.getHostname());
@@ -250,14 +253,14 @@
     @Test
     public void testGetOffer_RequestedAddressInUse() throws Exception {
         requestLeaseSelecting(TEST_MAC_1, TEST_INETADDR_1);
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY /* relayAddr */,
+        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, IPV4_ADDR_ANY /* relayAddr */,
                 TEST_INETADDR_1 /* reqAddr */, HOSTNAME_NONE);
         assertNotEquals(TEST_INETADDR_1, offer.getNetAddr());
     }
 
     @Test
     public void testGetOffer_RequestedAddressReserved() throws Exception {
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */,
+        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */,
                 TEST_RESERVED_ADDR /* reqAddr */, HOSTNAME_NONE);
         assertNotEquals(TEST_RESERVED_ADDR, offer.getNetAddr());
     }
@@ -265,7 +268,7 @@
     @Test
     public void testGetOffer_RequestedAddressInvalid() throws Exception {
         final Inet4Address invalidAddr = parseAddr4("192.168.42.0");
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */,
+        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */,
                 invalidAddr /* reqAddr */, HOSTNAME_NONE);
         assertNotEquals(invalidAddr, offer.getNetAddr());
     }
@@ -273,7 +276,7 @@
     @Test
     public void testGetOffer_RequestedAddressOutsideSubnet() throws Exception {
         final Inet4Address invalidAddr = parseAddr4("192.168.254.2");
-        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* relayAddr */,
+        DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* relayAddr */,
                 invalidAddr /* reqAddr */, HOSTNAME_NONE);
         assertNotEquals(invalidAddr, offer.getNetAddr());
     }
@@ -322,7 +325,7 @@
 
     @Test(expected = DhcpLeaseRepository.InvalidSubnetException.class)
     public void testRequestLease_SelectingRelayInInvalidSubnet() throws  Exception {
-        mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY /* clientAddr */,
+        mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, IPV4_ADDR_ANY /* clientAddr */,
                 parseAddr4("192.168.128.1") /* relayAddr */, TEST_INETADDR_1 /* reqAddr */,
                 true /* sidSet */, HOSTNAME_NONE);
     }
@@ -419,14 +422,14 @@
     public void testReleaseLease_StableOffer() throws Exception {
         for (MacAddress mac : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) {
             final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, mac,
-                    INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
 
             requestLeaseSelecting(mac, lease.getNetAddr());
             mRepo.releaseLease(CLIENTID_UNSPEC, mac, lease.getNetAddr());
 
             // Same lease is offered after it was released
             final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, mac,
-                    INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                    IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
             assertEquals(lease.getNetAddr(), newLease.getNetAddr());
         }
     }
@@ -434,13 +437,13 @@
     @Test
     public void testMarkLeaseDeclined() throws Exception {
         final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
 
         mRepo.markLeaseDeclined(lease.getNetAddr());
 
         // Same lease is not offered again
         final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
         assertNotEquals(lease.getNetAddr(), newLease.getNetAddr());
     }
 
@@ -457,16 +460,16 @@
 
         // Last 2 addresses: addresses marked declined should be used
         final DhcpLease firstLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1);
         requestLeaseSelecting(TEST_MAC_1, firstLease.getNetAddr());
 
         final DhcpLease secondLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2,
-                INET4_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2);
+                IPV4_ADDR_ANY /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2);
         requestLeaseSelecting(TEST_MAC_2, secondLease.getNetAddr());
 
         // Now out of addresses
         try {
-            mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, INET4_ANY /* relayAddr */,
+            mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, IPV4_ADDR_ANY /* relayAddr */,
                     INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
             fail("Repository should be out of addresses and throw");
         } catch (DhcpLeaseRepository.OutOfAddressesException e) { /* expected */ }
@@ -480,7 +483,8 @@
     private DhcpLease requestLease(@NonNull MacAddress macAddr, @NonNull Inet4Address clientAddr,
             @Nullable Inet4Address reqAddr, @Nullable String hostname, boolean sidSet)
             throws DhcpLeaseRepository.DhcpLeaseException {
-        return mRepo.requestLease(CLIENTID_UNSPEC, macAddr, clientAddr, INET4_ANY /* relayAddr */,
+        return mRepo.requestLease(CLIENTID_UNSPEC, macAddr, clientAddr,
+                IPV4_ADDR_ANY /* relayAddr */,
                 reqAddr, sidSet, hostname);
     }
 
@@ -490,7 +494,7 @@
     private DhcpLease requestLeaseSelecting(@NonNull MacAddress macAddr,
             @NonNull Inet4Address reqAddr, @Nullable String hostname)
             throws DhcpLeaseRepository.DhcpLeaseException {
-        return requestLease(macAddr, INET4_ANY /* clientAddr */, reqAddr, hostname,
+        return requestLease(macAddr, IPV4_ADDR_ANY /* clientAddr */, reqAddr, hostname,
                 true /* sidSet */);
     }
 
@@ -507,7 +511,7 @@
      */
     private DhcpLease requestLeaseInitReboot(@NonNull MacAddress macAddr,
             @NonNull Inet4Address reqAddr) throws DhcpLeaseRepository.DhcpLeaseException {
-        return requestLease(macAddr, INET4_ANY /* clientAddr */, reqAddr, HOSTNAME_NONE,
+        return requestLease(macAddr, IPV4_ADDR_ANY /* clientAddr */, reqAddr, HOSTNAME_NONE,
                 false /* sidSet */);
     }
 
diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
index a592809..7544e72 100644
--- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
+++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
@@ -16,9 +16,27 @@
 
 package android.net.dhcp;
 
-import static android.net.NetworkUtils.getBroadcastAddress;
-import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
-import static android.net.dhcp.DhcpPacket.*;
+import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS;
+import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER;
+import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME;
+import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_ACK;
+import static android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_OFFER;
+import static android.net.dhcp.DhcpPacket.DHCP_MTU;
+import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME;
+import static android.net.dhcp.DhcpPacket.DHCP_ROUTER;
+import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK;
+import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO;
+import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
+import static android.net.dhcp.DhcpPacket.ENCAP_L2;
+import static android.net.dhcp.DhcpPacket.ENCAP_L3;
+import static android.net.dhcp.DhcpPacket.INADDR_ANY;
+import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
+import static android.net.dhcp.DhcpPacket.ParseException;
+import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
+import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -30,11 +48,15 @@
 import android.net.LinkAddress;
 import android.net.NetworkUtils;
 import android.net.metrics.DhcpErrorEvent;
-import android.support.test.runner.AndroidJUnit4;
 import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.HexDump;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.ByteArrayOutputStream;
 import java.net.Inet4Address;
 import java.nio.ByteBuffer;
@@ -44,10 +66,6 @@
 import java.util.Collections;
 import java.util.Random;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class DhcpPacketTest {
@@ -60,9 +78,9 @@
             SERVER_ADDR, PREFIX_LENGTH);
     private static final String HOSTNAME = "testhostname";
     private static final short MTU = 1500;
-    // Use our own empty address instead of Inet4Address.ANY or INADDR_ANY to ensure that the code
+    // Use our own empty address instead of IPV4_ADDR_ANY or INADDR_ANY to ensure that the code
     // doesn't use == instead of equals when comparing addresses.
-    private static final Inet4Address ANY = (Inet4Address) v4Address("0.0.0.0");
+    private static final Inet4Address ANY = v4Address("0.0.0.0");
 
     private static final byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
 
diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
index 3ca0564..1004382 100644
--- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
+++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
@@ -17,8 +17,8 @@
 package android.net.dhcp;
 
 import static android.net.InetAddresses.parseNumericAddress;
-import static android.net.NetworkUtils.inet4AddressToIntHTH;
 import static android.net.dhcp.DhcpServingParams.MTU_UNSET;
+import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -27,8 +27,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.LinkAddress;
-import android.net.NetworkUtils;
 import android.net.dhcp.DhcpServingParams.InvalidParameterException;
+import android.net.shared.Inet4AddressUtils;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -200,7 +200,7 @@
     }
 
     private static int[] toIntArray(Collection<Inet4Address> addrs) {
-        return addrs.stream().mapToInt(NetworkUtils::inet4AddressToIntHTH).toArray();
+        return addrs.stream().mapToInt(Inet4AddressUtils::inet4AddressToIntHTH).toArray();
     }
 
     private static <T> void assertContains(@NonNull Set<T> set, @NonNull Set<T> subset) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index aff6f04..a1aefab 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -822,6 +822,9 @@
         dumpSetting(s, p,
                 Settings.Global.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS,
                 GlobalSettingsProto.Location.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS);
+        dumpSetting(s, p,
+                Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
+                GlobalSettingsProto.Location.IGNORE_SETTINGS_PACKAGE_WHITELIST);
         p.end(locationToken);
 
         final long lpmToken = p.start(GlobalSettingsProto.LOW_POWER_MODE);
@@ -1711,6 +1714,12 @@
         dumpSetting(s, p,
                 Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
                 SecureSettingsProto.Accessibility.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES);
+        dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS,
+                SecureSettingsProto.Accessibility.NON_INTERACTIVE_UI_TIMEOUT_MS);
+        dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS,
+                SecureSettingsProto.Accessibility.INTERACTIVE_UI_TIMEOUT_MS);
         p.end(accessibilityToken);
 
         dumpSetting(s, p,
diff --git a/packages/SystemUI/res/drawable/smart_reply_button_background.xml b/packages/SystemUI/res/drawable/smart_reply_button_background.xml
index 31119a9..5652b49 100644
--- a/packages/SystemUI/res/drawable/smart_reply_button_background.xml
+++ b/packages/SystemUI/res/drawable/smart_reply_button_background.xml
@@ -25,7 +25,7 @@
             android:insetRight="0dp"
             android:insetBottom="8dp">
             <shape android:shape="rectangle">
-                <corners android:radius="8dp" />
+              <corners android:radius="@dimen/smart_reply_button_corner_radius" />
                 <stroke android:width="@dimen/smart_reply_button_stroke_width"
                         android:color="@color/smart_reply_button_stroke" />
                 <solid android:color="@color/smart_reply_button_background"/>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a14259e..d435c67 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -888,8 +888,10 @@
     <dimen name="smart_reply_button_stroke_width">1dp</dimen>
     <dimen name="smart_reply_button_font_size">14sp</dimen>
     <dimen name="smart_reply_button_line_spacing_extra">6sp</dimen> <!-- Total line height 20sp. -->
-    <dimen name="smart_action_button_icon_size">24dp</dimen>
-    <dimen name="smart_action_button_icon_padding">10dp</dimen>
+    <!-- Corner radius = half of min_height to create rounded sides. -->
+    <dimen name="smart_reply_button_corner_radius">24dp</dimen>
+    <dimen name="smart_action_button_icon_size">18dp</dimen>
+    <dimen name="smart_action_button_icon_padding">8dp</dimen>
 
     <!-- A reasonable upper bound for the height of the smart reply button. The measuring code
             needs to start with a guess for the maximum size. Currently two-line smart reply buttons
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 190bd7a..ffa5de8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2305,8 +2305,8 @@
         <item quantity="other"><xliff:g id="num_apps" example="3">%1$d</xliff:g> applications are using your <xliff:g id="type" example="camera">%2$s</xliff:g>.</item>
     </plurals>
 
-    <!-- Action on Ongoing Privacy Dialog to dismiss [CHAR LIMIT=10]-->
-    <string name="ongoing_privacy_dialog_cancel">Cancel</string>
+    <!-- Action for accepting the Ongoing privacy dialog [CHAR LIMIT=10]-->
+    <string name="ongoing_privacy_dialog_ok">Got it</string>
 
     <!-- Action on Ongoing Privacy Dialog to open privacy hub [CHAR LIMIT=20]-->
     <string name="ongoing_privacy_dialog_open_settings">View details</string>
@@ -2337,6 +2337,7 @@
         <item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> other app</item>
         <item quantity="other"><xliff:g id="num_apps" example="3">%d</xliff:g> other apps</item>
     </plurals>
+
     <!-- Text for the quick setting tile for sensor privacy [CHAR LIMIT=30] -->
     <string name="sensor_privacy_mode">Sensors off</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 5fdf76f..b584f67 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -33,6 +33,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
@@ -590,9 +591,12 @@
 
     private int getStatusBarHeight() {
         if (getRootWindowInsets() != null) {
+            WindowInsets insets = getRootWindowInsets();
             return Math.max(
-                    getRootWindowInsets().getSystemWindowInsetTop(),
-                    getRootWindowInsets().getDisplayCutout().getSafeInsetTop());
+                    insets.getSystemWindowInsetTop(),
+                    insets.getDisplayCutout() != null
+                            ? insets.getDisplayCutout().getSafeInsetTop()
+                            : 0);
         }
 
         return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index f3ca938..4f870f6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -98,7 +98,9 @@
         if (insets != null) {
             return mBubblePaddingPx + Math.max(
                     insets.getSystemWindowInsetTop(),
-                    insets.getDisplayCutout().getSafeInsetTop());
+                    insets.getDisplayCutout() != null
+                            ? insets.getDisplayCutout().getSafeInsetTop()
+                            : 0);
         }
 
         return mBubblePaddingPx;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index a113a63..0f51376 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -211,7 +211,9 @@
                             - mBubblePadding
                             + Math.max(
                             insets.getSystemWindowInsetLeft(),
-                            insets.getDisplayCutout().getSafeInsetLeft());
+                            insets.getDisplayCutout() != null
+                                    ? insets.getDisplayCutout().getSafeInsetLeft()
+                                    : 0);
             mAllowableStackPositionRegion.right =
                     mLayout.getWidth()
                             - mIndividualBubbleSize
@@ -219,20 +221,26 @@
                             - mBubblePadding
                             - Math.max(
                             insets.getSystemWindowInsetRight(),
-                            insets.getDisplayCutout().getSafeInsetRight());
+                            insets.getDisplayCutout() != null
+                                ? insets.getDisplayCutout().getSafeInsetRight()
+                                : 0);
 
             mAllowableStackPositionRegion.top =
                     mBubblePadding
                             + Math.max(
                             insets.getSystemWindowInsetTop(),
-                            insets.getDisplayCutout().getSafeInsetTop());
+                            insets.getDisplayCutout() != null
+                                ? insets.getDisplayCutout().getSafeInsetTop()
+                                : 0);
             mAllowableStackPositionRegion.bottom =
                     mLayout.getHeight()
                             - mIndividualBubbleSize
                             - mBubblePadding
                             - Math.max(
                             insets.getSystemWindowInsetBottom(),
-                            insets.getDisplayCutout().getSafeInsetBottom());
+                            insets.getDisplayCutout() != null
+                                    ? insets.getDisplayCutout().getSafeInsetBottom()
+                                    : 0);
         }
 
         return mAllowableStackPositionRegion;
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
index 26c6d50..db5c244 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
@@ -20,6 +20,7 @@
 import android.content.DialogInterface
 import android.content.Intent
 import android.content.res.ColorStateList
+import android.os.UserHandle
 import android.util.IconDrawableFactory
 import android.view.Gravity
 import android.view.LayoutInflater
@@ -48,6 +49,7 @@
             R.dimen.ongoing_appops_dialog_icon_margin)
     private val MAX_ITEMS = context.resources.getInteger(R.integer.ongoing_appops_dialog_max_apps)
     private val iconFactory = IconDrawableFactory.newInstance(context, true)
+    private var dismissDialog: (() -> Unit)? = null
 
     init {
         val a = context.theme.obtainStyledAttributes(
@@ -58,8 +60,8 @@
 
     fun createDialog(): Dialog {
         val builder = AlertDialog.Builder(context).apply {
-            setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null)
-            setPositiveButton(R.string.ongoing_privacy_dialog_open_settings,
+            setPositiveButton(R.string.ongoing_privacy_dialog_ok, null)
+            setNeutralButton(R.string.ongoing_privacy_dialog_open_settings,
                     object : DialogInterface.OnClickListener {
                         val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE).putExtra(
                                 Intent.EXTRA_DURATION_MILLIS, TimeUnit.MINUTES.toMillis(1))
@@ -72,7 +74,9 @@
                     })
         }
         builder.setView(getContentView())
-        return builder.create()
+        val dialog = builder.create()
+        dismissDialog = dialog::dismiss
+        return dialog
     }
 
     fun getContentView(): View {
@@ -116,6 +120,7 @@
         return contentView
     }
 
+    @Suppress("DEPRECATION")
     private fun addAppItem(
         itemList: LinearLayout,
         app: PrivacyApplication,
@@ -152,6 +157,16 @@
         } else {
             icons.visibility = View.GONE
         }
+        item.setOnClickListener(object : View.OnClickListener {
+            val intent = Intent(Intent.ACTION_REVIEW_APP_PERMISSION_USAGE)
+                    .putExtra(Intent.EXTRA_PACKAGE_NAME, app.packageName)
+                    .putExtra(Intent.EXTRA_USER, UserHandle.getUserHandleForUid(app.uid))
+            override fun onClick(v: View?) {
+                Dependency.get(ActivityStarter::class.java)
+                        .postStartActivityDismissingKeyguard(intent, 0)
+                dismissDialog?.invoke()
+            }
+        })
         itemList.addView(item)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
index 9252167..dbe87d1 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
@@ -37,7 +37,7 @@
     val application: PrivacyApplication
 )
 
-data class PrivacyApplication(val packageName: String, val context: Context)
+data class PrivacyApplication(val packageName: String, val uid: Int, val context: Context)
     : Comparable<PrivacyApplication> {
 
     override fun compareTo(other: PrivacyApplication): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index 2339fae..f1c3bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -59,7 +59,8 @@
     @Suppress("DEPRECATION")
     private val uiHandler = Dependency.get(Dependency.MAIN_HANDLER)
     private var listening = false
-    val systemApp = PrivacyApplication(context.getString(R.string.device_services), context)
+    val systemApp =
+            PrivacyApplication(context.getString(R.string.device_services), SYSTEM_UID, context)
     private val callbacks = mutableListOf<WeakReference<Callback>>()
 
     private val notifyChanges = Runnable {
@@ -162,7 +163,7 @@
             else -> return null
         }
         if (appOpItem.uid == SYSTEM_UID) return PrivacyItem(type, systemApp)
-        val app = PrivacyApplication(appOpItem.packageName, context)
+        val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid, context)
         return PrivacyItem(type, app)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 904478e..0150852 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -111,6 +111,7 @@
     private static final int MSG_SHOW_CHARGING_ANIMATION       = 44 << MSG_SHIFT;
     private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT;
     private static final int MSG_SHOW_PINNING_TOAST_ESCAPE     = 46 << MSG_SHIFT;
+    private static final int MSG_DISPLAY_READY                 = 47 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -274,6 +275,11 @@
         default void onBiometricHelp(String message) { }
         default void onBiometricError(String error) { }
         default void hideBiometricDialog() { }
+
+        /**
+        * @see IStatusBar#onDisplayReady(int)
+        */
+        default void onDisplayReady(int displayId) { }
     }
 
     @VisibleForTesting
@@ -761,6 +767,13 @@
         }
     }
 
+    @Override
+    public void onDisplayReady(int displayId) {
+        synchronized (mLock) {
+            mHandler.obtainMessage(MSG_DISPLAY_READY, displayId, 0).sendToTarget();
+        }
+    }
+
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
@@ -1015,6 +1028,11 @@
                         mCallbacks.get(i).showPinningEscapeToast();
                     }
                     break;
+                case MSG_DISPLAY_READY:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).onDisplayReady(msg.arg1);
+                    }
+                    break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 9740d1d..e11ec2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -19,6 +19,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
+import static com.android.systemui.SysUiServiceProvider.getComponent;
 
 import android.content.Context;
 import android.hardware.display.DisplayManager;
@@ -34,6 +35,7 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -48,7 +50,7 @@
 
 /** A controller to handle navigation bars. */
 @Singleton
-public class NavigationBarController implements DisplayListener {
+public class NavigationBarController implements DisplayListener, Callbacks {
 
     private static final String TAG = NavigationBarController.class.getName();
 
@@ -65,13 +67,11 @@
         mHandler = handler;
         mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
         mDisplayManager.registerDisplayListener(this, mHandler);
+        getComponent(mContext, CommandQueue.class).addCallback(this);
     }
 
     @Override
-    public void onDisplayAdded(int displayId) {
-        Display display = mDisplayManager.getDisplay(displayId);
-        createNavigationBar(display);
-    }
+    public void onDisplayAdded(int displayId) { }
 
     @Override
     public void onDisplayRemoved(int displayId) {
@@ -79,7 +79,12 @@
     }
 
     @Override
-    public void onDisplayChanged(int displayId) {
+    public void onDisplayChanged(int displayId) { }
+
+    @Override
+    public void onDisplayReady(int displayId) {
+        Display display = mDisplayManager.getDisplay(displayId);
+        createNavigationBar(display);
     }
 
     // TODO(b/117478341): I use {@code includeDefaultDisplay} to make this method compatible to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 35b7ef4..5e52419 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -39,7 +39,6 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -221,6 +220,11 @@
             }
 
             @Override
+            public void onEntryReinflated(NotificationEntry entry) {
+                mExpansionStateLogger.onEntryReinflated(entry.key);
+            }
+
+            @Override
             public void onInflationError(
                     StatusBarNotification notification,
                     Exception exception) {
@@ -468,6 +472,13 @@
             mLoggedExpansionState.remove(key);
         }
 
+        @VisibleForTesting
+        void onEntryReinflated(String key) {
+            // When the notification is updated, we should consider the notification as not
+            // yet logged.
+            mLoggedExpansionState.remove(key);
+        }
+
         private State getState(String key) {
             State state = mExpansionStates.get(key);
             if (state == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index c4f027f..07c6587 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -479,13 +479,20 @@
         remeasureButtonsIfNecessary(accumulatedMeasures.mButtonPaddingHorizontal,
                                     accumulatedMeasures.mMaxChildHeight);
 
+        int buttonHeight = Math.max(getSuggestedMinimumHeight(), mPaddingTop
+                + accumulatedMeasures.mMaxChildHeight + mPaddingBottom);
+
+        // Set the corner radius to half the button height to make the side of the buttons look like
+        // a semicircle.
+        for (View smartSuggestionButton : smartSuggestions) {
+            setCornerRadius((Button) smartSuggestionButton, ((float) buttonHeight) / 2);
+        }
+
         setMeasuredDimension(
                 resolveSize(Math.max(getSuggestedMinimumWidth(),
                                      accumulatedMeasures.mMeasuredWidth),
                             widthMeasureSpec),
-                resolveSize(Math.max(getSuggestedMinimumHeight(), mPaddingTop
-                        + accumulatedMeasures.mMaxChildHeight + mPaddingBottom),
-                            heightMeasureSpec));
+                resolveSize(buttonHeight, heightMeasureSpec));
     }
 
     /**
@@ -806,6 +813,23 @@
         button.setTextColor(textColor);
     }
 
+    private void setCornerRadius(Button button, float radius) {
+        Drawable drawable = button.getBackground();
+        if (drawable instanceof RippleDrawable) {
+            // Mutate in case other notifications are using this drawable.
+            drawable = drawable.mutate();
+            RippleDrawable ripple = (RippleDrawable) drawable;
+            Drawable inset = ripple.getDrawable(0);
+            if (inset instanceof InsetDrawable) {
+                Drawable background = ((InsetDrawable) inset).getDrawable();
+                if (background instanceof GradientDrawable) {
+                    GradientDrawable gradientDrawable = (GradientDrawable) background;
+                    gradientDrawable.setCornerRadius(radius);
+                }
+            }
+        }
+    }
+
     private ActivityStarter getActivityStarter() {
         if (mActivityStarter == null) {
             mActivityStarter = Dependency.get(ActivityStarter.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
index 0f686df..db819d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -30,6 +30,7 @@
 import com.android.systemui.R;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Spy;
@@ -89,6 +90,7 @@
     }
 
     @Test
+    @Ignore("Sporadically failing due to DynamicAnimation not settling.")
     public void testFlingSideways() throws InterruptedException {
         // Hard fling directly upwards, no X velocity. The X fling should terminate pretty much
         // immediately, and spring to 0f, the y fling is hard enough that it will overshoot the top
@@ -119,6 +121,7 @@
     }
 
     @Test
+    @Ignore("Sporadically failing due to DynamicAnimation not settling.")
     public void testFlingUpFromBelowBottomCenter() throws InterruptedException {
         // Move to the center of the screen, just past the bottom.
         mStackController.moveFirstBubbleWithStackFollowing(mWidth / 2f, mHeight + 100);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt
index d3b3dae..f163b88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt
@@ -27,16 +27,20 @@
 @SmallTest
 class PrivacyDialogBuilderTest : SysuiTestCase() {
 
+    companion object {
+        val TEST_UID = 1
+    }
+
     @Test
     fun testGenerateAppsList() {
         val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
-                "Bar", context))
+                "Bar", TEST_UID, context))
         val bar3 = PrivacyItem(Privacy.TYPE_LOCATION, PrivacyApplication(
-                "Bar", context))
+                "Bar", TEST_UID, context))
         val foo0 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
-                "Foo", context))
+                "Foo", TEST_UID, context))
         val baz1 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
-                "Baz", context))
+                "Baz", TEST_UID, context))
 
         val items = listOf(bar2, foo0, baz1, bar3)
 
@@ -55,11 +59,14 @@
     @Test
     fun testOrder() {
         // We want location to always go last, so it will go in the "+ other apps"
-        val appCamera = PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication("Camera", context))
+        val appCamera = PrivacyItem(PrivacyType.TYPE_CAMERA,
+                PrivacyApplication("Camera", TEST_UID, context))
         val appMicrophone =
-                PrivacyItem(PrivacyType.TYPE_MICROPHONE, PrivacyApplication("Microphone", context))
+                PrivacyItem(PrivacyType.TYPE_MICROPHONE,
+                        PrivacyApplication("Microphone", TEST_UID, context))
         val appLocation =
-                PrivacyItem(PrivacyType.TYPE_LOCATION, PrivacyApplication("Location", context))
+                PrivacyItem(PrivacyType.TYPE_LOCATION,
+                        PrivacyApplication("Location", TEST_UID, context))
 
         val items = listOf(appLocation, appMicrophone, appCamera)
         val textBuilder = PrivacyDialogBuilder(context, items)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
index 5ff9d14..f1d9003 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
@@ -15,10 +15,10 @@
  */
 package com.android.systemui.statusbar.notification.logging;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.os.RemoteException;
@@ -31,6 +31,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.UiOffloadThread;
+import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -155,6 +156,27 @@
                 NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN.toMetricsEventEnum());
     }
 
+    @Test
+    public void testOnEntryReinflated() throws RemoteException {
+        mLogger.onExpansionChanged(NOTIFICATION_KEY, true, true,
+                NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
+        mLogger.onVisibilityChanged(
+                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+                Collections.emptyList());
+        waitForUiOffloadThread();
+        verify(mBarService).onNotificationExpansionChanged(
+                NOTIFICATION_KEY, true, true, ExpandableViewState.LOCATION_UNKNOWN);
+
+        mLogger.onEntryReinflated(NOTIFICATION_KEY);
+        mLogger.onVisibilityChanged(
+                Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
+                Collections.emptyList());
+        waitForUiOffloadThread();
+        // onNotificationExpansionChanged is called the second time.
+        verify(mBarService, times(2)).onNotificationExpansionChanged(
+                NOTIFICATION_KEY, true, true, ExpandableViewState.LOCATION_UNKNOWN);
+    }
+
     private NotificationVisibility createNotificationVisibility(String key, boolean visibility) {
         return createNotificationVisibility(key, visibility,
                 NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
index 73f3b43..6177344 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
@@ -51,6 +51,7 @@
 
     @Before
     public void setup() {
+        mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
         final Display display = createVirtualDisplay();
         final SysuiTestableContext context =
                 (SysuiTestableContext) mContext.createDisplayContext(display);
diff --git a/services/core/java/com/android/server/LooperStatsService.java b/services/core/java/com/android/server/LooperStatsService.java
index 4a8706e..2f7929c 100644
--- a/services/core/java/com/android/server/LooperStatsService.java
+++ b/services/core/java/com/android/server/LooperStatsService.java
@@ -55,11 +55,11 @@
             "debug.sys.looper_stats_enabled";
     private static final int DEFAULT_SAMPLING_INTERVAL = 100;
     private static final int DEFAULT_ENTRIES_SIZE_CAP = 2000;
-    private static final boolean DEFAULT_ENABLED = false;
+    private static final boolean DEFAULT_ENABLED = true;
 
     private final Context mContext;
     private final LooperStats mStats;
-    private boolean mEnabled = false;
+    private boolean mEnabled = DEFAULT_ENABLED;
 
     private LooperStatsService(Context context, LooperStats stats) {
         this.mContext = context;
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 8adc416..84577f1 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -16,6 +16,9 @@
 
 package com.android.server;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Environment;
@@ -46,6 +49,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.annotation.Retention;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -55,7 +59,8 @@
 
 /**
  * Monitors the health of packages on the system and notifies interested observers when packages
- * fail. All registered observers will be notified until an observer takes a mitigation action.
+ * fail. On failure, the registered observer with the least user impacting mitigation will
+ * be notified.
  */
 public class PackageWatchdog {
     private static final String TAG = "PackageWatchdog";
@@ -78,7 +83,8 @@
     private final Context mContext;
     // Handler to run package cleanup runnables
     private final Handler mTimerHandler;
-    private final Handler mIoHandler;
+    // Handler for processing IO and observer actions
+    private final Handler mWorkerHandler;
     // Contains (observer-name -> observer-handle) that have ever been registered from
     // previous boots. Observers with all packages expired are periodically pruned.
     // It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
@@ -101,7 +107,7 @@
         mPolicyFile = new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
                         "package-watchdog.xml"));
         mTimerHandler = new Handler(Looper.myLooper());
-        mIoHandler = BackgroundThread.getHandler();
+        mWorkerHandler = BackgroundThread.getHandler();
         mPackageCleanup = this::rescheduleCleanup;
         loadFromFile();
     }
@@ -115,7 +121,7 @@
         mContext = context;
         mPolicyFile = new AtomicFile(new File(context.getFilesDir(), "package-watchdog.xml"));
         mTimerHandler = new Handler(looper);
-        mIoHandler = mTimerHandler;
+        mWorkerHandler = mTimerHandler;
         mPackageCleanup = this::rescheduleCleanup;
         loadFromFile();
     }
@@ -228,49 +234,46 @@
     /**
      * Called when a process fails either due to a crash or ANR.
      *
-     * <p>All registered observers for the packages contained in the process will be notified in
-     * order of priority until an observer signifies that it has taken action and other observers
-     * should not notified.
+     * <p>For each package contained in the process, one registered observer with the least user
+     * impact will be notified for mitigation.
      *
      * <p>This method could be called frequently if there is a severe problem on the device.
      */
     public void onPackageFailure(String[] packages) {
-        ArrayMap<String, List<PackageHealthObserver>> packagesToReport = new ArrayMap<>();
-        synchronized (mLock) {
-            if (mAllObservers.isEmpty()) {
-                return;
-            }
+        mWorkerHandler.post(() -> {
+            synchronized (mLock) {
+                if (mAllObservers.isEmpty()) {
+                    return;
+                }
 
-            for (int pIndex = 0; pIndex < packages.length; pIndex++) {
-                // Observers interested in receiving packageName failures
-                List<PackageHealthObserver> observersToNotify = new ArrayList<>();
-                for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
-                    PackageHealthObserver registeredObserver =
-                            mAllObservers.valueAt(oIndex).mRegisteredObserver;
-                    if (registeredObserver != null) {
-                        observersToNotify.add(registeredObserver);
+                for (int pIndex = 0; pIndex < packages.length; pIndex++) {
+                    String packageToReport = packages[pIndex];
+                    // Observer that will receive failure for packageToReport
+                    PackageHealthObserver currentObserverToNotify = null;
+                    int currentObserverImpact = Integer.MAX_VALUE;
+
+                    // Find observer with least user impact
+                    for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
+                        ObserverInternal observer = mAllObservers.valueAt(oIndex);
+                        PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
+                        if (registeredObserver != null
+                                && observer.onPackageFailure(packageToReport)) {
+                            int impact = registeredObserver.onHealthCheckFailed(packageToReport);
+                            if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+                                    && impact < currentObserverImpact) {
+                                currentObserverToNotify = registeredObserver;
+                                currentObserverImpact = impact;
+                            }
+                        }
+                    }
+
+                    // Execute action with least user impact
+                    if (currentObserverToNotify != null) {
+                        currentObserverToNotify.execute(packageToReport);
                     }
                 }
-                // Save interested observers and notify them outside the lock
-                if (!observersToNotify.isEmpty()) {
-                    packagesToReport.put(packages[pIndex], observersToNotify);
-                }
             }
-        }
-
-        // Notify observers
-        for (int pIndex = 0; pIndex < packagesToReport.size(); pIndex++) {
-            List<PackageHealthObserver> observers = packagesToReport.valueAt(pIndex);
-            String packageName = packages[pIndex];
-            for (int oIndex = 0; oIndex < observers.size(); oIndex++) {
-                PackageHealthObserver observer = observers.get(oIndex);
-                if (mAllObservers.get(observer.getName()).onPackageFailure(packageName)
-                        && observer.onHealthCheckFailed(packageName)) {
-                    // Observer has handled, do not notify others
-                    break;
-                }
-            }
-        }
+        });
     }
 
     // TODO(zezeozue): Optimize write? Maybe only write a separate smaller file?
@@ -278,21 +281,46 @@
     /** Writes the package information to file during shutdown. */
     public void writeNow() {
         if (!mAllObservers.isEmpty()) {
-            mIoHandler.removeCallbacks(this::saveToFile);
+            mWorkerHandler.removeCallbacks(this::saveToFile);
             pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs);
             saveToFile();
             Slog.i(TAG, "Last write to update package durations");
         }
     }
 
+    /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
+    @Retention(SOURCE)
+    @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
+                     PackageHealthObserverImpact.USER_IMPACT_LOW,
+                     PackageHealthObserverImpact.USER_IMPACT_MEDIUM,
+                     PackageHealthObserverImpact.USER_IMPACT_HIGH})
+    public @interface PackageHealthObserverImpact {
+        /** No action to take. */
+        int USER_IMPACT_NONE = 0;
+        /* Action has low user impact, user of a device will barely notice. */
+        int USER_IMPACT_LOW = 1;
+        /* Action has medium user impact, user of a device will likely notice. */
+        int USER_IMPACT_MEDIUM = 3;
+        /* Action has high user impact, a last resort, user of a device will be very frustrated. */
+        int USER_IMPACT_HIGH = 5;
+    }
+
     /** Register instances of this interface to receive notifications on package failure. */
     public interface PackageHealthObserver {
         /**
          * Called when health check fails for the {@code packageName}.
-         * @return {@code true} if action was taken and other observers should not be notified of
-         * this failure, {@code false} otherwise.
+         *
+         * @return any one of {@link PackageHealthObserverImpact} to express the impact
+         * to the user on {@link #execute}
          */
-        boolean onHealthCheckFailed(String packageName);
+        @PackageHealthObserverImpact int onHealthCheckFailed(String packageName);
+
+        /**
+         * Executes mitigation for {@link #onHealthCheckFailed}.
+         *
+         * @return {@code true} if action was executed successfully, {@code false} otherwise
+         */
+        boolean execute(String packageName);
 
         // TODO(zezeozue): Ensure uniqueness?
         /**
@@ -442,8 +470,8 @@
     }
 
     private void saveToFileAsync() {
-        mIoHandler.removeCallbacks(this::saveToFile);
-        mIoHandler.post(this::saveToFile);
+        mWorkerHandler.removeCallbacks(this::saveToFile);
+        mWorkerHandler.post(this::saveToFile);
     }
 
     /**
@@ -606,7 +634,11 @@
             } else {
                 mFailures++;
             }
-            return mFailures >= TRIGGER_FAILURE_COUNT;
+            boolean failed = mFailures >= TRIGGER_FAILURE_COUNT;
+            if (failed) {
+                mFailures = 0;
+            }
+            return failed;
         }
     }
 }
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index d09823e..a5a515f 100644
--- a/services/core/java/com/android/server/ServiceWatcher.java
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -34,11 +34,11 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.util.Preconditions;
 
@@ -48,6 +48,9 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
 
 /**
  * Find the best Service, and bind to it.
@@ -61,16 +64,20 @@
     public static final String EXTRA_SERVICE_VERSION = "serviceVersion";
     public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
 
-    /**
-     * The runner that runs on the binder retrieved from {@link ServiceWatcher}.
-     */
+
+    /** Function to run on binder interface. */
     public interface BinderRunner {
-        /**
-         * Runs on the retrieved binder.
-         *
-         * @param binder the binder retrieved from the {@link ServiceWatcher}.
-         */
-        void run(IBinder binder);
+        /** Called to run client code with the binder. */
+        void run(IBinder binder) throws RemoteException;
+    }
+
+    /**
+     * Function to run on binder interface.
+     * @param <T> Type to return.
+     */
+    public interface BlockingBinderRunner<T> {
+        /** Called to run client code with the binder. */
+        T run(IBinder binder) throws RemoteException;
     }
 
     public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
@@ -120,18 +127,14 @@
 
     private final Handler mHandler;
 
-    // this lock is held to ensure the service binder is not exposed (via runOnBinder) until after
-    // the new service initialization work has completed
-    private final Object mBindLock = new Object();
-
     // read/write from handler thread
+    private IBinder mBestService;
     private int mCurrentUserId;
 
     // read from any thread, write from handler thread
     private volatile ComponentName mBestComponent;
     private volatile int mBestVersion;
     private volatile int mBestUserId;
-    private volatile IBinder mBestService;
 
     public ServiceWatcher(Context context, String logTag, String action,
             int overlaySwitchResId, int defaultServicePackageNameResId,
@@ -163,17 +166,9 @@
         mBestService = null;
     }
 
-    // called on handler thread
-    @GuardedBy("mBindLock")
-    protected void onBind() {
-        Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-    }
+    protected void onBind() {}
 
-    // called on handler thread
-    @GuardedBy("mBindLock")
-    protected void onUnbind() {
-        Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
-    }
+    protected void onUnbind() {}
 
     /**
      * Start this watcher, including binding to the current best match and
@@ -248,25 +243,6 @@
         return bestComponent == null ? null : bestComponent.getPackageName();
     }
 
-    /**
-     * Runs the given BinderRunner if currently connected. All invocations to runOnBinder are run
-     * serially.
-     */
-    public final void runOnBinder(BinderRunner runner) {
-        synchronized (mBindLock) {
-            IBinder service = mBestService;
-            if (service != null) {
-                try {
-                    runner.run(service);
-                } catch (Exception e) {
-                    // remote exceptions cannot be allowed to crash system server
-                    Log.e(TAG, "exception while while running " + runner + " on " + service
-                            + " from " + this, e);
-                }
-            }
-        }
-    }
-
     private boolean isServiceMissing() {
         return mContext.getPackageManager().queryIntentServicesAsUser(new Intent(mAction),
                 PackageManager.MATCH_DIRECT_BOOT_AWARE
@@ -380,28 +356,66 @@
         mBestUserId = UserHandle.USER_NULL;
     }
 
+    /**
+     * Runs the given function asynchronously if currently connected. Suppresses any RemoteException
+     * thrown during execution.
+     */
+    public final void runOnBinder(BinderRunner runner) {
+        runOnHandler(() -> {
+            if (mBestService == null) {
+                return;
+            }
+
+            try {
+                runner.run(mBestService);
+            } catch (RuntimeException e) {
+                // the code being run is privileged, but may be outside the system server, and thus
+                // we cannot allow runtime exceptions to crash the system server
+                Log.e(TAG, "exception while while running " + runner + " on " + mBestService
+                        + " from " + this, e);
+            } catch (RemoteException e) {
+                // do nothing
+            }
+        });
+    }
+
+    /**
+     * Runs the given function synchronously if currently connected, and returns the default value
+     * if not currently connected or if any exception is thrown.
+     */
+    public final <T> T runOnBinderBlocking(BlockingBinderRunner<T> runner, T defaultValue) {
+        try {
+            return runOnHandlerBlocking(() -> {
+                if (mBestService == null) {
+                    return defaultValue;
+                }
+
+                try {
+                    return runner.run(mBestService);
+                } catch (RemoteException e) {
+                    return defaultValue;
+                }
+            });
+        } catch (InterruptedException e) {
+            return defaultValue;
+        }
+    }
+
     @Override
     public final void onServiceConnected(ComponentName component, IBinder binder) {
-        mHandler.post(() -> {
+        runOnHandler(() -> {
             if (D) Log.d(mTag, component + " connected");
-
-            // hold the lock so that mBestService cannot be used by runOnBinder until complete
-            synchronized (mBindLock) {
-                mBestService = binder;
-                onBind();
-            }
+            mBestService = binder;
+            onBind();
         });
     }
 
     @Override
     public final void onServiceDisconnected(ComponentName component) {
-        mHandler.post(() -> {
+        runOnHandler(() -> {
             if (D) Log.d(mTag, component + " disconnected");
-
             mBestService = null;
-            synchronized (mBindLock) {
-                onUnbind();
-            }
+            onUnbind();
         });
     }
 
@@ -410,4 +424,32 @@
         ComponentName bestComponent = mBestComponent;
         return bestComponent == null ? "null" : bestComponent.toShortString() + "@" + mBestVersion;
     }
+
+    private void runOnHandler(Runnable r) {
+        if (Looper.myLooper() == mHandler.getLooper()) {
+            r.run();
+        } else {
+            mHandler.post(r);
+        }
+    }
+
+    private <T> T runOnHandlerBlocking(Callable<T> c) throws InterruptedException {
+        if (Looper.myLooper() == mHandler.getLooper()) {
+            try {
+                return c.call();
+            } catch (Exception e) {
+                // Function cannot throw exception, this should never happen
+                throw new IllegalStateException(e);
+            }
+        } else {
+            FutureTask<T> task = new FutureTask<>(c);
+            mHandler.post(task);
+            try {
+                return task.get();
+            } catch (ExecutionException e) {
+                // Function cannot throw exception, this should never happen
+                throw new IllegalStateException(e);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index ea6d435..f6e698f 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -33,8 +33,10 @@
 import android.media.AudioManager;
 import android.os.BatteryStats;
 import android.os.Binder;
+import android.os.ExternalVibration;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IExternalVibratorService;
 import android.os.IVibratorService;
 import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
@@ -75,16 +77,18 @@
     private static final String TAG = "VibratorService";
     private static final boolean DEBUG = false;
     private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
+    private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
 
     private static final long[] DOUBLE_CLICK_EFFECT_FALLBACK_TIMINGS = { 0, 30, 100, 30 };
 
-    // Scale levels. Each level is defined as the delta between the current setting and the default
-    // intensity for that type of vibration (i.e. current - default).
-    private static final int SCALE_VERY_LOW = -2;
-    private static final int SCALE_LOW = -1;
-    private static final int SCALE_NONE = 0;
-    private static final int SCALE_HIGH = 1;
-    private static final int SCALE_VERY_HIGH = 2;
+    // Scale levels. Each level, except MUTE, is defined as the delta between the current setting
+    // and the default intensity for that type of vibration (i.e. current - default).
+    private static final int SCALE_MUTE = IExternalVibratorService.SCALE_MUTE; // -100
+    private static final int SCALE_VERY_LOW = IExternalVibratorService.SCALE_VERY_LOW; // -2
+    private static final int SCALE_LOW = IExternalVibratorService.SCALE_LOW; // -1
+    private static final int SCALE_NONE = IExternalVibratorService.SCALE_NONE; // 0
+    private static final int SCALE_HIGH = IExternalVibratorService.SCALE_HIGH; // 1
+    private static final int SCALE_VERY_HIGH = IExternalVibratorService.SCALE_VERY_HIGH; // 2
 
     // Gamma adjustments for scale levels.
     private static final float SCALE_VERY_LOW_GAMMA = 2.0f;
@@ -111,6 +115,7 @@
     private final int mPreviousVibrationsLimit;
     private final boolean mAllowPriorityVibrationsInLowPowerMode;
     private final boolean mSupportsAmplitudeControl;
+    private final boolean mSupportsExternalControl;
     private final int mDefaultVibrationAmplitude;
     private final SparseArray<VibrationEffect> mFallbackEffects;
     private final SparseArray<Integer> mProcStatesCache = new SparseArray();
@@ -138,18 +143,20 @@
     @GuardedBy("mLock")
     private Vibration mCurrentVibration;
     private int mCurVibUid = -1;
+    private ExternalVibration mCurrentExternalVibration;
+    private boolean mVibratorUnderExternalControl;
     private boolean mLowPowerMode;
     private int mHapticFeedbackIntensity;
     private int mNotificationIntensity;
     private int mRingIntensity;
 
-    native static boolean vibratorExists();
-    native static void vibratorInit();
-    native static void vibratorOn(long milliseconds);
-    native static void vibratorOff();
-    native static boolean vibratorSupportsAmplitudeControl();
-    native static void vibratorSetAmplitude(int amplitude);
-    native static long vibratorPerformEffect(long effect, long strength);
+    static native boolean vibratorExists();
+    static native void vibratorInit();
+    static native void vibratorOn(long milliseconds);
+    static native void vibratorOff();
+    static native boolean vibratorSupportsAmplitudeControl();
+    static native void vibratorSetAmplitude(int amplitude);
+    static native long vibratorPerformEffect(long effect, long strength);
     static native boolean vibratorSupportsExternalControl();
     static native void vibratorSetExternalControl(boolean enabled);
 
@@ -218,6 +225,9 @@
         }
 
         public boolean isHapticFeedback() {
+            if (VibratorService.this.isHapticFeedback(usageHint)) {
+                return true;
+            }
             if (effect instanceof VibrationEffect.Prebaked) {
                 VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
                 switch (prebaked.getId()) {
@@ -239,19 +249,11 @@
         }
 
         public boolean isNotification() {
-            switch (usageHint) {
-                case AudioAttributes.USAGE_NOTIFICATION:
-                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
-                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
-                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
-                    return true;
-                default:
-                    return false;
-            }
+            return VibratorService.this.isNotification(usageHint);
         }
 
         public boolean isRingtone() {
-            return usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+            return VibratorService.this.isRingtone(usageHint);
         }
 
         public boolean isFromSystem() {
@@ -332,6 +334,7 @@
         vibratorOff();
 
         mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
+        mSupportsExternalControl = vibratorSupportsExternalControl();
 
         mContext = context;
         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
@@ -379,6 +382,8 @@
         mScaleLevels.put(SCALE_NONE, new ScaleLevel(SCALE_NONE_GAMMA));
         mScaleLevels.put(SCALE_HIGH, new ScaleLevel(SCALE_HIGH_GAMMA));
         mScaleLevels.put(SCALE_VERY_HIGH, new ScaleLevel(SCALE_VERY_HIGH_GAMMA));
+
+        ServiceManager.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
     }
 
     private VibrationEffect createEffectFromResource(int resId) {
@@ -562,6 +567,16 @@
                     }
                 }
 
+
+                // If something has external control of the vibrator, assume that it's more
+                // important for now.
+                if (mCurrentExternalVibration != null) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
+                    }
+                    return;
+                }
+
                 // If the current vibration is repeating and the incoming one is non-repeating,
                 // then ignore the non-repeating vibration. This is so that we don't cancel
                 // vibrations that are meant to grab the attention of the user, like ringtones and
@@ -648,6 +663,11 @@
                 mThread.cancel();
                 mThread = null;
             }
+            if (mCurrentExternalVibration != null) {
+                mCurrentExternalVibration.mute();
+                mCurrentExternalVibration = null;
+                setVibratorUnderExternalControl(false);
+            }
             doVibratorOff();
             reportFinishVibrationLocked();
         } finally {
@@ -1095,6 +1115,26 @@
         }
     }
 
+    private static boolean isNotification(int usageHint) {
+        switch (usageHint) {
+            case AudioAttributes.USAGE_NOTIFICATION:
+            case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+            case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+            case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static boolean isRingtone(int usageHint) {
+        return usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+    }
+
+    private static boolean isHapticFeedback(int usageHint) {
+        return usageHint == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
+    }
+
     private void noteVibratorOnLocked(int uid, long millis) {
         try {
             mBatteryStatsService.noteVibratorOn(uid, millis);
@@ -1116,6 +1156,18 @@
         }
     }
 
+    private void setVibratorUnderExternalControl(boolean externalControl) {
+        if (DEBUG) {
+            if (externalControl) {
+                Slog.d(TAG, "Vibrator going under external control.");
+            } else {
+                Slog.d(TAG, "Taking back control of vibrator.");
+            }
+        }
+        mVibratorUnderExternalControl = externalControl;
+        vibratorSetExternalControl(externalControl);
+    }
+
     private class VibrateThread extends Thread {
         private final VibrationEffect.Waveform mWaveform;
         private final int mUid;
@@ -1290,6 +1342,13 @@
             } else {
                 pw.println("null");
             }
+            pw.print("  mCurrentExternalVibration=");
+            if (mCurrentExternalVibration != null) {
+                pw.println(mCurrentExternalVibration.toString());
+            } else {
+                pw.println("null");
+            }
+            pw.println("  mVibratorUnderExternalControl=" + mVibratorUnderExternalControl);
             pw.println("  mLowPowerMode=" + mLowPowerMode);
             pw.println("  mHapticFeedbackIntensity=" + mHapticFeedbackIntensity);
             pw.println("  mNotificationIntensity=" + mNotificationIntensity);
@@ -1310,6 +1369,87 @@
         new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver);
     }
 
+    final class ExternalVibratorService extends IExternalVibratorService.Stub {
+        @Override
+        public int onExternalVibrationStart(ExternalVibration vib) {
+            if (!mSupportsExternalControl) {
+                return SCALE_MUTE;
+            }
+            if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
+                        vib.getUid(), -1 /*owningUid*/, true /*exported*/)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
+                        + " tried to play externally controlled vibration"
+                        + " without VIBRATE permission, ignoring.");
+                return SCALE_MUTE;
+            }
+
+            final int scaleLevel;
+            synchronized (mLock) {
+                if (!vib.equals(mCurrentExternalVibration)) {
+                    if (mCurrentExternalVibration == null) {
+                        // If we're not under external control right now, then cancel any normal
+                        // vibration that may be playing and ready the vibrator for external
+                        // control.
+                        doCancelVibrateLocked();
+                        setVibratorUnderExternalControl(true);
+                    }
+                    // At this point we either have an externally controlled vibration playing, or
+                    // no vibration playing. Since the interface defines that only one externally
+                    // controlled vibration can play at a time, by returning something other than
+                    // SCALE_MUTE from this function we can be assured that if we are currently
+                    // playing vibration, it will be muted in favor of the new vibration.
+                    //
+                    // Note that this doesn't support multiple concurrent external controls, as we
+                    // would need to mute the old one still if it came from a different controller.
+                    mCurrentExternalVibration = vib;
+                    if (DEBUG) {
+                        Slog.e(TAG, "Playing external vibration: " + vib);
+                    }
+                }
+                final int usage = vib.getAudioAttributes().getUsage();
+                final int defaultIntensity;
+                final int currentIntensity;
+                if (isRingtone(usage)) {
+                    defaultIntensity = mVibrator.getDefaultRingVibrationIntensity();
+                    currentIntensity = mRingIntensity;
+                } else if (isNotification(usage)) {
+                    defaultIntensity = mVibrator.getDefaultNotificationVibrationIntensity();
+                    currentIntensity = mNotificationIntensity;
+                } else if (isHapticFeedback(usage)) {
+                    defaultIntensity = mVibrator.getDefaultHapticFeedbackIntensity();
+                    currentIntensity = mHapticFeedbackIntensity;
+                } else {
+                    defaultIntensity = 0;
+                    currentIntensity = 0;
+                }
+                scaleLevel = currentIntensity - defaultIntensity;
+            }
+            if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) {
+                return scaleLevel;
+            } else {
+                // Presumably we want to play this but something about our scaling has gone
+                // wrong, so just play with no scaling.
+                Slog.w(TAG, "Error in scaling calculations, ended up with invalid scale level "
+                        + scaleLevel + " for vibration " + vib);
+                return SCALE_NONE;
+            }
+        }
+
+        @Override
+        public void onExternalVibrationStop(ExternalVibration vib) {
+            synchronized (mLock) {
+                if (vib.equals(mCurrentExternalVibration)) {
+                    mCurrentExternalVibration = null;
+                    setVibratorUnderExternalControl(false);
+                    if (DEBUG) {
+                        Slog.e(TAG, "Stopping external vibration" + vib);
+                    }
+                }
+            }
+        }
+    }
+
     private final class VibratorShellCommand extends ShellCommand {
 
         private final IBinder mToken;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index a376e7a..0890032 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -19,6 +19,7 @@
 import android.app.ActivityManager;
 import android.app.job.JobProtoEnums;
 import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -46,6 +47,7 @@
 import android.os.health.HealthStatsParceler;
 import android.os.health.HealthStatsWriter;
 import android.os.health.UidHealthStats;
+import android.provider.Settings;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.SignalStrength;
@@ -1651,4 +1653,23 @@
         return new HealthStatsParceler(uidWriter);
     }
 
+    /**
+     * Delay for sending ACTION_CHARGING after device is plugged in.
+     *
+     * @hide
+     */
+    public boolean setChargingStateUpdateDelayMillis(int delayMillis) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.POWER_SAVER, null);
+        final long ident = Binder.clearCallingIdentity();
+
+        try {
+            final ContentResolver contentResolver = mContext.getContentResolver();
+            return Settings.Global.putLong(contentResolver,
+                    Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
+                    delayMillis);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
 }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7ede6dc..26d2d17 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -16,8 +16,8 @@
 
 package com.android.server.appop;
 
-import static android.app.AppOpsManager.OP_PLAY_AUDIO;
 import static android.app.AppOpsManager.OP_NONE;
+import static android.app.AppOpsManager.OP_PLAY_AUDIO;
 import static android.app.AppOpsManager.UID_STATE_BACKGROUND;
 import static android.app.AppOpsManager.UID_STATE_CACHED;
 import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
@@ -94,9 +94,9 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
-
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
+
 import libcore.util.EmptyArray;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -1282,6 +1282,46 @@
         }
     }
 
+    /**
+     * Set all {@link #setMode (package) modes} for this uid to the default value.
+     *
+     * @param code The app-op
+     * @param uid The uid
+     */
+    private void setAllPkgModesToDefault(int code, int uid) {
+        synchronized (this) {
+            UidState uidState = getUidStateLocked(uid, false);
+            if (uidState == null) {
+                return;
+            }
+
+            ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+            if (pkgOps == null) {
+                return;
+            }
+
+            int numPkgs = pkgOps.size();
+            for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+                Ops ops = pkgOps.valueAt(pkgNum);
+
+                Op op = ops.get(code);
+                if (op == null) {
+                    continue;
+                }
+
+                int defaultMode = AppOpsManager.opToDefaultMode(code);
+                if (op.mode != defaultMode) {
+                    Slog.w(TAG, "resetting app-op mode for " + AppOpsManager.opToName(code) + " of "
+                            + pkgOps.keyAt(pkgNum));
+
+                    op.mode = defaultMode;
+
+                    scheduleWriteLocked();
+                }
+            }
+        }
+    }
+
     @Override
     public void setMode(int code, int uid, String packageName, int mode) {
         setMode(code, uid, packageName, mode, true, false);
@@ -4387,5 +4427,10 @@
         public void setUidMode(int code, int uid, int mode) {
             AppOpsService.this.setUidMode(code, uid, mode);
         }
+
+        @Override
+        public void setAllPkgModesToDefault(int code, int uid) {
+            AppOpsService.this.setAllPkgModesToDefault(code, uid);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 27edbbf..b71a751 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -174,10 +174,11 @@
                         @Override
                         public void onSuccess(int requestCode, int result, long timestamp) {
                             callback.onSuccess(requestCode, result, timestamp);
-                            userState.mAttentionCheckCache = new AttentionCheckCache(
-                                    SystemClock.uptimeMillis(), result,
-                                    timestamp);
-
+                            synchronized (mLock) {
+                                userState.mAttentionCheckCache = new AttentionCheckCache(
+                                        SystemClock.uptimeMillis(), result,
+                                        timestamp);
+                            }
                             StatsLog.write(StatsLog.ATTENTION_MANAGER_SERVICE_RESULT_REPORTED,
                                     result);
                         }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
new file mode 100644
index 0000000..d652f93
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -0,0 +1,807 @@
+/*
+ * 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 com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.AudioRoutesInfo;
+import android.media.AudioSystem;
+import android.media.IAudioRoutesObserver;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+
+/** @hide */
+/*package*/ final class AudioDeviceBroker {
+
+    private static final String TAG = "AudioDeviceBroker";
+
+    private static final long BROKER_WAKELOCK_TIMEOUT_MS = 5000; //5s
+
+    /*package*/ static final  int BTA2DP_DOCK_TIMEOUT_MS = 8000;
+    // Timeout for connection to bluetooth headset service
+    /*package*/ static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
+
+    private final @NonNull AudioService mAudioService;
+    private final @NonNull Context mContext;
+
+    /** Forced device usage for communications sent to AudioSystem */
+    private int mForcedUseForComm;
+    /**
+     * Externally reported force device usage state returned by getters: always consistent
+     * with requests by setters */
+    private int mForcedUseForCommExt;
+
+    // Manages all connected devices, only ever accessed on the message loop
+    //### or make it synchronized
+    private final AudioDeviceInventory mDeviceInventory;
+    // Manages notifications to BT service
+    private final BtHelper mBtHelper;
+
+
+    //-------------------------------------------------------------------
+    private static final Object sLastDeviceConnectionMsgTimeLock = new Object();
+    private static long sLastDeviceConnectMsgTime = 0;
+
+    private final Object mBluetoothA2dpEnabledLock = new Object();
+    // Request to override default use of A2DP for media.
+    @GuardedBy("mBluetoothA2dpEnabledLock")
+    private boolean mBluetoothA2dpEnabled;
+
+    // lock always taken synchronized on mConnectedDevices
+    /*package*/  final Object mA2dpAvrcpLock = new Object();
+    // lock always taken synchronized on mConnectedDevices
+    /*package*/  final Object mHearingAidLock = new Object();
+
+    // lock always taken when accessing AudioService.mSetModeDeathHandlers
+    /*package*/ final Object mSetModeLock = new Object();
+
+    //-------------------------------------------------------------------
+    /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
+        mContext = context;
+        mAudioService = service;
+        setupMessaging(context);
+        mBtHelper = new BtHelper(this);
+        mDeviceInventory = new AudioDeviceInventory(this);
+
+        mForcedUseForComm = AudioSystem.FORCE_NONE;
+        mForcedUseForCommExt = mForcedUseForComm;
+
+    }
+
+    /*package*/ Context getContext() {
+        return mContext;
+    }
+
+    //---------------------------------------------------------------------
+    // Communication from AudioService
+    // All methods are asynchronous and never block
+    // All permission checks are done in AudioService, all incoming calls are considered "safe"
+    // All post* methods are asynchronous
+
+    /*package*/ void onSystemReady() {
+        mBtHelper.onSystemReady();
+    }
+
+    /*package*/ void onAudioServerDied() {
+        // Restore forced usage for communications and record
+        onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied");
+        onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied");
+        // restore devices
+        sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
+    }
+
+    /*package*/ void setForceUse_Async(int useCase, int config, String eventSource) {
+        sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+                useCase, config, eventSource);
+    }
+
+    /*package*/ void toggleHdmiIfConnected_Async() {
+        sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE);
+    }
+
+    /*package*/ void disconnectAllBluetoothProfiles() {
+        mBtHelper.disconnectAllBluetoothProfiles();
+    }
+
+    /**
+     * Handle BluetoothHeadset intents where the action is one of
+     *   {@link BluetoothHeadset#ACTION_ACTIVE_DEVICE_CHANGED} or
+     *   {@link BluetoothHeadset#ACTION_AUDIO_STATE_CHANGED}.
+     * @param intent
+     */
+    /*package*/ void receiveBtEvent(@NonNull Intent intent) {
+        mBtHelper.receiveBtEvent(intent);
+    }
+
+    /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
+        synchronized (mBluetoothA2dpEnabledLock) {
+            if (mBluetoothA2dpEnabled == on) {
+                return;
+            }
+            mBluetoothA2dpEnabled = on;
+            mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
+            sendIILMsgNoDelay(MSG_IIL_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
+                    AudioSystem.FOR_MEDIA,
+                    mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
+                    source);
+        }
+    }
+
+    /*package*/ void setSpeakerphoneOn(boolean on, String eventSource) {
+        if (on) {
+            if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+                setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
+            }
+            mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
+        } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
+            mForcedUseForComm = AudioSystem.FORCE_NONE;
+        }
+
+        mForcedUseForCommExt = mForcedUseForComm;
+        setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+    }
+
+    /*package*/ boolean isSpeakerphoneOn() {
+        return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
+    }
+
+    /*package*/ void setWiredDeviceConnectionState(int type,
+            @AudioService.ConnectionState int state, String address, String name,
+            String caller) {
+        //TODO move logging here just like in setBluetooth* methods
+        mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
+    }
+
+    /*package*/ int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+            int profile, boolean suppressNoisyIntent, int a2dpVolume) {
+        AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+                "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state
+                        // only querying address as this is the only readily available field
+                        // on the device
+                        + " addr=" + device.getAddress()
+                        + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent
+                        + " vol=" + a2dpVolume)).printLog(TAG));
+        if (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE,
+                new BtHelper.BluetoothA2dpDeviceInfo(device))) {
+            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                    "A2DP connection state ignored"));
+            return 0;
+        }
+        return mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
+                device, state, profile, suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume);
+    }
+
+    /*package*/ int handleBluetoothA2dpActiveDeviceChange(
+            @NonNull BluetoothDevice device,
+            @AudioService.BtProfileConnectionState int state, int profile,
+            boolean suppressNoisyIntent, int a2dpVolume) {
+        return mDeviceInventory.handleBluetoothA2dpActiveDeviceChange(device, state, profile,
+                suppressNoisyIntent, a2dpVolume);
+    }
+
+    /*package*/ int setBluetoothHearingAidDeviceConnectionState(
+            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+            boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) {
+        AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+                "setHearingAidDeviceConnectionState state=" + state
+                        + " addr=" + device.getAddress()
+                        + " supprNoisy=" + suppressNoisyIntent
+                        + " src=" + eventSource)).printLog(TAG));
+        return mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
+                device, state, suppressNoisyIntent, musicDevice);
+    }
+
+    // never called by system components
+    /*package*/ void setBluetoothScoOnByApp(boolean on) {
+        mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+    }
+
+    /*package*/ boolean isBluetoothScoOnForApp() {
+        return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO;
+    }
+
+    /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
+        //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
+        if (on) {
+            // do not accept SCO ON if SCO audio is not connected
+            if (!mBtHelper.isBluetoothScoOn()) {
+                mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
+                return;
+            }
+            mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
+        } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+            mForcedUseForComm = AudioSystem.FORCE_NONE;
+        }
+        mForcedUseForCommExt = mForcedUseForComm;
+        AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off"));
+        sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+                AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+        sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+                AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource);
+        // Un-mute ringtone stream volume
+        mAudioService.setUpdateRingerModeServiceInt();
+    }
+
+    /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
+        return mDeviceInventory.startWatchingRoutes(observer);
+    }
+
+    /*package*/ AudioRoutesInfo getCurAudioRoutes() {
+        return mDeviceInventory.getCurAudioRoutes();
+    }
+
+    /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
+        synchronized (mA2dpAvrcpLock) {
+            return mBtHelper.isAvrcpAbsoluteVolumeSupported();
+        }
+    }
+
+    /*package*/ boolean isBluetoothA2dpOn() {
+        synchronized (mBluetoothA2dpEnabledLock) {
+            return mBluetoothA2dpEnabled;
+        }
+    }
+
+    /*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) {
+        sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index);
+    }
+
+    /*package*/ void postSetHearingAidVolumeIndex(int index, int streamType) {
+        sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
+    }
+
+    /*package*/ void postDisconnectBluetoothSco(int exceptPid) {
+        sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
+    }
+
+    /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
+        sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+    }
+
+    /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
+                @NonNull String eventSource) {
+        mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
+    }
+
+    /*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) {
+        mBtHelper.stopBluetoothScoForClient(cb, eventSource);
+    }
+
+    //---------------------------------------------------------------------
+    // Communication with (to) AudioService
+    //TODO check whether the AudioService methods are candidates to move here
+    /*package*/ void postAccessoryPlugMediaUnmute(int device) {
+        mAudioService.postAccessoryPlugMediaUnmute(device);
+    }
+
+    /*package*/ AudioService.VolumeStreamState getStreamState(int streamType) {
+        return mAudioService.getStreamState(streamType);
+    }
+
+    /*package*/ ArrayList<AudioService.SetModeDeathHandler> getSetModeDeathHandlers() {
+        return mAudioService.mSetModeDeathHandlers;
+    }
+
+    /*package*/ int getDeviceForStream(int streamType) {
+        return mAudioService.getDeviceForStream(streamType);
+    }
+
+    /*package*/ void setDeviceVolume(AudioService.VolumeStreamState streamState, int device) {
+        mAudioService.setDeviceVolume(streamState, device);
+    }
+
+    /*packages*/ void observeDevicesForAllStreams() {
+        mAudioService.observeDevicesForAllStreams();
+    }
+
+    /*package*/ boolean isInCommunication() {
+        return mAudioService.isInCommunication();
+    }
+
+    /*package*/ boolean hasMediaDynamicPolicy() {
+        return mAudioService.hasMediaDynamicPolicy();
+    }
+
+    /*package*/ ContentResolver getContentResolver() {
+        return mAudioService.getContentResolver();
+    }
+
+    /*package*/ void checkMusicActive(int deviceType, String caller) {
+        mAudioService.checkMusicActive(deviceType, caller);
+    }
+
+    /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) {
+        mAudioService.checkVolumeCecOnHdmiConnection(state, caller);
+    }
+
+    //---------------------------------------------------------------------
+    // Message handling on behalf of helper classes
+    /*package*/ void broadcastScoConnectionState(int state) {
+        sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
+    }
+
+    /*package*/ void broadcastBecomingNoisy() {
+        sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
+    }
+
+    //###TODO unify with handleSetA2dpSinkConnectionState
+    /*package*/ void postA2dpSinkConnection(int state,
+            @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
+        sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
+                state, btDeviceInfo, delay);
+    }
+
+    //###TODO unify with handleSetA2dpSourceConnectionState
+    /*package*/ void postA2dpSourceConnection(int state,
+            @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
+        sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
+                state, btDeviceInfo, delay);
+    }
+
+    /*package*/ void postSetWiredDeviceConnectionState(
+            AudioDeviceInventory.WiredDeviceConnectionState connectionState, int delay) {
+        sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE, connectionState, delay);
+    }
+
+    /*package*/ void postSetHearingAidConnectionState(
+            @AudioService.BtProfileConnectionState int state,
+            @NonNull BluetoothDevice device, int delay) {
+        sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE,
+                state,
+                device,
+                delay);
+    }
+
+    //---------------------------------------------------------------------
+    // Method forwarding between the helper classes (BtHelper, AudioDeviceInventory)
+    // only call from a "handle"* method or "on"* method
+
+    // Handles request to override default use of A2DP for media.
+    //@GuardedBy("mConnectedDevices")
+    /*package*/ void setBluetoothA2dpOnInt(boolean on, String source) {
+        // for logging only
+        final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
+                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+                .append(Binder.getCallingPid()).append(" src:").append(source).toString();
+
+        synchronized (mBluetoothA2dpEnabledLock) {
+            mBluetoothA2dpEnabled = on;
+            mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
+            onSetForceUse(
+                    AudioSystem.FOR_MEDIA,
+                    mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
+                    eventSource);
+        }
+    }
+
+    /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
+                                                       String deviceName) {
+        return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
+    }
+
+    /*package*/ void handleDisconnectA2dp() {
+        mDeviceInventory.disconnectA2dp();
+    }
+    /*package*/ void handleDisconnectA2dpSink() {
+        mDeviceInventory.disconnectA2dpSink();
+    }
+
+    /*package*/ void handleSetA2dpSinkConnectionState(@BluetoothProfile.BtProfileState int state,
+                @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
+        final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
+        //### DOESN'T HONOR SYNC ON DEVICES -> make a synchronized version?
+        // might be ok here because called on BT thread? + sync happening in
+        //  checkSendBecomingNoisyIntent
+        final int delay = mDeviceInventory.checkSendBecomingNoisyIntent(
+                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState,
+                AudioSystem.DEVICE_NONE);
+        final String addr = btDeviceInfo == null ? "null" : btDeviceInfo.getBtDevice().getAddress();
+
+        if (AudioService.DEBUG_DEVICES) {
+            Log.d(TAG, "handleSetA2dpSinkConnectionState btDevice= " + btDeviceInfo
+                    + " state= " + state
+                    + " is dock: " + btDeviceInfo.getBtDevice().isBluetoothDock());
+        }
+        sendILMsg(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE, SENDMSG_QUEUE,
+                state, btDeviceInfo, delay);
+    }
+
+    /*package*/ void handleDisconnectHearingAid() {
+        mDeviceInventory.disconnectHearingAid();
+    }
+
+    /*package*/ void handleSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state,
+            @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
+        final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
+        sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state,
+                btDeviceInfo);
+    }
+
+    /*package*/ void handleFailureToConnectToBtHeadsetService(int delay) {
+        sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay);
+    }
+
+    /*package*/ void handleCancelFailureToConnectToBtHeadsetService() {
+        mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
+    }
+
+    /*package*/ void postReportNewRoutes() {
+        sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
+    }
+
+    /*package*/ void cancelA2dpDockTimeout() {
+        mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
+    }
+
+    /*package*/ void postA2dpActiveDeviceChange(BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
+        sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
+    }
+
+    //###
+    // must be called synchronized on mConnectedDevices
+    /*package*/ boolean hasScheduledA2dpDockTimeout() {
+        return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
+    }
+
+    //###
+    // must be called synchronized on mConnectedDevices
+    /*package*/  boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
+        return mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE,
+                new BtHelper.BluetoothA2dpDeviceInfo(btDevice));
+    }
+
+    /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
+        sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
+    }
+
+    /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
+        synchronized (mA2dpAvrcpLock) {
+            mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
+        }
+    }
+
+    /*package*/ boolean getBluetoothA2dpEnabled() {
+        synchronized (mBluetoothA2dpEnabledLock) {
+            return mBluetoothA2dpEnabled;
+        }
+    }
+
+    /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
+        synchronized (mA2dpAvrcpLock) {
+            return mBtHelper.getA2dpCodec(device);
+        }
+    }
+
+    //---------------------------------------------------------------------
+    // Internal handling of messages
+    // These methods are ALL synchronous, in response to message handling in BrokerHandler
+    // Blocking in any of those will block the message queue
+
+    private void onSetForceUse(int useCase, int config, String eventSource) {
+        if (useCase == AudioSystem.FOR_MEDIA) {
+            postReportNewRoutes();
+        }
+        AudioService.sForceUseLogger.log(
+                new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
+        AudioSystem.setForceUse(useCase, config);
+    }
+
+    private void onSendBecomingNoisyIntent() {
+        AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+                "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
+        sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
+    }
+
+    //---------------------------------------------------------------------
+    // Message handling
+    private BrokerHandler mBrokerHandler;
+    private BrokerThread mBrokerThread;
+    private PowerManager.WakeLock mBrokerEventWakeLock;
+
+    private void setupMessaging(Context ctxt) {
+        final PowerManager pm = (PowerManager) ctxt.getSystemService(Context.POWER_SERVICE);
+        mBrokerEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                "handleAudioDeviceEvent");
+        mBrokerThread = new BrokerThread();
+        mBrokerThread.start();
+        waitForBrokerHandlerCreation();
+    }
+
+    private void waitForBrokerHandlerCreation() {
+        synchronized (this) {
+            while (mBrokerHandler == null) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Interruption while waiting on BrokerHandler");
+                }
+            }
+        }
+    }
+
+    /** Class that handles the device broker's message queue */
+    private class BrokerThread extends Thread {
+        BrokerThread() {
+            super("AudioDeviceBroker");
+        }
+
+        @Override
+        public void run() {
+            // Set this thread up so the handler will work on it
+            Looper.prepare();
+
+            synchronized (AudioDeviceBroker.this) {
+                mBrokerHandler = new BrokerHandler();
+
+                // Notify that the handler has been created
+                AudioDeviceBroker.this.notify();
+            }
+
+            Looper.loop();
+        }
+    }
+
+    /** Class that handles the message queue */
+    private class BrokerHandler extends Handler {
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_RESTORE_DEVICES:
+                    mDeviceInventory.onRestoreDevices();
+                    synchronized (mBluetoothA2dpEnabledLock) {
+                        mBtHelper.onAudioServerDiedRestoreA2dp();
+                    }
+                    break;
+                case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+                    mDeviceInventory.onSetWiredDeviceConnectionState(
+                            (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj);
+                    break;
+                case MSG_I_BROADCAST_BT_CONNECTION_STATE:
+                    mBtHelper.onBroadcastScoConnectionState(msg.arg1);
+                    break;
+                case MSG_IIL_SET_FORCE_USE: // intented fall-through
+                case MSG_IIL_SET_FORCE_BT_A2DP_USE:
+                    onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj);
+                    break;
+                case MSG_REPORT_NEW_ROUTES:
+                    mDeviceInventory.onReportNewRoutes();
+                    break;
+                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+                    mDeviceInventory.onSetA2dpSinkConnectionState(
+                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1, msg.arg2);
+                    break;
+                case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+                    mDeviceInventory.onSetA2dpSourceConnectionState(
+                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
+                    break;
+                case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+                    mDeviceInventory.onSetHearingAidConnectionState(
+                            (BluetoothDevice) msg.obj, msg.arg1);
+                    break;
+                case MSG_BT_HEADSET_CNCT_FAILED:
+                    mBtHelper.resetBluetoothSco();
+                    break;
+                case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+                    // msg.obj  == address of BTA2DP device
+                    mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
+                    break;
+                case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+                    final int a2dpCodec;
+                    final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
+                    synchronized (mA2dpAvrcpLock) {
+                        a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
+                    }
+                    mDeviceInventory.onBluetoothA2dpDeviceConfigChange(
+                            new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec));
+                    break;
+                case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
+                    onSendBecomingNoisyIntent();
+                    break;
+                case MSG_II_SET_HEARING_AID_VOLUME:
+                    mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
+                    break;
+                case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME:
+                    mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
+                    break;
+                case MSG_I_DISCONNECT_BT_SCO:
+                    mBtHelper.disconnectBluetoothSco(msg.arg1);
+                    break;
+                case MSG_TOGGLE_HDMI:
+                    mDeviceInventory.onToggleHdmi();
+                    break;
+                case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+                    mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
+                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj);
+                    break;
+                default:
+                    Log.wtf(TAG, "Invalid message " + msg.what);
+            }
+            if (isMessageHandledUnderWakelock(msg.what)) {
+                try {
+                    mBrokerEventWakeLock.release();
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception releasing wakelock", e);
+                }
+            }
+        }
+    }
+
+    // List of all messages. If a message has be handled under wakelock, add it to
+    //    the isMessageHandledUnderWakelock(int) method
+    // Naming of msg indicates arguments, using JNI argument grammar
+    // (e.g. II indicates two int args, IL indicates int and Obj arg)
+    private static final int MSG_RESTORE_DEVICES = 1;
+    private static final int MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE = 2;
+    private static final int MSG_I_BROADCAST_BT_CONNECTION_STATE = 3;
+    private static final int MSG_IIL_SET_FORCE_USE = 4;
+    private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5;
+    private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE = 6;
+    private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7;
+    private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8;
+    private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
+    private static final int MSG_IL_BTA2DP_DOCK_TIMEOUT = 10;
+    private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11;
+    private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12;
+    private static final int MSG_REPORT_NEW_ROUTES = 13;
+    private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
+    private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
+    private static final int MSG_I_DISCONNECT_BT_SCO = 16;
+    private static final int MSG_TOGGLE_HDMI = 17;
+    private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18;
+
+
+    private static boolean isMessageHandledUnderWakelock(int msgId) {
+        switch(msgId) {
+            case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+            case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+            case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+            case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+            case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+            case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+            case MSG_TOGGLE_HDMI:
+            case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    // Message helper methods
+
+    // sendMsg() flags
+    /** If the msg is already queued, replace it with this one. */
+    private static final int SENDMSG_REPLACE = 0;
+    /** If the msg is already queued, ignore this one and leave the old. */
+    private static final int SENDMSG_NOOP = 1;
+    /** If the msg is already queued, queue this one and leave the old. */
+    private static final int SENDMSG_QUEUE = 2;
+
+    private void sendMsg(int msg, int existingMsgPolicy, int delay) {
+        sendIILMsg(msg, existingMsgPolicy, 0, 0, null, delay);
+    }
+
+    private void sendILMsg(int msg, int existingMsgPolicy, int arg, Object obj, int delay) {
+        sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, delay);
+    }
+
+    private void sendLMsg(int msg, int existingMsgPolicy, Object obj, int delay) {
+        sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, delay);
+    }
+
+    private void sendIMsg(int msg, int existingMsgPolicy, int arg, int delay) {
+        sendIILMsg(msg, existingMsgPolicy, arg, 0, null, delay);
+    }
+
+    private void sendMsgNoDelay(int msg, int existingMsgPolicy) {
+        sendIILMsg(msg, existingMsgPolicy, 0, 0, null, 0);
+    }
+
+    private void sendIMsgNoDelay(int msg, int existingMsgPolicy, int arg) {
+        sendIILMsg(msg, existingMsgPolicy, arg, 0, null, 0);
+    }
+
+    private void sendIIMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2) {
+        sendIILMsg(msg, existingMsgPolicy, arg1, arg2, null, 0);
+    }
+
+    private void sendILMsgNoDelay(int msg, int existingMsgPolicy, int arg, Object obj) {
+        sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, 0);
+    }
+
+    private void sendLMsgNoDelay(int msg, int existingMsgPolicy, Object obj) {
+        sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, 0);
+    }
+
+    private void sendIILMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj) {
+        sendIILMsg(msg, existingMsgPolicy, arg1, arg2, obj, 0);
+    }
+
+    private void sendIILMsg(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj,
+                            int delay) {
+        if (existingMsgPolicy == SENDMSG_REPLACE) {
+            mBrokerHandler.removeMessages(msg);
+        } else if (existingMsgPolicy == SENDMSG_NOOP && mBrokerHandler.hasMessages(msg)) {
+            return;
+        }
+
+        if (isMessageHandledUnderWakelock(msg)) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mBrokerEventWakeLock.acquire(BROKER_WAKELOCK_TIMEOUT_MS);
+            } catch (Exception e) {
+                Log.e(TAG, "Exception acquiring wakelock", e);
+            }
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        synchronized (sLastDeviceConnectionMsgTimeLock) {
+            long time = SystemClock.uptimeMillis() + delay;
+
+            switch (msg) {
+                case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE:
+                case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+                case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+                case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+                case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+                case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+                    if (sLastDeviceConnectMsgTime >= time) {
+                        // add a little delay to make sure messages are ordered as expected
+                        time = sLastDeviceConnectMsgTime + 30;
+                    }
+                    sLastDeviceConnectMsgTime = time;
+                    break;
+                default:
+                    break;
+            }
+
+            mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj),
+                    time);
+        }
+    }
+
+    //-------------------------------------------------------------
+    // internal utilities
+    private void sendBroadcastToAll(Intent intent) {
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
new file mode 100644
index 0000000..eb76e6e
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -0,0 +1,1024 @@
+/*
+ * 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 com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
+import android.media.AudioDevicePort;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioPort;
+import android.media.AudioRoutesInfo;
+import android.media.AudioSystem;
+import android.media.IAudioRoutesObserver;
+import android.os.Binder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+
+/**
+ * Class to manage the inventory of all connected devices.
+ * This class is thread-safe.
+ */
+public final class AudioDeviceInventory {
+
+    private static final String TAG = "AS.AudioDeviceInventory";
+
+    // Actual list of connected devices
+    // Key for map created from DeviceInfo.makeDeviceListKey()
+    private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>();
+
+    private final @NonNull AudioDeviceBroker mDeviceBroker;
+
+    AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
+        mDeviceBroker = broker;
+    }
+
+    // cache of the address of the last dock the device was connected to
+    private String mDockAddress;
+
+    // Monitoring of audio routes.  Protected by mAudioRoutes.
+    final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
+    final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
+            new RemoteCallbackList<IAudioRoutesObserver>();
+
+    //------------------------------------------------------------
+    /**
+     * Class to store info about connected devices.
+     * Use makeDeviceListKey() to make a unique key for this list.
+     */
+    private static class DeviceInfo {
+        final int mDeviceType;
+        final String mDeviceName;
+        final String mDeviceAddress;
+        int mDeviceCodecFormat;
+
+        DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
+            mDeviceType = deviceType;
+            mDeviceName = deviceName;
+            mDeviceAddress = deviceAddress;
+            mDeviceCodecFormat = deviceCodecFormat;
+        }
+
+        @Override
+        public String toString() {
+            return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
+                    + " name:" + mDeviceName
+                    + " addr:" + mDeviceAddress
+                    + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
+        }
+
+        /**
+         * Generate a unique key for the mConnectedDevices List by composing the device "type"
+         * and the "address" associated with a specific instance of that device type
+         */
+        private static String makeDeviceListKey(int device, String deviceAddress) {
+            return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
+        }
+    }
+
+    /**
+     * A class just for packaging up a set of connection parameters.
+     */
+    /*package*/ class WiredDeviceConnectionState {
+        public final int mType;
+        public final @AudioService.ConnectionState int mState;
+        public final String mAddress;
+        public final String mName;
+        public final String mCaller;
+
+        /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
+                                               String address, String name, String caller) {
+            mType = type;
+            mState = state;
+            mAddress = address;
+            mName = name;
+            mCaller = caller;
+        }
+    }
+
+    //------------------------------------------------------------
+    // Message handling from AudioDeviceBroker
+
+    /**
+     * Restore previously connected devices. Use in case of audio server crash
+     * (see AudioService.onAudioServerDied() method)
+     */
+    /*package*/ void onRestoreDevices() {
+        synchronized (mConnectedDevices) {
+            for (int i = 0; i < mConnectedDevices.size(); i++) {
+                DeviceInfo di = mConnectedDevices.valueAt(i);
+                AudioSystem.setDeviceConnectionState(
+                        di.mDeviceType,
+                        AudioSystem.DEVICE_STATE_AVAILABLE,
+                        di.mDeviceAddress,
+                        di.mDeviceName,
+                        di.mDeviceCodecFormat);
+            }
+        }
+    }
+
+    /*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
+            @AudioService.BtProfileConnectionState int state, int a2dpVolume) {
+        final BluetoothDevice btDevice = btInfo.getBtDevice();
+        if (AudioService.DEBUG_DEVICES) {
+            Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state="
+                    + state + " is dock=" + btDevice.isBluetoothDock());
+        }
+        String address = btDevice.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                "A2DP sink connected: device addr=" + address + " state=" + state));
+
+        final int a2dpCodec;
+        synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+            a2dpCodec = btInfo.getCodec();
+        }
+
+        synchronized (mConnectedDevices) {
+            final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                    btDevice.getAddress());
+            final DeviceInfo di = mConnectedDevices.get(key);
+            boolean isConnected = di != null;
+
+            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+                if (btDevice.isBluetoothDock()) {
+                    if (state == BluetoothProfile.STATE_DISCONNECTED) {
+                        // introduction of a delay for transient disconnections of docks when
+                        // power is rapidly turned off/on, this message will be canceled if
+                        // we reconnect the dock under a preset delay
+                        makeA2dpDeviceUnavailableLater(address,
+                                AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
+                        // the next time isConnected is evaluated, it will be false for the dock
+                    }
+                } else {
+                    makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
+                }
+            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+                if (btDevice.isBluetoothDock()) {
+                    // this could be a reconnection after a transient disconnection
+                    mDeviceBroker.cancelA2dpDockTimeout();
+                    mDockAddress = address;
+                } else {
+                    // this could be a connection of another A2DP device before the timeout of
+                    // a dock: cancel the dock timeout, and make the dock unavailable now
+                    if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) {
+                        mDeviceBroker.cancelA2dpDockTimeout();
+                        makeA2dpDeviceUnavailableNow(mDockAddress,
+                                AudioSystem.AUDIO_FORMAT_DEFAULT);
+                    }
+                }
+                if (a2dpVolume != -1) {
+                    AudioService.VolumeStreamState streamState =
+                            mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
+                    // Convert index to internal representation in VolumeStreamState
+                    a2dpVolume = a2dpVolume * 10;
+                    streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                            "onSetA2dpSinkConnectionState");
+                    mDeviceBroker.setDeviceVolume(
+                            streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+                }
+                makeA2dpDeviceAvailable(address, btDevice.getName(),
+                        "onSetA2dpSinkConnectionState", a2dpCodec);
+            }
+        }
+    }
+
+    /*package*/ void onSetA2dpSourceConnectionState(
+            @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) {
+        final BluetoothDevice btDevice = btInfo.getBtDevice();
+        if (AudioService.DEBUG_DEVICES) {
+            Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state="
+                    + state);
+        }
+        String address = btDevice.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+
+        synchronized (mConnectedDevices) {
+            final String key = DeviceInfo.makeDeviceListKey(
+                    AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
+            final DeviceInfo di = mConnectedDevices.get(key);
+            boolean isConnected = di != null;
+
+            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+                makeA2dpSrcUnavailable(address);
+            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+                makeA2dpSrcAvailable(address);
+            }
+        }
+    }
+
+    /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice,
+                @AudioService.BtProfileConnectionState int state) {
+        String address = btDevice.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                "onSetHearingAidConnectionState addr=" + address));
+
+        synchronized (mConnectedDevices) {
+            final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
+                    btDevice.getAddress());
+            final DeviceInfo di = mConnectedDevices.get(key);
+            boolean isConnected = di != null;
+
+            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
+                makeHearingAidDeviceUnavailable(address);
+            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
+                makeHearingAidDeviceAvailable(address, btDevice.getName(),
+                        "onSetHearingAidConnectionState");
+            }
+        }
+    }
+
+    /*package*/ void onBluetoothA2dpDeviceConfigChange(
+            @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) {
+        final BluetoothDevice btDevice = btInfo.getBtDevice();
+        if (AudioService.DEBUG_DEVICES) {
+            Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
+        }
+        if (btDevice == null) {
+            return;
+        }
+        String address = btDevice.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                "onBluetoothA2dpDeviceConfigChange addr=" + address));
+
+        final int a2dpCodec = btInfo.getCodec();
+
+        synchronized (mConnectedDevices) {
+            if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
+                AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                        "A2dp config change ignored"));
+                return;
+            }
+            final String key =
+                    DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+            final DeviceInfo di = mConnectedDevices.get(key);
+            if (di == null) {
+                Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange");
+                return;
+            }
+            // Device is connected
+            if (di.mDeviceCodecFormat != a2dpCodec) {
+                di.mDeviceCodecFormat = a2dpCodec;
+                mConnectedDevices.replace(key, di);
+            }
+            if (AudioService.DEBUG_DEVICES) {
+                Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec="
+                        + di.mDeviceCodecFormat);
+            }
+            if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
+                    btDevice.getName(), di.mDeviceCodecFormat) != AudioSystem.AUDIO_STATUS_OK) {
+                // force A2DP device disconnection in case of error so that AudioService state
+                // is consistent with audio policy manager state
+                final int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+                setBluetoothA2dpDeviceConnectionState(
+                        btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
+                        false /* suppressNoisyIntent */, musicDevice,
+                        -1 /* a2dpVolume */);
+            }
+        }
+    }
+
+    /*package*/ void onBluetoothA2dpActiveDeviceChange(
+            @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo) {
+        final BluetoothDevice btDevice = btInfo.getBtDevice();
+        int a2dpVolume = btInfo.getVolume();
+        final int a2dpCodec = btInfo.getCodec();
+
+        if (AudioService.DEBUG_DEVICES) {
+            Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
+        }
+        String address = btDevice.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                "onBluetoothA2dpActiveDeviceChange addr=" + address));
+
+        synchronized (mConnectedDevices) {
+            //TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo
+            // for this type of message
+            if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
+                AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+                        "A2dp config change ignored"));
+                return;
+            }
+            final String key = DeviceInfo.makeDeviceListKey(
+                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+            final DeviceInfo di = mConnectedDevices.get(key);
+            if (di == null) {
+                return;
+            }
+
+            // Device is connected
+            if (a2dpVolume != -1) {
+                final AudioService.VolumeStreamState streamState =
+                        mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
+                // Convert index to internal representation in VolumeStreamState
+                a2dpVolume = a2dpVolume * 10;
+                streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                        "onBluetoothA2dpActiveDeviceChange");
+                mDeviceBroker.setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+            }
+
+            if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
+                    btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
+                int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+                // force A2DP device disconnection in case of error so that AudioService state is
+                // consistent with audio policy manager state
+                setBluetoothA2dpDeviceConnectionState(
+                        btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
+                        false /* suppressNoisyIntent */, musicDevice,
+                        -1 /* a2dpVolume */);
+            }
+        }
+    }
+
+    /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
+        synchronized (mConnectedDevices) {
+            makeA2dpDeviceUnavailableNow(address, a2dpCodec);
+        }
+    }
+
+    /*package*/ void onReportNewRoutes() {
+        int n = mRoutesObservers.beginBroadcast();
+        if (n > 0) {
+            AudioRoutesInfo routes;
+            synchronized (mCurAudioRoutes) {
+                routes = new AudioRoutesInfo(mCurAudioRoutes);
+            }
+            while (n > 0) {
+                n--;
+                IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n);
+                try {
+                    obs.dispatchAudioRoutesChanged(routes);
+                } catch (RemoteException e) { }
+            }
+        }
+        mRoutesObservers.finishBroadcast();
+        mDeviceBroker.observeDevicesForAllStreams();
+    }
+
+    private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG =
+            AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+                    | AudioSystem.DEVICE_OUT_LINE | AudioSystem.DEVICE_OUT_ALL_USB;
+
+    /*package*/ void onSetWiredDeviceConnectionState(
+                            AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
+        AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
+
+        synchronized (mConnectedDevices) {
+            if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
+                    && ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
+                mDeviceBroker.setBluetoothA2dpOnInt(true,
+                        "onSetWiredDeviceConnectionState state DISCONNECTED");
+            }
+
+            if (!handleDeviceConnection(wdcs.mState == 1, wdcs.mType, wdcs.mAddress,
+                    wdcs.mName)) {
+                // change of connection state failed, bailout
+                return;
+            }
+            if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
+                if ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
+                    mDeviceBroker.setBluetoothA2dpOnInt(false,
+                            "onSetWiredDeviceConnectionState state not DISCONNECTED");
+                }
+                mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller);
+            }
+            mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
+            sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName);
+            updateAudioRoutes(wdcs.mType, wdcs.mState);
+        }
+    }
+
+    /*package*/ void onToggleHdmi() {
+        synchronized (mConnectedDevices) {
+            // Is HDMI connected?
+            final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
+            final DeviceInfo di = mConnectedDevices.get(key);
+            if (di == null) {
+                Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
+                return;
+            }
+            // Toggle HDMI to retrigger broadcast with proper formats.
+            setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
+                    AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
+                    "android"); // disconnect
+            setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
+                    AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
+                    "android"); // reconnect
+        }
+    }
+    //------------------------------------------------------------
+    //
+
+    /**
+     * Implements the communication with AudioSystem to (dis)connect a device in the native layers
+     * @param connect true if connection
+     * @param device the device type
+     * @param address the address of the device
+     * @param deviceName human-readable name of device
+     * @return false if an error was reported by AudioSystem
+     */
+    /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
+            String deviceName) {
+        if (AudioService.DEBUG_DEVICES) {
+            Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
+                    + Integer.toHexString(device) + " address:" + address
+                    + " name:" + deviceName + ")");
+        }
+        synchronized (mConnectedDevices) {
+            final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
+            if (AudioService.DEBUG_DEVICES) {
+                Slog.i(TAG, "deviceKey:" + deviceKey);
+            }
+            DeviceInfo di = mConnectedDevices.get(deviceKey);
+            boolean isConnected = di != null;
+            if (AudioService.DEBUG_DEVICES) {
+                Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
+            }
+            if (connect && !isConnected) {
+                final int res = AudioSystem.setDeviceConnectionState(device,
+                        AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
+                        AudioSystem.AUDIO_FORMAT_DEFAULT);
+                if (res != AudioSystem.AUDIO_STATUS_OK) {
+                    Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device)
+                            + " due to command error " + res);
+                    return false;
+                }
+                mConnectedDevices.put(deviceKey, new DeviceInfo(
+                        device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                mDeviceBroker.postAccessoryPlugMediaUnmute(device);
+                return true;
+            } else if (!connect && isConnected) {
+                AudioSystem.setDeviceConnectionState(device,
+                        AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
+                        AudioSystem.AUDIO_FORMAT_DEFAULT);
+                // always remove even if disconnection failed
+                mConnectedDevices.remove(deviceKey);
+                return true;
+            }
+            Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
+                    + ", deviceSpec=" + di + ", connect=" + connect);
+        }
+        return false;
+    }
+
+
+    /*package*/ void disconnectA2dp() {
+        synchronized (mConnectedDevices) {
+            synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+                final ArraySet<String> toRemove = new ArraySet<>();
+                // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
+                mConnectedDevices.values().forEach(deviceInfo -> {
+                    if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+                        toRemove.add(deviceInfo.mDeviceAddress);
+                    }
+                });
+                if (toRemove.size() > 0) {
+                    final int delay = checkSendBecomingNoisyIntentInt(
+                            AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                            0, AudioSystem.DEVICE_NONE);
+                    toRemove.stream().forEach(deviceAddress ->
+                            makeA2dpDeviceUnavailableLater(deviceAddress, delay)
+                    );
+                }
+            }
+        }
+    }
+
+    /*package*/ void disconnectA2dpSink() {
+        synchronized (mConnectedDevices) {
+            final ArraySet<String> toRemove = new ArraySet<>();
+            // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
+            mConnectedDevices.values().forEach(deviceInfo -> {
+                if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
+                    toRemove.add(deviceInfo.mDeviceAddress);
+                }
+            });
+            toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress));
+        }
+    }
+
+    /*package*/ void disconnectHearingAid() {
+        synchronized (mConnectedDevices) {
+            synchronized (mDeviceBroker.mHearingAidLock) {
+                final ArraySet<String> toRemove = new ArraySet<>();
+                // Disconnect ALL DEVICE_OUT_HEARING_AID devices
+                mConnectedDevices.values().forEach(deviceInfo -> {
+                    if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
+                        toRemove.add(deviceInfo.mDeviceAddress);
+                    }
+                });
+                if (toRemove.size() > 0) {
+                    final int delay = checkSendBecomingNoisyIntentInt(
+                            AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
+                    toRemove.stream().forEach(deviceAddress ->
+                            // TODO delay not used?
+                            makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
+                    );
+                }
+            }
+        }
+    }
+
+    // must be called before removing the device from mConnectedDevices
+    // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
+    // from AudioSystem
+    /*package*/ int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) {
+        synchronized (mConnectedDevices) {
+            return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
+        }
+    }
+
+    /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
+        synchronized (mCurAudioRoutes) {
+            AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
+            mRoutesObservers.register(observer);
+            return routes;
+        }
+    }
+
+    /*package*/ AudioRoutesInfo getCurAudioRoutes() {
+        return mCurAudioRoutes;
+    }
+
+    /*package*/ int setBluetoothA2dpDeviceConnectionState(
+            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+            int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) {
+        int delay;
+        if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
+            throw new IllegalArgumentException("invalid profile " + profile);
+        }
+        synchronized (mConnectedDevices) {
+            if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
+                int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
+                delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                        intState, musicDevice);
+            } else {
+                delay = 0;
+            }
+
+            final int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
+
+            if (AudioService.DEBUG_DEVICES) {
+                Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device
+                        + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec
+                        + " suppressNoisyIntent: " + suppressNoisyIntent);
+            }
+
+            final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo =
+                    new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec);
+            if (profile == BluetoothProfile.A2DP) {
+                mDeviceBroker.postA2dpSinkConnection(state,
+                        a2dpDeviceInfo,
+                        delay);
+            } else { //profile == BluetoothProfile.A2DP_SINK
+                mDeviceBroker.postA2dpSourceConnection(state,
+                        a2dpDeviceInfo,
+                        delay);
+            }
+        }
+        return delay;
+    }
+
+    /*package*/ int handleBluetoothA2dpActiveDeviceChange(
+            @NonNull BluetoothDevice device,
+            @AudioService.BtProfileConnectionState int state, int profile,
+            boolean suppressNoisyIntent, int a2dpVolume) {
+        if (state == BluetoothProfile.STATE_DISCONNECTED) {
+            return setBluetoothA2dpDeviceConnectionState(device, state, profile,
+                    suppressNoisyIntent, AudioSystem.DEVICE_NONE, a2dpVolume);
+        }
+        // state == BluetoothProfile.STATE_CONNECTED
+        synchronized (mConnectedDevices) {
+            for (int i = 0; i < mConnectedDevices.size(); i++) {
+                final DeviceInfo deviceInfo = mConnectedDevices.valueAt(i);
+                if (deviceInfo.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
+                    continue;
+                }
+                // If A2DP device exists, this is either an active device change or
+                // device config change
+                final String existingDevicekey = mConnectedDevices.keyAt(i);
+                final String deviceName = device.getName();
+                final String address = device.getAddress();
+                final String newDeviceKey = DeviceInfo.makeDeviceListKey(
+                        AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+                int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
+                // Device not equal to existing device, active device change
+                if (!TextUtils.equals(existingDevicekey, newDeviceKey)) {
+                    mConnectedDevices.remove(existingDevicekey);
+                    mConnectedDevices.put(newDeviceKey, new DeviceInfo(
+                            AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName,
+                            address, a2dpCodec));
+                    mDeviceBroker.postA2dpActiveDeviceChange(
+                            new BtHelper.BluetoothA2dpDeviceInfo(
+                                    device, a2dpVolume, a2dpCodec));
+                    return 0;
+                } else {
+                    // Device config change for existing device
+                    mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device);
+                    return 0;
+                }
+            }
+        }
+        return 0;
+    }
+
+    /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
+                                                  String address, String name, String caller) {
+        synchronized (mConnectedDevices) {
+            int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
+            mDeviceBroker.postSetWiredDeviceConnectionState(
+                    new WiredDeviceConnectionState(type, state, address, name, caller),
+                    delay);
+            return delay;
+        }
+    }
+
+    /*package*/ int  setBluetoothHearingAidDeviceConnectionState(
+            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+            boolean suppressNoisyIntent, int musicDevice) {
+        int delay;
+        synchronized (mConnectedDevices) {
+            if (!suppressNoisyIntent) {
+                int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
+                delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID,
+                        intState, musicDevice);
+            } else {
+                delay = 0;
+            }
+            mDeviceBroker.postSetHearingAidConnectionState(state, device, delay);
+            return delay;
+        }
+    }
+
+
+    //-------------------------------------------------------------------
+    // Internal utilities
+
+    @GuardedBy("mConnectedDevices")
+    private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
+            int a2dpCodec) {
+        // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
+        // audio policy manager
+        AudioService.VolumeStreamState streamState =
+                mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC);
+        mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
+        // Reset A2DP suspend state each time a new sink is connected
+        AudioSystem.setParameters("A2dpSuspended=false");
+        mConnectedDevices.put(
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
+                new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
+                        address, a2dpCodec));
+        mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
+        setCurrentAudioRouteNameIfPossible(name);
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
+        if (address == null) {
+            return;
+        }
+        mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
+        mConnectedDevices.remove(
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
+        // Remove A2DP routes as well
+        setCurrentAudioRouteNameIfPossible(null);
+        if (mDockAddress == address) {
+            mDockAddress = null;
+        }
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
+        // prevent any activity on the A2DP audio output to avoid unwanted
+        // reconnection of the sink.
+        AudioSystem.setParameters("A2dpSuspended=true");
+        // retrieve DeviceInfo before removing device
+        final String deviceKey =
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+        final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey);
+        final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat :
+                AudioSystem.AUDIO_FORMAT_DEFAULT;
+        // the device will be made unavailable later, so consider it disconnected right away
+        mConnectedDevices.remove(deviceKey);
+        // send the delayed message to make the device unavailable later
+        mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs);
+    }
+
+
+    @GuardedBy("mConnectedDevices")
+    private void makeA2dpSrcAvailable(String address) {
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+                AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
+                AudioSystem.AUDIO_FORMAT_DEFAULT);
+        mConnectedDevices.put(
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
+                new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
+                        address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private void makeA2dpSrcUnavailable(String address) {
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+                AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
+                AudioSystem.AUDIO_FORMAT_DEFAULT);
+        mConnectedDevices.remove(
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) {
+        final int hearingAidVolIndex = mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC)
+                .getIndex(AudioSystem.DEVICE_OUT_HEARING_AID);
+        mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, AudioSystem.STREAM_MUSIC);
+
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
+                AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
+                AudioSystem.AUDIO_FORMAT_DEFAULT);
+        mConnectedDevices.put(
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
+                new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
+                        address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+        mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
+        mDeviceBroker.setDeviceVolume(
+                mDeviceBroker.getStreamState(AudioSystem.STREAM_MUSIC),
+                AudioSystem.DEVICE_OUT_HEARING_AID);
+        setCurrentAudioRouteNameIfPossible(name);
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private void makeHearingAidDeviceUnavailable(String address) {
+        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
+                AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
+                AudioSystem.AUDIO_FORMAT_DEFAULT);
+        mConnectedDevices.remove(
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
+        // Remove Hearing Aid routes as well
+        setCurrentAudioRouteNameIfPossible(null);
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private void setCurrentAudioRouteNameIfPossible(String name) {
+        synchronized (mCurAudioRoutes) {
+            if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
+                return;
+            }
+            if (name != null || !isCurrentDeviceConnected()) {
+                mCurAudioRoutes.bluetoothName = name;
+                mDeviceBroker.postReportNewRoutes();
+            }
+        }
+    }
+
+    @GuardedBy("mConnectedDevices")
+    private boolean isCurrentDeviceConnected() {
+        return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
+            TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
+    }
+
+    // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
+    // sent if:
+    // - none of these devices are connected anymore after one is disconnected AND
+    // - the device being disconnected is actually used for music.
+    // Access synchronized on mConnectedDevices
+    private int mBecomingNoisyIntentDevices =
+            AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+                    | AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI
+                    | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET
+                    | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
+                    | AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE
+                    | AudioSystem.DEVICE_OUT_HEARING_AID;
+
+    // must be called before removing the device from mConnectedDevices
+    // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
+    // from AudioSystem
+    @GuardedBy("mConnectedDevices")
+    private int checkSendBecomingNoisyIntentInt(int device, int state, int musicDevice) {
+        if (state != 0) {
+            return 0;
+        }
+        if ((device & mBecomingNoisyIntentDevices) == 0) {
+            return 0;
+        }
+        int delay = 0;
+        int devices = 0;
+        for (int i = 0; i < mConnectedDevices.size(); i++) {
+            int dev = mConnectedDevices.valueAt(i).mDeviceType;
+            if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
+                    && ((dev & mBecomingNoisyIntentDevices) != 0)) {
+                devices |= dev;
+            }
+        }
+        if (musicDevice == AudioSystem.DEVICE_NONE) {
+            musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+        }
+        // ignore condition on device being actually used for music when in communication
+        // because music routing is altered in this case.
+        // also checks whether media routing if affected by a dynamic policy
+        if (((device == musicDevice) || mDeviceBroker.isInCommunication())
+                && (device == devices) && !mDeviceBroker.hasMediaDynamicPolicy()) {
+            mDeviceBroker.broadcastBecomingNoisy();
+            delay = 1000;
+        }
+
+        return delay;
+    }
+
+    // Intent "extra" data keys.
+    private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
+    private static final String CONNECT_INTENT_KEY_STATE = "state";
+    private static final String CONNECT_INTENT_KEY_ADDRESS = "address";
+    private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
+    private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
+    private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
+    private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
+
+    private void sendDeviceConnectionIntent(int device, int state, String address,
+                                            String deviceName) {
+        if (AudioService.DEBUG_DEVICES) {
+            Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device)
+                    + " state:0x" + Integer.toHexString(state) + " address:" + address
+                    + " name:" + deviceName + ");");
+        }
+        Intent intent = new Intent();
+
+        switch(device) {
+            case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
+                intent.setAction(Intent.ACTION_HEADSET_PLUG);
+                intent.putExtra("microphone", 1);
+                break;
+            case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
+            case AudioSystem.DEVICE_OUT_LINE:
+                intent.setAction(Intent.ACTION_HEADSET_PLUG);
+                intent.putExtra("microphone", 0);
+                break;
+            case AudioSystem.DEVICE_OUT_USB_HEADSET:
+                intent.setAction(Intent.ACTION_HEADSET_PLUG);
+                intent.putExtra("microphone",
+                        AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
+                                == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
+                break;
+            case AudioSystem.DEVICE_IN_USB_HEADSET:
+                if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
+                        == AudioSystem.DEVICE_STATE_AVAILABLE) {
+                    intent.setAction(Intent.ACTION_HEADSET_PLUG);
+                    intent.putExtra("microphone", 1);
+                } else {
+                    // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
+                    return;
+                }
+                break;
+            case AudioSystem.DEVICE_OUT_HDMI:
+            case AudioSystem.DEVICE_OUT_HDMI_ARC:
+                configureHdmiPlugIntent(intent, state);
+                break;
+        }
+
+        if (intent.getAction() == null) {
+            return;
+        }
+
+        intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
+        intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
+        intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
+
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void updateAudioRoutes(int device, int state) {
+        int connType = 0;
+
+        switch (device) {
+            case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
+                connType = AudioRoutesInfo.MAIN_HEADSET;
+                break;
+            case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
+            case AudioSystem.DEVICE_OUT_LINE:
+                connType = AudioRoutesInfo.MAIN_HEADPHONES;
+                break;
+            case AudioSystem.DEVICE_OUT_HDMI:
+            case AudioSystem.DEVICE_OUT_HDMI_ARC:
+                connType = AudioRoutesInfo.MAIN_HDMI;
+                break;
+            case AudioSystem.DEVICE_OUT_USB_DEVICE:
+            case AudioSystem.DEVICE_OUT_USB_HEADSET:
+                connType = AudioRoutesInfo.MAIN_USB;
+                break;
+        }
+
+        synchronized (mCurAudioRoutes) {
+            if (connType == 0) {
+                return;
+            }
+            int newConn = mCurAudioRoutes.mainType;
+            if (state != 0) {
+                newConn |= connType;
+            } else {
+                newConn &= ~connType;
+            }
+            if (newConn != mCurAudioRoutes.mainType) {
+                mCurAudioRoutes.mainType = newConn;
+                mDeviceBroker.postReportNewRoutes();
+            }
+        }
+    }
+
+    private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) {
+        intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
+        intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
+        if (state != AudioService.CONNECTION_STATE_CONNECTED) {
+            return;
+        }
+        ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
+        int[] portGeneration = new int[1];
+        int status = AudioSystem.listAudioPorts(ports, portGeneration);
+        if (status != AudioManager.SUCCESS) {
+            Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent");
+            return;
+        }
+        for (AudioPort port : ports) {
+            if (!(port instanceof AudioDevicePort)) {
+                continue;
+            }
+            final AudioDevicePort devicePort = (AudioDevicePort) port;
+            if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI
+                    && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC) {
+                continue;
+            }
+            // found an HDMI port: format the list of supported encodings
+            int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
+            if (formats.length > 0) {
+                ArrayList<Integer> encodingList = new ArrayList(1);
+                for (int format : formats) {
+                    // a format in the list can be 0, skip it
+                    if (format != AudioFormat.ENCODING_INVALID) {
+                        encodingList.add(format);
+                    }
+                }
+                final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray();
+                intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
+            }
+            // find the maximum supported number of channels
+            int maxChannels = 0;
+            for (int mask : devicePort.channelMasks()) {
+                int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
+                if (channelCount > maxChannels) {
+                    maxChannels = channelCount;
+                }
+            }
+            intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index de389bc..df33bf2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -27,6 +27,7 @@
 import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -35,14 +36,9 @@
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
 import android.app.NotificationManager;
-import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothCodecConfig;
-import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -65,14 +61,12 @@
 import android.hardware.hdmi.HdmiTvClient;
 import android.hardware.usb.UsbManager;
 import android.media.AudioAttributes;
-import android.media.AudioDevicePort;
 import android.media.AudioFocusInfo;
 import android.media.AudioFocusRequest;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioPlaybackConfiguration;
-import android.media.AudioPort;
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
@@ -104,7 +98,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -120,8 +113,6 @@
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.util.AndroidRuntimeException;
-import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.MathUtils;
@@ -137,10 +128,8 @@
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.audio.AudioServiceEvents.ForceUseEvent;
 import com.android.server.audio.AudioServiceEvents.PhoneStateEvent;
 import com.android.server.audio.AudioServiceEvents.VolumeEvent;
-import com.android.server.audio.AudioServiceEvents.WiredDevConnectEvent;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
@@ -150,6 +139,8 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -175,19 +166,20 @@
         implements AccessibilityManager.TouchExplorationStateChangeListener,
             AccessibilityManager.AccessibilityServicesStateChangeListener {
 
-    private static final String TAG = "AudioService";
+    private static final String TAG = "AS.AudioService";
 
     /** Debug audio mode */
-    protected static final boolean DEBUG_MODE = Log.isLoggable(TAG + ".MOD", Log.DEBUG);
+    protected static final boolean DEBUG_MODE = false;
 
     /** Debug audio policy feature */
-    protected static final boolean DEBUG_AP = Log.isLoggable(TAG + ".AP", Log.DEBUG);
+    protected static final boolean DEBUG_AP = false;
 
     /** Debug volumes */
-    protected static final boolean DEBUG_VOL = Log.isLoggable(TAG + ".VOL", Log.DEBUG);
+    protected static final boolean DEBUG_VOL = false;
 
     /** debug calls to devices APIs */
-    protected static final boolean DEBUG_DEVICES = Log.isLoggable(TAG + ".DEVICES", Log.DEBUG);
+    protected static final boolean DEBUG_DEVICES = false;
+
     /** How long to delay before persisting a change in volume/ringer mode. */
     private static final int PERSIST_DELAY = 500;
 
@@ -213,11 +205,11 @@
         return mPlatformType == AudioSystem.PLATFORM_VOICE;
     }
 
-    private boolean isPlatformTelevision() {
+    /*package*/ boolean isPlatformTelevision() {
         return mPlatformType == AudioSystem.PLATFORM_TELEVISION;
     }
 
-    private boolean isPlatformAutomotive() {
+    /*package*/ boolean isPlatformAutomotive() {
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
     }
 
@@ -242,52 +234,40 @@
     private static final int MSG_SET_FORCE_USE = 8;
     private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
     private static final int MSG_SET_ALL_VOLUMES = 10;
-    private static final int MSG_REPORT_NEW_ROUTES = 12;
-    private static final int MSG_SET_FORCE_BT_A2DP_USE = 13;
-    private static final int MSG_CHECK_MUSIC_ACTIVE = 14;
-    private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 15;
-    private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 16;
-    private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 17;
-    private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 18;
-    private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 19;
-    private static final int MSG_UNLOAD_SOUND_EFFECTS = 20;
-    private static final int MSG_SYSTEM_READY = 21;
-    private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 22;
-    private static final int MSG_UNMUTE_STREAM = 24;
-    private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 25;
-    private static final int MSG_INDICATE_SYSTEM_READY = 26;
-    private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 27;
-    private static final int MSG_NOTIFY_VOL_EVENT = 28;
-    private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 29;
-    private static final int MSG_ENABLE_SURROUND_FORMATS = 30;
+    private static final int MSG_CHECK_MUSIC_ACTIVE = 11;
+    private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 12;
+    private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 13;
+    private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 14;
+    private static final int MSG_UNLOAD_SOUND_EFFECTS = 15;
+    private static final int MSG_SYSTEM_READY = 16;
+    private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 17;
+    private static final int MSG_UNMUTE_STREAM = 18;
+    private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 19;
+    private static final int MSG_INDICATE_SYSTEM_READY = 20;
+    private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 21;
+    private static final int MSG_NOTIFY_VOL_EVENT = 22;
+    private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 23;
+    private static final int MSG_ENABLE_SURROUND_FORMATS = 24;
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
-    private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 100;
-    private static final int MSG_SET_A2DP_SRC_CONNECTION_STATE = 101;
-    private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102;
-    private static final int MSG_A2DP_DEVICE_CONFIG_CHANGE = 103;
-    private static final int MSG_DISABLE_AUDIO_FOR_UID = 104;
-    private static final int MSG_SET_HEARING_AID_CONNECTION_STATE = 105;
-    private static final int MSG_BTA2DP_DOCK_TIMEOUT = 106;
-    private static final int MSG_A2DP_ACTIVE_DEVICE_CHANGE = 107;
+    private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
     // end of messages handled under wakelock
 
-    private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000;
-    // Timeout for connection to bluetooth headset service
-    private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
-
     // retry delay in case of failure to indicate system ready to AudioFlinger
     private static final int INDICATE_SYSTEM_READY_RETRY_DELAY_MS = 1000;
 
-    private static final int BT_HEARING_AID_GAIN_MIN = -128;
-
     /** @see AudioSystemThread */
     private AudioSystemThread mAudioSystemThread;
     /** @see AudioHandler */
     private AudioHandler mAudioHandler;
     /** @see VolumeStreamState */
     private VolumeStreamState[] mStreamStates;
+
+    /*package*/ VolumeStreamState getStreamState(int stream) {
+        return mStreamStates[stream];
+    }
+
     private SettingsObserver mSettingsObserver;
 
     private int mMode = AudioSystem.MODE_NORMAL;
@@ -477,135 +457,13 @@
     private final UserRestrictionsListener mUserRestrictionsListener =
             new AudioServiceUserRestrictionsListener();
 
-    // Devices currently connected
-    // Use makeDeviceListKey() to make a unique key for this list.
-    private class DeviceListSpec {
-        int mDeviceType;
-        String mDeviceName;
-        String mDeviceAddress;
-        int mDeviceCodecFormat;
-
-        DeviceListSpec(int deviceType, String deviceName, String deviceAddress,
-                int deviceCodecFormat) {
-            mDeviceType = deviceType;
-            mDeviceName = deviceName;
-            mDeviceAddress = deviceAddress;
-            mDeviceCodecFormat = deviceCodecFormat;
-        }
-
-        public String toString() {
-            return "[type:0x" + Integer.toHexString(mDeviceType) + " name:" + mDeviceName
-                    + " address:" + mDeviceAddress
-                    + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
-        }
-    }
-
-    // Generate a unique key for the mConnectedDevices List by composing the device "type"
-    // and the "address" associated with a specific instance of that device type
-    private String makeDeviceListKey(int device, String deviceAddress) {
-        return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
-    }
-
-    private final ArrayMap<String, DeviceListSpec> mConnectedDevices = new ArrayMap<>();
-
-    private class BluetoothA2dpDeviceInfo {
-        BluetoothDevice mBtDevice;
-        int mVolume;
-        int mCodec;
-
-        BluetoothA2dpDeviceInfo(BluetoothDevice btDevice) {
-            this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
-        }
-
-        BluetoothA2dpDeviceInfo(BluetoothDevice btDevice,
-                     int volume, int codec) {
-            mBtDevice = btDevice;
-            mVolume = volume;
-            mCodec = codec;
-        }
-
-        public BluetoothDevice getBtDevice() {
-            return mBtDevice;
-        }
-
-        public int getVolume() {
-            return mVolume;
-        }
-
-        public int getCodec() {
-            return mCodec;
-        }
-    }
-
-    private int mapBluetoothCodecToAudioFormat(int btCodecType) {
-        switch (btCodecType) {
-            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
-                return AudioSystem.AUDIO_FORMAT_SBC;
-            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
-                return AudioSystem.AUDIO_FORMAT_AAC;
-            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
-                return AudioSystem.AUDIO_FORMAT_APTX;
-            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
-                return AudioSystem.AUDIO_FORMAT_APTX_HD;
-            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
-                return AudioSystem.AUDIO_FORMAT_LDAC;
-            default:
-                return AudioSystem.AUDIO_FORMAT_DEFAULT;
-        }
-    }
-
-    // Forced device usage for communications
-    private int mForcedUseForComm;
-    private int mForcedUseForCommExt; // External state returned by getters: always consistent
-                                      // with requests by setters
-
     // List of binder death handlers for setMode() client processes.
     // The last process to have called setMode() is at the top of the list.
-    private final ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>();
-
-    // List of clients having issued a SCO start request
-    private final ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>();
-
-    // BluetoothHeadset API to control SCO connection
-    private BluetoothHeadset mBluetoothHeadset;
-
-    // Bluetooth headset device
-    private BluetoothDevice mBluetoothHeadsetDevice;
-
-    // Indicate if SCO audio connection is currently active and if the initiator is
-    // audio service (internal) or bluetooth headset (external)
-    private int mScoAudioState;
-    // SCO audio state is not active
-    private static final int SCO_STATE_INACTIVE = 0;
-    // SCO audio activation request waiting for headset service to connect
-    private static final int SCO_STATE_ACTIVATE_REQ = 1;
-    // SCO audio state is active or starting due to a request from AudioManager API
-    private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
-    // SCO audio deactivation request waiting for headset service to connect
-    private static final int SCO_STATE_DEACTIVATE_REQ = 4;
-    // SCO audio deactivation in progress, waiting for Bluetooth audio intent
-    private static final int SCO_STATE_DEACTIVATING = 5;
-
-    // SCO audio state is active due to an action in BT handsfree (either voice recognition or
-    // in call audio)
-    private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
-
-    // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
-    // originated from an app targeting an API version before JB MR2 and raw audio after that.
-    private int mScoAudioMode;
-    // SCO audio mode is undefined
-    private static final int SCO_MODE_UNDEFINED = -1;
-    // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
-    private static final int SCO_MODE_VIRTUAL_CALL = 0;
-    // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
-    private static final int SCO_MODE_RAW = 1;
-    // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
-    private static final int SCO_MODE_VR = 2;
-
-    private static final int SCO_MODE_MAX = 2;
-
-    // Current connection state indicated by bluetooth headset
-    private int mScoConnectionState;
+    // package-private so it can be accessed in AudioDeviceBroker.getSetModeDeathHandlers
+    //TODO candidate to be moved to separate class that handles synchronization
+    @GuardedBy("mDeviceBroker.mSetModeLock")
+    /*package*/ final ArrayList<SetModeDeathHandler> mSetModeDeathHandlers =
+            new ArrayList<SetModeDeathHandler>();
 
     // true if boot sequence has been completed
     private boolean mSystemReady;
@@ -636,15 +494,6 @@
     // Used to play ringtones outside system_server
     private volatile IRingtonePlayer mRingtonePlayer;
 
-    // Request to override default use of A2DP for media.
-    private boolean mBluetoothA2dpEnabled;
-    private final Object mBluetoothA2dpEnabledLock = new Object();
-
-    // Monitoring of audio routes.  Protected by mCurAudioRoutes.
-    final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
-    final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers
-            = new RemoteCallbackList<IAudioRoutesObserver>();
-
     // Devices for which the volume is fixed and VolumePanel slider should be disabled
     int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI |
             AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
@@ -669,17 +518,6 @@
 
     private final MediaFocusControl mMediaFocusControl;
 
-    // Reference to BluetoothA2dp to query for volume.
-    private BluetoothHearingAid mHearingAid;
-    // lock always taken synchronized on mConnectedDevices
-    private final Object mHearingAidLock = new Object();
-    // Reference to BluetoothA2dp to query for AbsoluteVolume.
-    private BluetoothA2dp mA2dp;
-    // lock always taken synchronized on mConnectedDevices
-    private final Object mA2dpAvrcpLock = new Object();
-    // If absolute volume is supported in AVRCP device
-    private boolean mAvrcpAbsVolSupported = false;
-
     // Pre-scale for Bluetooth Absolute Volume
     private float[] mPrescaleAbsoluteVolume = new float[] {
         0.5f,    // Pre-scale for index 1
@@ -687,8 +525,6 @@
         0.85f,   // Pre-scale for index 3
     };
 
-    private static Long mLastDeviceConnectMsgTime = new Long(0);
-
     private NotificationManager mNm;
     private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate;
     private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT;
@@ -705,15 +541,6 @@
     @GuardedBy("mSettingsLock")
     private int mAssistantUid;
 
-    // Intent "extra" data keys.
-    public static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
-    public static final String CONNECT_INTENT_KEY_STATE = "state";
-    public static final String CONNECT_INTENT_KEY_ADDRESS = "address";
-    public static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
-    public static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
-    public static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
-    public static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
-
     // Defines the format for the connection "address" for ALSA devices
     public static String makeAlsaAddressString(int card, int device) {
         return "card=" + card + ";device=" + device + ";";
@@ -858,8 +685,6 @@
         sSoundEffectVolumeDb = context.getResources().getInteger(
                 com.android.internal.R.integer.config_soundEffectVolumeDb);
 
-        mForcedUseForComm = AudioSystem.FORCE_NONE;
-
         createAudioSystemThread();
 
         AudioSystem.setErrorCallback(mAudioSystemCallback);
@@ -886,6 +711,8 @@
         mUseFixedVolume = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_useFixedVolume);
 
+        mDeviceBroker = new AudioDeviceBroker(mContext, this);
+
         // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[]
         // array initialized by updateStreamVolumeAlias()
         updateStreamVolumeAlias(false /*updateVolumes*/, TAG);
@@ -988,23 +815,7 @@
         sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE,
                 0, 0, null, 0);
 
-        mScoConnectionState = AudioManager.SCO_AUDIO_STATE_ERROR;
-        resetBluetoothSco();
-        getBluetoothHeadset();
-        //FIXME: this is to maintain compatibility with deprecated intent
-        // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
-        Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
-        newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
-                AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-        sendStickyBroadcastToAll(newIntent);
-
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        if (adapter != null) {
-            adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
-                                    BluetoothProfile.A2DP);
-            adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
-                                    BluetoothProfile.HEARING_AID);
-        }
+        mDeviceBroker.onSystemReady();
 
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
             synchronized (mHdmiClientLock) {
@@ -1065,39 +876,22 @@
 
         readAndSetLowRamDevice();
 
-        // Restore device connection states
-        synchronized (mConnectedDevices) {
-            for (int i = 0; i < mConnectedDevices.size(); i++) {
-                DeviceListSpec spec = mConnectedDevices.valueAt(i);
-                AudioSystem.setDeviceConnectionState(
-                                                spec.mDeviceType,
-                                                AudioSystem.DEVICE_STATE_AVAILABLE,
-                                                spec.mDeviceAddress,
-                                                spec.mDeviceName,
-                                                spec.mDeviceCodecFormat);
-            }
-        }
+        // Restore device connection states, BT state
+        mDeviceBroker.onAudioServerDied();
+
         // Restore call state
         if (AudioSystem.setPhoneState(mMode) ==  AudioSystem.AUDIO_STATUS_OK) {
             mModeLogger.log(new AudioEventLogger.StringEvent(
                 "onAudioServerDied causes setPhoneState(" + AudioSystem.modeToString(mMode) + ")"));
         }
 
-        // Restore forced usage for communications and record
-        mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm,
-                "onAudioServerDied"));
-        AudioSystem.setForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm);
-        mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_RECORD, mForcedUseForComm,
-                "onAudioServerDied"));
-        AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm);
         final int forSys;
         synchronized (mSettingsLock) {
             forSys = mCameraSoundForced ?
                     AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
         }
-        mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_SYSTEM, forSys,
-                "onAudioServerDied"));
-        AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, forSys);
+
+        mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM, forSys, "onAudioServerDied");
 
         // Restore stream volumes
         int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -1120,20 +914,10 @@
             RotationHelper.updateOrientation();
         }
 
-        synchronized (mBluetoothA2dpEnabledLock) {
-            final int forMed = mBluetoothA2dpEnabled ?
-                    AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
-            mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_MEDIA, forMed,
-                    "onAudioServerDied"));
-            AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, forMed);
-        }
-
         synchronized (mSettingsLock) {
             final int forDock = mDockAudioMediaEnabled ?
                     AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE;
-            mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, forDock,
-                    "onAudioServerDied"));
-            AudioSystem.setForceUse(AudioSystem.FOR_DOCK, forDock);
+            mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied");
             sendEncodedSurroundMode(mContentResolver, "onAudioServerDied");
             sendEnabledSurroundFormats(mContentResolver, true);
             updateAssistantUId(true);
@@ -1209,6 +993,45 @@
         }
     }
 
+    /**
+     * Called from AudioDeviceBroker when DEVICE_OUT_HDMI is connected or disconnected.
+     */
+    /*package*/ void checkVolumeCecOnHdmiConnection(int state, String caller) {
+        if (state != 0) {
+            // DEVICE_OUT_HDMI is now connected
+            if ((AudioSystem.DEVICE_OUT_HDMI & mSafeMediaVolumeDevices) != 0) {
+                sendMsg(mAudioHandler,
+                        MSG_CHECK_MUSIC_ACTIVE,
+                        SENDMSG_REPLACE,
+                        0,
+                        0,
+                        caller,
+                        MUSIC_ACTIVE_POLL_PERIOD_MS);
+            }
+
+            if (isPlatformTelevision()) {
+                mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
+                checkAllFixedVolumeDevices();
+                synchronized (mHdmiClientLock) {
+                    if (mHdmiManager != null && mHdmiPlaybackClient != null) {
+                        mHdmiCecSink = false;
+                        mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
+                    }
+                }
+            }
+            sendEnabledSurroundFormats(mContentResolver, true);
+        } else {
+            // DEVICE_OUT_HDMI disconnected
+            if (isPlatformTelevision()) {
+                synchronized (mHdmiClientLock) {
+                    if (mHdmiManager != null) {
+                        mHdmiCecSink = false;
+                    }
+                }
+            }
+        }
+    }
+
     private void checkAllFixedVolumeDevices()
     {
         int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -1373,7 +1196,7 @@
 
     private void sendEncodedSurroundMode(ContentResolver cr, String eventSource)
     {
-        int encodedSurroundMode = Settings.Global.getInt(
+        final int encodedSurroundMode = Settings.Global.getInt(
                 cr, Settings.Global.ENCODED_SURROUND_OUTPUT,
                 Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);
         sendEncodedSurroundMode(encodedSurroundMode, eventSource);
@@ -1402,13 +1225,8 @@
                 break;
         }
         if (forceSetting != AudioSystem.NUM_FORCE_CONFIG) {
-            sendMsg(mAudioHandler,
-                    MSG_SET_FORCE_USE,
-                    SENDMSG_QUEUE,
-                    AudioSystem.FOR_ENCODED_SURROUND,
-                    forceSetting,
-                    eventSource,
-                    0);
+            mDeviceBroker.setForceUse_Async(AudioSystem.FOR_ENCODED_SURROUND, forceSetting,
+                    eventSource);
         }
     }
 
@@ -1632,7 +1450,7 @@
                 + ", flags=" + flags + ", caller=" + caller
                 + ", volControlStream=" + mVolumeControlStream
                 + ", userSelect=" + mUserSelectedVolumeControlStream);
-        mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
+        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
                 direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
                         .append("/").append(caller).append(" uid:").append(uid).toString()));
         final int streamType;
@@ -1690,7 +1508,7 @@
                     + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
             return;
         }
-        mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
+        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
                 direction/*val1*/, flags/*val2*/, callingPackage));
         adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
                 Binder.getCallingUid());
@@ -1871,16 +1689,18 @@
             if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
                 (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
                 (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
-                synchronized (mA2dpAvrcpLock) {
-                    if (mA2dp != null && mAvrcpAbsVolSupported) {
-                        mA2dp.setAvrcpAbsoluteVolume(newIndex / 10);
-                    }
+                if (DEBUG_VOL) {
+                    Log.d(TAG, "adjustSreamVolume: postSetAvrcpAbsoluteVolumeIndex index="
+                            + newIndex + "stream=" + streamType);
                 }
+                mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex);
             }
 
             // Check if volume update should be send to Hearing Aid
             if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
-                setHearingAidVolume(newIndex, streamType);
+                Log.i(TAG, "adjustSreamVolume postSetHearingAidVolumeIndex index=" + newIndex
+                        + " stream=" + streamType);
+                mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType);
             }
 
             // Check if volume update should be sent to Hdmi system audio.
@@ -2052,7 +1872,7 @@
                     + " MODIFY_PHONE_STATE  callingPackage=" + callingPackage);
             return;
         }
-        mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
+        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
                 index/*val1*/, flags/*val2*/, callingPackage));
         setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
                 Binder.getCallingUid());
@@ -2127,18 +1947,20 @@
 
             index = rescaleIndex(index * 10, streamType, streamTypeAlias);
 
-            if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
-                (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
-                (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
-                synchronized (mA2dpAvrcpLock) {
-                    if (mA2dp != null && mAvrcpAbsVolSupported) {
-                        mA2dp.setAvrcpAbsoluteVolume(index / 10);
-                    }
+            if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+                    && (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0
+                    && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
+                if (DEBUG_VOL) {
+                    Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index
+                            + "stream=" + streamType);
                 }
+                mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10);
             }
 
             if ((device & AudioSystem.DEVICE_OUT_HEARING_AID) != 0) {
-                setHearingAidVolume(index, streamType);
+                Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index
+                        + " stream=" + streamType);
+                mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType);
             }
 
             if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
@@ -2881,6 +2703,10 @@
         }
     }
 
+    /*package*/ void setUpdateRingerModeServiceInt() {
+        setRingerModeInt(getRingerModeInternal(), false);
+    }
+
     /** @see AudioManager#shouldVibrate(int) */
     public boolean shouldVibrate(int vibrateType) {
         if (!mHasVibrator) return false;
@@ -2921,7 +2747,7 @@
 
     }
 
-    private class SetModeDeathHandler implements IBinder.DeathRecipient {
+    /*package*/ class SetModeDeathHandler implements IBinder.DeathRecipient {
         private IBinder mCb; // To be notified of client's death
         private int mPid;
         private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client
@@ -2934,7 +2760,7 @@
         public void binderDied() {
             int oldModeOwnerPid = 0;
             int newModeOwnerPid = 0;
-            synchronized(mSetModeDeathHandlers) {
+            synchronized (mDeviceBroker.mSetModeLock) {
                 Log.w(TAG, "setMode() client died");
                 if (!mSetModeDeathHandlers.isEmpty()) {
                     oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
@@ -2949,9 +2775,7 @@
             // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
             // SCO connections not started by the application changing the mode when pid changes
             if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
-                final long ident = Binder.clearCallingIdentity();
-                disconnectBluetoothSco(newModeOwnerPid);
-                Binder.restoreCallingIdentity(ident);
+                mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
             }
         }
 
@@ -2994,7 +2818,7 @@
 
         int oldModeOwnerPid = 0;
         int newModeOwnerPid = 0;
-        synchronized(mSetModeDeathHandlers) {
+        synchronized (mDeviceBroker.mSetModeLock) {
             if (!mSetModeDeathHandlers.isEmpty()) {
                 oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
             }
@@ -3006,11 +2830,11 @@
         // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
         // SCO connections not started by the application changing the mode when pid changes
         if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
-            disconnectBluetoothSco(newModeOwnerPid);
+            mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
         }
     }
 
-    // must be called synchronized on mSetModeDeathHandlers
+    // must be called synchronized on mSetModeLock
     // setModeInt() returns a valid PID if the audio mode was successfully set to
     // any mode other than NORMAL.
     private int setModeInt(int mode, IBinder cb, int pid, String caller) {
@@ -3380,26 +3204,12 @@
         final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
                 .append(Binder.getCallingPid()).toString();
-
-        if (on) {
-            if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
-                    sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
-                            AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE,
-                            eventSource, 0);
-            }
-            mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
-        } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER){
-            mForcedUseForComm = AudioSystem.FORCE_NONE;
-        }
-
-        mForcedUseForCommExt = mForcedUseForComm;
-        sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
-                AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
+        mDeviceBroker.setSpeakerphoneOn(on, eventSource);
     }
 
     /** @see AudioManager#isSpeakerphoneOn() */
     public boolean isSpeakerphoneOn() {
-        return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
+        return mDeviceBroker.isSpeakerphoneOn();
     }
 
     /** @see AudioManager#setBluetoothScoOn(boolean) */
@@ -3410,7 +3220,7 @@
 
         // Only enable calls from system components
         if (UserHandle.getCallingAppId() >= FIRST_APPLICATION_UID) {
-            mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+            mDeviceBroker.setBluetoothScoOnByApp(on);
             return;
         }
 
@@ -3418,95 +3228,57 @@
         final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on)
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
                 .append(Binder.getCallingPid()).toString();
-        setBluetoothScoOnInt(on, eventSource);
+
+        mDeviceBroker.setBluetoothScoOn(on, eventSource);
     }
 
-    public void setBluetoothScoOnInt(boolean on, String eventSource) {
-        Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
-        if (on) {
-            // do not accept SCO ON if SCO audio is not connected
-            synchronized (mScoClients) {
-                if ((mBluetoothHeadset != null)
-                        && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                            != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
-                    mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
-                    Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
-                            + mBluetoothHeadsetDevice + " is not in audio connected mode");
-                    return;
-                }
-            }
-            mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
-        } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
-            mForcedUseForComm = AudioSystem.FORCE_NONE;
-        }
-        mForcedUseForCommExt = mForcedUseForComm;
-        AudioSystem.setParameters("BT_SCO="+ (on ? "on" : "off"));
-        sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
-                AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
-        sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
-                AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource, 0);
-        // Un-mute ringtone stream volume
-        setRingerModeInt(getRingerModeInternal(), false);
-    }
-
-    /** @see AudioManager#isBluetoothScoOn() */
+    /** @see AudioManager#isBluetoothScoOn()
+     * Note that it doesn't report internal state, but state seen by apps (which may have
+     * called setBluetoothScoOn() */
     public boolean isBluetoothScoOn() {
-        return (mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO);
+        return mDeviceBroker.isBluetoothScoOnForApp();
     }
 
+    // TODO investigate internal users due to deprecation of SDK API
     /** @see AudioManager#setBluetoothA2dpOn(boolean) */
     public void setBluetoothA2dpOn(boolean on) {
         // for logging only
         final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
                 .append(Binder.getCallingPid()).toString();
-
-        synchronized (mBluetoothA2dpEnabledLock) {
-            if (mBluetoothA2dpEnabled == on) {
-                return;
-            }
-            mBluetoothA2dpEnabled = on;
-            sendMsg(mAudioHandler, MSG_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE,
-                    AudioSystem.FOR_MEDIA,
-                    mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
-                    eventSource, 0);
-        }
+        mDeviceBroker.setBluetoothA2dpOn_Async(on, eventSource);
     }
 
     /** @see AudioManager#isBluetoothA2dpOn() */
     public boolean isBluetoothA2dpOn() {
-        synchronized (mBluetoothA2dpEnabledLock) {
-            return mBluetoothA2dpEnabled;
-        }
+        return mDeviceBroker.isBluetoothA2dpOn();
     }
 
     /** @see AudioManager#startBluetoothSco() */
     public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
-        int scoAudioMode =
+        final int scoAudioMode =
                 (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
-                        SCO_MODE_VIRTUAL_CALL : SCO_MODE_UNDEFINED;
-        startBluetoothScoInt(cb, scoAudioMode);
+                        BtHelper.SCO_MODE_VIRTUAL_CALL : BtHelper.SCO_MODE_UNDEFINED;
+        final String eventSource = new StringBuilder("startBluetoothSco()")
+                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+                .append(Binder.getCallingPid()).toString();
+        startBluetoothScoInt(cb, scoAudioMode, eventSource);
     }
 
     /** @see AudioManager#startBluetoothScoVirtualCall() */
     public void startBluetoothScoVirtualCall(IBinder cb) {
-        startBluetoothScoInt(cb, SCO_MODE_VIRTUAL_CALL);
+        final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()")
+                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+                .append(Binder.getCallingPid()).toString();
+        startBluetoothScoInt(cb, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
     }
 
-    void startBluetoothScoInt(IBinder cb, int scoAudioMode){
+    void startBluetoothScoInt(IBinder cb, int scoAudioMode, @NonNull String eventSource) {
         if (!checkAudioSettingsPermission("startBluetoothSco()") ||
                 !mSystemReady) {
             return;
         }
-        ScoClient client = getScoClient(cb, true);
-        // The calling identity must be cleared before calling ScoClient.incCount().
-        // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
-        // and this must be done on behalf of system server to make sure permissions are granted.
-        // The caller identity must be cleared after getScoClient() because it is needed if a new
-        // client is created.
-        final long ident = Binder.clearCallingIdentity();
-        client.incCount(scoAudioMode);
-        Binder.restoreCallingIdentity(ident);
+        mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource);
     }
 
     /** @see AudioManager#stopBluetoothSco() */
@@ -3515,648 +3287,15 @@
                 !mSystemReady) {
             return;
         }
-        ScoClient client = getScoClient(cb, false);
-        // The calling identity must be cleared before calling ScoClient.decCount().
-        // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
-        // and this must be done on behalf of system server to make sure permissions are granted.
-        final long ident = Binder.clearCallingIdentity();
-        if (client != null) {
-            client.decCount();
-        }
-        Binder.restoreCallingIdentity(ident);
+        final String eventSource =  new StringBuilder("stopBluetoothSco()")
+                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+                .append(Binder.getCallingPid()).toString();
+        mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
     }
 
 
-    private class ScoClient implements IBinder.DeathRecipient {
-        private IBinder mCb; // To be notified of client's death
-        private int mCreatorPid;
-        private int mStartcount; // number of SCO connections started by this client
-
-        ScoClient(IBinder cb) {
-            mCb = cb;
-            mCreatorPid = Binder.getCallingPid();
-            mStartcount = 0;
-        }
-
-        public void binderDied() {
-            synchronized(mScoClients) {
-                Log.w(TAG, "SCO client died");
-                int index = mScoClients.indexOf(this);
-                if (index < 0) {
-                    Log.w(TAG, "unregistered SCO client died");
-                } else {
-                    clearCount(true);
-                    mScoClients.remove(this);
-                }
-            }
-        }
-
-        public void incCount(int scoAudioMode) {
-            synchronized(mScoClients) {
-                requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
-                if (mStartcount == 0) {
-                    try {
-                        mCb.linkToDeath(this, 0);
-                    } catch (RemoteException e) {
-                        // client has already died!
-                        Log.w(TAG, "ScoClient  incCount() could not link to "+mCb+" binder death");
-                    }
-                }
-                mStartcount++;
-            }
-        }
-
-        public void decCount() {
-            synchronized(mScoClients) {
-                if (mStartcount == 0) {
-                    Log.w(TAG, "ScoClient.decCount() already 0");
-                } else {
-                    mStartcount--;
-                    if (mStartcount == 0) {
-                        try {
-                            mCb.unlinkToDeath(this, 0);
-                        } catch (NoSuchElementException e) {
-                            Log.w(TAG, "decCount() going to 0 but not registered to binder");
-                        }
-                    }
-                    requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
-                }
-            }
-        }
-
-        public void clearCount(boolean stopSco) {
-            synchronized(mScoClients) {
-                if (mStartcount != 0) {
-                    try {
-                        mCb.unlinkToDeath(this, 0);
-                    } catch (NoSuchElementException e) {
-                        Log.w(TAG, "clearCount() mStartcount: "+mStartcount+" != 0 but not registered to binder");
-                    }
-                }
-                mStartcount = 0;
-                if (stopSco) {
-                    requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
-                }
-            }
-        }
-
-        public int getCount() {
-            return mStartcount;
-        }
-
-        public IBinder getBinder() {
-            return mCb;
-        }
-
-        public int getPid() {
-            return mCreatorPid;
-        }
-
-        public int totalCount() {
-            synchronized(mScoClients) {
-                int count = 0;
-                for (ScoClient mScoClient : mScoClients) {
-                    count += mScoClient.getCount();
-                }
-                return count;
-            }
-        }
-
-        private void requestScoState(int state, int scoAudioMode) {
-            checkScoAudioState();
-            int clientCount = totalCount();
-            if (clientCount != 0) {
-                Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
-                        + ", clientCount=" + clientCount);
-                return;
-            }
-            if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
-                // Make sure that the state transitions to CONNECTING even if we cannot initiate
-                // the connection.
-                broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
-                // Accept SCO audio activation only in NORMAL audio mode or if the mode is
-                // currently controlled by the same client process.
-                synchronized(mSetModeDeathHandlers) {
-                    int modeOwnerPid =  mSetModeDeathHandlers.isEmpty()
-                            ? 0 : mSetModeDeathHandlers.get(0).getPid();
-                    if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
-                        Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
-                                + modeOwnerPid + " != creatorPid " + mCreatorPid);
-                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                        return;
-                    }
-                    switch (mScoAudioState) {
-                        case SCO_STATE_INACTIVE:
-                            mScoAudioMode = scoAudioMode;
-                            if (scoAudioMode == SCO_MODE_UNDEFINED) {
-                                mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
-                                if (mBluetoothHeadsetDevice != null) {
-                                    mScoAudioMode = Settings.Global.getInt(mContentResolver,
-                                            "bluetooth_sco_channel_"
-                                                    + mBluetoothHeadsetDevice.getAddress(),
-                                            SCO_MODE_VIRTUAL_CALL);
-                                    if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
-                                        mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
-                                    }
-                                }
-                            }
-                            if (mBluetoothHeadset == null) {
-                                if (getBluetoothHeadset()) {
-                                    mScoAudioState = SCO_STATE_ACTIVATE_REQ;
-                                } else {
-                                    Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
-                                            + " connection, mScoAudioMode=" + mScoAudioMode);
-                                    broadcastScoConnectionState(
-                                            AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                                }
-                                break;
-                            }
-                            if (mBluetoothHeadsetDevice == null) {
-                                Log.w(TAG, "requestScoState: no active device while connecting,"
-                                        + " mScoAudioMode=" + mScoAudioMode);
-                                broadcastScoConnectionState(
-                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                                break;
-                            }
-                            if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
-                                    mBluetoothHeadsetDevice, mScoAudioMode)) {
-                                mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                            } else {
-                                Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
-                                        + " failed, mScoAudioMode=" + mScoAudioMode);
-                                broadcastScoConnectionState(
-                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                            }
-                            break;
-                        case SCO_STATE_DEACTIVATING:
-                            mScoAudioState = SCO_STATE_ACTIVATE_REQ;
-                            break;
-                        case SCO_STATE_DEACTIVATE_REQ:
-                            mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
-                            break;
-                        default:
-                            Log.w(TAG, "requestScoState: failed to connect in state "
-                                    + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
-                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                            break;
-
-                    }
-                }
-            } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-                switch (mScoAudioState) {
-                    case SCO_STATE_ACTIVE_INTERNAL:
-                        if (mBluetoothHeadset == null) {
-                            if (getBluetoothHeadset()) {
-                                mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
-                            } else {
-                                Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
-                                        + " disconnection, mScoAudioMode=" + mScoAudioMode);
-                                mScoAudioState = SCO_STATE_INACTIVE;
-                                broadcastScoConnectionState(
-                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                            }
-                            break;
-                        }
-                        if (mBluetoothHeadsetDevice == null) {
-                            mScoAudioState = SCO_STATE_INACTIVE;
-                            broadcastScoConnectionState(
-                                    AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                            break;
-                        }
-                        if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
-                                mBluetoothHeadsetDevice, mScoAudioMode)) {
-                            mScoAudioState = SCO_STATE_DEACTIVATING;
-                        } else {
-                            mScoAudioState = SCO_STATE_INACTIVE;
-                            broadcastScoConnectionState(
-                                    AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                        }
-                        break;
-                    case SCO_STATE_ACTIVATE_REQ:
-                        mScoAudioState = SCO_STATE_INACTIVE;
-                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                        break;
-                    default:
-                        Log.w(TAG, "requestScoState: failed to disconnect in state "
-                                + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
-                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                        break;
-                }
-            }
-        }
-    }
-
-    private void checkScoAudioState() {
-        synchronized (mScoClients) {
-            if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
-                    mScoAudioState == SCO_STATE_INACTIVE &&
-                    mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                            != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-            }
-        }
-    }
-
-
-    private ScoClient getScoClient(IBinder cb, boolean create) {
-        synchronized(mScoClients) {
-            for (ScoClient existingClient : mScoClients) {
-                if (existingClient.getBinder() == cb) {
-                    return existingClient;
-                }
-            }
-            if (create) {
-                ScoClient newClient = new ScoClient(cb);
-                mScoClients.add(newClient);
-                return newClient;
-            }
-            return null;
-        }
-    }
-
-    public void clearAllScoClients(int exceptPid, boolean stopSco) {
-        synchronized(mScoClients) {
-            ScoClient savedClient = null;
-            for (ScoClient cl : mScoClients) {
-                if (cl.getPid() != exceptPid) {
-                    cl.clearCount(stopSco);
-                } else {
-                    savedClient = cl;
-                }
-            }
-            mScoClients.clear();
-            if (savedClient != null) {
-                mScoClients.add(savedClient);
-            }
-        }
-    }
-
-    private boolean getBluetoothHeadset() {
-        boolean result = false;
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        if (adapter != null) {
-            result = adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
-                                    BluetoothProfile.HEADSET);
-        }
-        // If we could not get a bluetooth headset proxy, send a failure message
-        // without delay to reset the SCO audio state and clear SCO clients.
-        // If we could get a proxy, send a delayed failure message that will reset our state
-        // in case we don't receive onServiceConnected().
-        sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
-                SENDMSG_REPLACE, 0, 0, null, result ? BT_HEADSET_CNCT_TIMEOUT_MS : 0);
-        return result;
-    }
-
-    /**
-     * Disconnect all SCO connections started by {@link AudioManager} except those started by
-     * {@param exceptPid}
-     *
-     * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
-     */
-    private void disconnectBluetoothSco(int exceptPid) {
-        synchronized(mScoClients) {
-            checkScoAudioState();
-            if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
-                return;
-            }
-            clearAllScoClients(exceptPid, true);
-        }
-    }
-
-    private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
-            BluetoothDevice device, int scoAudioMode) {
-        switch (scoAudioMode) {
-            case SCO_MODE_RAW:
-                return bluetoothHeadset.disconnectAudio();
-            case SCO_MODE_VIRTUAL_CALL:
-                return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
-            case SCO_MODE_VR:
-                return bluetoothHeadset.stopVoiceRecognition(device);
-            default:
-                return false;
-        }
-    }
-
-    private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
-            BluetoothDevice device, int scoAudioMode) {
-        switch (scoAudioMode) {
-            case SCO_MODE_RAW:
-                return bluetoothHeadset.connectAudio();
-            case SCO_MODE_VIRTUAL_CALL:
-                return bluetoothHeadset.startScoUsingVirtualVoiceCall();
-            case SCO_MODE_VR:
-                return bluetoothHeadset.startVoiceRecognition(device);
-            default:
-                return false;
-        }
-    }
-
-    private void resetBluetoothSco() {
-        synchronized(mScoClients) {
-            clearAllScoClients(0, false);
-            mScoAudioState = SCO_STATE_INACTIVE;
-            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-        }
-        AudioSystem.setParameters("A2dpSuspended=false");
-        setBluetoothScoOnInt(false, "resetBluetoothSco");
-    }
-
-    private void broadcastScoConnectionState(int state) {
-        sendMsg(mAudioHandler, MSG_BROADCAST_BT_CONNECTION_STATE,
-                SENDMSG_QUEUE, state, 0, null, 0);
-    }
-
-    private void onBroadcastScoConnectionState(int state) {
-        if (state != mScoConnectionState) {
-            Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
-            newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
-            newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
-                    mScoConnectionState);
-            sendStickyBroadcastToAll(newIntent);
-            mScoConnectionState = state;
-        }
-    }
-
-    private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
-        if (btDevice == null) {
-            return true;
-        }
-        String address = btDevice.getAddress();
-        BluetoothClass btClass = btDevice.getBluetoothClass();
-        int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
-        int[] outDeviceTypes = {
-            AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
-            AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
-            AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT
-        };
-        if (btClass != null) {
-            switch (btClass.getDeviceClass()) {
-                case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
-                case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
-                    outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET };
-                    break;
-                case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
-                    outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT };
-                    break;
-            }
-        }
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-        String btDeviceName =  btDevice.getName();
-        boolean result = false;
-        if (isActive) {
-            result |= handleDeviceConnection(isActive, outDeviceTypes[0], address, btDeviceName);
-        } else {
-            for (int outDeviceType : outDeviceTypes) {
-                result |= handleDeviceConnection(isActive, outDeviceType, address, btDeviceName);
-            }
-        }
-        // handleDeviceConnection() && result to make sure the method get executed
-        result = handleDeviceConnection(isActive, inDevice, address, btDeviceName) && result;
-        return result;
-    }
-
-    private void setBtScoActiveDevice(BluetoothDevice btDevice) {
-        synchronized (mScoClients) {
-            Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
-            final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
-            if (!Objects.equals(btDevice, previousActiveDevice)) {
-                if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
-                    Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
-                            + previousActiveDevice);
-                }
-                if (!handleBtScoActiveDeviceChange(btDevice, true)) {
-                    Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
-                    // set mBluetoothHeadsetDevice to null when failing to add new device
-                    btDevice = null;
-                }
-                mBluetoothHeadsetDevice = btDevice;
-                if (mBluetoothHeadsetDevice == null) {
-                    resetBluetoothSco();
-                }
-            }
-        }
-    }
-
-    private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
-        new BluetoothProfile.ServiceListener() {
-        public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            BluetoothDevice btDevice;
-            List<BluetoothDevice> deviceList;
-            switch(profile) {
-            case BluetoothProfile.A2DP:
-                synchronized (mConnectedDevices) {
-                    synchronized (mA2dpAvrcpLock) {
-                        mA2dp = (BluetoothA2dp) proxy;
-                        deviceList = mA2dp.getConnectedDevices();
-                        if (deviceList.size() > 0) {
-                            btDevice = deviceList.get(0);
-                            int state = mA2dp.getConnectionState(btDevice);
-                            int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
-                            int delay = checkSendBecomingNoisyIntent(
-                                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, intState,
-                                    AudioSystem.DEVICE_NONE);
-                            final String addr = btDevice == null ? "null" : btDevice.getAddress();
-                            mDeviceLogger.log(new AudioEventLogger.StringEvent(
-                                    "A2DP service connected: device addr=" + addr
-                                    + " state=" + state));
-                            queueMsgUnderWakeLock(mAudioHandler,
-                                    MSG_SET_A2DP_SINK_CONNECTION_STATE,
-                                    state,
-                                    0 /* arg2 unused */,
-                                    new BluetoothA2dpDeviceInfo(btDevice),
-                                    delay);
-                        }
-                    }
-                }
-                break;
-
-            case BluetoothProfile.A2DP_SINK:
-                deviceList = proxy.getConnectedDevices();
-                if (deviceList.size() > 0) {
-                    btDevice = deviceList.get(0);
-                    synchronized (mConnectedDevices) {
-                        int state = proxy.getConnectionState(btDevice);
-                        queueMsgUnderWakeLock(mAudioHandler,
-                                MSG_SET_A2DP_SRC_CONNECTION_STATE,
-                                state,
-                                0 /* arg2 unused */,
-                                new BluetoothA2dpDeviceInfo(btDevice),
-                                0 /* delay */);
-                    }
-                }
-                break;
-
-            case BluetoothProfile.HEADSET:
-                synchronized (mScoClients) {
-                    // Discard timeout message
-                    mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
-                    mBluetoothHeadset = (BluetoothHeadset) proxy;
-                    setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice());
-                    // Refresh SCO audio state
-                    checkScoAudioState();
-                    // Continue pending action if any
-                    if (mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
-                            mScoAudioState == SCO_STATE_DEACTIVATE_REQ) {
-                        boolean status = false;
-                        if (mBluetoothHeadsetDevice != null) {
-                            switch (mScoAudioState) {
-                                case SCO_STATE_ACTIVATE_REQ:
-                                    status = connectBluetoothScoAudioHelper(mBluetoothHeadset,
-                                            mBluetoothHeadsetDevice, mScoAudioMode);
-                                    if (status) {
-                                        mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                                    }
-                                    break;
-                                case SCO_STATE_DEACTIVATE_REQ:
-                                    status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
-                                            mBluetoothHeadsetDevice, mScoAudioMode);
-                                    if (status) {
-                                        mScoAudioState = SCO_STATE_DEACTIVATING;
-                                    }
-                                    break;
-                            }
-                        }
-                        if (!status) {
-                            mScoAudioState = SCO_STATE_INACTIVE;
-                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                        }
-                    }
-                }
-                break;
-
-            case BluetoothProfile.HEARING_AID:
-                synchronized (mConnectedDevices) {
-                    synchronized (mHearingAidLock) {
-                        mHearingAid = (BluetoothHearingAid) proxy;
-                        deviceList = mHearingAid.getConnectedDevices();
-                        if (deviceList.size() > 0) {
-                            btDevice = deviceList.get(0);
-                            int state = mHearingAid.getConnectionState(btDevice);
-                            int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
-                            int delay = checkSendBecomingNoisyIntent(
-                                    AudioSystem.DEVICE_OUT_HEARING_AID, intState,
-                                    AudioSystem.DEVICE_NONE);
-                            queueMsgUnderWakeLock(mAudioHandler,
-                                    MSG_SET_HEARING_AID_CONNECTION_STATE,
-                                    state,
-                                    0 /* arg2 unused */,
-                                    btDevice,
-                                    delay);
-                        }
-                    }
-                }
-
-                break;
-
-            default:
-                break;
-            }
-        }
-        public void onServiceDisconnected(int profile) {
-
-            switch (profile) {
-            case BluetoothProfile.A2DP:
-                disconnectA2dp();
-                break;
-
-            case BluetoothProfile.A2DP_SINK:
-                disconnectA2dpSink();
-                break;
-
-            case BluetoothProfile.HEADSET:
-                disconnectHeadset();
-                break;
-
-            case BluetoothProfile.HEARING_AID:
-                disconnectHearingAid();
-                break;
-
-            default:
-                break;
-            }
-        }
-    };
-
-    void disconnectAllBluetoothProfiles() {
-        disconnectA2dp();
-        disconnectA2dpSink();
-        disconnectHeadset();
-        disconnectHearingAid();
-    }
-
-    void disconnectA2dp() {
-        synchronized (mConnectedDevices) {
-            synchronized (mA2dpAvrcpLock) {
-                ArraySet<String> toRemove = null;
-                // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
-                for (int i = 0; i < mConnectedDevices.size(); i++) {
-                    DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
-                    if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
-                        toRemove = toRemove != null ? toRemove : new ArraySet<String>();
-                        toRemove.add(deviceSpec.mDeviceAddress);
-                    }
-                }
-                if (toRemove != null) {
-                    int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                            0, AudioSystem.DEVICE_NONE);
-                    for (int i = 0; i < toRemove.size(); i++) {
-                        makeA2dpDeviceUnavailableLater(toRemove.valueAt(i), delay);
-                    }
-                }
-            }
-        }
-    }
-
-    void disconnectA2dpSink() {
-        synchronized (mConnectedDevices) {
-            ArraySet<String> toRemove = null;
-            // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
-            for(int i = 0; i < mConnectedDevices.size(); i++) {
-                DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
-                if (deviceSpec.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
-                    toRemove = toRemove != null ? toRemove : new ArraySet<String>();
-                    toRemove.add(deviceSpec.mDeviceAddress);
-                }
-            }
-            if (toRemove != null) {
-                for (int i = 0; i < toRemove.size(); i++) {
-                    makeA2dpSrcUnavailable(toRemove.valueAt(i));
-                }
-            }
-        }
-    }
-
-    void disconnectHeadset() {
-        synchronized (mScoClients) {
-            setBtScoActiveDevice(null);
-            mBluetoothHeadset = null;
-        }
-    }
-
-    void disconnectHearingAid() {
-        synchronized (mConnectedDevices) {
-            synchronized (mHearingAidLock) {
-                ArraySet<String> toRemove = null;
-                // Disconnect ALL DEVICE_OUT_HEARING_AID devices
-                for (int i = 0; i < mConnectedDevices.size(); i++) {
-                    DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
-                    if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
-                        toRemove = toRemove != null ? toRemove : new ArraySet<String>();
-                        toRemove.add(deviceSpec.mDeviceAddress);
-                    }
-                }
-                if (toRemove != null) {
-                    int delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID,
-                            0, AudioSystem.DEVICE_NONE);
-                    for (int i = 0; i < toRemove.size(); i++) {
-                        makeHearingAidDeviceUnavailable(toRemove.valueAt(i) /*, delay*/);
-                    }
-                }
-            }
-        }
+    /*package*/ ContentResolver getContentResolver() {
+        return mContentResolver;
     }
 
     private void onCheckMusicActive(String caller) {
@@ -4173,8 +3312,8 @@
                             caller,
                             MUSIC_ACTIVE_POLL_PERIOD_MS);
                     int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
-                    if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) &&
-                            (index > safeMediaVolumeIndex(device))) {
+                    if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
+                            && (index > safeMediaVolumeIndex(device))) {
                         // Approximate cumulative active music time
                         mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
                         if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
@@ -4192,8 +3331,7 @@
         mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
     }
 
-    private int getSafeUsbMediaVolumeIndex()
-    {
+    private int getSafeUsbMediaVolumeIndex() {
         // determine UI volume index corresponding to the wanted safe gain in dBFS
         int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
         int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
@@ -4201,7 +3339,7 @@
         mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f;
 
-        while (Math.abs(max-min) > 1) {
+        while (Math.abs(max - min) > 1) {
             int index = (max + min) / 2;
             float gainDB = AudioSystem.getStreamVolumeDB(
                     AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET);
@@ -4518,7 +3656,7 @@
                 || adjust == AudioManager.ADJUST_TOGGLE_MUTE;
     }
 
-    private boolean isInCommunication() {
+    /*package*/ boolean isInCommunication() {
         boolean IsInCall = false;
 
         TelecomManager telecomManager =
@@ -4671,25 +3809,9 @@
         } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
             return;
         }
-        synchronized (mLastDeviceConnectMsgTime) {
-            long time = SystemClock.uptimeMillis() + delay;
 
-            if (msg == MSG_SET_A2DP_SRC_CONNECTION_STATE ||
-                msg == MSG_SET_A2DP_SINK_CONNECTION_STATE ||
-                msg == MSG_SET_HEARING_AID_CONNECTION_STATE ||
-                msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE ||
-                msg == MSG_A2DP_DEVICE_CONFIG_CHANGE ||
-                msg == MSG_A2DP_ACTIVE_DEVICE_CHANGE ||
-                msg == MSG_BTA2DP_DOCK_TIMEOUT) {
-                if (mLastDeviceConnectMsgTime >= time) {
-                  // add a little delay to make sure messages are ordered as expected
-                  time = mLastDeviceConnectMsgTime + 30;
-                }
-                mLastDeviceConnectMsgTime = time;
-            }
-
-            handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
-        }
+        final long time = SystemClock.uptimeMillis() + delay;
+        handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
     }
 
     boolean checkAudioSettingsPermission(String method) {
@@ -4704,7 +3826,7 @@
         return false;
     }
 
-    private int getDeviceForStream(int stream) {
+    /*package*/ int getDeviceForStream(int stream) {
         int device = getDevicesForStream(stream);
         if ((device & (device - 1)) != 0) {
             // Multiple device selection is either:
@@ -4749,160 +3871,94 @@
         }
     }
 
-    private int getA2dpCodec(BluetoothDevice device) {
-        synchronized (mA2dpAvrcpLock) {
-            if (mA2dp == null) {
-                return AudioSystem.AUDIO_FORMAT_DEFAULT;
-            }
-            BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
-            if (btCodecStatus == null) {
-                return AudioSystem.AUDIO_FORMAT_DEFAULT;
-            }
-            BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
-            if (btCodecConfig == null) {
-                return AudioSystem.AUDIO_FORMAT_DEFAULT;
-            }
-            return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
-        }
+
+    /*package*/ void observeDevicesForAllStreams() {
+        observeDevicesForStreams(-1);
     }
 
-    /*
-     * A class just for packaging up a set of connection parameters.
+    /*package*/ static final int CONNECTION_STATE_DISCONNECTED = 0;
+    /*package*/ static final int CONNECTION_STATE_CONNECTED = 1;
+    /**
+     * The states that can be used with AudioService.setWiredDeviceConnectionState()
+     * Attention: those values differ from those in BluetoothProfile, follow annotations to
+     * distinguish between @ConnectionState and @BtProfileConnectionState
      */
-    class WiredDeviceConnectionState {
-        public final int mType;
-        public final int mState;
-        public final String mAddress;
-        public final String mName;
-        public final String mCaller;
+    @IntDef({
+            CONNECTION_STATE_DISCONNECTED,
+            CONNECTION_STATE_CONNECTED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ConnectionState {}
 
-        public WiredDeviceConnectionState(int type, int state, String address, String name,
-                String caller) {
-            mType = type;
-            mState = state;
-            mAddress = address;
-            mName = name;
-            mCaller = caller;
-        }
-    }
-
-    public void setWiredDeviceConnectionState(int type, int state, String address, String name,
+    /**
+     * see AudioManager.setWiredDeviceConnectionState()
+     */
+    public void setWiredDeviceConnectionState(int type,
+            @ConnectionState int state, String address, String name,
             String caller) {
-        synchronized (mConnectedDevices) {
-            if (DEBUG_DEVICES) {
-                Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:"
-                        + address + ")");
-            }
-            int delay = checkSendBecomingNoisyIntent(type, state, AudioSystem.DEVICE_NONE);
-            queueMsgUnderWakeLock(mAudioHandler,
-                    MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
-                    0 /* arg1 unused */,
-                    0 /* arg2 unused */,
-                    new WiredDeviceConnectionState(type, state, address, name, caller),
-                    delay);
+        if (state != CONNECTION_STATE_CONNECTED
+                && state != CONNECTION_STATE_DISCONNECTED) {
+            throw new IllegalArgumentException("Invalid state " + state);
         }
+        mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller);
     }
 
+    /**
+     * @hide
+     * The states that can be used with AudioService.setBluetoothHearingAidDeviceConnectionState()
+     * and AudioService.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent()
+     */
+    @IntDef({
+            BluetoothProfile.STATE_DISCONNECTED,
+            BluetoothProfile.STATE_CONNECTED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BtProfileConnectionState {}
+
     public int setBluetoothHearingAidDeviceConnectionState(
-            BluetoothDevice device, int state, boolean suppressNoisyIntent,
-            int musicDevice)
+            @NonNull BluetoothDevice device, @BtProfileConnectionState int state,
+            boolean suppressNoisyIntent, int musicDevice)
     {
-        int delay;
-        mDeviceLogger.log((new AudioEventLogger.StringEvent(
-                "setHearingAidDeviceConnectionState state=" + state
-                            + " addr=" + device.getAddress()
-                            + " supprNoisy=" + suppressNoisyIntent)).printLog(TAG));
-        synchronized (mConnectedDevices) {
-            if (!suppressNoisyIntent) {
-                int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
-                delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_HEARING_AID,
-                        intState, musicDevice);
-            } else {
-                delay = 0;
-            }
-            queueMsgUnderWakeLock(mAudioHandler,
-                    MSG_SET_HEARING_AID_CONNECTION_STATE,
-                    state,
-                    0 /* arg2 unused */,
-                    device,
-                    delay);
+        if (device == null) {
+            throw new IllegalArgumentException("Illegal null device");
         }
-        return delay;
+        if (state != BluetoothProfile.STATE_CONNECTED
+                && state != BluetoothProfile.STATE_DISCONNECTED) {
+            throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
+                    + " (dis)connection, got " + state);
+        }
+        return mDeviceBroker.setBluetoothHearingAidDeviceConnectionState(
+                device, state, suppressNoisyIntent, musicDevice, "AudioService");
     }
 
-    public int setBluetoothA2dpDeviceConnectionState(
-            BluetoothDevice device, int state, int profile)
-    {
-        return setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                device, state, profile, false /* suppressNoisyIntent */,
-                -1 /* a2dpVolume */);
+    /**
+     * See AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent()
+     */
+    public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+            int profile, boolean suppressNoisyIntent, int a2dpVolume) {
+        if (device == null) {
+            throw new IllegalArgumentException("Illegal null device");
+        }
+        if (state != BluetoothProfile.STATE_CONNECTED
+                && state != BluetoothProfile.STATE_DISCONNECTED) {
+            throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
+                    + " (dis)connection, got " + state);
+        }
+        return mDeviceBroker.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(device, state,
+                profile, suppressNoisyIntent, a2dpVolume);
     }
 
-    public int setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(BluetoothDevice device,
-            int state, int profile, boolean suppressNoisyIntent, int a2dpVolume)
-    {
-        mDeviceLogger.log((new AudioEventLogger.StringEvent(
-                "setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent state=" + state
-                // only querying address as this is the only readily available field on the device
-                + " addr=" + device.getAddress()
-                + " prof=" + profile + " supprNoisy=" + suppressNoisyIntent
-                + " vol=" + a2dpVolume)).printLog(TAG));
-        if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, device)) {
-            mDeviceLogger.log(new AudioEventLogger.StringEvent("A2DP connection state ignored"));
-            return 0;
-        }
-        return setBluetoothA2dpDeviceConnectionStateInt(
-                device, state, profile, suppressNoisyIntent,
-                AudioSystem.DEVICE_NONE, a2dpVolume);
-    }
-
-    public int setBluetoothA2dpDeviceConnectionStateInt(
-            BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent,
-            int musicDevice, int a2dpVolume)
-    {
-        int delay;
-        if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
-            throw new IllegalArgumentException("invalid profile " + profile);
-        }
-        synchronized (mConnectedDevices) {
-            if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
-                int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
-                delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                        intState, musicDevice);
-            } else {
-                delay = 0;
-            }
-
-            int a2dpCodec = getA2dpCodec(device);
-
-            if (DEBUG_DEVICES) {
-                Log.d(TAG, "setBluetoothA2dpDeviceConnectionStateInt device: " + device
-                        + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec
-                        + " suppressNoisyIntent: " + suppressNoisyIntent);
-            }
-
-            queueMsgUnderWakeLock(mAudioHandler,
-                    (profile == BluetoothProfile.A2DP ?
-                        MSG_SET_A2DP_SINK_CONNECTION_STATE : MSG_SET_A2DP_SRC_CONNECTION_STATE),
-                    state,
-                    0, /* arg2 unused */
-                    new BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec),
-                    delay);
-        }
-        return delay;
-    }
-
+    /**
+     * See AudioManager.handleBluetoothA2dpDeviceConfigChange()
+     * @param device
+     */
     public void handleBluetoothA2dpDeviceConfigChange(BluetoothDevice device)
     {
-        synchronized (mConnectedDevices) {
-            int a2dpCodec = getA2dpCodec(device);
-            queueMsgUnderWakeLock(mAudioHandler,
-                    MSG_A2DP_DEVICE_CONFIG_CHANGE,
-                    0 /* arg1 unused */,
-                    0 /* arg2 unused */,
-                    new BluetoothA2dpDeviceInfo(device, -1, a2dpCodec),
-                    0 /* delay */);
+        if (device == null) {
+            throw new IllegalArgumentException("Illegal null device");
         }
+        mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device);
     }
 
     /**
@@ -4912,52 +3968,18 @@
     public int handleBluetoothA2dpActiveDeviceChange(
             BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent,
             int a2dpVolume) {
+        if (device == null) {
+            throw new IllegalArgumentException("Illegal null device");
+        }
         if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
             throw new IllegalArgumentException("invalid profile " + profile);
         }
-
-        synchronized (mConnectedDevices) {
-            if (state == BluetoothA2dp.STATE_CONNECTED) {
-                for (int i = 0; i < mConnectedDevices.size(); i++) {
-                    DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
-                    if (deviceSpec.mDeviceType != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
-                        continue;
-                    }
-                    // If A2DP device exists, this is either an active device change or
-                    // device config change
-                    String existingDevicekey = mConnectedDevices.keyAt(i);
-                    String deviceName = device.getName();
-                    String address = device.getAddress();
-                    String newDeviceKey = makeDeviceListKey(
-                            AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
-                    int a2dpCodec = getA2dpCodec(device);
-                    // Device not equal to existing device, active device change
-                    if (!TextUtils.equals(existingDevicekey, newDeviceKey)) {
-                        mConnectedDevices.remove(existingDevicekey);
-                        mConnectedDevices.put(newDeviceKey, new DeviceListSpec(
-                                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName,
-                                address, a2dpCodec));
-                        queueMsgUnderWakeLock(mAudioHandler,
-                                MSG_A2DP_ACTIVE_DEVICE_CHANGE,
-                                0,
-                                0,
-                                new BluetoothA2dpDeviceInfo(
-                                        device, a2dpVolume, a2dpCodec),
-                                0 /* delay */);
-                        return 0;
-                    } else {
-                        // Device config change for existing device
-                        handleBluetoothA2dpDeviceConfigChange(device);
-                        return 0;
-                    }
-                }
-            }
+        if (state != BluetoothProfile.STATE_CONNECTED
+                && state != BluetoothProfile.STATE_DISCONNECTED) {
+            throw new IllegalArgumentException("Invalid state " + state);
         }
-
-        // New device connection or a device disconnect
-        return setBluetoothA2dpDeviceConnectionStateInt(
-                    device, state, profile, suppressNoisyIntent,
-                    AudioSystem.DEVICE_NONE, a2dpVolume);
+        return mDeviceBroker.handleBluetoothA2dpActiveDeviceChange(device, state, profile,
+                suppressNoisyIntent, a2dpVolume);
     }
 
     private static final int DEVICE_MEDIA_UNMUTED_ON_PLUG =
@@ -4967,24 +3989,27 @@
             AudioSystem.DEVICE_OUT_ALL_USB |
             AudioSystem.DEVICE_OUT_HDMI;
 
+    /*package*/ void postAccessoryPlugMediaUnmute(int newDevice) {
+        sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
+                newDevice, 0, null, 0);
+    }
+
     private void onAccessoryPlugMediaUnmute(int newDevice) {
         if (DEBUG_VOL) {
             Log.i(TAG, String.format("onAccessoryPlugMediaUnmute newDevice=%d [%s]",
                     newDevice, AudioSystem.getOutputDeviceName(newDevice)));
         }
-        synchronized (mConnectedDevices) {
-            if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
-                    && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
-                    && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
-                    && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
-                    && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0)
-            {
-                if (DEBUG_VOL) {
-                    Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
-                            newDevice, AudioSystem.getOutputDeviceName(newDevice)));
-                }
-                mStreamStates[AudioSystem.STREAM_MUSIC].mute(false);
+
+        if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+                && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
+                && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
+                && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
+                && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) {
+            if (DEBUG_VOL) {
+                Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
+                        newDevice, AudioSystem.getOutputDeviceName(newDevice)));
             }
+            mStreamStates[AudioSystem.STREAM_MUSIC].mute(false);
         }
     }
 
@@ -4994,7 +4019,7 @@
 
     // NOTE: Locking order for synchronized objects related to volume or ringer mode management:
     //  1 mScoclient OR mSafeMediaVolumeState
-    //  2   mSetModeDeathHandlers
+    //  2   mSetModeLock
     //  3     mSettingsLock
     //  4       VolumeStreamState.class
     public class VolumeStreamState {
@@ -5138,11 +4163,11 @@
         }
 
         // must be called while synchronized VolumeStreamState.class
-        public void applyDeviceVolume_syncVSS(int device) {
+        /*package*/ void applyDeviceVolume_syncVSS(int device, boolean isAvrcpAbsVolSupported) {
             int index;
             if (mIsMuted) {
                 index = 0;
-            } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) {
+            } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && isAvrcpAbsVolSupported) {
                 index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
             } else if ((device & mFullVolumeDevices) != 0) {
                 index = (mIndexMax + 5)/10;
@@ -5151,10 +4176,11 @@
             } else {
                 index = (getIndex(device) + 5)/10;
             }
-            AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
+            AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
         }
 
         public void applyAllVolumes() {
+            final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
             synchronized (VolumeStreamState.class) {
                 // apply device specific volumes first
                 int index;
@@ -5164,7 +4190,7 @@
                         if (mIsMuted) {
                             index = 0;
                         } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
-                                mAvrcpAbsVolSupported) {
+                                isAvrcpAbsVolSupported) {
                             index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
                         } else if ((device & mFullVolumeDevices) != 0) {
                             index = (mIndexMax + 5)/10;
@@ -5173,7 +4199,7 @@
                         } else {
                             index = (mIndexMap.valueAt(i) + 5)/10;
                         }
-                        AudioSystem.setStreamVolumeIndex(mStreamType, index, device);
+                        AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
                     }
                 }
                 // apply default volume last: by convention , default device volume will be used
@@ -5183,7 +4209,7 @@
                 } else {
                     index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10;
                 }
-                AudioSystem.setStreamVolumeIndex(
+                AudioSystem.setStreamVolumeIndexAS(
                         mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT);
             }
         }
@@ -5373,6 +4399,7 @@
         }
 
         public void checkFixedVolumeDevices() {
+            final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
             synchronized (VolumeStreamState.class) {
                 // ignore settings for fixed volume devices: volume should always be at max or 0
                 if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) {
@@ -5383,7 +4410,7 @@
                                 || (((device & mFixedVolumeDevices) != 0) && index != 0)) {
                             mIndexMap.put(device, mIndexMax);
                         }
-                        applyDeviceVolume_syncVSS(device);
+                        applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
                     }
                 }
             }
@@ -5465,11 +4492,13 @@
         }
     }
 
-    private void setDeviceVolume(VolumeStreamState streamState, int device) {
+    /*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {
+
+        final boolean isAvrcpAbsVolSupported = mDeviceBroker.isAvrcpAbsoluteVolumeSupported();
 
         synchronized (VolumeStreamState.class) {
             // Apply volume
-            streamState.applyDeviceVolume_syncVSS(device);
+            streamState.applyDeviceVolume_syncVSS(device, isAvrcpAbsVolSupported);
 
             // Apply change to all streams using this one as alias
             int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -5479,11 +4508,13 @@
                     // Make sure volume is also maxed out on A2DP device for aliased stream
                     // that may have a different device selected
                     int streamDevice = getDeviceForStream(streamType);
-                    if ((device != streamDevice) && mAvrcpAbsVolSupported &&
-                            ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
-                        mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
+                    if ((device != streamDevice) && isAvrcpAbsVolSupported
+                            && ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
+                        mStreamStates[streamType].applyDeviceVolume_syncVSS(device,
+                                isAvrcpAbsVolSupported);
                     }
-                    mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
+                    mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice,
+                            isAvrcpAbsVolSupported);
                 }
             }
         }
@@ -5762,12 +4793,6 @@
             }
         }
 
-        private void setForceUse(int usage, int config, String eventSource) {
-            synchronized (mConnectedDevices) {
-                setForceUseInt_SyncDevices(usage, config, eventSource);
-            }
-        }
-
         private void onPersistSafeVolumeState(int state) {
             Settings.Global.putInt(mContentResolver,
                     Settings.Global.AUDIO_SAFE_VOLUME_STATE,
@@ -5834,56 +4859,20 @@
                     onPlaySoundEffect(msg.arg1, msg.arg2);
                     break;
 
-                case MSG_BTA2DP_DOCK_TIMEOUT:
-                    // msg.obj  == address of BTA2DP device
-                    synchronized (mConnectedDevices) {
-                        makeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
-                    }
-                    mAudioEventWakeLock.release();
-                    break;
-
                 case MSG_SET_FORCE_USE:
-                case MSG_SET_FORCE_BT_A2DP_USE:
-                    setForceUse(msg.arg1, msg.arg2, (String) msg.obj);
-                    break;
-
-                case MSG_BT_HEADSET_CNCT_FAILED:
-                    resetBluetoothSco();
-                    break;
-
-                case MSG_SET_WIRED_DEVICE_CONNECTION_STATE:
-                    {   WiredDeviceConnectionState connectState =
-                            (WiredDeviceConnectionState)msg.obj;
-                        mDeviceLogger.log(new WiredDevConnectEvent(connectState));
-                        onSetWiredDeviceConnectionState(connectState.mType, connectState.mState,
-                                connectState.mAddress, connectState.mName, connectState.mCaller);
-                        mAudioEventWakeLock.release();
+                {
+                    final String eventSource = (String) msg.obj;
+                    final int useCase = msg.arg1;
+                    final int config = msg.arg2;
+                    if (useCase == AudioSystem.FOR_MEDIA) {
+                        Log.wtf(TAG, "Invalid force use FOR_MEDIA in AudioService from "
+                                + eventSource);
+                        break;
                     }
-                    break;
-
-                case MSG_SET_A2DP_SRC_CONNECTION_STATE:
-                    onSetA2dpSourceConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
-                    mAudioEventWakeLock.release();
-                    break;
-
-                case MSG_SET_A2DP_SINK_CONNECTION_STATE:
-                    onSetA2dpSinkConnectionState((BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
-                    mAudioEventWakeLock.release();
-                    break;
-
-                case MSG_SET_HEARING_AID_CONNECTION_STATE:
-                    onSetHearingAidConnectionState((BluetoothDevice)msg.obj, msg.arg1);
-                    mAudioEventWakeLock.release();
-                    break;
-
-                case MSG_A2DP_DEVICE_CONFIG_CHANGE:
-                    onBluetoothA2dpDeviceConfigChange((BluetoothA2dpDeviceInfo) msg.obj);
-                    mAudioEventWakeLock.release();
-                    break;
-
-                case MSG_A2DP_ACTIVE_DEVICE_CHANGE:
-                    onBluetoothA2dpActiveDeviceChange((BluetoothA2dpDeviceInfo) msg.obj);
-                    mAudioEventWakeLock.release();
+                    sForceUseLogger.log(
+                            new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
+                    AudioSystem.setForceUse(useCase, config);
+                }
                     break;
 
                 case MSG_DISABLE_AUDIO_FOR_UID:
@@ -5892,35 +4881,10 @@
                     mAudioEventWakeLock.release();
                     break;
 
-                case MSG_REPORT_NEW_ROUTES: {
-                    int N = mRoutesObservers.beginBroadcast();
-                    if (N > 0) {
-                        AudioRoutesInfo routes;
-                        synchronized (mCurAudioRoutes) {
-                            routes = new AudioRoutesInfo(mCurAudioRoutes);
-                        }
-                        while (N > 0) {
-                            N--;
-                            IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(N);
-                            try {
-                                obs.dispatchAudioRoutesChanged(routes);
-                            } catch (RemoteException e) {
-                            }
-                        }
-                    }
-                    mRoutesObservers.finishBroadcast();
-                    observeDevicesForStreams(-1);
-                    break;
-                }
-
                 case MSG_CHECK_MUSIC_ACTIVE:
                     onCheckMusicActive((String) msg.obj);
                     break;
 
-                case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
-                    onSendBecomingNoisyIntent();
-                    break;
-
                 case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED:
                 case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
                     onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED),
@@ -5930,10 +4894,6 @@
                     onPersistSafeVolumeState(msg.arg1);
                     break;
 
-                case MSG_BROADCAST_BT_CONNECTION_STATE:
-                    onBroadcastScoConnectionState(msg.arg1);
-                    break;
-
                 case MSG_SYSTEM_READY:
                     onSystemReady();
                     break;
@@ -6033,20 +4993,7 @@
             if (mEncodedSurroundMode != newSurroundMode) {
                 // Send to AudioPolicyManager
                 sendEncodedSurroundMode(newSurroundMode, "SettingsObserver");
-                synchronized(mConnectedDevices) {
-                    // Is HDMI connected?
-                    String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
-                    DeviceListSpec deviceSpec = mConnectedDevices.get(key);
-                    if (deviceSpec != null) {
-                        // Toggle HDMI to retrigger broadcast with proper formats.
-                        setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
-                                AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
-                                "android"); // disconnect
-                        setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
-                                AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
-                                "android"); // reconnect
-                    }
-                }
+                mDeviceBroker.toggleHdmiIfConnected_Async();
                 mEncodedSurroundMode = newSurroundMode;
                 mSurroundModeChanged = true;
             } else {
@@ -6055,515 +5002,18 @@
         }
     }
 
-    // must be called synchronized on mConnectedDevices
-    private void makeA2dpDeviceAvailable(
-            String address, String name, String eventSource, int a2dpCodec) {
-        // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
-        // audio policy manager
-        VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
-        setBluetoothA2dpOnInt(true, eventSource);
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
-        // Reset A2DP suspend state each time a new sink is connected
-        AudioSystem.setParameters("A2dpSuspended=false");
-        mConnectedDevices.put(
-                makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
-                new DeviceListSpec(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
-                                   address, a2dpCodec));
-        sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
-                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, null, 0);
-        setCurrentAudioRouteNameIfPossible(name);
-    }
-
-    private void onSendBecomingNoisyIntent() {
-        mDeviceLogger.log((new AudioEventLogger.StringEvent(
-                "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
-        sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
-        if (address == null) {
-            return;
-        }
-        synchronized (mA2dpAvrcpLock) {
-            mAvrcpAbsVolSupported = false;
-        }
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
-        mConnectedDevices.remove(
-                makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
-        // Remove A2DP routes as well
-        setCurrentAudioRouteNameIfPossible(null);
-        if (mDockAddress == address) {
-            mDockAddress = null;
-        }
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
-        // prevent any activity on the A2DP audio output to avoid unwanted
-        // reconnection of the sink.
-        AudioSystem.setParameters("A2dpSuspended=true");
-        // Retrieve deviceSpec before removing device
-        String deviceKey = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
-        DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey);
-        int a2dpCodec = deviceSpec != null ? deviceSpec.mDeviceCodecFormat :
-                                AudioSystem.AUDIO_FORMAT_DEFAULT;
-        // the device will be made unavailable later, so consider it disconnected right away
-        mConnectedDevices.remove(deviceKey);
-        // send the delayed message to make the device unavailable later
-        queueMsgUnderWakeLock(mAudioHandler,
-                MSG_BTA2DP_DOCK_TIMEOUT,
-                a2dpCodec,
-                0,
-                address,
-                delayMs);
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void makeA2dpSrcAvailable(String address) {
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
-                AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
-                AudioSystem.AUDIO_FORMAT_DEFAULT);
-        mConnectedDevices.put(
-                makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
-                new DeviceListSpec(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
-                                   address, AudioSystem.AUDIO_FORMAT_DEFAULT));
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void makeA2dpSrcUnavailable(String address) {
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
-                AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
-                AudioSystem.AUDIO_FORMAT_DEFAULT);
-        mConnectedDevices.remove(
-                makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
-    }
-
-    private void setHearingAidVolume(int index, int streamType) {
-        synchronized (mHearingAidLock) {
-            if (mHearingAid != null) {
-                //hearing aid expect volume value in range -128dB to 0dB
-                int gainDB = (int)AudioSystem.getStreamVolumeDB(streamType, index/10,
-                        AudioSystem.DEVICE_OUT_HEARING_AID);
-                if (gainDB < BT_HEARING_AID_GAIN_MIN)
-                    gainDB = BT_HEARING_AID_GAIN_MIN;
-                mHearingAid.setVolume(gainDB);
-            }
-        }
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void makeHearingAidDeviceAvailable(String address, String name, String eventSource) {
-        int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(AudioSystem.DEVICE_OUT_HEARING_AID);
-        setHearingAidVolume(index, AudioSystem.STREAM_MUSIC);
-
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
-                AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
-                AudioSystem.AUDIO_FORMAT_DEFAULT);
-        mConnectedDevices.put(
-                makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
-                new DeviceListSpec(AudioSystem.DEVICE_OUT_HEARING_AID, name,
-                                   address, AudioSystem.AUDIO_FORMAT_DEFAULT));
-        sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
-                AudioSystem.DEVICE_OUT_HEARING_AID, 0, null, 0);
-        sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
-                            AudioSystem.DEVICE_OUT_HEARING_AID, 0,
-                            mStreamStates[AudioSystem.STREAM_MUSIC], 0);
-        setCurrentAudioRouteNameIfPossible(name);
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void makeHearingAidDeviceUnavailable(String address) {
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
-                AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
-                AudioSystem.AUDIO_FORMAT_DEFAULT);
-        mConnectedDevices.remove(
-                makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
-        // Remove Hearing Aid routes as well
-        setCurrentAudioRouteNameIfPossible(null);
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private void cancelA2dpDeviceTimeout() {
-        mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT);
-    }
-
-    // must be called synchronized on mConnectedDevices
-    private boolean hasScheduledA2dpDockTimeout() {
-        return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT);
-    }
-
-    private void onSetA2dpSinkConnectionState(BluetoothA2dpDeviceInfo btInfo, int state)
-    {
-        if (btInfo == null) {
-            return;
-        }
-
-        BluetoothDevice btDevice = btInfo.getBtDevice();
-        int a2dpVolume = btInfo.getVolume();
-        int a2dpCodec = btInfo.getCodec();
-
-        if (btDevice == null) {
-            return;
-        }
-        if (DEBUG_DEVICES) {
-            Log.d(TAG, "onSetA2dpSinkConnectionState btDevice= " + btDevice + " state= " + state
-                    + " is dock: " + btDevice.isBluetoothDock());
-        }
-        String address = btDevice.getAddress();
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-
-        synchronized (mConnectedDevices) {
-            final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                                                 btDevice.getAddress());
-            final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
-            boolean isConnected = deviceSpec != null;
-
-            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
-                if (btDevice.isBluetoothDock()) {
-                    if (state == BluetoothProfile.STATE_DISCONNECTED) {
-                        // introduction of a delay for transient disconnections of docks when
-                        // power is rapidly turned off/on, this message will be canceled if
-                        // we reconnect the dock under a preset delay
-                        makeA2dpDeviceUnavailableLater(address, BTA2DP_DOCK_TIMEOUT_MILLIS);
-                        // the next time isConnected is evaluated, it will be false for the dock
-                    }
-                } else {
-                    makeA2dpDeviceUnavailableNow(address, deviceSpec.mDeviceCodecFormat);
-                }
-            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                if (btDevice.isBluetoothDock()) {
-                    // this could be a reconnection after a transient disconnection
-                    cancelA2dpDeviceTimeout();
-                    mDockAddress = address;
-                } else {
-                    // this could be a connection of another A2DP device before the timeout of
-                    // a dock: cancel the dock timeout, and make the dock unavailable now
-                    if (hasScheduledA2dpDockTimeout() && mDockAddress != null) {
-                        cancelA2dpDeviceTimeout();
-                        makeA2dpDeviceUnavailableNow(mDockAddress,
-                                AudioSystem.AUDIO_FORMAT_DEFAULT);
-                    }
-                }
-                if (a2dpVolume != -1) {
-                    VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
-                    // Convert index to internal representation in VolumeStreamState
-                    a2dpVolume = a2dpVolume * 10;
-                    streamState.setIndex(a2dpVolume, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                            "onSetA2dpSinkConnectionState");
-                    setDeviceVolume(streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
-                }
-                makeA2dpDeviceAvailable(address, btDevice.getName(),
-                        "onSetA2dpSinkConnectionState", a2dpCodec);
-            }
-        }
-    }
-
-    private void onSetA2dpSourceConnectionState(BluetoothA2dpDeviceInfo btInfo, int state)
-    {
-        if (btInfo == null) {
-            return;
-        }
-        BluetoothDevice btDevice = btInfo.getBtDevice();
-
-        if (DEBUG_VOL) {
-            Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state=" + state);
-        }
-        if (btDevice == null) {
-            return;
-        }
-        String address = btDevice.getAddress();
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-
-        synchronized (mConnectedDevices) {
-            final String key = makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
-            final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
-            boolean isConnected = deviceSpec != null;
-
-            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
-                makeA2dpSrcUnavailable(address);
-            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                makeA2dpSrcAvailable(address);
-            }
-        }
-    }
-
-    private void onSetHearingAidConnectionState(BluetoothDevice btDevice, int state)
-    {
-        if (DEBUG_DEVICES) {
-            Log.d(TAG, "onSetHearingAidConnectionState btDevice=" + btDevice+", state=" + state);
-        }
-        if (btDevice == null) {
-            return;
-        }
-        String address = btDevice.getAddress();
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-
-        synchronized (mConnectedDevices) {
-            final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
-                                                 btDevice.getAddress());
-            final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
-            boolean isConnected = deviceSpec != null;
-
-            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
-                makeHearingAidDeviceUnavailable(address);
-            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                makeHearingAidDeviceAvailable(address, btDevice.getName(),
-                        "onSetHearingAidConnectionState");
-            }
-        }
-    }
-
-    private void setCurrentAudioRouteNameIfPossible(String name) {
-        synchronized (mCurAudioRoutes) {
-            if (!TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
-                if (name != null || !isCurrentDeviceConnected()) {
-                    mCurAudioRoutes.bluetoothName = name;
-                    sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
-                            SENDMSG_NOOP, 0, 0, null, 0);
-                }
-            }
-        }
-    }
-
-    private boolean isCurrentDeviceConnected() {
-        for (int i = 0; i < mConnectedDevices.size(); i++) {
-            DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i);
-            if (TextUtils.equals(deviceSpec.mDeviceName, mCurAudioRoutes.bluetoothName)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void onBluetoothA2dpDeviceConfigChange(BluetoothA2dpDeviceInfo btInfo)
-    {
-        if (btInfo == null) {
-            return;
-        }
-        BluetoothDevice btDevice = btInfo.getBtDevice();
-        int a2dpCodec = btInfo.getCodec();
-
-        if (btDevice == null) {
-            return;
-        }
-        if (DEBUG_DEVICES) {
-            Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
-        }
-        String address = btDevice.getAddress();
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-        mDeviceLogger.log(new AudioEventLogger.StringEvent(
-                "onBluetoothA2dpDeviceConfigChange addr=" + address));
-
-        int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
-        synchronized (mConnectedDevices) {
-            if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) {
-                mDeviceLogger.log(new AudioEventLogger.StringEvent(
-                        "A2dp config change ignored"));
-                return;
-            }
-            final String key = makeDeviceListKey(device, address);
-            final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
-            if (deviceSpec == null) {
-                return;
-            }
-            // Device is connected
-            if (deviceSpec.mDeviceCodecFormat != a2dpCodec) {
-                deviceSpec.mDeviceCodecFormat = a2dpCodec;
-                mConnectedDevices.replace(key, deviceSpec);
-            }
-            if (DEBUG_DEVICES) {
-                Log.d(TAG, "onBluetoothA2dpDeviceConfigChange: codec="
-                        + deviceSpec.mDeviceCodecFormat);
-            }
-            if (AudioSystem.handleDeviceConfigChange(device, address,
-                       btDevice.getName(), deviceSpec.mDeviceCodecFormat)
-                       != AudioSystem.AUDIO_STATUS_OK) {
-                int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
-                // force A2DP device disconnection in case of error so that AudioService state is
-                // consistent with audio policy manager state
-                setBluetoothA2dpDeviceConnectionStateInt(
-                        btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
-                        false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */);
-            }
-        }
-    }
-
-    /** message handler for MSG_A2DP_ACTIVE_DEVICE_CHANGE */
-    public void onBluetoothA2dpActiveDeviceChange(BluetoothA2dpDeviceInfo btInfo) {
-        if (btInfo == null) {
-            return;
-        }
-        BluetoothDevice btDevice = btInfo.getBtDevice();
-        int a2dpVolume = btInfo.getVolume();
-        int a2dpCodec = btInfo.getCodec();
-
-        if (btDevice == null) {
-            return;
-        }
-        if (DEBUG_DEVICES) {
-            Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
-        }
-        String address = btDevice.getAddress();
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-        mDeviceLogger.log(new AudioEventLogger.StringEvent(
-                "onBluetoothA2dpActiveDeviceChange addr=" + address));
-
-        int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
-        synchronized (mConnectedDevices) {
-            if (mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE, btDevice)) {
-                mDeviceLogger.log(new AudioEventLogger.StringEvent(
-                        "A2dp config change ignored"));
-                return;
-            }
-            final String key = makeDeviceListKey(device, address);
-            final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
-            if (deviceSpec == null) {
-                return;
-            }
-
-            // Device is connected
-            if (a2dpVolume != -1) {
-                VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
-                // Convert index to internal representation in VolumeStreamState
-                a2dpVolume = a2dpVolume * 10;
-                streamState.setIndex(a2dpVolume, device,
-                        "onBluetoothA2dpActiveDeviceChange");
-                setDeviceVolume(streamState, device);
-            }
-
-            if (AudioSystem.handleDeviceConfigChange(device, address,
-                    btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
-                int musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
-                // force A2DP device disconnection in case of error so that AudioService state is
-                // consistent with audio policy manager state
-                setBluetoothA2dpDeviceConnectionStateInt(
-                        btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
-                        false /* suppressNoisyIntent */, musicDevice, -1 /* a2dpVolume */);
-            }
-        }
-    }
-
     public void avrcpSupportsAbsoluteVolume(String address, boolean support) {
         // address is not used for now, but may be used when multiple a2dp devices are supported
-        synchronized (mA2dpAvrcpLock) {
-            mAvrcpAbsVolSupported = support;
-            sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
+        mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support);
+        sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
                     mStreamStates[AudioSystem.STREAM_MUSIC], 0);
-        }
-    }
-
-    private boolean handleDeviceConnection(boolean connect, int device, String address,
-            String deviceName) {
-        if (DEBUG_DEVICES) {
-            Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + Integer.toHexString(device)
-                    + " address:" + address + " name:" + deviceName + ")");
-        }
-        synchronized (mConnectedDevices) {
-            String deviceKey = makeDeviceListKey(device, address);
-            if (DEBUG_DEVICES) {
-                Slog.i(TAG, "deviceKey:" + deviceKey);
-            }
-            DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey);
-            boolean isConnected = deviceSpec != null;
-            if (DEBUG_DEVICES) {
-                Slog.i(TAG, "deviceSpec:" + deviceSpec + " is(already)Connected:" + isConnected);
-            }
-            if (connect && !isConnected) {
-                final int res = AudioSystem.setDeviceConnectionState(device,
-                        AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
-                        AudioSystem.AUDIO_FORMAT_DEFAULT);
-                if (res != AudioSystem.AUDIO_STATUS_OK) {
-                    Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device) +
-                            " due to command error " + res );
-                    return false;
-                }
-                mConnectedDevices.put(deviceKey, new DeviceListSpec(device,
-                                deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
-                sendMsg(mAudioHandler, MSG_ACCESSORY_PLUG_MEDIA_UNMUTE, SENDMSG_QUEUE,
-                        device, 0, null, 0);
-                return true;
-            } else if (!connect && isConnected) {
-                AudioSystem.setDeviceConnectionState(device,
-                        AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
-                        AudioSystem.AUDIO_FORMAT_DEFAULT);
-                // always remove even if disconnection failed
-                mConnectedDevices.remove(deviceKey);
-                return true;
-            }
-            Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + ", deviceSpec="
-                       + deviceSpec + ", connect=" + connect);
-        }
-        return false;
-    }
-
-    // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
-    // sent if:
-    // - none of these devices are connected anymore after one is disconnected AND
-    // - the device being disconnected is actually used for music.
-    // Access synchronized on mConnectedDevices
-    int mBecomingNoisyIntentDevices =
-            AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
-            AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI |
-            AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
-            AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE |
-            AudioSystem.DEVICE_OUT_HEARING_AID;
-
-    // must be called before removing the device from mConnectedDevices
-    // Called synchronized on mConnectedDevices
-    // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
-    // from AudioSystem
-    private int checkSendBecomingNoisyIntent(int device, int state, int musicDevice) {
-        int delay = 0;
-        if ((state == 0) && ((device & mBecomingNoisyIntentDevices) != 0)) {
-            int devices = 0;
-            for (int i = 0; i < mConnectedDevices.size(); i++) {
-                int dev = mConnectedDevices.valueAt(i).mDeviceType;
-                if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
-                        && ((dev & mBecomingNoisyIntentDevices) != 0)) {
-                    devices |= dev;
-                }
-            }
-            if (musicDevice == AudioSystem.DEVICE_NONE) {
-                musicDevice = getDeviceForStream(AudioSystem.STREAM_MUSIC);
-            }
-            // ignore condition on device being actually used for music when in communication
-            // because music routing is altered in this case.
-            // also checks whether media routing if affected by a dynamic policy
-            if (((device == musicDevice) || isInCommunication()) && (device == devices)
-                    && !hasMediaDynamicPolicy()) {
-                mAudioHandler.removeMessages(MSG_BROADCAST_AUDIO_BECOMING_NOISY);
-                sendMsg(mAudioHandler,
-                        MSG_BROADCAST_AUDIO_BECOMING_NOISY,
-                        SENDMSG_REPLACE,
-                        0,
-                        0,
-                        null,
-                        0);
-                delay = 1000;
-            }
-        }
-
-        return delay;
     }
 
     /**
      * @return true if there is currently a registered dynamic mixing policy that affects media
      */
-    private boolean hasMediaDynamicPolicy() {
+    /*package*/ boolean hasMediaDynamicPolicy() {
         synchronized (mAudioPolicies) {
             if (mAudioPolicies.isEmpty()) {
                 return false;
@@ -6578,213 +5028,25 @@
         }
     }
 
-    private void updateAudioRoutes(int device, int state)
-    {
-        int connType = 0;
-
-        if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
-            connType = AudioRoutesInfo.MAIN_HEADSET;
-        } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
-                   device == AudioSystem.DEVICE_OUT_LINE) {
-            connType = AudioRoutesInfo.MAIN_HEADPHONES;
-        } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
-                device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
-            connType = AudioRoutesInfo.MAIN_HDMI;
-        } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE||
-                device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
-            connType = AudioRoutesInfo.MAIN_USB;
-        }
-
-        synchronized (mCurAudioRoutes) {
-            if (connType != 0) {
-                int newConn = mCurAudioRoutes.mainType;
-                if (state != 0) {
-                    newConn |= connType;
-                } else {
-                    newConn &= ~connType;
-                }
-                if (newConn != mCurAudioRoutes.mainType) {
-                    mCurAudioRoutes.mainType = newConn;
-                    sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
-                            SENDMSG_NOOP, 0, 0, null, 0);
-                }
-            }
+    /*package*/ void checkMusicActive(int deviceType, String caller) {
+        if ((deviceType & mSafeMediaVolumeDevices) != 0) {
+            sendMsg(mAudioHandler,
+                    MSG_CHECK_MUSIC_ACTIVE,
+                    SENDMSG_REPLACE,
+                    0,
+                    0,
+                    caller,
+                    MUSIC_ACTIVE_POLL_PERIOD_MS);
         }
     }
 
-    private void sendDeviceConnectionIntent(int device, int state, String address,
-            String deviceName) {
-        if (DEBUG_DEVICES) {
-            Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) +
-                    " state:0x" + Integer.toHexString(state) + " address:" + address +
-                    " name:" + deviceName + ");");
-        }
-        Intent intent = new Intent();
-
-        if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
-            intent.setAction(Intent.ACTION_HEADSET_PLUG);
-            intent.putExtra("microphone", 1);
-        } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE ||
-                   device == AudioSystem.DEVICE_OUT_LINE) {
-            intent.setAction(Intent.ACTION_HEADSET_PLUG);
-            intent.putExtra("microphone",  0);
-        } else if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
-            intent.setAction(Intent.ACTION_HEADSET_PLUG);
-            intent.putExtra("microphone",
-                    AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
-                        == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
-        } else if (device == AudioSystem.DEVICE_IN_USB_HEADSET) {
-            if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
-                    == AudioSystem.DEVICE_STATE_AVAILABLE) {
-                intent.setAction(Intent.ACTION_HEADSET_PLUG);
-                intent.putExtra("microphone", 1);
-            } else {
-                // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
-                return;
-            }
-        } else if (device == AudioSystem.DEVICE_OUT_HDMI ||
-                device == AudioSystem.DEVICE_OUT_HDMI_ARC) {
-            configureHdmiPlugIntent(intent, state);
-        }
-
-        if (intent.getAction() == null) {
-            return;
-        }
-
-        intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
-        intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
-        intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
-
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG =
-            AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
-            AudioSystem.DEVICE_OUT_LINE |
-            AudioSystem.DEVICE_OUT_ALL_USB;
-
-    private void onSetWiredDeviceConnectionState(int device, int state, String address,
-            String deviceName, String caller) {
-        if (DEBUG_DEVICES) {
-            Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device)
-                    + " state:" + Integer.toHexString(state)
-                    + " address:" + address
-                    + " deviceName:" + deviceName
-                    + " caller: " + caller + ");");
-        }
-
-        synchronized (mConnectedDevices) {
-            if ((state == 0) && ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
-                setBluetoothA2dpOnInt(true, "onSetWiredDeviceConnectionState state 0");
-            }
-
-            if (!handleDeviceConnection(state == 1, device, address, deviceName)) {
-                // change of connection state failed, bailout
-                return;
-            }
-            if (state != 0) {
-                if ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
-                    setBluetoothA2dpOnInt(false, "onSetWiredDeviceConnectionState state not 0");
-                }
-                if ((device & mSafeMediaVolumeDevices) != 0) {
-                    sendMsg(mAudioHandler,
-                            MSG_CHECK_MUSIC_ACTIVE,
-                            SENDMSG_REPLACE,
-                            0,
-                            0,
-                            caller,
-                            MUSIC_ACTIVE_POLL_PERIOD_MS);
-                }
-                // Television devices without CEC service apply software volume on HDMI output
-                if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
-                    mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
-                    checkAllFixedVolumeDevices();
-                    synchronized (mHdmiClientLock) {
-                        if (mHdmiManager != null && mHdmiPlaybackClient != null) {
-                            mHdmiCecSink = false;
-                            mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
-                        }
-                    }
-                }
-                if ((device & AudioSystem.DEVICE_OUT_HDMI) != 0) {
-                    sendEnabledSurroundFormats(mContentResolver, true);
-                }
-            } else {
-                if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
-                    synchronized (mHdmiClientLock) {
-                        if (mHdmiManager != null) {
-                            mHdmiCecSink = false;
-                        }
-                    }
-                }
-            }
-            sendDeviceConnectionIntent(device, state, address, deviceName);
-            updateAudioRoutes(device, state);
-        }
-    }
-
-    private void configureHdmiPlugIntent(Intent intent, int state) {
-        intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
-        intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
-        if (state == 1) {
-            ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
-            int[] portGeneration = new int[1];
-            int status = AudioSystem.listAudioPorts(ports, portGeneration);
-            if (status == AudioManager.SUCCESS) {
-                for (AudioPort port : ports) {
-                    if (port instanceof AudioDevicePort) {
-                        final AudioDevicePort devicePort = (AudioDevicePort) port;
-                        if (devicePort.type() == AudioManager.DEVICE_OUT_HDMI ||
-                                devicePort.type() == AudioManager.DEVICE_OUT_HDMI_ARC) {
-                            // format the list of supported encodings
-                            int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
-                            if (formats.length > 0) {
-                                ArrayList<Integer> encodingList = new ArrayList(1);
-                                for (int format : formats) {
-                                    // a format in the list can be 0, skip it
-                                    if (format != AudioFormat.ENCODING_INVALID) {
-                                        encodingList.add(format);
-                                    }
-                                }
-                                int[] encodingArray = new int[encodingList.size()];
-                                for (int i = 0 ; i < encodingArray.length ; i++) {
-                                    encodingArray[i] = encodingList.get(i);
-                                }
-                                intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
-                            }
-                            // find the maximum supported number of channels
-                            int maxChannels = 0;
-                            for (int mask : devicePort.channelMasks()) {
-                                int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
-                                if (channelCount > maxChannels) {
-                                    maxChannels = channelCount;
-                                }
-                            }
-                            intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /* cache of the address of the last dock the device was connected to */
-    private String mDockAddress;
-
     /**
      * Receiver for misc intent broadcasts the Phone app cares about.
      */
     private class AudioServiceBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
+            final String action = intent.getAction();
             int outDevice;
             int inDevice;
             int state;
@@ -6812,75 +5074,16 @@
                 }
                 // Low end docks have a menu to enable or disable audio
                 // (see mDockAudioMediaEnabled)
-                if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
-                      ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) &&
-                       (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) {
-                    mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_DOCK, config,
-                            "ACTION_DOCK_EVENT intent"));
-                    AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config);
+                if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK)
+                        || ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED)
+                                && (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) {
+                    mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, config,
+                            "ACTION_DOCK_EVENT intent");
                 }
                 mDockState = dockState;
-            } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
-                BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                setBtScoActiveDevice(btDevice);
-            } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
-                boolean broadcast = false;
-                int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
-                synchronized (mScoClients) {
-                    int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
-                    // broadcast intent if the connection was initated by AudioService
-                    if (!mScoClients.isEmpty() &&
-                            (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
-                             mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
-                             mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
-                             mScoAudioState == SCO_STATE_DEACTIVATING)) {
-                        broadcast = true;
-                    }
-                    switch (btState) {
-                        case BluetoothHeadset.STATE_AUDIO_CONNECTED:
-                            scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
-                            if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
-                                mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
-                                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-                            }
-                            setBluetoothScoOn(true);
-                            break;
-                        case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
-                            setBluetoothScoOn(false);
-                            scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
-                            // startBluetoothSco called after stopBluetoothSco
-                            if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
-                                if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
-                                        && connectBluetoothScoAudioHelper(mBluetoothHeadset,
-                                        mBluetoothHeadsetDevice, mScoAudioMode)) {
-                                    mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                                    broadcast = false;
-                                    break;
-                                }
-                            }
-                            // Tear down SCO if disconnected from external
-                            clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
-                            mScoAudioState = SCO_STATE_INACTIVE;
-                            break;
-                        case BluetoothHeadset.STATE_AUDIO_CONNECTING:
-                            if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
-                                mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
-                                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-                            }
-                        default:
-                            // do not broadcast CONNECTING or invalid state
-                            broadcast = false;
-                            break;
-                    }
-                }
-                if (broadcast) {
-                    broadcastScoConnectionState(scoAudioState);
-                    //FIXME: this is to maintain compatibility with deprecated intent
-                    // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
-                    Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
-                    newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
-                    sendStickyBroadcastToAll(newIntent);
-                }
+            } else if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)
+                    || action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+                mDeviceBroker.receiveBtEvent(intent);
             } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
                 if (mMonitorRotation) {
                     RotationHelper.enable();
@@ -6898,13 +5101,7 @@
                 if (mUserSwitchedReceived) {
                     // attempt to stop music playback for background user except on first user
                     // switch (i.e. first boot)
-                    sendMsg(mAudioHandler,
-                            MSG_BROADCAST_AUDIO_BECOMING_NOISY,
-                            SENDMSG_REPLACE,
-                            0,
-                            0,
-                            null,
-                            0);
+                    mDeviceBroker.broadcastBecomingNoisy();
                 }
                 mUserSwitchedReceived = true;
                 // the current audio focus owner is no longer valid
@@ -6938,7 +5135,7 @@
                 state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
                 if (state == BluetoothAdapter.STATE_OFF ||
                         state == BluetoothAdapter.STATE_TURNING_OFF) {
-                    disconnectAllBluetoothProfiles();
+                    mDeviceBroker.disconnectAllBluetoothProfiles();
                 }
             } else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) ||
                     action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) {
@@ -7178,22 +5375,17 @@
                         // take new state into account for streams muted by ringer mode
                         setRingerModeInt(getRingerModeInternal(), false);
                     }
-
-                    sendMsg(mAudioHandler,
-                            MSG_SET_FORCE_USE,
-                            SENDMSG_QUEUE,
-                            AudioSystem.FOR_SYSTEM,
+                    mDeviceBroker.setForceUse_Async(AudioSystem.FOR_SYSTEM,
                             cameraSoundForced ?
                                     AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE,
-                            new String("handleConfigurationChanged"),
-                            0);
-
+                            "handleConfigurationChanged");
                     sendMsg(mAudioHandler,
                             MSG_SET_ALL_VOLUMES,
                             SENDMSG_QUEUE,
                             0,
                             0,
                             mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED], 0);
+
                 }
             }
             mVolumeController.setLayoutDirection(config.getLayoutDirection());
@@ -7202,28 +5394,6 @@
         }
     }
 
-    // Handles request to override default use of A2DP for media.
-    // Must be called synchronized on mConnectedDevices
-    public void setBluetoothA2dpOnInt(boolean on, String eventSource) {
-        synchronized (mBluetoothA2dpEnabledLock) {
-            mBluetoothA2dpEnabled = on;
-            mAudioHandler.removeMessages(MSG_SET_FORCE_BT_A2DP_USE);
-            setForceUseInt_SyncDevices(AudioSystem.FOR_MEDIA,
-                    mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP,
-                            eventSource);
-        }
-    }
-
-    // Must be called synchronized on mConnectedDevices
-    private void setForceUseInt_SyncDevices(int usage, int config, String eventSource) {
-        if (usage == AudioSystem.FOR_MEDIA) {
-            sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES,
-                    SENDMSG_NOOP, 0, 0, null, 0);
-        }
-        mForceUseLogger.log(new ForceUseEvent(usage, config, eventSource));
-        AudioSystem.setForceUse(usage, config);
-    }
-
     @Override
     public void setRingtonePlayer(IRingtonePlayer player) {
         mContext.enforceCallingOrSelfPermission(REMOTE_AUDIO_PLAYBACK, null);
@@ -7237,11 +5407,7 @@
 
     @Override
     public AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
-        synchronized (mCurAudioRoutes) {
-            AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
-            mRoutesObservers.register(observer);
-            return routes;
-        }
+        return mDeviceBroker.startWatchingRoutes(observer);
     }
 
 
@@ -7283,9 +5449,9 @@
     // the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
     private int mSafeUsbMediaVolumeIndex;
     // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
-    private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET |
-                                                AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
-                                                AudioSystem.DEVICE_OUT_USB_HEADSET;
+    /*package*/ final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET
+            | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
+            | AudioSystem.DEVICE_OUT_USB_HEADSET;
     // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
     // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
     // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
@@ -7438,9 +5604,8 @@
                     mHdmiSystemAudioSupported = on;
                     final int config = on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED :
                         AudioSystem.FORCE_NONE;
-                    mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_HDMI_SYSTEM_AUDIO,
-                            config, "setHdmiSystemAudioSupported"));
-                    AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config);
+                    mDeviceBroker.setForceUse_Async(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, config,
+                            "setHdmiSystemAudioSupported");
                 }
                 device = getDevicesForStream(AudioSystem.STREAM_MUSIC);
             }
@@ -7553,14 +5718,14 @@
     // logs for wired + A2DP device connections:
     // - wired: logged before onSetWiredDeviceConnectionState() is executed
     // - A2DP: logged at reception of method call
-    final private AudioEventLogger mDeviceLogger = new AudioEventLogger(
-            LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection");
+    /*package*/ static final AudioEventLogger sDeviceLogger = new AudioEventLogger(
+            LOG_NB_EVENTS_DEVICE_CONNECTION, "wired/A2DP/hearing aid device connection BLABLI");
 
-    final private AudioEventLogger mForceUseLogger = new AudioEventLogger(
+    static final AudioEventLogger sForceUseLogger = new AudioEventLogger(
             LOG_NB_EVENTS_FORCE_USE,
             "force use (logged before setForceUse() is executed)");
 
-    final private AudioEventLogger mVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME,
+    static final AudioEventLogger sVolumeLogger = new AudioEventLogger(LOG_NB_EVENTS_VOLUME,
             "volume changes (logged when command received by AudioService)");
 
     final private AudioEventLogger mDynPolicyLogger = new AudioEventLogger(LOG_NB_EVENTS_DYN_POLICY,
@@ -7613,8 +5778,9 @@
         dumpStreamStates(pw);
         dumpRingerMode(pw);
         pw.println("\nAudio routes:");
-        pw.print("  mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mainType));
-        pw.print("  mBluetoothName="); pw.println(mCurAudioRoutes.bluetoothName);
+        pw.print("  mMainType=0x"); pw.println(Integer.toHexString(
+                mDeviceBroker.getCurAudioRoutes().mainType));
+        pw.print("  mBluetoothName="); pw.println(mDeviceBroker.getCurAudioRoutes().bluetoothName);
 
         pw.println("\nOther state:");
         pw.print("  mVolumeController="); pw.println(mVolumeController);
@@ -7630,7 +5796,8 @@
         pw.print("  mCameraSoundForced="); pw.println(mCameraSoundForced);
         pw.print("  mHasVibrator="); pw.println(mHasVibrator);
         pw.print("  mVolumePolicy="); pw.println(mVolumePolicy);
-        pw.print("  mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported);
+        pw.print("  mAvrcpAbsVolSupported=");
+        pw.println(mDeviceBroker.isAvrcpAbsoluteVolumeSupported());
 
         dumpAudioPolicies(pw);
         mDynPolicyLogger.dump(pw);
@@ -7643,11 +5810,11 @@
         pw.println("\nEvent logs:");
         mModeLogger.dump(pw);
         pw.println("\n");
-        mDeviceLogger.dump(pw);
+        sDeviceLogger.dump(pw);
         pw.println("\n");
-        mForceUseLogger.dump(pw);
+        sForceUseLogger.dump(pw);
         pw.println("\n");
-        mVolumeLogger.dump(pw);
+        sVolumeLogger.dump(pw);
     }
 
     private static String safeMediaVolumeStateToString(int state) {
@@ -8270,6 +6437,11 @@
     }
 
     //======================
+    // Audio device management
+    //======================
+    private final AudioDeviceBroker mDeviceBroker;
+
+    //======================
     // Audio policy proxy
     //======================
     private static final class AudioDeviceArray {
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 9d9e35b..7ccb45e 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -19,7 +19,7 @@
 import android.media.AudioManager;
 import android.media.AudioSystem;
 
-import com.android.server.audio.AudioService.WiredDeviceConnectionState;
+import com.android.server.audio.AudioDeviceInventory.WiredDeviceConnectionState;
 
 
 public class AudioServiceEvents {
@@ -89,9 +89,11 @@
     }
 
     final static class VolumeEvent extends AudioEventLogger.Event {
-        final static int VOL_ADJUST_SUGG_VOL = 0;
-        final static int VOL_ADJUST_STREAM_VOL = 1;
-        final static int VOL_SET_STREAM_VOL = 2;
+        static final int VOL_ADJUST_SUGG_VOL = 0;
+        static final int VOL_ADJUST_STREAM_VOL = 1;
+        static final int VOL_SET_STREAM_VOL = 2;
+        static final int VOL_SET_HEARING_AID_VOL = 3;
+        static final int VOL_SET_AVRCP_VOL = 4;
 
         final int mOp;
         final int mStream;
@@ -107,6 +109,24 @@
             mCaller = caller;
         }
 
+        VolumeEvent(int op, int index, int gainDb) {
+            mOp = op;
+            mVal1 = index;
+            mVal2 = gainDb;
+            //unused
+            mStream = -1;
+            mCaller = null;
+        }
+
+        VolumeEvent(int op, int index) {
+            mOp = op;
+            mVal1 = index;
+            //unused
+            mVal2 = 0;
+            mStream = -1;
+            mCaller = null;
+        }
+
         @Override
         public String eventToString() {
             switch (mOp) {
@@ -131,6 +151,15 @@
                             .append(" flags:0x").append(Integer.toHexString(mVal2))
                             .append(") from ").append(mCaller)
                             .toString();
+                case VOL_SET_HEARING_AID_VOL:
+                    return new StringBuilder("setHearingAidVolume:")
+                            .append(" index:").append(mVal1)
+                            .append(" gain dB:").append(mVal2)
+                            .toString();
+                case VOL_SET_AVRCP_VOL:
+                    return new StringBuilder("setAvrcpVolume:")
+                            .append(" index:").append(mVal1)
+                            .toString();
                default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
             }
         }
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
new file mode 100644
index 0000000..bf32501
--- /dev/null
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -0,0 +1,949 @@
+/*
+ * 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 com.android.server.audio;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.AudioSystem;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * @hide
+ * Class to encapsulate all communication with Bluetooth services
+ */
+public class BtHelper {
+
+    private static final String TAG = "AS.BtHelper";
+
+    private final @NonNull AudioDeviceBroker mDeviceBroker;
+
+    BtHelper(@NonNull AudioDeviceBroker broker) {
+        mDeviceBroker = broker;
+    }
+
+    // List of clients having issued a SCO start request
+    private final ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>();
+
+    // BluetoothHeadset API to control SCO connection
+    private BluetoothHeadset mBluetoothHeadset;
+
+    // Bluetooth headset device
+    private BluetoothDevice mBluetoothHeadsetDevice;
+
+    // Indicate if SCO audio connection is currently active and if the initiator is
+    // audio service (internal) or bluetooth headset (external)
+    private int mScoAudioState;
+    // SCO audio state is not active
+    private static final int SCO_STATE_INACTIVE = 0;
+    // SCO audio activation request waiting for headset service to connect
+    private static final int SCO_STATE_ACTIVATE_REQ = 1;
+    // SCO audio state is active or starting due to a request from AudioManager API
+    private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
+    // SCO audio deactivation request waiting for headset service to connect
+    private static final int SCO_STATE_DEACTIVATE_REQ = 4;
+    // SCO audio deactivation in progress, waiting for Bluetooth audio intent
+    private static final int SCO_STATE_DEACTIVATING = 5;
+
+    // SCO audio state is active due to an action in BT handsfree (either voice recognition or
+    // in call audio)
+    private static final int SCO_STATE_ACTIVE_EXTERNAL = 2;
+
+    // Indicates the mode used for SCO audio connection. The mode is virtual call if the request
+    // originated from an app targeting an API version before JB MR2 and raw audio after that.
+    private int mScoAudioMode;
+    // SCO audio mode is undefined
+    /*package*/   static final int SCO_MODE_UNDEFINED = -1;
+    // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall())
+    /*package*/  static final int SCO_MODE_VIRTUAL_CALL = 0;
+    // SCO audio mode is raw audio (BluetoothHeadset.connectAudio())
+    private  static final int SCO_MODE_RAW = 1;
+    // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition())
+    private  static final int SCO_MODE_VR = 2;
+
+    private static final int SCO_MODE_MAX = 2;
+
+    // Current connection state indicated by bluetooth headset
+    private int mScoConnectionState;
+
+    private static final int BT_HEARING_AID_GAIN_MIN = -128;
+
+    @GuardedBy("mDeviceBroker.mHearingAidLock")
+    private BluetoothHearingAid mHearingAid;
+
+    // Reference to BluetoothA2dp to query for AbsoluteVolume.
+    @GuardedBy("mDeviceBroker.mA2dpAvrcpLock")
+    private BluetoothA2dp mA2dp;
+    // If absolute volume is supported in AVRCP device
+    @GuardedBy("mDeviceBroker.mA2dpAvrcpLock")
+    private boolean mAvrcpAbsVolSupported = false;
+
+    //----------------------------------------------------------------------
+    /*package*/ static class BluetoothA2dpDeviceInfo {
+        private final @NonNull BluetoothDevice mBtDevice;
+        private final int mVolume;
+        private final int mCodec;
+
+        BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) {
+            this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
+        }
+
+        BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) {
+            mBtDevice = btDevice;
+            mVolume = volume;
+            mCodec = codec;
+        }
+
+        public @NonNull BluetoothDevice getBtDevice() {
+            return mBtDevice;
+        }
+
+        public int getVolume() {
+            return mVolume;
+        }
+
+        public int getCodec() {
+            return mCodec;
+        }
+    }
+
+    //----------------------------------------------------------------------
+    // Interface for AudioDeviceBroker
+
+    /*package*/ void onSystemReady() {
+        mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
+        resetBluetoothSco();
+        getBluetoothHeadset();
+
+        //FIXME: this is to maintain compatibility with deprecated intent
+        // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
+        Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
+        newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
+                AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+        sendStickyBroadcastToAll(newIntent);
+
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            adapter.getProfileProxy(mDeviceBroker.getContext(),
+                    mBluetoothProfileServiceListener, BluetoothProfile.A2DP);
+            adapter.getProfileProxy(mDeviceBroker.getContext(),
+                    mBluetoothProfileServiceListener, BluetoothProfile.HEARING_AID);
+        }
+    }
+
+    @GuardedBy("mBluetoothA2dpEnabledLock")
+    /*package*/ void onAudioServerDiedRestoreA2dp() {
+        final int forMed = mDeviceBroker.getBluetoothA2dpEnabled()
+                ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP;
+        mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, forMed, "onAudioServerDied()");
+    }
+
+    @GuardedBy("mA2dpAvrcpLock")
+    /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
+        return (mA2dp != null && mAvrcpAbsVolSupported);
+    }
+
+    @GuardedBy("mA2dpAvrcpLock")
+    /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
+        mAvrcpAbsVolSupported = supported;
+    }
+
+    /*package*/ void setAvrcpAbsoluteVolumeIndex(int index) {
+        synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+            if (mA2dp == null) {
+                if (AudioService.DEBUG_VOL) {
+                    Log.d(TAG, "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp");
+                    return;
+                }
+            }
+            if (!mAvrcpAbsVolSupported) {
+                return;
+            }
+            if (AudioService.DEBUG_VOL) {
+                Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index);
+            }
+            AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+                    AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index / 10));
+            mA2dp.setAvrcpAbsoluteVolume(index / 10);
+        }
+    }
+
+    @GuardedBy("mA2dpAvrcpLock")
+    /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
+        if (mA2dp == null) {
+            return AudioSystem.AUDIO_FORMAT_DEFAULT;
+        }
+        final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
+        if (btCodecStatus == null) {
+            return AudioSystem.AUDIO_FORMAT_DEFAULT;
+        }
+        final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig();
+        if (btCodecConfig == null) {
+            return AudioSystem.AUDIO_FORMAT_DEFAULT;
+        }
+        return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
+    }
+
+    /*package*/ void receiveBtEvent(Intent intent) {
+        final String action = intent.getAction();
+        if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
+            BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            setBtScoActiveDevice(btDevice);
+        } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+            boolean broadcast = false;
+            int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
+            synchronized (mScoClients) {
+                int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+                // broadcast intent if the connection was initated by AudioService
+                if (!mScoClients.isEmpty()
+                        && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL
+                                || mScoAudioState == SCO_STATE_ACTIVATE_REQ
+                                || mScoAudioState == SCO_STATE_DEACTIVATE_REQ
+                                || mScoAudioState == SCO_STATE_DEACTIVATING)) {
+                    broadcast = true;
+                }
+                switch (btState) {
+                    case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+                        scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+                        if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+                                && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+                            mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+                        }
+                        mDeviceBroker.setBluetoothScoOn(true, "BtHelper.receiveBtEvent");
+                        break;
+                    case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+                        mDeviceBroker.setBluetoothScoOn(false, "BtHelper.receiveBtEvent");
+                        scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+                        // startBluetoothSco called after stopBluetoothSco
+                        if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
+                            if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
+                                    && connectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                            mBluetoothHeadsetDevice, mScoAudioMode)) {
+                                mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                                broadcast = false;
+                                break;
+                            }
+                        }
+                        // Tear down SCO if disconnected from external
+                        clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
+                        mScoAudioState = SCO_STATE_INACTIVE;
+                        break;
+                    case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+                        if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+                                && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+                            mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+                        }
+                        break;
+                    default:
+                        // do not broadcast CONNECTING or invalid state
+                        broadcast = false;
+                        break;
+                }
+            }
+            if (broadcast) {
+                broadcastScoConnectionState(scoAudioState);
+                //FIXME: this is to maintain compatibility with deprecated intent
+                // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
+                Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED);
+                newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
+                sendStickyBroadcastToAll(newIntent);
+            }
+        }
+    }
+
+    /**
+     *
+     * @return false if SCO isn't connected
+     */
+    /*package*/ boolean isBluetoothScoOn() {
+        synchronized (mScoClients) {
+            if ((mBluetoothHeadset != null)
+                    && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                            != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
+                Log.w(TAG, "isBluetoothScoOn(true) returning false because "
+                        + mBluetoothHeadsetDevice + " is not in audio connected mode");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Disconnect all SCO connections started by {@link AudioManager} except those started by
+     * {@param exceptPid}
+     *
+     * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
+     */
+    /*package*/ void disconnectBluetoothSco(int exceptPid) {
+        synchronized (mScoClients) {
+            checkScoAudioState();
+            if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
+                return;
+            }
+            clearAllScoClients(exceptPid, true);
+        }
+    }
+
+    /*package*/ void startBluetoothScoForClient(IBinder cb, int scoAudioMode,
+                @NonNull String eventSource) {
+        ScoClient client = getScoClient(cb, true);
+        // The calling identity must be cleared before calling ScoClient.incCount().
+        // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+        // and this must be done on behalf of system server to make sure permissions are granted.
+        // The caller identity must be cleared after getScoClient() because it is needed if a new
+        // client is created.
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            eventSource += " client count before=" + client.getCount();
+            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
+            client.incCount(scoAudioMode);
+        } catch (NullPointerException e) {
+            Log.e(TAG, "Null ScoClient", e);
+        }
+        Binder.restoreCallingIdentity(ident);
+    }
+
+    /*package*/ void stopBluetoothScoForClient(IBinder cb, @NonNull String eventSource) {
+        ScoClient client = getScoClient(cb, false);
+        // The calling identity must be cleared before calling ScoClient.decCount().
+        // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
+        // and this must be done on behalf of system server to make sure permissions are granted.
+        final long ident = Binder.clearCallingIdentity();
+        if (client != null) {
+            eventSource += " client count before=" + client.getCount();
+            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
+            client.decCount();
+        }
+        Binder.restoreCallingIdentity(ident);
+    }
+
+
+    /*package*/ void setHearingAidVolume(int index, int streamType) {
+        synchronized (mDeviceBroker.mHearingAidLock) {
+            if (mHearingAid == null) {
+                if (AudioService.DEBUG_VOL) {
+                    Log.i(TAG, "setHearingAidVolume: null mHearingAid");
+                }
+                return;
+            }
+            //hearing aid expect volume value in range -128dB to 0dB
+            int gainDB = (int) AudioSystem.getStreamVolumeDB(streamType, index / 10,
+                    AudioSystem.DEVICE_OUT_HEARING_AID);
+            if (gainDB < BT_HEARING_AID_GAIN_MIN) {
+                gainDB = BT_HEARING_AID_GAIN_MIN;
+            }
+            if (AudioService.DEBUG_VOL) {
+                Log.i(TAG, "setHearingAidVolume: calling mHearingAid.setVolume idx="
+                        + index + " gain=" + gainDB);
+            }
+            AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+                    AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
+            mHearingAid.setVolume(gainDB);
+        }
+    }
+
+    //----------------------------------------------------------------------
+    private void broadcastScoConnectionState(int state) {
+        mDeviceBroker.broadcastScoConnectionState(state);
+    }
+
+    /*package*/ void onBroadcastScoConnectionState(int state) {
+        if (state == mScoConnectionState) {
+            return;
+        }
+        Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
+        newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state);
+        newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE,
+                mScoConnectionState);
+        sendStickyBroadcastToAll(newIntent);
+        mScoConnectionState = state;
+    }
+
+    private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
+        if (btDevice == null) {
+            return true;
+        }
+        String address = btDevice.getAddress();
+        BluetoothClass btClass = btDevice.getBluetoothClass();
+        int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
+        int[] outDeviceTypes = {
+                AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
+                AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
+                AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT
+        };
+        if (btClass != null) {
+            switch (btClass.getDeviceClass()) {
+                case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+                case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
+                    outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET };
+                    break;
+                case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+                    outDeviceTypes = new int[] { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT };
+                    break;
+            }
+        }
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+        String btDeviceName =  btDevice.getName();
+        boolean result = false;
+        if (isActive) {
+            result |= mDeviceBroker.handleDeviceConnection(
+                    isActive, outDeviceTypes[0], address, btDeviceName);
+        } else {
+            for (int outDeviceType : outDeviceTypes) {
+                result |= mDeviceBroker.handleDeviceConnection(
+                        isActive, outDeviceType, address, btDeviceName);
+            }
+        }
+        // handleDeviceConnection() && result to make sure the method get executed
+        result = mDeviceBroker.handleDeviceConnection(
+                isActive, inDevice, address, btDeviceName) && result;
+        return result;
+    }
+
+    private void setBtScoActiveDevice(BluetoothDevice btDevice) {
+        synchronized (mScoClients) {
+            Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
+            final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
+            if (Objects.equals(btDevice, previousActiveDevice)) {
+                return;
+            }
+            if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
+                Log.w(TAG, "setBtScoActiveDevice() failed to remove previous device "
+                        + previousActiveDevice);
+            }
+            if (!handleBtScoActiveDeviceChange(btDevice, true)) {
+                Log.e(TAG, "setBtScoActiveDevice() failed to add new device " + btDevice);
+                // set mBluetoothHeadsetDevice to null when failing to add new device
+                btDevice = null;
+            }
+            mBluetoothHeadsetDevice = btDevice;
+            if (mBluetoothHeadsetDevice == null) {
+                resetBluetoothSco();
+            }
+        }
+    }
+
+    private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+            new BluetoothProfile.ServiceListener() {
+                public void onServiceConnected(int profile, BluetoothProfile proxy) {
+                    final BluetoothDevice btDevice;
+                    List<BluetoothDevice> deviceList;
+                    switch(profile) {
+                        case BluetoothProfile.A2DP:
+                            synchronized (mDeviceBroker.mA2dpAvrcpLock) {
+                                mA2dp = (BluetoothA2dp) proxy;
+                                deviceList = mA2dp.getConnectedDevices();
+                                if (deviceList.size() > 0) {
+                                    btDevice = deviceList.get(0);
+                                    if (btDevice == null) {
+                                        Log.e(TAG, "Invalid null device in BT profile listener");
+                                        return;
+                                    }
+                                    final @BluetoothProfile.BtProfileState int state =
+                                            mA2dp.getConnectionState(btDevice);
+                                    mDeviceBroker.handleSetA2dpSinkConnectionState(
+                                            state, new BluetoothA2dpDeviceInfo(btDevice));
+                                }
+                            }
+                            break;
+
+                        case BluetoothProfile.A2DP_SINK:
+                            deviceList = proxy.getConnectedDevices();
+                            if (deviceList.size() > 0) {
+                                btDevice = deviceList.get(0);
+                                final @BluetoothProfile.BtProfileState int state =
+                                        proxy.getConnectionState(btDevice);
+                                mDeviceBroker.handleSetA2dpSourceConnectionState(
+                                        state, new BluetoothA2dpDeviceInfo(btDevice));
+                            }
+                            break;
+
+                        case BluetoothProfile.HEADSET:
+                            synchronized (mScoClients) {
+                                // Discard timeout message
+                                mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
+                                mBluetoothHeadset = (BluetoothHeadset) proxy;
+                                setBtScoActiveDevice(mBluetoothHeadset.getActiveDevice());
+                                // Refresh SCO audio state
+                                checkScoAudioState();
+                                // Continue pending action if any
+                                if (mScoAudioState == SCO_STATE_ACTIVATE_REQ
+                                        || mScoAudioState == SCO_STATE_DEACTIVATE_REQ) {
+                                    boolean status = false;
+                                    if (mBluetoothHeadsetDevice != null) {
+                                        switch (mScoAudioState) {
+                                            case SCO_STATE_ACTIVATE_REQ:
+                                                status = connectBluetoothScoAudioHelper(
+                                                        mBluetoothHeadset,
+                                                        mBluetoothHeadsetDevice, mScoAudioMode);
+                                                if (status) {
+                                                    mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                                                }
+                                                break;
+                                            case SCO_STATE_DEACTIVATE_REQ:
+                                                status = disconnectBluetoothScoAudioHelper(
+                                                        mBluetoothHeadset,
+                                                        mBluetoothHeadsetDevice, mScoAudioMode);
+                                                if (status) {
+                                                    mScoAudioState = SCO_STATE_DEACTIVATING;
+                                                }
+                                                break;
+                                        }
+                                    }
+                                    if (!status) {
+                                        mScoAudioState = SCO_STATE_INACTIVE;
+                                        broadcastScoConnectionState(
+                                                AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                                    }
+                                }
+                            }
+                            break;
+
+                        case BluetoothProfile.HEARING_AID:
+                            mHearingAid = (BluetoothHearingAid) proxy;
+                            deviceList = mHearingAid.getConnectedDevices();
+                            if (deviceList.size() > 0) {
+                                btDevice = deviceList.get(0);
+                                final @BluetoothProfile.BtProfileState int state =
+                                        mHearingAid.getConnectionState(btDevice);
+                                mDeviceBroker.setBluetoothHearingAidDeviceConnectionState(
+                                        btDevice, state,
+                                        /*suppressNoisyIntent*/ false,
+                                        /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE,
+                                        /*eventSource*/ "mBluetoothProfileServiceListener");
+                            }
+                            break;
+
+                        default:
+                            break;
+                    }
+                }
+                public void onServiceDisconnected(int profile) {
+
+                    switch (profile) {
+                        case BluetoothProfile.A2DP:
+                            mDeviceBroker.handleDisconnectA2dp();
+                            break;
+
+                        case BluetoothProfile.A2DP_SINK:
+                            mDeviceBroker.handleDisconnectA2dpSink();
+                            break;
+
+                        case BluetoothProfile.HEADSET:
+                            disconnectHeadset();
+                            break;
+
+                        case BluetoothProfile.HEARING_AID:
+                            mDeviceBroker.handleDisconnectHearingAid();
+                            break;
+
+                        default:
+                            break;
+                    }
+                }
+            };
+
+    void disconnectAllBluetoothProfiles() {
+        mDeviceBroker.handleDisconnectA2dp();
+        mDeviceBroker.handleDisconnectA2dpSink();
+        disconnectHeadset();
+        mDeviceBroker.handleDisconnectHearingAid();
+    }
+
+    private void disconnectHeadset() {
+        synchronized (mScoClients) {
+            setBtScoActiveDevice(null);
+            mBluetoothHeadset = null;
+        }
+    }
+
+    //----------------------------------------------------------------------
+    private class ScoClient implements IBinder.DeathRecipient {
+        private IBinder mCb; // To be notified of client's death
+        private int mCreatorPid;
+        private int mStartcount; // number of SCO connections started by this client
+
+        ScoClient(IBinder cb) {
+            mCb = cb;
+            mCreatorPid = Binder.getCallingPid();
+            mStartcount = 0;
+        }
+
+        public void binderDied() {
+            synchronized (mScoClients) {
+                Log.w(TAG, "SCO client died");
+                int index = mScoClients.indexOf(this);
+                if (index < 0) {
+                    Log.w(TAG, "unregistered SCO client died");
+                } else {
+                    clearCount(true);
+                    mScoClients.remove(this);
+                }
+            }
+        }
+
+        public void incCount(int scoAudioMode) {
+            synchronized (mScoClients) {
+                requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
+                if (mStartcount == 0) {
+                    try {
+                        mCb.linkToDeath(this, 0);
+                    } catch (RemoteException e) {
+                        // client has already died!
+                        Log.w(TAG, "ScoClient  incCount() could not link to "
+                                + mCb + " binder death");
+                    }
+                }
+                mStartcount++;
+            }
+        }
+
+        public void decCount() {
+            synchronized (mScoClients) {
+                if (mStartcount == 0) {
+                    Log.w(TAG, "ScoClient.decCount() already 0");
+                } else {
+                    mStartcount--;
+                    if (mStartcount == 0) {
+                        try {
+                            mCb.unlinkToDeath(this, 0);
+                        } catch (NoSuchElementException e) {
+                            Log.w(TAG, "decCount() going to 0 but not registered to binder");
+                        }
+                    }
+                    requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
+                }
+            }
+        }
+
+        public void clearCount(boolean stopSco) {
+            synchronized (mScoClients) {
+                if (mStartcount != 0) {
+                    try {
+                        mCb.unlinkToDeath(this, 0);
+                    } catch (NoSuchElementException e) {
+                        Log.w(TAG, "clearCount() mStartcount: "
+                                + mStartcount + " != 0 but not registered to binder");
+                    }
+                }
+                mStartcount = 0;
+                if (stopSco) {
+                    requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
+                }
+            }
+        }
+
+        public int getCount() {
+            return mStartcount;
+        }
+
+        public IBinder getBinder() {
+            return mCb;
+        }
+
+        public int getPid() {
+            return mCreatorPid;
+        }
+
+        public int totalCount() {
+            synchronized (mScoClients) {
+                int count = 0;
+                for (ScoClient mScoClient : mScoClients) {
+                    count += mScoClient.getCount();
+                }
+                return count;
+            }
+        }
+
+        private void requestScoState(int state, int scoAudioMode) {
+            checkScoAudioState();
+            int clientCount = totalCount();
+            if (clientCount != 0) {
+                Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
+                        + ", clientCount=" + clientCount);
+                return;
+            }
+            if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+                // Make sure that the state transitions to CONNECTING even if we cannot initiate
+                // the connection.
+                broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
+                // Accept SCO audio activation only in NORMAL audio mode or if the mode is
+                // currently controlled by the same client process.
+                synchronized (mDeviceBroker.mSetModeLock) {
+                    int modeOwnerPid =  mDeviceBroker.getSetModeDeathHandlers().isEmpty()
+                            ? 0 : mDeviceBroker.getSetModeDeathHandlers().get(0).getPid();
+                    if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
+                        Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
+                                + modeOwnerPid + " != creatorPid " + mCreatorPid);
+                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                        return;
+                    }
+                    switch (mScoAudioState) {
+                        case SCO_STATE_INACTIVE:
+                            mScoAudioMode = scoAudioMode;
+                            if (scoAudioMode == SCO_MODE_UNDEFINED) {
+                                mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+                                if (mBluetoothHeadsetDevice != null) {
+                                    mScoAudioMode = Settings.Global.getInt(
+                                            mDeviceBroker.getContentResolver(),
+                                            "bluetooth_sco_channel_"
+                                                    + mBluetoothHeadsetDevice.getAddress(),
+                                            SCO_MODE_VIRTUAL_CALL);
+                                    if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
+                                        mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+                                    }
+                                }
+                            }
+                            if (mBluetoothHeadset == null) {
+                                if (getBluetoothHeadset()) {
+                                    mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+                                } else {
+                                    Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+                                            + " connection, mScoAudioMode=" + mScoAudioMode);
+                                    broadcastScoConnectionState(
+                                            AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                                }
+                                break;
+                            }
+                            if (mBluetoothHeadsetDevice == null) {
+                                Log.w(TAG, "requestScoState: no active device while connecting,"
+                                        + " mScoAudioMode=" + mScoAudioMode);
+                                broadcastScoConnectionState(
+                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                                break;
+                            }
+                            if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                    mBluetoothHeadsetDevice, mScoAudioMode)) {
+                                mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                            } else {
+                                Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
+                                        + " failed, mScoAudioMode=" + mScoAudioMode);
+                                broadcastScoConnectionState(
+                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                            }
+                            break;
+                        case SCO_STATE_DEACTIVATING:
+                            mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+                            break;
+                        case SCO_STATE_DEACTIVATE_REQ:
+                            mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
+                            break;
+                        default:
+                            Log.w(TAG, "requestScoState: failed to connect in state "
+                                    + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                            break;
+
+                    }
+                }
+            } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                switch (mScoAudioState) {
+                    case SCO_STATE_ACTIVE_INTERNAL:
+                        if (mBluetoothHeadset == null) {
+                            if (getBluetoothHeadset()) {
+                                mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
+                            } else {
+                                Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+                                        + " disconnection, mScoAudioMode=" + mScoAudioMode);
+                                mScoAudioState = SCO_STATE_INACTIVE;
+                                broadcastScoConnectionState(
+                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                            }
+                            break;
+                        }
+                        if (mBluetoothHeadsetDevice == null) {
+                            mScoAudioState = SCO_STATE_INACTIVE;
+                            broadcastScoConnectionState(
+                                    AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                            break;
+                        }
+                        if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                mBluetoothHeadsetDevice, mScoAudioMode)) {
+                            mScoAudioState = SCO_STATE_DEACTIVATING;
+                        } else {
+                            mScoAudioState = SCO_STATE_INACTIVE;
+                            broadcastScoConnectionState(
+                                    AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                        }
+                        break;
+                    case SCO_STATE_ACTIVATE_REQ:
+                        mScoAudioState = SCO_STATE_INACTIVE;
+                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                        break;
+                    default:
+                        Log.w(TAG, "requestScoState: failed to disconnect in state "
+                                + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                        break;
+                }
+            }
+        }
+    }
+
+    //-----------------------------------------------------
+    // Utilities
+    private void sendStickyBroadcastToAll(Intent intent) {
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mDeviceBroker.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
+            BluetoothDevice device, int scoAudioMode) {
+        switch (scoAudioMode) {
+            case SCO_MODE_RAW:
+                return bluetoothHeadset.disconnectAudio();
+            case SCO_MODE_VIRTUAL_CALL:
+                return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
+            case SCO_MODE_VR:
+                return bluetoothHeadset.stopVoiceRecognition(device);
+            default:
+                return false;
+        }
+    }
+
+    private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
+            BluetoothDevice device, int scoAudioMode) {
+        switch (scoAudioMode) {
+            case SCO_MODE_RAW:
+                return bluetoothHeadset.connectAudio();
+            case SCO_MODE_VIRTUAL_CALL:
+                return bluetoothHeadset.startScoUsingVirtualVoiceCall();
+            case SCO_MODE_VR:
+                return bluetoothHeadset.startVoiceRecognition(device);
+            default:
+                return false;
+        }
+    }
+
+    /*package*/ void resetBluetoothSco() {
+        synchronized (mScoClients) {
+            clearAllScoClients(0, false);
+            mScoAudioState = SCO_STATE_INACTIVE;
+            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+        }
+        AudioSystem.setParameters("A2dpSuspended=false");
+        mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
+    }
+
+
+    private void checkScoAudioState() {
+        synchronized (mScoClients) {
+            if (mBluetoothHeadset != null
+                    && mBluetoothHeadsetDevice != null
+                    && mScoAudioState == SCO_STATE_INACTIVE
+                    && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                            != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+            }
+        }
+    }
+
+
+    private ScoClient getScoClient(IBinder cb, boolean create) {
+        synchronized (mScoClients) {
+            for (ScoClient existingClient : mScoClients) {
+                if (existingClient.getBinder() == cb) {
+                    return existingClient;
+                }
+            }
+            if (create) {
+                ScoClient newClient = new ScoClient(cb);
+                mScoClients.add(newClient);
+                return newClient;
+            }
+            return null;
+        }
+    }
+
+    private void clearAllScoClients(int exceptPid, boolean stopSco) {
+        synchronized (mScoClients) {
+            ScoClient savedClient = null;
+            for (ScoClient cl : mScoClients) {
+                if (cl.getPid() != exceptPid) {
+                    cl.clearCount(stopSco);
+                } else {
+                    savedClient = cl;
+                }
+            }
+            mScoClients.clear();
+            if (savedClient != null) {
+                mScoClients.add(savedClient);
+            }
+        }
+    }
+
+    private boolean getBluetoothHeadset() {
+        boolean result = false;
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            result = adapter.getProfileProxy(mDeviceBroker.getContext(),
+                    mBluetoothProfileServiceListener, BluetoothProfile.HEADSET);
+        }
+        // If we could not get a bluetooth headset proxy, send a failure message
+        // without delay to reset the SCO audio state and clear SCO clients.
+        // If we could get a proxy, send a delayed failure message that will reset our state
+        // in case we don't receive onServiceConnected().
+        mDeviceBroker.handleFailureToConnectToBtHeadsetService(
+                result ? AudioDeviceBroker.BT_HEADSET_CNCT_TIMEOUT_MS : 0);
+        return result;
+    }
+
+    private int mapBluetoothCodecToAudioFormat(int btCodecType) {
+        switch (btCodecType) {
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
+                return AudioSystem.AUDIO_FORMAT_SBC;
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
+                return AudioSystem.AUDIO_FORMAT_AAC;
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
+                return AudioSystem.AUDIO_FORMAT_APTX;
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
+                return AudioSystem.AUDIO_FORMAT_APTX_HD;
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
+                return AudioSystem.AUDIO_FORMAT_LDAC;
+            default:
+                return AudioSystem.AUDIO_FORMAT_DEFAULT;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index dc564ba..3a25d98 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -202,7 +202,7 @@
                     if (mPrivilegedAlarmActiveCount++ == 0) {
                         mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
                                 AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
-                        AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+                        AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
                                 mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
                     }
                 } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
@@ -211,7 +211,7 @@
                         if (AudioSystem.getStreamVolumeIndex(
                                 AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
                                 mMaxAlarmVolume) {
-                            AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+                            AudioSystem.setStreamVolumeIndexAS(AudioSystem.STREAM_ALARM,
                                     mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
                         }
                     }
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 41fedc5..15d66e6 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -29,10 +29,12 @@
 import android.app.ActivityTaskManager;
 import android.app.AppOpsManager;
 import android.app.IActivityTaskManager;
+import android.app.KeyguardManager;
 import android.app.TaskStackListener;
 import android.app.UserSwitchObserver;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.hardware.biometrics.BiometricAuthenticator;
@@ -377,6 +379,13 @@
                 new BiometricTaskStackListener();
         private final Random mRandom = new Random();
 
+        // TODO(b/123378871): Remove when moved.
+        // When BiometricPrompt#setEnableFallback is set to true, we need to store the client (app)
+        // receiver. BiometricService internally launches CDCA which invokes BiometricService to
+        // start authentication (normal path). When auth is success/rejected, CDCA will use an aidl
+        // method to poke BiometricService - the result will then be forwarded to this receiver.
+        private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver;
+
         // 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;
@@ -705,6 +714,22 @@
                 }
             }
 
+            // Launch CDC instead if necessary. CDC will return results through an AIDL call, since
+            // we can't get activity results. Store the receiver somewhere so we can forward the
+            // result back to the client.
+            // TODO(b/123378871): Remove when moved.
+            if (bundle.getBoolean(BiometricPrompt.KEY_ENABLE_FALLBACK)) {
+                mConfirmDeviceCredentialReceiver = receiver;
+                final KeyguardManager kgm = getContext().getSystemService(KeyguardManager.class);
+                // Use this so we don't need to duplicate logic..
+                final Intent intent = kgm.createConfirmDeviceCredentialIntent(null /* title */,
+                        null /* description */);
+                // Then give it the bundle to do magic behavior..
+                intent.putExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE, bundle);
+                getContext().startActivityAsUser(intent, UserHandle.CURRENT);
+                return;
+            }
+
             mHandler.post(() -> {
                 final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
                 final int modality = result.first;
@@ -745,6 +770,36 @@
             });
         }
 
+        @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;
+        }
+
+        @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
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 3db6a74..fcf821f2 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -48,14 +48,12 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
-import android.util.StatsLog;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.DumpUtils;
 import com.android.server.SystemServerInitThreadPool;
-import com.android.server.biometrics.AuthenticationClient;
 import com.android.server.biometrics.BiometricServiceBase;
 import com.android.server.biometrics.BiometricUtils;
 import com.android.server.biometrics.ClientMonitor;
@@ -588,11 +586,6 @@
         public void onAcquired(final long deviceId, final int acquiredInfo, final int vendorCode) {
             mHandler.post(() -> {
                 FingerprintService.super.handleAcquired(deviceId, acquiredInfo, vendorCode);
-                if (getLockoutMode() == AuthenticationClient.LOCKOUT_NONE
-                        && getCurrentClient() instanceof AuthenticationClient) {
-                    // Ignore enrollment acquisitions or acquisitions when we are locked out.
-                    StatsLog.write(StatsLog.FINGERPRINT_ACQUIRED, mCurrentUserId, mIsCrypto);
-                }
             });
         }
 
@@ -602,22 +595,6 @@
             mHandler.post(() -> {
                 Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
                 FingerprintService.super.handleAuthenticated(fp, token);
-                // Send authentication to statsd.
-                final boolean authenticated = fingerId != 0;
-                StatsLog.write(StatsLog.FINGERPRINT_AUTHENTICATED, mCurrentUserId, mIsCrypto,
-                        authenticated);
-                if (!authenticated) {
-                    // If we failed to authenticate because of a lockout, inform statsd.
-                    final int lockoutMode = getLockoutMode();
-                    if (lockoutMode == AuthenticationClient.LOCKOUT_TIMED) {
-                        StatsLog.write(StatsLog.FINGERPRINT_ERROR_OCCURRED, mCurrentUserId,
-                                mIsCrypto, StatsLog.FINGERPRINT_ERROR_OCCURRED__ERROR__LOCKOUT);
-                    } else if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
-                        StatsLog.write(StatsLog.FINGERPRINT_ERROR_OCCURRED, mCurrentUserId,
-                                mIsCrypto,
-                                StatsLog.FINGERPRINT_ERROR_OCCURRED__ERROR__PERMANENT_LOCKOUT);
-                    }
-                }
             });
         }
 
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index cb3f91b..b89768a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -36,6 +36,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.ColorSpace;
 import android.graphics.Point;
 import android.hardware.SensorManager;
 import android.hardware.display.AmbientBrightnessDayStats;
@@ -93,9 +94,9 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
+import com.android.server.display.ColorDisplayService.ColorDisplayServiceInternal;
 import com.android.server.wm.SurfaceAnimationThread;
 import com.android.server.wm.WindowManagerInternal;
-import com.android.server.display.ColorDisplayService.ColorDisplayServiceInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -294,6 +295,7 @@
     // is rejected by the system.
     private final Curve mMinimumBrightnessCurve;
     private final Spline mMinimumBrightnessSpline;
+    private final ColorSpace mWideColorSpace;
 
     public DisplayManagerService(Context context) {
         this(context, new Injector());
@@ -322,6 +324,8 @@
         PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
         mCurrentUserId = UserHandle.USER_SYSTEM;
+        ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces();
+        mWideColorSpace = colorSpaces[1];
     }
 
     public void setupSchedulerPolicies() {
@@ -1073,6 +1077,10 @@
         return mMinimumBrightnessCurve;
     }
 
+    int getPreferredWideGamutColorSpaceIdInternal() {
+        return mWideColorSpace.getId();
+    }
+
     private void setBrightnessConfigurationForUserInternal(
             @Nullable BrightnessConfiguration c, @UserIdInt int userId,
             @Nullable String packageName) {
@@ -2128,6 +2136,16 @@
             }
         }
 
+        @Override // Binder call
+        public int getPreferredWideGamutColorSpaceId() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return getPreferredWideGamutColorSpaceIdInternal();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         void setBrightness(int brightness) {
             Settings.System.putIntForUser(mContext.getContentResolver(),
                     Settings.System.SCREEN_BRIGHTNESS, brightness, UserHandle.USER_CURRENT);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index cfc85da..4349b4a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -34,6 +34,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.Printer;
@@ -756,6 +757,14 @@
          */
         private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>();
 
+        private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
+        static {
+            Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE);
+        }
+
+        private static final UserManagerInternal sUserManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+
         private boolean mCopyOnWrite = false;
         @NonNull
         private String mEnabledInputMethodsStrCache = "";
@@ -833,7 +842,9 @@
             if (mCopyOnWrite) {
                 mCopyOnWriteDataStore.put(key, str);
             } else {
-                Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId);
+                final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
+                        ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
+                Settings.Secure.putStringForUser(mResolver, key, str, userId);
             }
         }
 
@@ -852,14 +863,16 @@
             if (mCopyOnWrite) {
                 mCopyOnWriteDataStore.put(key, String.valueOf(value));
             } else {
-                Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId);
+                final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
+                        ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
+                Settings.Secure.putIntForUser(mResolver, key, value, userId);
             }
         }
 
         private int getInt(String key, int defaultValue) {
             if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
                 final String result = mCopyOnWriteDataStore.get(key);
-                return result != null ? Integer.parseInt(result) : 0;
+                return result != null ? Integer.parseInt(result) : defaultValue;
             }
             return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId);
         }
diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
index f1de371..e6f0ed9 100644
--- a/services/core/java/com/android/server/location/GeocoderProxy.java
+++ b/services/core/java/com/android/server/location/GeocoderProxy.java
@@ -20,8 +20,6 @@
 import android.location.Address;
 import android.location.GeocoderParams;
 import android.location.IGeocodeProvider;
-import android.os.RemoteException;
-import android.util.Log;
 
 import com.android.internal.os.BackgroundThread;
 import com.android.server.ServiceWatcher;
@@ -68,35 +66,22 @@
 
     public String getFromLocation(double latitude, double longitude, int maxResults,
             GeocoderParams params, List<Address> addrs) {
-        final String[] result = new String[]{"Service not Available"};
-        mServiceWatcher.runOnBinder(binder -> {
+        return mServiceWatcher.runOnBinderBlocking(binder -> {
             IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
-            try {
-                result[0] = provider.getFromLocation(
-                        latitude, longitude, maxResults, params, addrs);
-            } catch (RemoteException e) {
-                Log.w(TAG, e);
-            }
-        });
-        return result[0];
+            return provider.getFromLocation(latitude, longitude, maxResults, params, addrs);
+        }, "Service not Available");
     }
 
     public String getFromLocationName(String locationName,
             double lowerLeftLatitude, double lowerLeftLongitude,
             double upperRightLatitude, double upperRightLongitude, int maxResults,
             GeocoderParams params, List<Address> addrs) {
-        final String[] result = new String[]{"Service not Available"};
-        mServiceWatcher.runOnBinder(binder -> {
+        return mServiceWatcher.runOnBinderBlocking(binder -> {
             IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
-            try {
-                result[0] = provider.getFromLocationName(locationName, lowerLeftLatitude,
-                        lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
-                        maxResults, params, addrs);
-            } catch (RemoteException e) {
-                Log.w(TAG, e);
-            }
-        });
-        return result[0];
+            return provider.getFromLocationName(locationName, lowerLeftLatitude,
+                    lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
+                    maxResults, params, addrs);
+        }, "Service not Available");
     }
 
 }
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index a6da8c5..6b5b1be 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -127,20 +127,16 @@
         return mServiceWatcher.start();
     }
 
-    private void initializeService(IBinder binder) {
+    private void initializeService(IBinder binder) throws RemoteException {
         ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
         if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher);
 
-        try {
-            service.setLocationProviderManager(mManager);
+        service.setLocationProviderManager(mManager);
 
-            synchronized (mRequestLock) {
-                if (mRequest != null) {
-                    service.setRequest(mRequest, mWorkSource);
-                }
+        synchronized (mRequestLock) {
+            if (mRequest != null) {
+                service.setRequest(mRequest, mWorkSource);
             }
-        } catch (RemoteException e) {
-            Log.w(TAG, e);
         }
     }
 
@@ -157,63 +153,44 @@
         }
         mServiceWatcher.runOnBinder(binder -> {
             ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
-            try {
-                service.setRequest(request, source);
-            } catch (RemoteException e) {
-                Log.w(TAG, e);
-            }
+            service.setRequest(request, source);
         });
     }
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println(" service=" + mServiceWatcher);
-        mServiceWatcher.runOnBinder(binder -> {
+        mServiceWatcher.runOnBinderBlocking(binder -> {
             try {
                 TransferPipe.dumpAsync(binder, fd, args);
             } catch (IOException | RemoteException e) {
-                pw.println(" failed to dump location provider: " + e);
+                pw.println(" failed to dump location provider");
             }
-        });
+            return null;
+        }, null);
     }
 
     @Override
     public int getStatus(Bundle extras) {
-        int[] status = new int[] {LocationProvider.TEMPORARILY_UNAVAILABLE};
-        mServiceWatcher.runOnBinder(binder -> {
+        return mServiceWatcher.runOnBinderBlocking(binder -> {
             ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
-            try {
-                status[0] = service.getStatus(extras);
-            } catch (RemoteException e) {
-                Log.w(TAG, e);
-            }
-        });
-        return status[0];
+            return service.getStatus(extras);
+        }, LocationProvider.TEMPORARILY_UNAVAILABLE);
     }
 
     @Override
     public long getStatusUpdateTime() {
-        long[] updateTime = new long[] {0L};
-        mServiceWatcher.runOnBinder(binder -> {
+        return mServiceWatcher.runOnBinderBlocking(binder -> {
             ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
-            try {
-                updateTime[0] = service.getStatusUpdateTime();
-            } catch (RemoteException e) {
-                Log.w(TAG, e);
-            }
-        });
-        return updateTime[0];
+            return service.getStatusUpdateTime();
+        }, 0L);
     }
 
     @Override
     public void sendExtraCommand(String command, Bundle extras) {
         mServiceWatcher.runOnBinder(binder -> {
             ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
-            try {
-                service.sendExtraCommand(command, extras);
-            } catch (RemoteException e) {
-                Log.w(TAG, e);
-            }
+            service.sendExtraCommand(command, extras);
         });
     }
 }
diff --git a/services/core/java/com/android/server/location/OWNERS b/services/core/java/com/android/server/location/OWNERS
index 92b4d5f..c2c95e6 100644
--- a/services/core/java/com/android/server/location/OWNERS
+++ b/services/core/java/com/android/server/location/OWNERS
@@ -1,6 +1,8 @@
+aadmal@google.com
 arthuri@google.com
 bduddie@google.com
 gomo@google.com
 sooniln@google.com
 weiwa@google.com
 wyattriley@google.com
+yuhany@google.com
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a164686..de3f50a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1087,7 +1087,8 @@
                     || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
                     || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)
                     || action.equals(Intent.ACTION_PACKAGES_SUSPENDED)
-                    || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)) {
+                    || action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
+                    || action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
                 int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
                         UserHandle.USER_ALL);
                 String pkgList[] = null;
@@ -1108,6 +1109,23 @@
                     uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
                     cancelNotifications = false;
                     unhideNotifications = true;
+                } else if (action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
+                    final int distractionRestrictions =
+                            intent.getIntExtra(Intent.EXTRA_DISTRACTION_RESTRICTIONS,
+                                    PackageManager.RESTRICTION_NONE);
+                    if ((distractionRestrictions
+                            & PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) != 0) {
+                        pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                        uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+                        cancelNotifications = false;
+                        hideNotifications = true;
+                    } else {
+                        pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                        uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+                        cancelNotifications = false;
+                        unhideNotifications = true;
+                    }
+
                 } else if (queryRestart) {
                     pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
                     uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
@@ -1651,6 +1669,7 @@
         IntentFilter suspendedPkgFilter = new IntentFilter();
         suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
         suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+        suspendedPkgFilter.addAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
         getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL,
                 suspendedPkgFilter, null, null);
 
@@ -7743,6 +7762,20 @@
         mPackageIntentReceiver.onReceive(getContext(), intent);
     }
 
+    @VisibleForTesting
+    protected void simulatePackageDistractionBroadcast(int flag, String[] pkgs) {
+        // only use for testing: mimic receive broadcast that package is (un)distracting
+        // but does not actually register that info with packagemanager
+        final Bundle extras = new Bundle();
+        extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs);
+        extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag);
+
+        final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
+        intent.putExtras(extras);
+
+        mPackageIntentReceiver.onReceive(getContext(), intent);
+    }
+
     /**
      * Wrapper for a StatusBarNotification object that allows transfer across a oneway
      * binder without sending large amounts of data over a oneway transaction.
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index ac965fa..ab49ebb 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -667,15 +667,15 @@
                                         getUserSentiment()));
                     }
                 }
-                if (signals.containsKey(Adjustment.KEY_SMART_ACTIONS)) {
+                if (signals.containsKey(Adjustment.KEY_CONTEXTUAL_ACTIONS)) {
                     setSystemGeneratedSmartActions(
-                            signals.getParcelableArrayList(Adjustment.KEY_SMART_ACTIONS));
+                            signals.getParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS));
                     MetricsLogger.action(getAdjustmentLogMaker()
                             .addTaggedData(MetricsEvent.ADJUSTMENT_KEY_SMART_ACTIONS,
                                     getSystemGeneratedSmartActions().size()));
                 }
-                if (signals.containsKey(Adjustment.KEY_SMART_REPLIES)) {
-                    setSmartReplies(signals.getCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES));
+                if (signals.containsKey(Adjustment.KEY_TEXT_REPLIES)) {
+                    setSmartReplies(signals.getCharSequenceArrayList(Adjustment.KEY_TEXT_REPLIES));
                     MetricsLogger.action(getAdjustmentLogMaker()
                             .addTaggedData(MetricsEvent.ADJUSTMENT_KEY_SMART_REPLIES,
                                     getSmartReplies().size()));
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index 3d88f20..2aaa1ed 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -176,6 +176,14 @@
                     // only use for testing
                     mDirectService.simulatePackageSuspendBroadcast(false, getNextArgRequired());
                 }
+                case "distract_package": {
+                    // only use for testing
+                    // Flag values are in
+                    // {@link android.content.pm.PackageManager.DistractionRestriction}.
+                    mDirectService.simulatePackageDistractionBroadcast(
+                            Integer.parseInt(getNextArgRequired()),
+                            getNextArgRequired().split(","));
+                }
                 break;
                 case "post":
                 case "notify":
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index eab5c8f..146a2f3 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -28,8 +28,10 @@
 import android.app.PackageDeleteObserver;
 import android.app.PackageInstallObserver;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.IntentSender.SendIntentException;
 import android.content.pm.ApplicationInfo;
@@ -138,6 +140,8 @@
 
     private final Callbacks mCallbacks;
 
+    private volatile boolean mBootCompleted = false;
+
     /**
      * File storing persisted {@link #mSessions} metadata.
      */
@@ -203,9 +207,20 @@
         mStagingManager = new StagingManager(pm);
     }
 
+    private void setBootCompleted()  {
+        mBootCompleted = true;
+    }
+
     public void systemReady() {
         mAppOps = mContext.getSystemService(AppOpsManager.class);
 
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                setBootCompleted();
+                mContext.unregisterReceiver(this);
+            }
+        }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
         synchronized (mSessions) {
             readSessionsLocked();
 
@@ -1126,8 +1141,10 @@
 
         public void onStagedSessionChanged(PackageInstallerSession session) {
             writeSessionsAsync();
-            // TODO(b/118865310): don't send broadcast if system is not ready.
-            mPm.sendSessionUpdatedBroadcast(session.generateInfo(false), session.userId);
+            if (mBootCompleted) {
+                mPm.sendSessionUpdatedBroadcast(session.generateInfo(false),
+                        session.userId);
+            }
         }
 
         public void onSessionFinished(final PackageInstallerSession session, boolean success) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 692c032..6f1eeeb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -39,6 +39,7 @@
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
@@ -258,6 +259,8 @@
                     return runSetHarmfulAppWarning();
                 case "get-harmful-app-warning":
                     return runGetHarmfulAppWarning();
+                case "get-stagedsessions":
+                    return getStagedSessions();
                 case "uninstall-system-updates":
                     return uninstallSystemUpdates();
                 default: {
@@ -282,6 +285,28 @@
         return -1;
     }
 
+    private int getStagedSessions() {
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            List<SessionInfo> stagedSessionsList =
+                    mInterface.getPackageInstaller().getStagedSessions().getList();
+            for (SessionInfo session: stagedSessionsList) {
+                pw.println("appPackageName = " + session.getAppPackageName()
+                        + "; sessionId = " + session.getSessionId()
+                        + "; isStaged = " + session.isStaged()
+                        + "; isSessionReady = " + session.isSessionReady()
+                        + "; isSessionApplied = " + session.isSessionApplied()
+                        + "; isSessionFailed = " + session.isSessionFailed() + ";");
+            }
+        } catch (RemoteException e) {
+            pw.println("Failure ["
+                    + e.getClass().getName() + " - "
+                    + e.getMessage() + "]");
+            return 0;
+        }
+        return 1;
+    }
+
     private int uninstallSystemUpdates() {
         final PrintWriter pw = getOutPrintWriter();
         List<String> failedUninstalls = new LinkedList<>();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 30b5e49..3c89d78 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1049,6 +1049,8 @@
                     updatedUserIds);
             updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origPermissions,
                     permissionsState, pkg, updatedUserIds);
+
+            setAppOpsLocked(permissionsState, pkg);
         }
 
         // Persist the runtime permissions state for users with changes. If permissions
@@ -1387,6 +1389,60 @@
         return updatedUserIds;
     }
 
+    /**
+     * Fix app-op modes for runtime permissions.
+     *
+     * @param permsState The state of the permissions of the package
+     * @param pkg The package information
+     */
+    private void setAppOpsLocked(@NonNull PermissionsState permsState,
+            @NonNull PackageParser.Package pkg) {
+        for (int userId : UserManagerService.getInstance().getUserIds()) {
+            int numPerms = pkg.requestedPermissions.size();
+            for (int i = 0; i < numPerms; i++) {
+                String permission = pkg.requestedPermissions.get(i);
+
+                int op = permissionToOpCode(permission);
+                if (op == OP_NONE) {
+                    continue;
+                }
+
+                // Runtime permissions are per uid, not per package, hence per package app-op
+                // modes should never have been set. It is possible to set them via the shell
+                // though. Revert such settings during boot to get the device back into a good
+                // state.
+                LocalServices.getService(AppOpsManagerInternal.class).setAllPkgModesToDefault(
+                        op, getUid(userId, getAppId(pkg.applicationInfo.uid)));
+
+                // For pre-M apps the runtime permission do not store the state
+                if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+                    continue;
+                }
+
+                PermissionState state = permsState.getRuntimePermissionState(permission, userId);
+                if (state == null) {
+                    continue;
+                }
+
+                // Adjust app-op mods for foreground/background permissions. If an package used to
+                // have both fg and bg permission granted and it lost the bg permission during an
+                // upgrade the app-op mode should get downgraded to foreground.
+                if (state.isGranted()) {
+                    BasePermission bp = mSettings.getPermission(permission);
+
+                    if (bp != null && bp.perm != null && bp.perm.info != null
+                            && bp.perm.info.backgroundPermission != null) {
+                        PermissionState bgState = permsState.getRuntimePermissionState(
+                                bp.perm.info.backgroundPermission, userId);
+
+                        setAppOpMode(permission, pkg, userId, bgState != null && bgState.isGranted()
+                                        ? MODE_ALLOWED : MODE_FOREGROUND);
+                    }
+                }
+            }
+        }
+    }
+
     private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
         boolean allowed = false;
         final int NP = PackageParser.NEW_PERMISSIONS.length;
diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
index 076c94c..09bacd6 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -7,6 +7,17 @@
                     "include-filter": "com.google.android.permission.gts.DefaultPermissionGrantPolicyTest"
                 }
             ]
+        },
+        {
+            "name": "CtsPermissionTestCases",
+            "options": [
+                {
+                    "include-filter": "android.permission.cts.BackgroundPermissionsTest"
+                },
+                {
+                    "include-filter": "android.permission.cts.SplitPermissionTest"
+                }
+            ]
         }
     ]
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java
new file mode 100644
index 0000000..a2c8dac
--- /dev/null
+++ b/services/core/java/com/android/server/power/AttentionDetector.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import android.attention.AttentionManagerInternal;
+import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.SystemClock;
+import android.service.attention.AttentionService;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+import java.io.PrintWriter;
+
+/**
+ * Class responsible for checking if the user is currently paying attention to the phone and
+ * notifying {@link PowerManagerService} that user activity should be renewed.
+ *
+ * This class also implements a limit of how long the extension should be, to avoid security
+ * issues where the device would never be locked.
+ */
+public class AttentionDetector {
+
+    private static final String TAG = "AttentionDetector";
+    private static final boolean DEBUG = false;
+
+    /**
+     * Invoked whenever user attention is detected.
+     */
+    private final Runnable mOnUserAttention;
+
+    /**
+     * The maximum time, in millis, that the phone can stay unlocked because of attention events,
+     * triggered by any user.
+     */
+    @VisibleForTesting
+    protected long mMaximumExtensionMillis;
+
+    private final Object mLock;
+
+    /**
+     * {@link android.service.attention.AttentionService} API timeout.
+     */
+    private long mMaxAttentionApiTimeoutMillis;
+
+    /**
+     * Last known user activity.
+     */
+    private long mLastUserActivityTime;
+
+    @VisibleForTesting
+    protected AttentionManagerInternal mAttentionManager;
+
+    /**
+     * If we're currently waiting for an attention callback
+     */
+    private boolean mRequested;
+
+    /**
+     * Current wakefulness of the device. {@see PowerManagerInternal}
+     */
+    private int mWakefulness;
+
+    @VisibleForTesting
+    final AttentionCallbackInternal mCallback = new AttentionCallbackInternal() {
+
+        @Override
+        public void onSuccess(int requestCode, int result, long timestamp) {
+            Slog.v(TAG, "onSuccess: " + requestCode + ", " + result
+                    + " - current requestCode: " + getRequestCode());
+            synchronized (mLock) {
+                if (requestCode == getRequestCode() && mRequested) {
+                    mRequested = false;
+                    if (mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+                        if (DEBUG) Slog.d(TAG, "Device slept before receiving callback.");
+                        return;
+                    }
+                    if (result == AttentionService.ATTENTION_SUCCESS_PRESENT) {
+                        mOnUserAttention.run();
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onFailure(int requestCode, int error) {
+            Slog.i(TAG, "Failed to check attention: " + error);
+            synchronized (mLock) {
+                if (requestCode == getRequestCode()) {
+                    mRequested = false;
+                }
+            }
+        }
+    };
+
+    public AttentionDetector(Runnable onUserAttention, Object lock) {
+        mOnUserAttention = onUserAttention;
+        mLock = lock;
+    }
+
+    public void systemReady(Context context) {
+        mAttentionManager = LocalServices.getService(AttentionManagerInternal.class);
+        mMaximumExtensionMillis = context.getResources().getInteger(
+                com.android.internal.R.integer.config_attentionMaximumExtension);
+        mMaxAttentionApiTimeoutMillis = context.getResources().getInteger(
+                com.android.internal.R.integer.config_attentionApiTimeout);
+    }
+
+    public long updateUserActivity(long nextScreenDimming) {
+        if (!isAttentionServiceSupported()) {
+            return nextScreenDimming;
+        }
+
+        final long now = SystemClock.uptimeMillis();
+        final long whenToCheck = nextScreenDimming - getAttentionTimeout();
+        final long whenToStopExtending = mLastUserActivityTime + mMaximumExtensionMillis;
+        if (now < whenToCheck) {
+            if (DEBUG) {
+                Slog.d(TAG, "Do not check for attention yet, wait " + (whenToCheck - now));
+            }
+            return nextScreenDimming;
+        } else if (whenToStopExtending < whenToCheck) {
+            if (DEBUG) {
+                Slog.d(TAG, "Let device sleep to avoid false results and improve security "
+                        + (whenToCheck - whenToStopExtending));
+            }
+            return nextScreenDimming;
+        } else if (mRequested) {
+            if (DEBUG) {
+                Slog.d(TAG, "Pending attention callback, wait. " + getRequestCode());
+            }
+            return whenToCheck;
+        }
+
+        // Ideally we should attribute mRequested to the result of #checkAttention, but the
+        // callback might arrive before #checkAttention returns (if there are cached results.)
+        // This means that we must assume that the request was successful, and then cancel it
+        // afterwards if AttentionManager couldn't deliver it.
+        mRequested = true;
+        final boolean sent = mAttentionManager.checkAttention(getRequestCode(),
+                getAttentionTimeout(), mCallback);
+        if (!sent) {
+            mRequested = false;
+        }
+
+        Slog.v(TAG, "Checking user attention with request code: " + getRequestCode());
+        return whenToCheck;
+    }
+
+    /**
+     * Handles user activity by cancelling any pending attention requests and keeping track of when
+     * the activity happened.
+     *
+     * @param eventTime Activity time, in uptime millis.
+     * @param event Activity type as defined in {@link PowerManager}.
+     * @return 0 when activity was ignored, 1 when handled, -1 when invalid.
+     */
+    public int onUserActivity(long eventTime, int event) {
+        switch (event) {
+            case PowerManager.USER_ACTIVITY_EVENT_ATTENTION:
+                return 0;
+            case PowerManager.USER_ACTIVITY_EVENT_OTHER:
+            case PowerManager.USER_ACTIVITY_EVENT_BUTTON:
+            case PowerManager.USER_ACTIVITY_EVENT_TOUCH:
+            case PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY:
+                cancelCurrentRequestIfAny();
+                mLastUserActivityTime = eventTime;
+                return 1;
+            default:
+                if (DEBUG) {
+                    Slog.d(TAG, "Attention not reset. Unknown activity event: " + event);
+                }
+                return -1;
+        }
+    }
+
+    public void onWakefulnessChangeStarted(int wakefulness) {
+        mWakefulness = wakefulness;
+        if (wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+            cancelCurrentRequestIfAny();
+        }
+    }
+
+    private void cancelCurrentRequestIfAny() {
+        if (mRequested) {
+            mAttentionManager.cancelAttentionCheck(getRequestCode());
+            mRequested = false;
+        }
+    }
+
+    @VisibleForTesting
+    int getRequestCode() {
+        return (int) (mLastUserActivityTime % Integer.MAX_VALUE);
+    }
+
+    @VisibleForTesting
+    long getAttentionTimeout() {
+        return mMaxAttentionApiTimeoutMillis;
+    }
+
+    /**
+     * {@see AttentionManagerInternal#isAttentionServiceSupported}
+     */
+    @VisibleForTesting
+    boolean isAttentionServiceSupported() {
+        return mAttentionManager.isAttentionServiceSupported();
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.print("AttentionDetector:");
+        pw.print(" mMaximumExtensionMillis=" + mMaximumExtensionMillis);
+        pw.print(" mMaxAttentionApiTimeoutMillis=" + mMaxAttentionApiTimeoutMillis);
+        pw.print(" mLastUserActivityTime(excludingAttention)=" + mLastUserActivityTime);
+        pw.print(" mAttentionServiceSupported=" + isAttentionServiceSupported());
+        pw.print(" mRequested=" + mRequested);
+    }
+}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a027873..3be6480 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -229,6 +229,7 @@
     private final BatterySaverController mBatterySaverController;
     private final BatterySaverStateMachine mBatterySaverStateMachine;
     private final BatterySavingStats mBatterySavingStats;
+    private final AttentionDetector mAttentionDetector;
     private final BinderService mBinderService;
     private final LocalService mLocalService;
     private final NativeWrapper mNativeWrapper;
@@ -736,6 +737,7 @@
         mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
         mConstants = new Constants(mHandler);
         mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+        mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock);
 
         mBatterySavingStats = new BatterySavingStats(mLock);
         mBatterySaverPolicy =
@@ -804,6 +806,7 @@
             mDisplayManagerInternal = getLocalService(DisplayManagerInternal.class);
             mPolicy = getLocalService(WindowManagerPolicy.class);
             mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
+            mAttentionDetector.systemReady(mContext);
 
             PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
             mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting();
@@ -1326,6 +1329,16 @@
         }
     }
 
+    private void onUserAttention() {
+        synchronized (mLock) {
+            if (userActivityNoUpdateLocked(SystemClock.uptimeMillis(),
+                    PowerManager.USER_ACTIVITY_EVENT_ATTENTION, 0 /* flags */,
+                    Process.SYSTEM_UID)) {
+                updatePowerStateLocked();
+            }
+        }
+    }
+
     private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) {
         if (DEBUG_SPEW) {
             Slog.d(TAG, "userActivityNoUpdateLocked: eventTime=" + eventTime
@@ -1346,6 +1359,7 @@
             }
 
             mNotifier.onUserActivity(event, uid);
+            mAttentionDetector.onUserActivity(eventTime, event);
 
             if (mUserInactiveOverrideFromWindowManager) {
                 mUserInactiveOverrideFromWindowManager = false;
@@ -1593,6 +1607,7 @@
             if (mNotifier != null) {
                 mNotifier.onWakefulnessChangeStarted(wakefulness, reason);
             }
+            mAttentionDetector.onWakefulnessChangeStarted(wakefulness);
         }
     }
 
@@ -2085,6 +2100,10 @@
                     nextTimeout = -1;
                 }
 
+                if ((mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0) {
+                    nextTimeout = mAttentionDetector.updateUserActivity(nextTimeout);
+                }
+
                 if (nextProfileTimeout > 0) {
                     nextTimeout = Math.min(nextTimeout, nextProfileTimeout);
                 }
@@ -3477,6 +3496,7 @@
 
             mBatterySaverPolicy.dump(pw);
             mBatterySaverStateMachine.dump(pw);
+            mAttentionDetector.dump(pw);
 
             pw.println();
             final int numProfiles = mProfilePowerState.size();
diff --git a/services/core/java/com/android/server/role/FinancialSmsManager.java b/services/core/java/com/android/server/role/FinancialSmsManager.java
new file mode 100644
index 0000000..2ec3993
--- /dev/null
+++ b/services/core/java/com/android/server/role/FinancialSmsManager.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.role;
+
+import android.Manifest;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.sms.FinancialSmsService;
+import android.service.sms.IFinancialSmsService;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * This class binds to {@code FinancialSmsService}.
+ */
+final class FinancialSmsManager {
+
+    private static final String TAG = "FinancialSmsManager";
+
+    private final Context mContext;
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private ServiceConnection mServiceConnection;
+
+    @GuardedBy("mLock")
+    private IFinancialSmsService mRemoteService;
+
+    @GuardedBy("mLock")
+    private ArrayList<Command> mQueuedCommands;
+
+    FinancialSmsManager(Context context) {
+        mContext = context;
+    }
+
+    @Nullable
+    ServiceInfo getServiceInfo() {
+        final String packageName =
+                mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
+        if (packageName == null) {
+            Slog.w(TAG, "no external services package!");
+            return null;
+        }
+
+        final Intent intent = new Intent(FinancialSmsService.ACTION_FINANCIAL_SERVICE_INTENT);
+        intent.setPackage(packageName);
+        final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+                PackageManager.GET_SERVICES);
+        if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+            Slog.w(TAG, "No valid components found.");
+            return null;
+        }
+        return resolveInfo.serviceInfo;
+    }
+
+    @Nullable
+    private ComponentName getServiceComponentName() {
+        final ServiceInfo serviceInfo = getServiceInfo();
+        if (serviceInfo == null) return null;
+
+        final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+        if (!Manifest.permission.BIND_FINANCIAL_SMS_SERVICE.equals(serviceInfo.permission)) {
+            Slog.w(TAG, name.flattenToShortString() + " does not require permission "
+                    + Manifest.permission.BIND_FINANCIAL_SMS_SERVICE);
+            return null;
+        }
+
+        return name;
+    }
+
+    void reset() {
+        synchronized (mLock) {
+            if (mServiceConnection != null) {
+                mContext.unbindService(mServiceConnection);
+                mServiceConnection = null;
+            } else {
+                Slog.d(TAG, "reset(): service is not bound. Do nothing.");
+            }
+        }
+    }
+
+    /**
+     * Run a command, starting the service connection if necessary.
+     */
+    private void connectAndRun(@NonNull Command command) {
+        synchronized (mLock) {
+            if (mRemoteService != null) {
+                try {
+                    command.run(mRemoteService);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "exception calling service: " + e);
+                }
+                return;
+            } else {
+                if (mQueuedCommands == null) {
+                    mQueuedCommands = new ArrayList<>(1);
+                }
+                mQueuedCommands.add(command);
+                // If we're already connected, don't create a new connection, just leave - the
+                // command will be run when the service connects
+                if (mServiceConnection != null) return;
+            }
+
+            // Create the connection
+            mServiceConnection = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                    synchronized (mLock) {
+                        mRemoteService = IFinancialSmsService.Stub.asInterface(service);
+                        if (mQueuedCommands != null) {
+                            final int size = mQueuedCommands.size();
+                            for (int i = 0; i < size; i++) {
+                                final Command queuedCommand = mQueuedCommands.get(i);
+                                try {
+                                    queuedCommand.run(mRemoteService);
+                                } catch (RemoteException e) {
+                                    Slog.w(TAG, "exception calling " + name + ": " + e);
+                                }
+                            }
+                            mQueuedCommands = null;
+                        }
+                    }
+                }
+
+                @Override
+                @MainThread
+                public void onServiceDisconnected(ComponentName name) {
+                    synchronized (mLock) {
+                        mRemoteService = null;
+                    }
+                }
+
+                @Override
+                public void onBindingDied(ComponentName name) {
+                    synchronized (mLock) {
+                        mRemoteService = null;
+                    }
+                }
+
+                @Override
+                public void onNullBinding(ComponentName name) {
+                    synchronized (mLock) {
+                        mRemoteService = null;
+                    }
+                }
+            };
+
+            final ComponentName component = getServiceComponentName();
+            if (component != null) {
+                final Intent intent = new Intent();
+                intent.setComponent(component);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mContext.bindServiceAsUser(intent, mServiceConnection, Context.BIND_AUTO_CREATE,
+                            UserHandle.getUserHandleForUid(UserHandle.getCallingUserId()));
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        }
+    }
+
+    void getSmsMessages(RemoteCallback callback, @Nullable Bundle params) {
+        connectAndRun((service) -> service.getSmsMessages(callback, params));
+    }
+
+    void dump(String prefix, PrintWriter pw) {
+        final ComponentName impl = getServiceComponentName();
+        pw.print(prefix); pw.print("User ID: "); pw.println(UserHandle.getCallingUserId());
+        pw.print(prefix); pw.print("Queued commands: ");
+        if (mQueuedCommands == null) {
+            pw.println("N/A");
+        } else {
+            pw.println(mQueuedCommands.size());
+        }
+        pw.print(prefix); pw.print("Implementation: ");
+        if (impl == null) {
+            pw.println("N/A");
+            return;
+        }
+        pw.println(impl.flattenToShortString());
+    }
+
+    private interface Command {
+        void run(IFinancialSmsService service) throws RemoteException;
+    }
+}
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index 1c7596b..7d8c7c9 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -33,16 +33,24 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.PermissionChecker;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.Signature;
+import android.database.CursorWindow;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
+import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
+import android.service.sms.FinancialSmsService;
+import android.telephony.IFinancialSmsCallback;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.PackageUtils;
@@ -620,5 +628,51 @@
 
             dumpOutputStream.flush();
         }
+
+        /**
+         * Get filtered SMS messages for financial app.
+         */
+        @Override
+        public void getSmsMessagesForFinancialApp(
+                String callingPkg, Bundle params, IFinancialSmsCallback callback) {
+            int mode = PermissionChecker.checkCallingOrSelfPermission(
+                    getContext(),
+                    AppOpsManager.OPSTR_SMS_FINANCIAL_TRANSACTIONS);
+
+            if (mode == PermissionChecker.PERMISSION_GRANTED) {
+                FinancialSmsManager financialSmsManager = new FinancialSmsManager(getContext());
+                financialSmsManager.getSmsMessages(new RemoteCallback((result) -> {
+                    CursorWindow messages = null;
+                    if (result == null) {
+                        Slog.w(LOG_TAG, "result is null.");
+                    } else {
+                        messages = result.getParcelable(FinancialSmsService.EXTRA_SMS_MSGS);
+                    }
+                    try {
+                        callback.onGetSmsMessagesForFinancialApp(messages);
+                    } catch (RemoteException e) {
+                        // do nothing
+                    }
+                }), params);
+            } else {
+                try {
+                    callback.onGetSmsMessagesForFinancialApp(null);
+                } catch (RemoteException e) {
+                    // do nothing
+                }
+            }
+        }
+
+        private int getUidForPackage(String packageName) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                return getContext().getPackageManager().getApplicationInfo(packageName,
+                        PackageManager.MATCH_ANY_USER).uid;
+            } catch (NameNotFoundException nnfe) {
+                return -1;
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 8b4c410..e59228a 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -28,7 +28,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
 import android.content.pm.ParceledListSlice;
-import android.content.pm.StringParceledListSlice;
 import android.content.pm.VersionedPackage;
 import android.content.rollback.IRollbackManager;
 import android.content.rollback.PackageRollbackInfo;
@@ -56,12 +55,10 @@
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
-import java.util.Set;
 
 /**
  * Implementation of service that manages APK level rollbacks.
@@ -200,48 +197,20 @@
     }
 
     @Override
-    public RollbackInfo getAvailableRollback(String packageName) {
+    public ParceledListSlice getAvailableRollbacks() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_ROLLBACKS,
-                "getAvailableRollback");
+                "getAvailableRollbacks");
 
-        RollbackData data = getRollbackForPackage(packageName);
-        if (data == null) {
-            return null;
-        }
-
-        // Note: The rollback for the package ought to be for the currently
-        // installed version, otherwise the rollback data is out of date. In
-        // that rare case, we'll check when we execute the rollback whether
-        // it's out of date or not, so no need to check package versions here.
-
-        for (PackageRollbackInfo info : data.packages) {
-            if (info.getPackageName().equals(packageName)) {
-                // TODO: Once the RollbackInfo API supports info about
-                // dependant packages, add that info here.
-                return new RollbackInfo(data.rollbackId, info);
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public StringParceledListSlice getPackagesWithAvailableRollbacks() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.MANAGE_ROLLBACKS,
-                "getPackagesWithAvailableRollbacks");
-
-        final Set<String> packageNames = new HashSet<>();
         synchronized (mLock) {
             ensureRollbackDataLoadedLocked();
+            List<RollbackInfo> rollbacks = new ArrayList<>();
             for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
                 RollbackData data = mAvailableRollbacks.get(i);
-                for (PackageRollbackInfo info : data.packages) {
-                    packageNames.add(info.getPackageName());
-                }
+                rollbacks.add(new RollbackInfo(data.rollbackId, data.packages));
             }
+            return new ParceledListSlice<>(rollbacks);
         }
-        return new StringParceledListSlice(new ArrayList<>(packageNames));
     }
 
     @Override
@@ -258,7 +227,7 @@
     }
 
     @Override
-    public void executeRollback(RollbackInfo rollback, String callerPackageName,
+    public void commitRollback(int rollbackId, String callerPackageName,
             IntentSender statusReceiver) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_ROLLBACKS,
@@ -269,28 +238,21 @@
         appOps.checkPackage(callingUid, callerPackageName);
 
         getHandler().post(() ->
-                executeRollbackInternal(rollback, callerPackageName, statusReceiver));
+                commitRollbackInternal(rollbackId, callerPackageName, statusReceiver));
     }
 
     /**
-     * Performs the actual work to execute a rollback.
+     * Performs the actual work to commit a rollback.
      * The work is done on the current thread. This may be a long running
      * operation.
      */
-    private void executeRollbackInternal(RollbackInfo rollback,
+    private void commitRollbackInternal(int rollbackId,
             String callerPackageName, IntentSender statusReceiver) {
-        String targetPackageName = rollback.targetPackage.getPackageName();
-        Log.i(TAG, "Initiating rollback of " + targetPackageName);
+        Log.i(TAG, "Initiating rollback");
 
-        // Get the latest RollbackData for the target package.
-        final RollbackData data = getRollbackForPackage(targetPackageName);
+        RollbackData data = getRollbackForId(rollbackId);
         if (data == null) {
-            sendFailure(statusReceiver, "No rollback available for package.");
-            return;
-        }
-
-        if (data.rollbackId != rollback.getRollbackId()) {
-            sendFailure(statusReceiver, "Rollback for package is out of date.");
+            sendFailure(statusReceiver, "Rollback unavailable");
             return;
         }
 
@@ -335,14 +297,8 @@
         PackageManager pm = context.getPackageManager();
         try {
             PackageInstaller packageInstaller = pm.getPackageInstaller();
-            String installerPackageName = pm.getInstallerPackageName(targetPackageName);
-            if (installerPackageName == null) {
-                sendFailure(statusReceiver, "Cannot find installer package");
-                return;
-            }
             PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
                     PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-            parentParams.setInstallerPackageName(installerPackageName);
             parentParams.setAllowDowngrade(true);
             parentParams.setMultiPackage();
             int parentSessionId = packageInstaller.createSession(parentParams);
@@ -351,6 +307,11 @@
             for (PackageRollbackInfo info : data.packages) {
                 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                         PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+                String installerPackageName = pm.getInstallerPackageName(info.getPackageName());
+                if (installerPackageName == null) {
+                    sendFailure(statusReceiver, "Cannot find installer package");
+                    return;
+                }
                 params.setInstallerPackageName(installerPackageName);
                 params.setAllowDowngrade(true);
                 int sessionId = packageInstaller.createSession(params);
@@ -389,10 +350,11 @@
                                 return;
                             }
 
-                            addRecentlyExecutedRollback(rollback);
+                            addRecentlyExecutedRollback(
+                                    new RollbackInfo(data.rollbackId, data.packages));
                             sendSuccess(statusReceiver);
 
-                            Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+                            Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
 
                             // TODO: This call emits the warning "Calling a method in the
                             // system process without a qualified user". Fix that.
@@ -406,7 +368,7 @@
             data.inProgress = true;
             parentSession.commit(receiver.getIntentSender());
         } catch (IOException e) {
-            Log.e(TAG, "Unable to roll back " + targetPackageName, e);
+            Log.e(TAG, "Rollback failed", e);
             sendFailure(statusReceiver, "IOException: " + e.toString());
             return;
         }
@@ -537,9 +499,12 @@
             boolean changed = false;
             while (iter.hasNext()) {
                 RollbackInfo rollback = iter.next();
-                if (packageName.equals(rollback.targetPackage.getPackageName())) {
-                    iter.remove();
-                    changed = true;
+                for (PackageRollbackInfo info : rollback.getPackages()) {
+                    if (packageName.equals(info.getPackageName())) {
+                        iter.remove();
+                        changed = true;
+                        break;
+                    }
                 }
             }
 
@@ -935,6 +900,25 @@
         return null;
     }
 
+    /*
+     * Returns the RollbackData, if any, for an available rollback with the
+     * given rollbackId.
+     */
+    private RollbackData getRollbackForId(int rollbackId) {
+        synchronized (mLock) {
+            // TODO: Have ensureRollbackDataLoadedLocked return the list of
+            // available rollbacks, to hopefully avoid forgetting to call it?
+            ensureRollbackDataLoadedLocked();
+            for (int i = 0; i < mAvailableRollbacks.size(); ++i) {
+                RollbackData data = mAvailableRollbacks.get(i);
+                if (data.rollbackId == rollbackId) {
+                    return data;
+                }
+            }
+        }
+        return null;
+    }
+
     @GuardedBy("mLock")
     private int allocateRollbackIdLocked() throws IOException {
         int n = 0;
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 1f2f1cc..fcae618 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInstaller;
+import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
 import android.os.Handler;
@@ -26,6 +27,7 @@
 
 import com.android.server.PackageWatchdog;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 
 import java.util.List;
 
@@ -38,10 +40,12 @@
     private static final String TAG = "RollbackPackageHealthObserver";
     private static final String NAME = "rollback-observer";
     private Context mContext;
+    private RollbackManager mRollbackManager;
     private Handler mHandler;
 
     RollbackPackageHealthObserver(Context context) {
         mContext = context;
+        mRollbackManager = mContext.getSystemService(RollbackManager.class);
         HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
         handlerThread.start();
         mHandler = handlerThread.getThreadHandler();
@@ -49,16 +53,48 @@
     }
 
     @Override
-    public boolean onHealthCheckFailed(String packageName) {
-        RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
-        RollbackInfo rollback = rollbackManager.getAvailableRollback(packageName);
-        if (rollback != null) {
-            // TODO(zezeozue): Only rollback if rollback version == failed package version
-            mHandler.post(() -> executeRollback(rollbackManager, rollback));
-            return true;
+    public int onHealthCheckFailed(String packageName) {
+        RollbackInfo rollback = getAvailableRollback(packageName);
+        if (rollback == null) {
+            // Don't handle the notification, no rollbacks available for the package
+            return PackageHealthObserverImpact.USER_IMPACT_NONE;
         }
-        // Don't handle the notification, no rollbacks available
-        return false;
+        // Rollback is available, we may get a callback into #execute
+        return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+    }
+
+    @Override
+    public boolean execute(String packageName) {
+        RollbackInfo rollback = getAvailableRollback(packageName);
+        if (rollback == null) {
+            // Expected a rollback to be available, what happened?
+            return false;
+        }
+
+        // TODO(zezeozue): Only rollback if rollback version == failed package version
+        LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> {
+            int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                    PackageInstaller.STATUS_FAILURE);
+            if (status == PackageInstaller.STATUS_SUCCESS) {
+                // TODO(zezeozue); Log success metrics
+                // Rolledback successfully, no action required by other observers
+            } else {
+                // TODO(zezeozue); Log failure metrics
+                // Rollback failed other observers should have a shot
+            }
+        });
+
+        // TODO(zezeozue): Log initiated metrics
+        mHandler.post(() ->
+                mRollbackManager.commitRollback(rollback.getRollbackId(),
+                    rollbackReceiver.getIntentSender()));
+        // Assume rollback executed successfully
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
     }
 
     /**
@@ -69,26 +105,15 @@
         PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
     }
 
-    private void executeRollback(RollbackManager manager, RollbackInfo rollback) {
-        // TODO(zezeozue): Log initiated metrics
-        LocalIntentReceiver rollbackReceiver = new LocalIntentReceiver((Intent result) -> {
-            mHandler.post(() -> {
-                int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                        PackageInstaller.STATUS_FAILURE);
-                if (status == PackageInstaller.STATUS_SUCCESS) {
-                    // TODO(zezeozue); Log success metrics
-                    // Rolledback successfully, no action required by other observers
-                } else {
-                    // TODO(zezeozue); Log failure metrics
-                    // Rollback failed other observers should have a shot
+    private RollbackInfo getAvailableRollback(String packageName) {
+        for (RollbackInfo rollback : mRollbackManager.getAvailableRollbacks()) {
+            for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
+                if (packageName.equals(packageRollback.getPackageName())) {
+                    // TODO(zezeozue): Only rollback if rollback version == failed package version
+                    return rollback;
                 }
-            });
-        });
-        manager.executeRollback(rollback, rollbackReceiver.getIntentSender());
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
+            }
+        }
+        return null;
     }
 }
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 7738be9..3b24b3e 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -114,13 +114,9 @@
                 for (int i = 0; i < array.length(); ++i) {
                     JSONObject element = array.getJSONObject(i);
                     int rollbackId = element.getInt("rollbackId");
-                    String packageName = element.getString("packageName");
-                    long higherVersionCode = element.getLong("higherVersionCode");
-                    long lowerVersionCode = element.getLong("lowerVersionCode");
-                    PackageRollbackInfo target = new PackageRollbackInfo(
-                            new VersionedPackage(packageName, higherVersionCode),
-                            new VersionedPackage(packageName, lowerVersionCode));
-                    RollbackInfo rollback = new RollbackInfo(rollbackId, target);
+                    List<PackageRollbackInfo> packages = packageRollbackInfosFromJson(
+                            element.getJSONArray("packages"));
+                    RollbackInfo rollback = new RollbackInfo(rollbackId, packages);
                     recentlyExecutedRollbacks.add(rollback);
                 }
             } catch (IOException | JSONException e) {
@@ -155,18 +151,8 @@
     void saveAvailableRollback(RollbackData data) throws IOException {
         try {
             JSONObject dataJson = new JSONObject();
-            JSONArray packagesJson = new JSONArray();
-            for (PackageRollbackInfo info : data.packages) {
-                JSONObject infoJson = new JSONObject();
-                infoJson.put("packageName", info.getPackageName());
-                infoJson.put("higherVersionCode",
-                        info.getVersionRolledBackFrom().getLongVersionCode());
-                infoJson.put("lowerVersionCode",
-                        info.getVersionRolledBackTo().getVersionCode());
-                packagesJson.put(infoJson);
-            }
             dataJson.put("rollbackId", data.rollbackId);
-            dataJson.put("packages", packagesJson);
+            dataJson.put("packages", toJson(data.packages));
             dataJson.put("timestamp", data.timestamp.toString());
 
             PrintWriter pw = new PrintWriter(new File(data.backupDir, "rollback.json"));
@@ -200,11 +186,7 @@
                 RollbackInfo rollback = recentlyExecutedRollbacks.get(i);
                 JSONObject element = new JSONObject();
                 element.put("rollbackId", rollback.getRollbackId());
-                element.put("packageName", rollback.targetPackage.getPackageName());
-                element.put("higherVersionCode",
-                        rollback.targetPackage.getVersionRolledBackFrom().getLongVersionCode());
-                element.put("lowerVersionCode",
-                        rollback.targetPackage.getVersionRolledBackTo().getLongVersionCode());
+                element.put("packages", toJson(rollback.getPackages()));
                 array.put(element);
             }
 
@@ -229,18 +211,7 @@
 
             int rollbackId = dataJson.getInt("rollbackId");
             RollbackData data = new RollbackData(rollbackId, backupDir);
-
-            JSONArray packagesJson = dataJson.getJSONArray("packages");
-            for (int i = 0; i < packagesJson.length(); ++i) {
-                JSONObject infoJson = packagesJson.getJSONObject(i);
-                String packageName = infoJson.getString("packageName");
-                long higherVersionCode = infoJson.getLong("higherVersionCode");
-                long lowerVersionCode = infoJson.getLong("lowerVersionCode");
-                data.packages.add(new PackageRollbackInfo(
-                        new VersionedPackage(packageName, higherVersionCode),
-                        new VersionedPackage(packageName, lowerVersionCode)));
-            }
-
+            data.packages.addAll(packageRollbackInfosFromJson(dataJson.getJSONArray("packages")));
             data.timestamp = Instant.parse(dataJson.getString("timestamp"));
             return data;
         } catch (JSONException | DateTimeParseException e) {
@@ -248,6 +219,40 @@
         }
     }
 
+    private JSONObject toJson(PackageRollbackInfo info) throws JSONException {
+        JSONObject json = new JSONObject();
+        json.put("packageName", info.getPackageName());
+        json.put("higherVersionCode", info.getVersionRolledBackFrom().getLongVersionCode());
+        json.put("lowerVersionCode", info.getVersionRolledBackTo().getLongVersionCode());
+        return json;
+    }
+
+    private PackageRollbackInfo packageRollbackInfoFromJson(JSONObject json) throws JSONException {
+        String packageName = json.getString("packageName");
+        long higherVersionCode = json.getLong("higherVersionCode");
+        long lowerVersionCode = json.getLong("lowerVersionCode");
+        return new PackageRollbackInfo(
+                new VersionedPackage(packageName, higherVersionCode),
+                new VersionedPackage(packageName, lowerVersionCode));
+    }
+
+    private JSONArray toJson(List<PackageRollbackInfo> infos) throws JSONException {
+        JSONArray json = new JSONArray();
+        for (PackageRollbackInfo info : infos) {
+            json.put(toJson(info));
+        }
+        return json;
+    }
+
+    private List<PackageRollbackInfo> packageRollbackInfosFromJson(JSONArray json)
+            throws JSONException {
+        List<PackageRollbackInfo> infos = new ArrayList<>();
+        for (int i = 0; i < json.length(); ++i) {
+            infos.add(packageRollbackInfoFromJson(json.getJSONObject(i)));
+        }
+        return infos;
+    }
+
     /**
      * Deletes a file completely.
      * If the file is a directory, its contents are deleted as well.
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index acede7d..c6d2870 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -246,6 +246,10 @@
     @Nullable
     private final KernelCpuThreadReader mKernelCpuThreadReader;
 
+    private long mDebugElapsedClockPreviousValue = 0;
+    private long mDebugElapsedClockPullCount = 0;
+    private long mDebugFailingElapsedClockPreviousValue = 0;
+    private long mDebugFailingElapsedClockPullCount = 0;
     private BatteryStatsHelper mBatteryStatsHelper = null;
     private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000;
     private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS;
@@ -1726,6 +1730,56 @@
         }
     }
 
+    private void pullDebugElapsedClock(int tagId,
+            long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+        final long elapsedMillis = SystemClock.elapsedRealtime();
+        final long clockDiffMillis = mDebugElapsedClockPreviousValue == 0
+                ? 0 : elapsedMillis - mDebugElapsedClockPreviousValue;
+
+        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+        e.writeLong(mDebugElapsedClockPullCount);
+        e.writeLong(elapsedMillis);
+        // Log it twice to be able to test multi-value aggregation from ValueMetric.
+        e.writeLong(elapsedMillis);
+        e.writeLong(clockDiffMillis);
+        e.writeInt(1 /* always set */);
+        pulledData.add(e);
+
+        if (mDebugElapsedClockPullCount % 2 == 1) {
+            StatsLogEventWrapper e2 = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+            e2.writeLong(mDebugElapsedClockPullCount);
+            e2.writeLong(elapsedMillis);
+            // Log it twice to be able to test multi-value aggregation from ValueMetric.
+            e2.writeLong(elapsedMillis);
+            e2.writeLong(clockDiffMillis);
+            e2.writeInt(2 /* set on odd pulls */);
+            pulledData.add(e2);
+        }
+
+        mDebugElapsedClockPullCount++;
+        mDebugElapsedClockPreviousValue = elapsedMillis;
+    }
+
+    private void pullDebugFailingElapsedClock(int tagId,
+            long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
+        StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+        final long elapsedMillis = SystemClock.elapsedRealtime();
+        // Fails every 10 buckets.
+        if (mDebugFailingElapsedClockPullCount++ % 10 == 0) {
+            mDebugFailingElapsedClockPreviousValue = elapsedMillis;
+            throw new RuntimeException("Failing debug elapsed clock");
+        }
+
+        e.writeLong(mDebugFailingElapsedClockPullCount);
+        e.writeLong(elapsedMillis);
+        // Log it twice to be able to test multi-value aggregation from ValueMetric.
+        e.writeLong(elapsedMillis);
+        e.writeLong(mDebugFailingElapsedClockPreviousValue == 0
+                ? 0 : elapsedMillis - mDebugFailingElapsedClockPreviousValue);
+        mDebugFailingElapsedClockPreviousValue = elapsedMillis;
+        pulledData.add(e);
+    }
+
     /**
      * Pulls various data.
      */
@@ -1843,7 +1897,7 @@
                 pullCategorySize(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
-            case StatsLog.NUM_FINGERPRINTS: {
+            case StatsLog.NUM_FINGERPRINTS_ENROLLED: {
                 pullNumFingerprints(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
@@ -1892,6 +1946,14 @@
                 pullTemperature(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+            case StatsLog.DEBUG_ELAPSED_CLOCK: {
+                pullDebugElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
+                break;
+            }
+            case StatsLog.DEBUG_FAILING_ELAPSED_CLOCK: {
+                pullDebugFailingElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
+                break;
+            }
             default:
                 Slog.w(TAG, "No such tagId data as " + tagId);
                 return null;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index a66f0ca..b9b5aae 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -100,4 +100,11 @@
      * @param rotation rotation suggestion
      */
     void onProposedRotationChanged(int rotation, boolean isValid);
+
+    /**
+     * Notifies System UI that the display is ready to show system decorations.
+     *
+     * @param displayId display ID
+     */
+    void onDisplayReady(int displayId);
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 8d2bab4..7e87c29 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -443,6 +443,15 @@
                 } catch (RemoteException ex) {}
             }
         }
+
+        @Override
+        public void onDisplayReady(int displayId) {
+            if (mBar != null) {
+                try {
+                    mBar.onDisplayReady(displayId);
+                } catch (RemoteException ex) { }
+            }
+        }
     };
 
     private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index 23c042a..4adce58 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -66,6 +66,7 @@
     private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness";
 
     private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal;
+    private boolean mShouldSetUpTestHarnessMode;
 
     public TestHarnessModeService(Context context) {
         super(context);
@@ -96,6 +97,7 @@
             // There's no data to apply, so leave it as-is.
             return;
         }
+        mShouldSetUpTestHarnessMode = true;
         PersistentData persistentData = PersistentData.fromBytes(testHarnessModeData);
 
         SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, persistentData.mEnabled ? "1" : "0");
@@ -124,6 +126,9 @@
     }
 
     private void disableAutoSync() {
+        if (!mShouldSetUpTestHarnessMode) {
+            return;
+        }
         UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
         ContentResolver
             .setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier());
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 69040a2..b0ef8a0 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -469,6 +469,7 @@
      */
     private void extractColors(WallpaperData wallpaper) {
         String cropFile = null;
+        boolean defaultImageWallpaper = false;
         int wallpaperId;
 
         if (wallpaper.equals(mFallbackWallpaper)) {
@@ -482,6 +483,8 @@
                     || wallpaper.wallpaperComponent == null;
             if (imageWallpaper && wallpaper.cropFile != null && wallpaper.cropFile.exists()) {
                 cropFile = wallpaper.cropFile.getAbsolutePath();
+            } else if (imageWallpaper && !wallpaper.cropExists() && !wallpaper.sourceExists()) {
+                defaultImageWallpaper = true;
             }
             wallpaperId = wallpaper.wallpaperId;
         }
@@ -493,6 +496,25 @@
                 colors = WallpaperColors.fromBitmap(bitmap);
                 bitmap.recycle();
             }
+        } else if (defaultImageWallpaper) {
+            // There is no crop and source file because this is default image wallpaper.
+            try (final InputStream is =
+                         WallpaperManager.openDefaultWallpaper(mContext, FLAG_SYSTEM)) {
+                if (is != null) {
+                    try {
+                        final BitmapFactory.Options options = new BitmapFactory.Options();
+                        final Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
+                        if (bitmap != null) {
+                            colors = WallpaperColors.fromBitmap(bitmap);
+                            bitmap.recycle();
+                        }
+                    } catch (OutOfMemoryError e) {
+                        Slog.w(TAG, "Can't decode default wallpaper stream", e);
+                    }
+                }
+            } catch (IOException e) {
+                Slog.w(TAG, "Can't close default wallpaper stream", e);
+            }
         }
 
         if (colors == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 65d66f4..e817dd4 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -558,22 +558,26 @@
     }
 
     /**
-     * Pause all activities in either all of the stacks or just the back stacks.
+     * Pause all activities in either all of the stacks or just the back stacks. This is done before
+     * resuming a new activity and to make sure that previously active activities are
+     * paused in stacks that are no longer visible or in pinned windowing mode. This does not
+     * pause activities in visible stacks, so if an activity is launched within the same stack/task,
+     * then we should explicitly pause that stack's top activity.
      * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
      * @param resuming The resuming activity.
      * @param dontWait The resuming activity isn't going to wait for all activities to be paused
      *                 before resuming.
-     * @return true if any activity was paused as a result of this call.
+     * @return {@code true} if any activity was paused as a result of this call.
      */
     boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) {
         boolean someActivityPaused = false;
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = mStacks.get(stackNdx);
-            // TODO(b/111541062): Check if resumed activity on this display instead
-            if (!mRootActivityContainer.isTopDisplayFocusedStack(stack)
-                    && stack.getResumedActivity() != null) {
+            final ActivityRecord resumedActivity = stack.getResumedActivity();
+            if (resumedActivity != null
+                    && (!stack.shouldBeVisible(resuming) || !stack.isFocusable())) {
                 if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
-                        " mResumedActivity=" + stack.getResumedActivity());
+                        " mResumedActivity=" + resumedActivity);
                 someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
                         dontWait);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b8634d8..b7c35c0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1946,30 +1946,90 @@
         try {
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                     WindowVisibilityItem.obtain(true /* showWindow */));
-            if (shouldPauseWhenBecomingVisible()) {
-                // An activity must be in the {@link PAUSING} state for the system to validate
-                // the move to {@link PAUSED}.
-                setState(PAUSING, "makeVisibleIfNeeded");
-                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
-                        PauseActivityItem.obtain(finishing, false /* userLeaving */,
-                                configChangeFlags, false /* dontReport */));
-            }
+            makeActiveIfNeeded(null /* activeActivity*/);
         } catch (Exception e) {
             Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e);
         }
     }
 
-    /** Check if activity should be moved to PAUSED state when it becomes visible. */
-    private boolean shouldPauseWhenBecomingVisible() {
-        // If the activity is stopped or stopping, cycle to the paused state. We avoid doing
+    /**
+     * Make activity resumed or paused if needed.
+     * @param activeActivity an activity that is resumed or just completed pause action.
+     *                       We won't change the state of this activity.
+     */
+    boolean makeActiveIfNeeded(ActivityRecord activeActivity) {
+        if (shouldResumeActivity(activeActivity)) {
+            if (DEBUG_VISIBILITY) {
+                Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this);
+            }
+            return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */,
+                    null /* options */);
+        } else if (shouldPauseActivity(activeActivity)) {
+            if (DEBUG_VISIBILITY) {
+                Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this);
+            }
+            // An activity must be in the {@link PAUSING} state for the system to validate
+            // the move to {@link PAUSED}.
+            setState(PAUSING, "makeVisibleIfNeeded");
+            try {
+                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+                        PauseActivityItem.obtain(finishing, false /* userLeaving */,
+                                configChangeFlags, false /* dontReport */));
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check if activity should be moved to PAUSED state. The activity:
+     * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
+     * - should be non-focusable
+     * - should not be currently pausing or paused
+     * @param activeActivity the activity that is active or just completed pause action. We won't
+     *                       resume if this activity is active.
+     */
+    private boolean shouldPauseActivity(ActivityRecord activeActivity) {
+        return shouldMakeActive(activeActivity) && !isFocusable() && !isState(PAUSING, PAUSED);
+    }
+
+    /**
+     * Check if activity should be moved to RESUMED state. The activity:
+     * - should be eligible to be made active (see {@link #shouldMakeActive(ActivityRecord)})
+     * - should be focusable
+     * @param activeActivity the activity that is active or just completed pause action. We won't
+     *                       resume if this activity is active.
+     */
+    private boolean shouldResumeActivity(ActivityRecord activeActivity) {
+        return shouldMakeActive(activeActivity) && isFocusable() && !isState(RESUMED);
+    }
+
+    /**
+     * Check if activity is eligible to be made active (resumed of paused). The activity:
+     * - should be paused, stopped or stopping
+     * - should not be the currently active one or launching behind other tasks
+     * - should be either the topmost in task, or right below the top activity that is finishing
+     * If all of these conditions are not met at the same time, the activity cannot be made active.
+     */
+    private boolean shouldMakeActive(ActivityRecord activeActivity) {
+        // If the activity is stopped, stopping, cycle to an active state. We avoid doing
         // this when there is an activity waiting to become translucent as the extra binder
         // calls will lead to noticeable jank. A later call to
-        // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to the proper
-        // paused state. We also avoid doing this for the activity the stack supervisor
-        // considers the resumed activity, as normal means will bring the activity from STOPPED
-        // to RESUMED. Adding PAUSING in this scenario will lead to double lifecycles.
-        if (!isState(STOPPED, STOPPING) || getActivityStack().mTranslucentActivityWaiting != null
-                || isResumedActivityOnDisplay()) {
+        // ActivityStack#ensureActivitiesVisibleLocked will bring the activity to a proper
+        // active state.
+        if (!isState(RESUMED, PAUSED, STOPPED, STOPPING)
+                || getActivityStack().mTranslucentActivityWaiting != null) {
+            return false;
+        }
+
+        if (this == activeActivity) {
+            return false;
+        }
+
+        if (this.mLaunchTaskBehind) {
+            // This activity is being launched from behind, which means that it's not intended to be
+            // presented to user right now, even if it's set to be visible.
             return false;
         }
 
@@ -1979,14 +2039,14 @@
             throw new IllegalStateException("Activity not found in its task");
         }
         if (positionInTask == task.mActivities.size() - 1) {
-            // It's the topmost activity in the task - should become paused now
+            // It's the topmost activity in the task - should become resumed now
             return true;
         }
         // Check if activity above is finishing now and this one becomes the topmost in task.
         final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1);
         if (activityAbove.finishing && results == null) {
-            // We will only allow pausing if activity above wasn't launched for result. Otherwise it
-            // will cause this activity to resume before getting result.
+            // We will only allow making active if activity above wasn't launched for result.
+            // Otherwise it will cause this activity to resume before getting result.
             return true;
         }
         return false;
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 3a754c4..c97e4e8 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -357,6 +357,11 @@
      */
     boolean mForceHidden = false;
 
+    /**
+     * Used to keep resumeTopActivityUncheckedLocked() from being entered recursively
+     */
+    boolean mInResumeTopActivity = false;
+
     private boolean mUpdateBoundsDeferred;
     private boolean mUpdateBoundsDeferredCalled;
     private boolean mUpdateDisplayedBoundsDeferredCalled;
@@ -1732,6 +1737,7 @@
             "Activity paused: token=" + token + ", timeout=" + timeout);
 
         final ActivityRecord r = isInStackLocked(token);
+
         if (r != null) {
             mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
             if (mPausingActivity == r) {
@@ -2088,8 +2094,7 @@
             boolean aboveTop = top != null;
             final boolean stackShouldBeVisible = shouldBeVisible(starting);
             boolean behindFullscreenActivity = !stackShouldBeVisible;
-            boolean resumeNextActivity = mRootActivityContainer.isTopDisplayFocusedStack(this)
-                    && (isInStackLocked(starting) == null);
+            boolean resumeNextActivity = isFocusable() && isInStackLocked(starting) == null;
             final boolean isTopNotPinnedStack =
                     isAttached() && getDisplay().isTopNotPinnedStack(this);
             for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -2150,6 +2155,10 @@
                             if (r.handleAlreadyVisible()) {
                                 resumeNextActivity = false;
                             }
+
+                            if (notifyClients) {
+                                r.makeActiveIfNeeded(starting);
+                            }
                         } else {
                             r.makeVisibleIfNeeded(starting, notifyClients);
                         }
@@ -2327,7 +2336,7 @@
                 r.setVisible(true);
             }
             if (r != starting) {
-                mStackSupervisor.startSpecificActivityLocked(r, andResume, false);
+                mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */);
                 return true;
             }
         }
@@ -2505,7 +2514,7 @@
      */
     @GuardedBy("mService")
     boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
-        if (mStackSupervisor.inResumeTopActivity) {
+        if (mInResumeTopActivity) {
             // Don't even start recursing.
             return false;
         }
@@ -2513,7 +2522,7 @@
         boolean result = false;
         try {
             // Protect against recursion.
-            mStackSupervisor.inResumeTopActivity = true;
+            mInResumeTopActivity = true;
             result = resumeTopActivityInnerLocked(prev, options);
 
             // When resuming the top activity, it may be necessary to pause the top activity (for
@@ -2528,7 +2537,7 @@
                 checkReadyForSleep();
             }
         } finally {
-            mStackSupervisor.inResumeTopActivity = false;
+            mInResumeTopActivity = false;
         }
 
         return result;
@@ -2561,7 +2570,7 @@
         // Find the next top-most activity to resume in this stack that is not finishing and is
         // focusable. If it is not focusable, we will fall into the case below to resume the
         // top activity in the next focusable task.
-        final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
+        ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
 
         final boolean hasRunningActivity = next != null;
 
@@ -2649,6 +2658,12 @@
         if (!mRootActivityContainer.allPausedActivitiesComplete()) {
             if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE,
                     "resumeTopActivityLocked: Skip resume: some activity pausing.");
+
+            // Adding previous activity to the waiting visible list, or it would be stopped
+            // before top activity being visible.
+            if (prev != null && !next.nowVisible) {
+                mStackSupervisor.mActivitiesWaitingForVisibleActivity.add(prev);
+            }
             return false;
         }
 
@@ -2858,7 +2873,9 @@
             // the screen based on the new activity order.
             boolean notUpdated = true;
 
-            if (isFocusedStackOnDisplay()) {
+            // Activity should also be visible if set mLaunchTaskBehind to true (see
+            // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
+            if (shouldBeVisible(next)) {
                 // We have special rotation behavior when here is some active activity that
                 // requests specific orientation or Keyguard is locked. Make sure all activity
                 // visibilities are set correctly as well as the transition is updated if needed
@@ -4087,6 +4104,12 @@
         mStackSupervisor.mFinishingActivities.add(r);
         r.resumeKeyDispatchingLocked();
         mRootActivityContainer.resumeFocusedStacksTopActivities();
+        // If activity was not paused at this point - explicitly pause it to start finishing
+        // process. Finishing will be completed once it reports pause back.
+        if (r.isState(RESUMED) && mPausingActivity != null) {
+            startPausingLocked(false /* userLeaving */, false /* uiSleeping */, next /* resuming */,
+                    false /* dontWait */);
+        }
         return r;
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 3a288ca..a83ef34 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -327,9 +327,6 @@
      */
     PowerManager.WakeLock mGoingToSleep;
 
-    /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */
-    boolean inResumeTopActivity;
-
     /**
      * Temporary rect used during docked stack resize calculation so we don't need to create a new
      * object each time.
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 2807094..43c1206 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -179,7 +179,10 @@
                 .setActivityOptions(options.toBundle())
                 .execute();
         mLastHomeActivityStartRecord = tmpOutRecord[0];
-        if (mSupervisor.inResumeTopActivity) {
+        final ActivityDisplay display =
+                mService.mRootActivityContainer.getActivityDisplay(displayId);
+        final ActivityStack homeStack = display != null ? display.getHomeStack() : null;
+        if (homeStack != null && homeStack.mInResumeTopActivity) {
             // If we are in resume section already, home activity will be initialized, but not
             // resumed (to avoid recursive resume) and will stay that way until something pokes it
             // again. We need to schedule another resume.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 38580bc..b0e5811 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -982,7 +982,8 @@
 
     /** Returns true if uid is in a persistent state. */
     private boolean isUidPersistentSystemProcess(int uid) {
-        return (mService.getUidStateLocked(uid) <= ActivityManager.PROCESS_STATE_PERSISTENT_UI);
+        return (uid == Process.SYSTEM_UID)
+                || (mService.getUidStateLocked(uid) <= ActivityManager.PROCESS_STATE_PERSISTENT_UI);
     }
 
     private void maybeLogActivityStart(int callingUid, String callingPackage, int realCallingUid,
@@ -1632,7 +1633,7 @@
                 // Also, we don't want to resume activities in a task that currently has an overlay
                 // as the starting activity just needs to be in the visible paused state until the
                 // over is removed.
-                mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+                mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);
                 // Go ahead and tell window manager to execute app transition for this activity
                 // since the app transition will not be triggered through the resume channel.
                 mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
index 29645f6..ed029cd 100644
--- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java
+++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.view.SurfaceControl.METADATA_OWNER_UID;
+import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
+
 import static com.android.server.wm.AppWindowThumbnailProto.HEIGHT;
 import static com.android.server.wm.AppWindowThumbnailProto.SURFACE_ANIMATOR;
 import static com.android.server.wm.AppWindowThumbnailProto.WIDTH;
@@ -82,7 +85,8 @@
                 .setName("thumbnail anim: " + appToken.toString())
                 .setBufferSize(mWidth, mHeight)
                 .setFormat(PixelFormat.TRANSLUCENT)
-                .setMetadata(appToken.windowType,
+                .setMetadata(METADATA_WINDOW_TYPE, appToken.windowType)
+                .setMetadata(METADATA_OWNER_UID,
                         window != null ? window.mOwnerUid : Binder.getCallingUid())
                 .build();
 
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 4006332..bbf115f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2574,6 +2574,10 @@
         }
     }
 
+    void notifyDisplayReady() {
+        mHandler.post(() -> getStatusBarManagerInternal().onDisplayReady(getDisplayId()));
+    }
+
     /**
      * Return the display width available after excluding any screen
      * decorations that could never be removed in Honeycomb. That is, system bar or
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 9b72141..e95ac5c 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -1108,28 +1108,41 @@
             return false;
         }
 
+        boolean result = false;
         if (targetStack != null && (targetStack.isTopStackOnDisplay()
                 || getTopDisplayFocusedStack() == targetStack)) {
-            return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
+            result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
         }
 
-        // Resume all top activities in focused stacks on all displays.
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+            boolean resumedOnDisplay = false;
             final ActivityDisplay display = mActivityDisplays.get(displayNdx);
-            final ActivityStack focusedStack = display.getFocusedStack();
-            if (focusedStack == null) {
-                continue;
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                final ActivityRecord topRunningActivity = stack.topRunningActivityLocked();
+                if (!stack.isFocusableAndVisible() || topRunningActivity == null) {
+                    continue;
+                }
+                if (topRunningActivity.isState(RESUMED)) {
+                    // Kick off any lingering app transitions form the MoveTaskToFront operation.
+                    stack.executeAppTransition(targetOptions);
+                } else {
+                    resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target);
+                }
             }
-            final ActivityRecord r = focusedStack.topRunningActivityLocked();
-            if (r == null || !r.isState(RESUMED)) {
-                focusedStack.resumeTopActivityUncheckedLocked(null, null);
-            } else if (r.isState(RESUMED)) {
-                // Kick off any lingering app transitions form the MoveTaskToFront operation.
-                focusedStack.executeAppTransition(targetOptions);
+            if (!resumedOnDisplay) {
+                // In cases when there are no valid activities (e.g. device just booted or launcher
+                // crashed) it's possible that nothing was resumed on a display. Requesting resume
+                // of top activity in focused stack explicitly will make sure that at least home
+                // activity is started and resumed, and no recursion occurs.
+                final ActivityStack focusedStack = display.getFocusedStack();
+                if (focusedStack != null) {
+                    focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions);
+                }
             }
         }
 
-        return false;
+        return result;
     }
 
     void applySleepTokens(boolean applyToStacks) {
@@ -1283,16 +1296,21 @@
     public void onDisplayAdded(int displayId) {
         if (DEBUG_STACK) Slog.v(TAG, "Display added displayId=" + displayId);
         synchronized (mService.mGlobalLock) {
-            getActivityDisplayOrCreate(displayId);
+            final ActivityDisplay display = getActivityDisplayOrCreate(displayId);
             // Do not start home before booting, or it may accidentally finish booting before it
             // starts. Instead, we expect home activities to be launched when the system is ready
             // (ActivityManagerService#systemReady).
             if (mService.isBooted() || mService.isBooting()) {
-                startHomeOnDisplay(mCurrentUser, "displayAdded", displayId);
+                startSystemDecorations(display.mDisplayContent);
             }
         }
     }
 
+    private void startSystemDecorations(final DisplayContent displayContent) {
+        startHomeOnDisplay(mCurrentUser, "displayAdded", displayContent.getDisplayId());
+        displayContent.getDisplayPolicy().notifyDisplayReady();
+    }
+
     @Override
     public void onDisplayRemoved(int displayId) {
         if (DEBUG_STACK) Slog.v(TAG, "Display removed displayId=" + displayId);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a7dd55b..476bd6e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -22,6 +22,7 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.res.Configuration.EMPTY;
+import static android.view.SurfaceControl.METADATA_TASK_ID;
 
 import static com.android.server.EventLogTags.WM_TASK_REMOVED;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
@@ -643,6 +644,11 @@
         return getAppAnimationLayer(ANIMATION_LAYER_HOME);
     }
 
+    @Override
+    SurfaceControl.Builder makeSurface() {
+        return super.makeSurface().setMetadata(METADATA_TASK_ID, mTaskId);
+    }
+
     boolean isTaskAnimating() {
         final RecentsAnimationController recentsAnim = mWmService.getRecentsAnimationController();
         if (recentsAnim != null) {
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 69dcaf4..0529ed1 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -698,6 +698,14 @@
             return false;
         }
 
+        final boolean toTopOfStack = position == MAX_VALUE;
+        if (toTopOfStack && toStack.getResumedActivity() != null
+                && toStack.topRunningActivityLocked() != null) {
+            // Pause the resumed activity on the target stack while re-parenting task on top of it.
+            toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
+                    null /* resuming */, false /* pauseImmediately */);
+        }
+
         final int toStackWindowingMode = toStack.getWindowingMode();
         final ActivityRecord topActivity = getTopActivity();
 
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index c2a8e7e..dea3597 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -18,6 +18,8 @@
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Surface.SCALING_MODE_SCALE_TO_WINDOW;
+import static android.view.SurfaceControl.METADATA_OWNER_UID;
+import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
 
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -35,7 +37,6 @@
 import android.os.Trace;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.WindowContentFrameStats;
@@ -103,7 +104,8 @@
                 .setBufferSize(w, h)
                 .setFormat(format)
                 .setFlags(flags)
-                .setMetadata(windowType, ownerUid);
+                .setMetadata(METADATA_WINDOW_TYPE, windowType)
+                .setMetadata(METADATA_OWNER_UID, ownerUid);
         mSurfaceControl = b.build();
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
index e99dd4f..bbecc63 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
@@ -16,6 +16,9 @@
 
 package com.android.server.net.ipmemorystore;
 
+import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ContentValues;
@@ -27,7 +30,6 @@
 import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQuery;
-import android.net.NetworkUtils;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.Status;
 import android.util.Log;
@@ -200,7 +202,7 @@
         if (null == attributes) return values;
         if (null != attributes.assignedV4Address) {
             values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
-                    NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address));
+                    inet4AddressToIntHTH(attributes.assignedV4Address));
         }
         if (null != attributes.groupHint) {
             values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
@@ -254,7 +256,7 @@
                 getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
         final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
         if (0 != assignedV4AddressInt) {
-            builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt));
+            builder.setAssignedV4Address(intToInet4AddressHTH(assignedV4AddressInt));
         }
         builder.setGroupHint(groupHint);
         if (null != dnsAddressesBlob) {
diff --git a/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java b/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java
index f068c3a..1fe2328 100644
--- a/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java
+++ b/services/net/java/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -16,7 +16,7 @@
 
 package android.net.dhcp;
 
-import static android.net.NetworkUtils.inet4AddressToIntHTH;
+import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
 
 import android.annotation.NonNull;
 import android.net.LinkAddress;
diff --git a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
index 2c368c8..0007350 100644
--- a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
+++ b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
@@ -73,7 +73,7 @@
     public static DhcpResultsParcelable toStableParcelable(@Nullable DhcpResults results) {
         if (results == null) return null;
         final DhcpResultsParcelable p = new DhcpResultsParcelable();
-        p.baseConfiguration = toStableParcelable((StaticIpConfiguration) results);
+        p.baseConfiguration = toStableParcelable(results.toStaticIpConfiguration());
         p.leaseDuration = results.leaseDuration;
         p.mtu = results.mtu;
         p.serverAddress = parcelAddress(results.serverAddress);
diff --git a/services/net/java/android/net/shared/NetdService.java b/services/net/java/android/net/shared/NetdService.java
index be0f5f2..f5ae725 100644
--- a/services/net/java/android/net/shared/NetdService.java
+++ b/services/net/java/android/net/shared/NetdService.java
@@ -16,6 +16,7 @@
 
 package android.net.shared;
 
+import android.content.Context;
 import android.net.INetd;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -28,7 +29,6 @@
  */
 public class NetdService {
     private static final String TAG = NetdService.class.getSimpleName();
-    private static final String NETD_SERVICE_NAME = "netd";
     private static final long BASE_TIMEOUT_MS = 100;
     private static final long MAX_TIMEOUT_MS = 1000;
 
@@ -48,7 +48,7 @@
         // NOTE: ServiceManager does no caching for the netd service,
         // because netd is not one of the defined common services.
         final INetd netdInstance = INetd.Stub.asInterface(
-                ServiceManager.getService(NETD_SERVICE_NAME));
+                ServiceManager.getService(Context.NETD_SERVICE));
         if (netdInstance == null) {
             Log.w(TAG, "WARNING: returning null INetd instance.");
         }
diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
new file mode 100644
index 0000000..9f1cbcd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static android.os.BatteryStats.Uid.NUM_USER_ACTIVITY_TYPES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.attention.AttentionManagerInternal;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.SystemClock;
+import android.service.attention.AttentionService;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class AttentionDetectorTest extends AndroidTestCase {
+
+    private @Mock AttentionManagerInternal mAttentionManagerInternal;
+    private @Mock Runnable mOnUserAttention;
+    private TestableAttentionDetector mAttentionDetector;
+    private long mAttentionTimeout;
+    private long mNextDimming;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mAttentionManagerInternal.checkAttention(anyInt(), anyLong(), any()))
+                .thenReturn(true);
+        mAttentionDetector = new TestableAttentionDetector();
+        mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_AWAKE);
+        mAttentionDetector.setAttentionServiceSupported(true);
+        mNextDimming = SystemClock.uptimeMillis() + 3000L;
+    }
+
+    @Test
+    public void testOnUserActivity_checksAttention() {
+        long when = registerAttention();
+        verify(mAttentionManagerInternal).checkAttention(anyInt(), anyLong(), any());
+        assertThat(when).isLessThan(mNextDimming);
+    }
+
+    @Test
+    public void testOnUserActivity_doesntCheckIfNotSupported() {
+        mAttentionDetector.setAttentionServiceSupported(false);
+        long when = registerAttention();
+        verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+        assertThat(mNextDimming).isEqualTo(when);
+    }
+
+    @Test
+    public void onUserActivity_ignoresWhiteListedActivityTypes() {
+        for (int i = 0; i < NUM_USER_ACTIVITY_TYPES; i++) {
+            int result = mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(), i);
+            if (result == -1) {
+                throw new AssertionError("User activity " + i + " isn't listed in"
+                        + " AttentionDetector#onUserActivity. Please consider how this new activity"
+                        + " type affects the attention service.");
+            }
+        }
+    }
+
+    @Test
+    public void testUpdateUserActivity_ignoresWhenItsNotTimeYet() {
+        long now = SystemClock.uptimeMillis();
+        mNextDimming = now;
+        mAttentionDetector.onUserActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH);
+        mAttentionDetector.updateUserActivity(mNextDimming + 5000L);
+        verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+    }
+
+    @Test
+    public void testOnUserActivity_ignoresAfterMaximumExtension() {
+        long now = SystemClock.uptimeMillis();
+        mAttentionDetector.onUserActivity(now - 15000L, PowerManager.USER_ACTIVITY_EVENT_TOUCH);
+        mAttentionDetector.updateUserActivity(now + 2000L);
+        verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+    }
+
+    @Test
+    public void testOnUserActivity_skipsIfAlreadyScheduled() {
+        registerAttention();
+        reset(mAttentionManagerInternal);
+        long when = mAttentionDetector.updateUserActivity(mNextDimming);
+        verify(mAttentionManagerInternal, never()).checkAttention(anyInt(), anyLong(), any());
+        assertThat(when).isLessThan(mNextDimming);
+    }
+
+    @Test
+    public void testOnWakefulnessChangeStarted_cancelsRequestWhenNotAwake() {
+        registerAttention();
+        mAttentionDetector.onWakefulnessChangeStarted(PowerManagerInternal.WAKEFULNESS_ASLEEP);
+        verify(mAttentionManagerInternal).cancelAttentionCheck(anyInt());
+    }
+
+    @Test
+    public void testCallbackOnSuccess_ignoresIfNoAttention() {
+        registerAttention();
+        mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(),
+                AttentionService.ATTENTION_SUCCESS_ABSENT, SystemClock.uptimeMillis());
+        verify(mOnUserAttention, never()).run();
+    }
+
+    @Test
+    public void testCallbackOnSuccess_callsCallback() {
+        registerAttention();
+        mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(),
+                AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis());
+        verify(mOnUserAttention).run();
+    }
+
+    @Test
+    public void testCallbackOnFailure_unregistersCurrentRequestCode() {
+        registerAttention();
+        mAttentionDetector.mCallback.onFailure(mAttentionDetector.getRequestCode(),
+                AttentionService.ATTENTION_FAILURE_UNKNOWN);
+        mAttentionDetector.mCallback.onSuccess(mAttentionDetector.getRequestCode(),
+                AttentionService.ATTENTION_SUCCESS_PRESENT, SystemClock.uptimeMillis());
+        verify(mOnUserAttention, never()).run();
+    }
+
+    private long registerAttention() {
+        mAttentionTimeout = 4000L;
+        mAttentionDetector.onUserActivity(SystemClock.uptimeMillis(),
+                PowerManager.USER_ACTIVITY_EVENT_TOUCH);
+        return mAttentionDetector.updateUserActivity(mNextDimming);
+    }
+
+    private class TestableAttentionDetector extends AttentionDetector {
+
+        private boolean mAttentionServiceSupported;
+
+        TestableAttentionDetector() {
+            super(AttentionDetectorTest.this.mOnUserAttention, new Object());
+            mAttentionManager = mAttentionManagerInternal;
+            mMaximumExtensionMillis = 10000L;
+        }
+
+        void setAttentionServiceSupported(boolean supported) {
+            mAttentionServiceSupported = supported;
+        }
+
+        @Override
+        public boolean isAttentionServiceSupported() {
+            return mAttentionServiceSupported;
+        }
+
+        @Override
+        public long getAttentionTimeout() {
+            return mAttentionTimeout;
+        }
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
index 410ab87..e658d17 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
@@ -59,7 +59,7 @@
         signals.putStringArrayList(Adjustment.KEY_PEOPLE, people);
         ArrayList<Notification.Action> smartActions = new ArrayList<>();
         smartActions.add(createAction());
-        signals.putParcelableArrayList(Adjustment.KEY_SMART_ACTIONS, smartActions);
+        signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, smartActions);
         Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0);
         r.addAdjustment(adjustment);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 6222923..9c6ab0a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3410,6 +3410,77 @@
     }
 
     @Test
+    public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast() {
+        // Post 2 notifications from 2 packages
+        NotificationRecord pkgA = new NotificationRecord(mContext,
+                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+        mService.addNotification(pkgA);
+        NotificationRecord pkgB = new NotificationRecord(mContext,
+                generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+        mService.addNotification(pkgB);
+
+        // on broadcast, hide one of the packages
+        mService.simulatePackageDistractionBroadcast(
+                PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a"});
+        ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class);
+        verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture());
+        assertEquals(1, captorHide.getValue().size());
+        assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName());
+
+        // on broadcast, unhide the package
+        mService.simulatePackageDistractionBroadcast(
+                PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a"});
+        ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class);
+        verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture());
+        assertEquals(1, captorUnhide.getValue().size());
+        assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName());
+    }
+
+    @Test
+    public void testHideAndUnhideNotificationsOnDistractingPackageBroadcast_multiPkg() {
+        // Post 2 notifications from 2 packages
+        NotificationRecord pkgA = new NotificationRecord(mContext,
+                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+        mService.addNotification(pkgA);
+        NotificationRecord pkgB = new NotificationRecord(mContext,
+                generateSbn("b", 1001, 9, 0), mTestNotificationChannel);
+        mService.addNotification(pkgB);
+
+        // on broadcast, hide one of the packages
+        mService.simulatePackageDistractionBroadcast(
+                PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a", "b"});
+        ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class);
+        verify(mListeners, times(2)).notifyHiddenLocked(captorHide.capture());
+        assertEquals(2, captorHide.getValue().size());
+        assertEquals("a", captorHide.getValue().get(0).sbn.getPackageName());
+        assertEquals("b", captorHide.getValue().get(1).sbn.getPackageName());
+
+        // on broadcast, unhide the package
+        mService.simulatePackageDistractionBroadcast(
+                PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a", "b"});
+        ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class);
+        verify(mListeners, times(2)).notifyUnhiddenLocked(captorUnhide.capture());
+        assertEquals(2, captorUnhide.getValue().size());
+        assertEquals("a", captorUnhide.getValue().get(0).sbn.getPackageName());
+        assertEquals("b", captorUnhide.getValue().get(1).sbn.getPackageName());
+    }
+
+    @Test
+    public void testNoNotificationsHiddenOnDistractingPackageBroadcast() {
+        // post notification from this package
+        final NotificationRecord notif1 = generateNotificationRecord(
+                mTestNotificationChannel, 1, null, true);
+        mService.addNotification(notif1);
+
+        // on broadcast, nothing is hidden since no notifications are of package "test_package"
+        mService.simulatePackageDistractionBroadcast(
+                PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"test_package"});
+        ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
+        verify(mListeners, times(1)).notifyHiddenLocked(captor.capture());
+        assertEquals(0, captor.getValue().size());
+    }
+
+    @Test
     public void testCanUseManagedServicesLowRamNoWatchNullPkg() {
         when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
         when(mActivityManager.isLowRamDevice()).thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 8be63fc..319ffed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -31,6 +31,7 @@
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
 import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
 import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
+import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING;
 
@@ -75,6 +76,9 @@
         mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build();
         mTask = mStack.getChildAt(0);
         mActivity = mTask.getTopActivity();
+
+        doReturn(false).when(mService).isBooting();
+        doReturn(true).when(mService).isBooted();
     }
 
     @Test
@@ -117,22 +121,23 @@
 
         mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
 
-        // The activity is in the focused stack so it should not move to paused.
+        // The activity is in the focused stack so it should be resumed.
         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
-        assertTrue(mActivity.isState(STOPPED));
+        assertTrue(mActivity.isState(RESUMED));
         assertFalse(pauseFound.value);
 
-        // Clear focused stack
-        final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
-        when(display.getFocusedStack()).thenReturn(null);
+        // Make the activity non focusable
+        mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
+        doReturn(false).when(mActivity).isFocusable();
 
-        // In the unfocused stack, the activity should move to paused.
+        // If the activity is not focusable, it should move to paused.
         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
         assertTrue(mActivity.isState(PAUSING));
         assertTrue(pauseFound.value);
 
         // Make sure that the state does not change for current non-stopping states.
         mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped");
+        doReturn(true).when(mActivity).isFocusable();
 
         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 68df87e..ea8f33f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -55,6 +55,7 @@
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
@@ -425,6 +426,7 @@
             doReturn(mock(IPackageManager.class)).when(this).getPackageManager();
             // allow background activity starts by default
             doReturn(true).when(this).isBackgroundActivityStartsEnabled();
+            doNothing().when(this).updateCpuStats();
         }
 
         void setup(IntentFirewall intentFirewall, PendingIntentController intentController,
@@ -580,6 +582,8 @@
             doNothing().when(this).acquireLaunchWakelock();
             doReturn(mKeyguardController).when(this).getKeyguardController();
 
+            mLaunchingActivity = mock(PowerManager.WakeLock.class);
+
             initialize();
         }
 
diff --git a/telephony/java/android/telephony/IFinancialSmsCallback.aidl b/telephony/java/android/telephony/IFinancialSmsCallback.aidl
new file mode 100644
index 0000000..aa88615
--- /dev/null
+++ b/telephony/java/android/telephony/IFinancialSmsCallback.aidl
@@ -0,0 +1,34 @@
+/*
+** 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.telephony;
+
+import android.app.PendingIntent;
+import android.database.CursorWindow;
+import android.net.Uri;
+import android.os.Bundle;
+import com.android.internal.telephony.SmsRawData;
+
+/** Interface for returning back the financial sms messages asynchrously.
+ *  @hide
+ */
+interface IFinancialSmsCallback {
+    /**
+     * Return sms messages back to calling financial app.
+     *
+     * @param messages the sms messages returned for cinancial app.
+     */
+    oneway void onGetSmsMessagesForFinancialApp(in CursorWindow messages);
+}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index d777bf1..fae7920 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -16,6 +16,9 @@
 
 package android.telephony;
 
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressAutoDoc;
 import android.annotation.SystemApi;
@@ -30,8 +33,10 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.database.CursorWindow;
 import android.net.Uri;
 import android.os.BaseBundle;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -49,6 +54,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 /*
  * TODO(code review): Curious question... Why are a lot of these
@@ -2144,6 +2150,116 @@
         }
     }
 
+    /** callback for providing asynchronous sms messages for financial app. */
+    public abstract static class FinancialSmsCallback {
+        /**
+         * Callback to send sms messages back to financial app asynchronously.
+         *
+         * @param msgs SMS messages.
+         */
+        public abstract void onFinancialSmsMessages(CursorWindow msgs);
+    };
+
+    /**
+     * Get SMS messages for the calling financial app.
+     * The result will be delivered asynchronously in the passing in callback interface.
+     *
+     * @param params the parameters to filter SMS messages returned.
+     * @param executor the executor on which callback will be invoked.
+     * @param callback a callback to receive CursorWindow with SMS messages.
+     */
+    @RequiresPermission(android.Manifest.permission.SMS_FINANCIAL_TRANSACTIONS)
+    public void getSmsMessagesForFinancialApp(
+            Bundle params,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull FinancialSmsCallback callback) {
+        try {
+            ISms iccSms = getISmsServiceOrThrow();
+            iccSms.getSmsMessagesForFinancialApp(
+                    getSubscriptionId(), ActivityThread.currentPackageName(), params,
+                    new IFinancialSmsCallback.Stub() {
+                        public void onGetSmsMessagesForFinancialApp(CursorWindow msgs) {
+                            Binder.withCleanCallingIdentity(() -> executor.execute(
+                                    () -> callback.onFinancialSmsMessages(msgs)));
+                        }});
+        } catch (RemoteException ex) {
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @see #createAppSpecificSmsTokenWithPackageInfo().
+     * The prefixes is a list of prefix {@code String} separated by this delimiter.
+     * @hide
+     */
+    public static final String REGEX_PREFIX_DELIMITER = ",";
+    /**
+     * @see #createAppSpecificSmsTokenWithPackageInfo().
+     * The success status to be added into the intent to be sent to the calling package.
+     * @hide
+     */
+    public static final int RESULT_STATUS_SUCCESS = 0;
+    /**
+     * @see #createAppSpecificSmsTokenWithPackageInfo().
+     * The timeout status to be added into the intent to be sent to the calling package.
+     * @hide
+     */
+    public static final int RESULT_STATUS_TIMEOUT = 1;
+    /**
+     * @see #createAppSpecificSmsTokenWithPackageInfo().
+     * Intent extra key of the retrieved SMS message as a {@code String}.
+     * @hide
+     */
+    public static final String EXTRA_SMS_MESSAGE = "android.telephony.extra.SMS_MESSAGE";
+    /**
+     * @see #createAppSpecificSmsTokenWithPackageInfo().
+     * Intent extra key of SMS retriever status, which indicates whether the request for the
+     * coming SMS message is SUCCESS or TIMEOUT
+     * @hide
+     */
+    public static final String EXTRA_STATUS = "android.telephony.extra.STATUS";
+    /**
+     * @see #createAppSpecificSmsTokenWithPackageInfo().
+     * [Optional] Intent extra key of the retrieved Sim card subscription Id if any. {@code int}
+     * @hide
+     */
+    public static final String EXTRA_SIM_SUBSCRIPTION_ID =
+            "android.telephony.extra.SIM_SUBSCRIPTION_ID";
+
+    /**
+     * Create a single use app specific incoming SMS request for the calling package.
+     *
+     * This method returns a token that if included in a subsequent incoming SMS message, and the
+     * SMS message has a prefix from the given prefixes list, the provided {@code intent} will be
+     * sent with the SMS data to the calling package.
+     *
+     * The token is only good for one use within a reasonable amount of time. After an SMS has been
+     * received containing the token all subsequent SMS messages with the token will be routed as
+     * normal.
+     *
+     * An app can only have one request at a time, if the app already has a request pending it will
+     * be replaced with a new request.
+     *
+     * @param prefixes this is a list of prefixes string separated by REGEX_PREFIX_DELIMITER. The
+     *  matching SMS message should have at least one of the prefixes in the beginning of the
+     *  message.
+     * @param intent this intent is sent when the matching SMS message is received.
+     * @return Token to include in an SMS message.
+     */
+    @Nullable
+    public String createAppSpecificSmsTokenWithPackageInfo(
+            @Nullable String prefixes, @NonNull PendingIntent intent) {
+        try {
+            ISms iccSms = getISmsServiceOrThrow();
+            return iccSms.createAppSpecificSmsTokenWithPackageInfo(getSubscriptionId(),
+                ActivityThread.currentPackageName(), prefixes, intent);
+
+        } catch (RemoteException ex) {
+            ex.rethrowFromSystemServer();
+            return null;
+        }
+    }
+
     /**
      * Filters a bundle to only contain MMS config variables.
      *
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e710e0e..98fc725 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6848,13 +6848,13 @@
     /**
      * Values used to return status for hasCarrierPrivileges call.
      */
-    /** @hide */ @SystemApi
+    /** @hide */ @SystemApi @TestApi
     public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1;
-    /** @hide */ @SystemApi
+    /** @hide */ @SystemApi @TestApi
     public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0;
-    /** @hide */ @SystemApi
+    /** @hide */ @SystemApi @TestApi
     public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1;
-    /** @hide */ @SystemApi
+    /** @hide */ @SystemApi @TestApi
     public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2;
 
     /**
@@ -7056,6 +7056,7 @@
 
     /** @hide */
     @SystemApi
+    @TestApi
     @SuppressLint("Doclava125")
     public int checkCarrierPrivilegesForPackage(String pkgName) {
         try {
diff --git a/telephony/java/android/telephony/ims/ImsException.java b/telephony/java/android/telephony/ims/ImsException.java
new file mode 100644
index 0000000..ac4d17a
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsException.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class defines an IMS-related exception that has been thrown while interacting with a
+ * device or carrier provided ImsService implementation.
+ * @hide
+ */
+@SystemApi
+public class ImsException extends Exception {
+
+    /**
+     * The operation has failed due to an unknown or unspecified error.
+     */
+    public static final int CODE_ERROR_UNSPECIFIED = 0;
+    /**
+     * The operation has failed because there is no {@link ImsService} available to service it. This
+     * may be due to an {@link ImsService} crash or other illegal state.
+     * <p>
+     * This is a temporary error and the operation may be retried until the connection to the
+     * {@link ImsService} is restored.
+     */
+    public static final int CODE_ERROR_SERVICE_UNAVAILABLE = 1;
+
+    /**
+     * This device or carrier configuration does not support IMS for this subscription.
+     * <p>
+     * This is a permanent configuration error and there should be no retry.
+     */
+    public static final int CODE_ERROR_UNSUPPORTED_OPERATION = 2;
+
+    /**@hide*/
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "CODE_ERROR_", value = {
+            CODE_ERROR_UNSPECIFIED,
+            CODE_ERROR_SERVICE_UNAVAILABLE,
+            CODE_ERROR_UNSUPPORTED_OPERATION
+    })
+    public @interface ImsErrorCode {}
+
+    private int mCode = CODE_ERROR_UNSPECIFIED;
+
+    /**
+     * A new {@link ImsException} with an unspecified {@link ImsErrorCode} code.
+     * @param message an optional message to detail the error condition more specifically.
+     */
+    public ImsException(@Nullable String message) {
+        super(getMessage(message, CODE_ERROR_UNSPECIFIED));
+    }
+
+    /**
+     * A new {@link ImsException} that includes an {@link ImsErrorCode} error code.
+     * @param message an optional message to detail the error condition more specifically.
+     */
+    public ImsException(@Nullable String message, @ImsErrorCode int code) {
+        super(getMessage(message, code));
+        mCode = code;
+    }
+
+    /**
+     * A new {@link ImsException} that includes an {@link ImsErrorCode} error code and a
+     * {@link Throwable} that contains the original error that was thrown to lead to this Exception.
+     * @param message an optional message to detail the error condition more specifically.
+     * @param cause the {@link Throwable} that caused this {@link ImsException} to be created.
+     */
+    public ImsException(@Nullable String message, @ImsErrorCode  int code, Throwable cause) {
+        super(getMessage(message, code), cause);
+        mCode = code;
+    }
+
+    /**
+     * @return the IMS Error code that is associated with this {@link ImsException}.
+     */
+    public @ImsErrorCode int getCode() {
+        return mCode;
+    }
+
+    private static String getMessage(String message, int code) {
+        StringBuilder builder;
+        if (!TextUtils.isEmpty(message)) {
+            builder = new StringBuilder(message);
+            builder.append(" (code: ");
+            builder.append(code);
+            builder.append(")");
+            return builder.toString();
+        } else {
+            return "code: " + code;
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 5b2e635..eb99d5d 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -54,7 +54,7 @@
  * registration and MmTel capability status callbacks, as well as query/modify user settings for the
  * associated subscription.
  *
- * @see #createForSubscriptionId(Context, int)
+ * @see #createForSubscriptionId(int)
  * @hide
  */
 @SystemApi
@@ -315,15 +315,12 @@
     /**
      * Create an instance of ImsManager for the subscription id specified.
      *
-     * @param context The context to create this ImsMmTelManager instance within.
      * @param subId The ID of the subscription that this ImsMmTelManager will use.
      * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
-     * @throws IllegalArgumentException if the subscription is invalid or
-     *         the subscription ID is not an active subscription.
+     * @throws IllegalArgumentException if the subscription is invalid.
      */
-    public static ImsMmTelManager createForSubscriptionId(Context context, int subId) {
-        if (!SubscriptionManager.isValidSubscriptionId(subId)
-                || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) {
+    public static ImsMmTelManager createForSubscriptionId(int subId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             throw new IllegalArgumentException("Invalid subscription ID");
         }
 
@@ -331,7 +328,7 @@
     }
 
     /**
-     * Only visible for testing, use {@link #createForSubscriptionId(Context, int)} instead.
+     * Only visible for testing, use {@link #createForSubscriptionId(int)} instead.
      * @hide
      */
     @VisibleForTesting
@@ -341,7 +338,7 @@
 
     /**
      * Registers a {@link RegistrationCallback} with the system, which will provide registration
-     * updates for the subscription specified in {@link #createForSubscriptionId(Context, int)}. Use
+     * updates for the subscription specified in {@link #createForSubscriptionId(int)}. Use
      * {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to Subscription changed
      * events and call {@link #unregisterImsRegistrationCallback(RegistrationCallback)} to clean up.
      *
@@ -354,13 +351,14 @@
      * @throws IllegalArgumentException if the subscription associated with this callback is not
      * active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or
      * {@link CapabilityCallback} callback.
-     * @throws IllegalStateException if the subscription associated with this callback is valid, but
+     * @throws ImsException if the subscription associated with this callback is valid, but
      * the {@link ImsService} associated with the subscription is not available. This can happen if
-     * the service crashed, for example.
+     * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
+     * reason.
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void registerImsRegistrationCallback(@CallbackExecutor Executor executor,
-            @NonNull RegistrationCallback c) {
+            @NonNull RegistrationCallback c) throws ImsException {
         if (c == null) {
             throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
         }
@@ -372,6 +370,8 @@
             getITelephony().registerImsRegistrationCallback(mSubId, c.getBinder());
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
+        } catch (IllegalStateException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 
@@ -403,7 +403,7 @@
     /**
      * Registers a {@link CapabilityCallback} with the system, which will provide MmTel service
      * availability updates for the subscription specified in
-     * {@link #createForSubscriptionId(Context, int)}. The method {@link #isAvailable(int, int)}
+     * {@link #createForSubscriptionId(int)}. The method {@link #isAvailable(int, int)}
      * can also be used to query this information at any time.
      *
      * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to
@@ -419,13 +419,14 @@
      * @throws IllegalArgumentException if the subscription associated with this callback is not
      * active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or
      * {@link CapabilityCallback} callback.
-     * @throws IllegalStateException if the subscription associated with this callback is valid, but
+     * @throws ImsException if the subscription associated with this callback is valid, but
      * the {@link ImsService} associated with the subscription is not available. This can happen if
-     * the service crashed, for example.
+     * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
+     * reason.
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void registerMmTelCapabilityCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull CapabilityCallback c) {
+            @NonNull CapabilityCallback c) throws ImsException {
         if (c == null) {
             throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
         }
@@ -437,6 +438,8 @@
             getITelephony().registerMmTelCapabilityCallback(mSubId, c.getBinder());
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
+        }  catch (IllegalStateException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 
@@ -796,14 +799,6 @@
         }
     }
 
-    private static SubscriptionManager getSubscriptionManager(Context context) {
-        SubscriptionManager manager = context.getSystemService(SubscriptionManager.class);
-        if (manager == null) {
-            throw new RuntimeException("Could not find SubscriptionManager.");
-        }
-        return manager;
-    }
-
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(
                 ServiceManager.getService(Context.TELEPHONY_SERVICE));
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 086a765..b171f79 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -172,15 +172,13 @@
 
     /**
      * Create a new {@link ProvisioningManager} for the subscription specified.
-     * @param context The context that this manager will use.
+     *
      * @param subId The ID of the subscription that this ProvisioningManager will use.
      * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
-     * @throws IllegalArgumentException if the subscription is invalid or
-     *         the subscription ID is not an active subscription.
+     * @throws IllegalArgumentException if the subscription is invalid.
      */
-    public static ProvisioningManager createForSubscriptionId(Context context, int subId) {
-        if (!SubscriptionManager.isValidSubscriptionId(subId)
-                || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) {
+    public static ProvisioningManager createForSubscriptionId(int subId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             throw new IllegalArgumentException("Invalid subscription ID");
         }
 
@@ -202,18 +200,21 @@
      * @see SubscriptionManager.OnSubscriptionsChangedListener
      * @throws IllegalArgumentException if the subscription associated with this callback is not
      * active (SIM is not inserted, ESIM inactive) or the subscription is invalid.
-     * @throws IllegalStateException if the subscription associated with this callback is valid, but
+     * @throws ImsException if the subscription associated with this callback is valid, but
      * the {@link ImsService} associated with the subscription is not available. This can happen if
-     * the service crashed, for example.
+     * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
+     * reason.
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void registerProvisioningChangedCallback(@CallbackExecutor Executor executor,
-            @NonNull Callback callback) {
+            @NonNull Callback callback) throws ImsException {
         callback.setExecutor(executor);
         try {
             getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder());
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
+        }  catch (IllegalStateException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 
@@ -332,6 +333,7 @@
      * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
      * @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise.
      */
+    @WorkerThread
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setProvisioningStatusForCapability(
             @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
@@ -358,6 +360,7 @@
      * provisioning, false if the capability does require provisioning and has not been
      * provisioned yet.
      */
+    @WorkerThread
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public boolean getProvisioningStatusForCapability(
             @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java
index f35e886..fea763e 100644
--- a/telephony/java/com/android/ims/ImsException.java
+++ b/telephony/java/com/android/ims/ImsException.java
@@ -21,8 +21,10 @@
 /**
  * This class defines a general IMS-related exception.
  *
+ * @deprecated Use {@link android.telephony.ims.ImsException} instead.
  * @hide
  */
+@Deprecated
 public class ImsException extends Exception {
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index a4eb424..b51eda3 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -18,6 +18,8 @@
 
 import android.app.PendingIntent;
 import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.IFinancialSmsCallback;
 import com.android.internal.telephony.SmsRawData;
 
 /** Interface for applications to access the ICC phone book.
@@ -560,4 +562,30 @@
      * @param intent PendingIntent to be sent when an SMS is received containing the token.
      */
     String createAppSpecificSmsToken(int subId, String callingPkg, in PendingIntent intent);
+
+    /**
+     * Create an app-only incoming SMS request for the calling package.
+     *
+     * If an incoming text contains the token returned by this method the provided
+     * <code>PendingIntent</code> will be sent containing the SMS data.
+     *
+     * @param subId the SIM id.
+     * @param callingPkg the package name of the calling app.
+     * @param prefixes the caller provided prefixes
+     * @param intent PendingIntent to be sent when a SMS is received containing the token and one
+     *   of the prefixes
+     */
+    String createAppSpecificSmsTokenWithPackageInfo(
+            int subId, String callingPkg, String prefixes, in PendingIntent intent);
+
+    /**
+     * Get sms inbox messages for the calling financial app.
+     *
+     * @param subId the SIM id.
+     * @param callingPkg the package name of the calling app.
+     * @param params parameters to filter the sms messages.
+     * @param callback the callback interface to deliver the result.
+     */
+    void getSmsMessagesForFinancialApp(
+        int subId, String callingPkg, in Bundle params, in IFinancialSmsCallback callback);
 }
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index 1cdf44d..12c5c30 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -18,6 +18,8 @@
 
 import android.app.PendingIntent;
 import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.IFinancialSmsCallback;
 
 import java.util.List;
 
@@ -188,4 +190,16 @@
     public String createAppSpecificSmsToken(int subId, String callingPkg, PendingIntent intent) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public String createAppSpecificSmsTokenWithPackageInfo(
+            int subId, String callingPkg, String prefixes, PendingIntent intent) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void getSmsMessagesForFinancialApp(
+            int subId, String callingPkg, Bundle params, IFinancialSmsCallback callback) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/test-mock/src/android/test/mock/MockCursor.java b/test-mock/src/android/test/mock/MockCursor.java
index 576f24a..f69db2c 100644
--- a/test-mock/src/android/test/mock/MockCursor.java
+++ b/test-mock/src/android/test/mock/MockCursor.java
@@ -24,6 +24,8 @@
 import android.net.Uri;
 import android.os.Bundle;
 
+import java.util.List;
+
 /**
  * A mock {@link android.database.Cursor} class that isolates the test code from real
  * Cursor implementation.
@@ -226,11 +228,21 @@
     }
 
     @Override
+    public void setNotificationUris(ContentResolver cr, List<Uri> uris) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
     public Uri getNotificationUri() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
     @Override
+    public List<Uri> getNotificationUris() {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
     public void unregisterContentObserver(ContentObserver observer) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
diff --git a/tests/DexLoggerIntegrationTests/Android.mk b/tests/DexLoggerIntegrationTests/Android.mk
index 979d13a..ee02a72 100644
--- a/tests/DexLoggerIntegrationTests/Android.mk
+++ b/tests/DexLoggerIntegrationTests/Android.mk
@@ -35,7 +35,6 @@
 
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE := DexLoggerNativeTestLibrary
-LOCAL_MULTILIB := first
 LOCAL_SRC_FILES := src/cpp/com_android_dcl_Jni.cpp
 LOCAL_C_INCLUDES += \
     $(JNI_H_INCLUDE)
@@ -44,8 +43,6 @@
 
 include $(BUILD_SHARED_LIBRARY)
 
-dexloggertest_so := $(LOCAL_BUILT_MODULE)
-
 # And a standalone native executable that we can exec.
 
 include $(CLEAR_VARS)
@@ -73,11 +70,15 @@
     android-support-test \
     truth-prebuilt \
 
+# Include both versions of the .so if we have 2 arch
+LOCAL_MULTILIB := both
+LOCAL_JNI_SHARED_LIBRARIES := \
+    DexLoggerNativeTestLibrary \
+
 # This gets us the javalib.jar built by DexLoggerTestLibrary above as well as the various
 # native binaries.
 LOCAL_JAVA_RESOURCE_FILES := \
     $(dexloggertest_jar) \
-    $(dexloggertest_so) \
-    $(dexloggertest_executable)
+    $(dexloggertest_executable) \
 
 include $(BUILD_PACKAGE)
diff --git a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
index d68769b..e92cc56 100644
--- a/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
+++ b/tests/DexLoggerIntegrationTests/src/com/android/server/pm/dex/DexLoggerIntegrationTests.java
@@ -21,6 +21,7 @@
 
 import android.app.UiAutomation;
 import android.content.Context;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
@@ -147,7 +148,7 @@
         String expectedNameHash =
                 "996223BAD4B4FE75C57A3DEC61DB9C0B38E0A7AD479FC95F33494F4BC55A0F0E";
         String expectedContentHash =
-                copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+                copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile);
 
         System.load(privateCopyFile.toString());
 
@@ -170,7 +171,7 @@
         String expectedNameHash =
                 "8C39990C560B4F36F83E208E279F678746FE23A790E4C50F92686584EA2041CA";
         String expectedContentHash =
-                copyAndHashResource("/DexLoggerNativeTestLibrary.so", privateCopyFile);
+                copyAndHashResource(libraryPath("DexLoggerNativeTestLibrary.so"), privateCopyFile);
 
         System.load(privateCopyFile.toString());
 
@@ -307,6 +308,12 @@
         return new File(sContext.getDir("dcl", Context.MODE_PRIVATE), name);
     }
 
+    private String libraryPath(final String libraryName) {
+        // This may be deprecated. but it tells us the ABI of this process which is exactly what we
+        // want.
+        return "/lib/" + Build.CPU_ABI + "/" + libraryName;
+    }
+
     private static String copyAndHashResource(String resourcePath, File copyTo) throws Exception {
         MessageDigest hasher = MessageDigest.getInstance("SHA-256");
 
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index ec07037..86af642 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -26,8 +26,8 @@
 import android.support.test.InstrumentationRegistry;
 
 import com.android.server.PackageWatchdog.PackageHealthObserver;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -45,23 +45,22 @@
 public class PackageWatchdogTest {
     private static final String APP_A = "com.package.a";
     private static final String APP_B = "com.package.b";
+    private static final String APP_C = "com.package.c";
+    private static final String APP_D = "com.package.d";
     private static final String OBSERVER_NAME_1 = "observer1";
     private static final String OBSERVER_NAME_2 = "observer2";
     private static final String OBSERVER_NAME_3 = "observer3";
+    private static final String OBSERVER_NAME_4 = "observer4";
     private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1);
     private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5);
     private TestLooper mTestLooper;
 
     @Before
     public void setUp() throws Exception {
-        mTestLooper = new TestLooper();
-        mTestLooper.startAutoDispatch();
-    }
-
-    @After
-    public void tearDown() throws Exception {
         new File(InstrumentationRegistry.getContext().getFilesDir(),
                 "package-watchdog.xml").delete();
+        mTestLooper = new TestLooper();
+        mTestLooper.startAutoDispatch();
     }
 
     /**
@@ -154,7 +153,6 @@
         assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
         assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
 
-
         // Then advance time and run IO Handler so file is saved
         mTestLooper.dispatchAll();
 
@@ -198,47 +196,191 @@
             watchdog.onPackageFailure(new String[]{APP_A});
         }
 
+        // Run handler so package failures are dispatched to observers
+        mTestLooper.dispatchAll();
+
         // Verify that observers are not notified
         assertEquals(0, observer1.mFailedPackages.size());
         assertEquals(0, observer2.mFailedPackages.size());
     }
 
     /**
-     * Test package failure and notifies all observer since none handles the failure
+     * Test package failure and does not notify any observer because they are not observing
+     * the failed packages.
      */
     @Test
-    public void testPackageFailureNotifyAll() throws Exception {
+    public void testPackageFailureNotifyNone() throws Exception {
         PackageWatchdog watchdog = createWatchdog();
-        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
-        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
 
-        // Start observing for observer1 and observer2 without handling failures
+
         watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
-        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION);
 
-        // Then fail APP_A and APP_B above the threshold
+        // Then fail APP_C (not observed) above the threshold
         for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
-            watchdog.onPackageFailure(new String[]{APP_A, APP_B});
+            watchdog.onPackageFailure(new String[]{APP_C});
         }
 
-        // Verify all observers are notifed of all package failures
-        List<String> observer1Packages = observer1.mFailedPackages;
-        List<String> observer2Packages = observer2.mFailedPackages;
-        assertEquals(2, observer1Packages.size());
-        assertEquals(1, observer2Packages.size());
-        assertEquals(APP_A, observer1Packages.get(0));
-        assertEquals(APP_B, observer1Packages.get(1));
-        assertEquals(APP_A, observer2Packages.get(0));
+        // Run handler so package failures are dispatched to observers
+        mTestLooper.dispatchAll();
+
+        // Verify that observers are not notified
+        assertEquals(0, observer1.mFailedPackages.size());
+        assertEquals(0, observer2.mFailedPackages.size());
     }
 
     /**
-     * Test package failure and notifies only one observer because it handles the failure
+     * Test package failure and notifies only least impact observers.
      */
     @Test
-    public void testPackageFailureNotifyOne() throws Exception {
+    public void testPackageFailureNotifyAllDifferentImpacts() throws Exception {
         PackageWatchdog watchdog = createWatchdog();
-        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, true /* shouldHandle */);
-        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, true /* shouldHandle */);
+        TestObserver observerNone = new TestObserver(OBSERVER_NAME_1,
+                PackageHealthObserverImpact.USER_IMPACT_NONE);
+        TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2,
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+        TestObserver observerMid = new TestObserver(OBSERVER_NAME_3,
+                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+        TestObserver observerLow = new TestObserver(OBSERVER_NAME_4,
+                PackageHealthObserverImpact.USER_IMPACT_LOW);
+
+        // Start observing for all impact observers
+        watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+                SHORT_DURATION);
+        watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
+                SHORT_DURATION);
+        watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
+                SHORT_DURATION);
+        watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
+                SHORT_DURATION);
+
+        // Then fail all apps above the threshold
+        for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
+            watchdog.onPackageFailure(new String[]{APP_A, APP_B, APP_C, APP_D});
+        }
+
+        // Run handler so package failures are dispatched to observers
+        mTestLooper.dispatchAll();
+
+        // Verify least impact observers are notifed of package failures
+        List<String> observerNonePackages = observerNone.mFailedPackages;
+        List<String> observerHighPackages = observerHigh.mFailedPackages;
+        List<String> observerMidPackages = observerMid.mFailedPackages;
+        List<String> observerLowPackages = observerLow.mFailedPackages;
+
+        // APP_D failure observed by only observerNone is not caught cos its impact is none
+        assertEquals(0, observerNonePackages.size());
+        // APP_C failure is caught by observerHigh cos it's the lowest impact observer
+        assertEquals(1, observerHighPackages.size());
+        assertEquals(APP_C, observerHighPackages.get(0));
+        // APP_B failure is caught by observerMid cos it's the lowest impact observer
+        assertEquals(1, observerMidPackages.size());
+        assertEquals(APP_B, observerMidPackages.get(0));
+        // APP_A failure is caught by observerLow cos it's the lowest impact observer
+        assertEquals(1, observerLowPackages.size());
+        assertEquals(APP_A, observerLowPackages.get(0));
+    }
+
+    /**
+     * Test package failure and least impact observers are notified successively.
+     * State transistions:
+     *
+     * <ul>
+     * <li>(observer1:low, observer2:mid) -> {observer1}
+     * <li>(observer1:high, observer2:mid) -> {observer2}
+     * <li>(observer1:high, observer2:none) -> {observer1}
+     * <li>(observer1:none, observer2:none) -> {}
+     * <ul>
+     */
+    @Test
+    public void testPackageFailureNotifyLeastSuccessively() throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1,
+                PackageHealthObserverImpact.USER_IMPACT_LOW);
+        TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2,
+                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+
+        // Start observing for observerFirst and observerSecond with failure handling
+        watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+        watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+
+        // Then fail APP_A above the threshold
+        for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
+            watchdog.onPackageFailure(new String[]{APP_A});
+        }
+        // Run handler so package failures are dispatched to observers
+        mTestLooper.dispatchAll();
+
+        // Verify only observerFirst is notifed
+        assertEquals(1, observerFirst.mFailedPackages.size());
+        assertEquals(APP_A, observerFirst.mFailedPackages.get(0));
+        assertEquals(0, observerSecond.mFailedPackages.size());
+
+        // After observerFirst handles failure, next action it has is high impact
+        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH;
+        observerFirst.mFailedPackages.clear();
+        observerSecond.mFailedPackages.clear();
+
+        // Then fail APP_A again above the threshold
+        for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
+            watchdog.onPackageFailure(new String[]{APP_A});
+        }
+        // Run handler so package failures are dispatched to observers
+        mTestLooper.dispatchAll();
+
+        // Verify only observerSecond is notifed cos it has least impact
+        assertEquals(1, observerSecond.mFailedPackages.size());
+        assertEquals(APP_A, observerSecond.mFailedPackages.get(0));
+        assertEquals(0, observerFirst.mFailedPackages.size());
+
+        // After observerSecond handles failure, it has no further actions
+        observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
+        observerFirst.mFailedPackages.clear();
+        observerSecond.mFailedPackages.clear();
+
+        // Then fail APP_A again above the threshold
+        for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
+            watchdog.onPackageFailure(new String[]{APP_A});
+        }
+        // Run handler so package failures are dispatched to observers
+        mTestLooper.dispatchAll();
+
+        // Verify only observerFirst is notifed cos it has the only action
+        assertEquals(1, observerFirst.mFailedPackages.size());
+        assertEquals(APP_A, observerFirst.mFailedPackages.get(0));
+        assertEquals(0, observerSecond.mFailedPackages.size());
+
+        // After observerFirst handles failure, it too has no further actions
+        observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
+        observerFirst.mFailedPackages.clear();
+        observerSecond.mFailedPackages.clear();
+
+        // Then fail APP_A again above the threshold
+        for (int i = 0; i < TRIGGER_FAILURE_COUNT; i++) {
+            watchdog.onPackageFailure(new String[]{APP_A});
+        }
+        // Run handler so package failures are dispatched to observers
+        mTestLooper.dispatchAll();
+
+        // Verify no observer is notified cos no actions left
+        assertEquals(0, observerFirst.mFailedPackages.size());
+        assertEquals(0, observerSecond.mFailedPackages.size());
+    }
+
+    /**
+     * Test package failure and notifies only one observer even with observer impact tie.
+     */
+    @Test
+    public void testPackageFailureNotifyOneSameImpact() throws Exception {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
 
         // Start observing for observer1 and observer2 with failure handling
         watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
@@ -249,6 +391,9 @@
             watchdog.onPackageFailure(new String[]{APP_A});
         }
 
+        // Run handler so package failures are dispatched to observers
+        mTestLooper.dispatchAll();
+
         // Verify only one observer is notifed
         assertEquals(1, observer1.mFailedPackages.size());
         assertEquals(APP_A, observer1.mFailedPackages.get(0));
@@ -262,21 +407,26 @@
 
     private static class TestObserver implements PackageHealthObserver {
         private final String mName;
-        private boolean mShouldHandle;
+        private int mImpact;
         final List<String> mFailedPackages = new ArrayList<>();
 
         TestObserver(String name) {
             mName = name;
+            mImpact = PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
         }
 
-        TestObserver(String name, boolean shouldHandle) {
+        TestObserver(String name, int impact) {
             mName = name;
-            mShouldHandle = shouldHandle;
+            mImpact = impact;
         }
 
-        public boolean onHealthCheckFailed(String packageName) {
+        public int onHealthCheckFailed(String packageName) {
+            return mImpact;
+        }
+
+        public boolean execute(String packageName) {
             mFailedPackages.add(packageName);
-            return mShouldHandle;
+            return true;
         }
 
         public String getName() {
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
index 030641b..e10f866 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackBroadcastReceiver.java
@@ -29,7 +29,7 @@
 
 /**
  * A broadcast receiver that can be used to get
- * ACTION_PACKAGE_ROLLBACK_EXECUTED broadcasts.
+ * ACTION_ROLLBACK_COMMITTED broadcasts.
  */
 class RollbackBroadcastReceiver extends BroadcastReceiver {
 
@@ -43,7 +43,7 @@
      */
     RollbackBroadcastReceiver() {
         IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+        filter.addAction(Intent.ACTION_ROLLBACK_COMMITTED);
         InstrumentationRegistry.getContext().registerReceiver(this, filter);
     }
 
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 13ac4f0..0493ef8 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -31,11 +31,8 @@
 import android.util.Log;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import org.junit.Ignore;
@@ -43,6 +40,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.List;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -86,8 +84,8 @@
                     Manifest.permission.DELETE_PACKAGES,
                     Manifest.permission.MANAGE_ROLLBACKS);
 
-            // Register a broadcast receiver for notification when the rollback is
-            // done executing.
+            // Register a broadcast receiver for notification when the
+            // rollback has been committed.
             RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
             RollbackManager rm = RollbackTestUtils.getRollbackManager();
 
@@ -99,12 +97,11 @@
             // uninstalled and when rollback manager deletes the rollback. Fix it
             // so that's not the case!
             for (int i = 0; i < 5; ++i) {
-                for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
-                    if (TEST_APP_A.equals(info.targetPackage.getPackageName())) {
-                        Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
-                        Thread.sleep(1000);
-                        break;
-                    }
+                RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                        rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
+                if (rollback != null) {
+                    Log.i(TAG, "Sleeping 1 second to wait for uninstall to take effect.");
+                    Thread.sleep(1000);
                 }
             }
 
@@ -113,13 +110,11 @@
             // between when the app is uninstalled and when the previously
             // available rollback, if any, is removed.
             Thread.sleep(1000);
-            assertNull(rm.getAvailableRollback(TEST_APP_A));
-            assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
+            assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A));
 
-            // There should be no recently executed rollbacks for this package.
-            for (RollbackInfo info : rm.getRecentlyExecutedRollbacks()) {
-                assertNotEquals(TEST_APP_A, info.targetPackage.getPackageName());
-            }
+            // There should be no recently committed rollbacks for this package.
+            assertNull(getUniqueRollbackInfoForPackage(
+                        rm.getRecentlyCommittedRollbacks(), TEST_APP_A));
 
             // Install v1 of the app (without rollbacks enabled).
             RollbackTestUtils.install("RollbackTestAppAv1.apk", false);
@@ -134,10 +129,9 @@
             // between when the app is installed and when the rollback
             // is made available.
             Thread.sleep(1000);
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
-            RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollback);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+            RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
 
             // We should not have received any rollback requests yet.
             // TODO: Possibly flaky if, by chance, some other app on device
@@ -145,7 +139,7 @@
             assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS));
 
             // Roll back the app.
-            RollbackTestUtils.rollback(rollback);
+            RollbackTestUtils.rollback(rollback.getRollbackId());
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
 
             // Verify we received a broadcast for the rollback.
@@ -156,15 +150,9 @@
             assertNull(broadcastReceiver.poll(0, TimeUnit.SECONDS));
 
             // Verify the recent rollback has been recorded.
-            rollback = null;
-            for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
-                    assertNull(rollback);
-                    rollback = r;
-                }
-            }
-            assertNotNull(rollback);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+            rollback = getUniqueRollbackInfoForPackage(
+                    rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
 
             broadcastReceiver.unregister();
             context.unregisterReceiver(enableRollbackReceiver);
@@ -202,31 +190,28 @@
             // is made available.
             Thread.sleep(1000);
 
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
-            RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollbackA);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+            RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
 
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
-            RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
-            assertNotNull(rollbackB);
-            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+            RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_B);
+            assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
 
             // Reload the persisted data.
             rm.reloadPersistedData();
 
             // The apps should still be available for rollback.
-            rollbackA = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollbackA);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+            rollbackA = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
 
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
-            rollbackB = rm.getAvailableRollback(TEST_APP_B);
-            assertNotNull(rollbackB);
-            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+            rollbackB = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_B);
+            assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
 
             // Rollback of B should not rollback A
-            RollbackTestUtils.rollback(rollbackB);
+            RollbackTestUtils.rollback(rollbackB.getRollbackId());
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
         } finally {
@@ -264,31 +249,26 @@
             // is made available.
             Thread.sleep(1000);
 
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
-            RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollbackA);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+            RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoForAandB(rollbackA);
 
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
-            RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
-            assertNotNull(rollbackB);
-            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+            RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_B);
+            assertRollbackInfoForAandB(rollbackB);
 
             // Reload the persisted data.
             rm.reloadPersistedData();
 
             // The apps should still be available for rollback.
-            rollbackA = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollbackA);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA.targetPackage);
+            rollbackA = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoForAandB(rollbackA);
 
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_B));
-            rollbackB = rm.getAvailableRollback(TEST_APP_B);
-            assertNotNull(rollbackB);
-            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB.targetPackage);
+            rollbackB = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APP_B);
+            assertRollbackInfoForAandB(rollbackB);
 
             // Rollback of B should rollback A as well
-            RollbackTestUtils.rollback(rollbackB);
+            RollbackTestUtils.rollback(rollbackB.getRollbackId());
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
         } finally {
@@ -297,10 +277,10 @@
     }
 
     /**
-     * Test that recently executed rollback data is properly persisted.
+     * Test that recently committed rollback data is properly persisted.
      */
     @Test
-    public void testRecentlyExecutedRollbackPersistence() throws Exception {
+    public void testRecentlyCommittedRollbackPersistence() throws Exception {
         try {
             RollbackTestUtils.adoptShellPermissionIdentity(
                     Manifest.permission.INSTALL_PACKAGES,
@@ -319,37 +299,27 @@
             // between when the app is installed and when the rollback
             // is made available.
             Thread.sleep(1000);
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
-            RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
+            RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
 
             // Roll back the app.
-            RollbackTestUtils.rollback(rollback);
+            RollbackTestUtils.rollback(rollback.getRollbackId());
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
 
             // Verify the recent rollback has been recorded.
-            rollback = null;
-            for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
-                    assertNull(rollback);
-                    rollback = r;
-                }
-            }
+            rollback = getUniqueRollbackInfoForPackage(
+                    rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
             assertNotNull(rollback);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
 
             // Reload the persisted data.
             rm.reloadPersistedData();
 
             // Verify the recent rollback is still recorded.
-            rollback = null;
-            for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                if (TEST_APP_A.equals(r.targetPackage.getPackageName())) {
-                    assertNull(rollback);
-                    rollback = r;
-                }
-            }
+            rollback = getUniqueRollbackInfoForPackage(
+                    rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
             assertNotNull(rollback);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
         } finally {
             RollbackTestUtils.dropShellPermissionIdentity();
         }
@@ -378,17 +348,16 @@
             // between when the app is installed and when the rollback
             // is made available.
             Thread.sleep(1000);
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
-            RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollback);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+            RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback);
 
             // Expire the rollback.
             rm.expireRollbackForPackage(TEST_APP_A);
 
             // The rollback should no longer be available.
-            assertNull(rm.getAvailableRollback(TEST_APP_A));
-            assertFalse(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
+            assertNull(getUniqueRollbackInfoForPackage(
+                        rm.getAvailableRollbacks(), TEST_APP_A));
         } finally {
             RollbackTestUtils.dropShellPermissionIdentity();
         }
@@ -459,8 +428,9 @@
             processUserData(TEST_APP_A);
 
             RollbackManager rm = RollbackTestUtils.getRollbackManager();
-            RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
-            RollbackTestUtils.rollback(rollback);
+            RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            RollbackTestUtils.rollback(rollback.getRollbackId());
             processUserData(TEST_APP_A);
         } finally {
             RollbackTestUtils.dropShellPermissionIdentity();
@@ -469,12 +439,12 @@
 
     /**
      * Test restrictions on rollback broadcast sender.
-     * A random app should not be able to send a PACKAGE_ROLLBACK_EXECUTED broadcast.
+     * A random app should not be able to send a ROLLBACK_COMMITTED broadcast.
      */
     @Test
     public void testRollbackBroadcastRestrictions() throws Exception {
         RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
-        Intent broadcast = new Intent(Intent.ACTION_PACKAGE_ROLLBACK_EXECUTED);
+        Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
         try {
             InstrumentationRegistry.getContext().sendBroadcast(broadcast);
             fail("Succeeded in sending restricted broadcast from app context.");
@@ -516,21 +486,21 @@
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
 
             // Both test apps should now be available for rollback, and the
-            // targetPackage returned for rollback should be correct.
+            // RollbackInfo returned for the rollbacks should be correct.
             // TODO: See if there is a way to remove this race condition
             // between when the app is installed and when the rollback
             // is made available.
             Thread.sleep(1000);
-            RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollbackA);
-            assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
+            RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
 
-            RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
-            assertNotNull(rollbackB);
-            assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
+            RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_B);
+            assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
 
             // Executing rollback should roll back the correct package.
-            RollbackTestUtils.rollback(rollbackA);
+            RollbackTestUtils.rollback(rollbackA.getRollbackId());
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
 
@@ -539,7 +509,7 @@
             RollbackTestUtils.install("RollbackTestAppAv2.apk", true);
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
 
-            RollbackTestUtils.rollback(rollbackB);
+            RollbackTestUtils.rollback(rollbackB.getRollbackId());
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
         } finally {
@@ -558,21 +528,14 @@
         RollbackManager rm = RollbackTestUtils.getRollbackManager();
 
         try {
-            rm.getAvailableRollback(TEST_APP_A);
+            rm.getAvailableRollbacks();
             fail("expected SecurityException");
         } catch (SecurityException e) {
             // Expected.
         }
 
         try {
-            rm.getPackagesWithAvailableRollbacks();
-            fail("expected SecurityException");
-        } catch (SecurityException e) {
-            // Expected.
-        }
-
-        try {
-            rm.getRecentlyExecutedRollbacks();
+            rm.getRecentlyCommittedRollbacks();
             fail("expected SecurityException");
         } catch (SecurityException e) {
             // Expected.
@@ -581,7 +544,7 @@
         try {
             // TODO: What if the implementation checks arguments for non-null
             // first? Then this test isn't valid.
-            rm.executeRollback(null, null);
+            rm.commitRollback(0, null);
             fail("expected SecurityException");
         } catch (SecurityException e) {
             // Expected.
@@ -629,27 +592,26 @@
             assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
 
             // TEST_APP_A should now be available for rollback.
-            assertTrue(rm.getPackagesWithAvailableRollbacks().contains(TEST_APP_A));
-            RollbackInfo rollback = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollback);
-
-            // TODO: Test the dependent apps for rollback are correct once we
-            // support that in the RollbackInfo API.
+            RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoForAandB(rollback);
 
             // Rollback the app. It should cause both test apps to be rolled
             // back.
-            RollbackTestUtils.rollback(rollback);
+            RollbackTestUtils.rollback(rollback.getRollbackId());
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A));
             assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_B));
 
-            // We should not see a recent rollback listed for TEST_APP_B
-            for (RollbackInfo r : rm.getRecentlyExecutedRollbacks()) {
-                assertNotEquals(TEST_APP_B, r.targetPackage.getPackageName());
-            }
+            // We should see recent rollbacks listed for both A and B.
+            Thread.sleep(1000);
+            RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+                    rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
 
-            // TODO: Test the listed dependent apps for the recently executed
-            // rollback are correct once we support that in the RollbackInfo
-            // API.
+            RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+                    rm.getRecentlyCommittedRollbacks(), TEST_APP_B);
+            assertRollbackInfoForAandB(rollbackB);
+
+            assertEquals(rollbackA.getRollbackId(), rollbackB.getRollbackId());
         } finally {
             RollbackTestUtils.dropShellPermissionIdentity();
         }
@@ -697,13 +659,13 @@
             // between when the app is installed and when the rollback
             // is made available.
             Thread.sleep(1000);
-            RollbackInfo rollbackA = rm.getAvailableRollback(TEST_APP_A);
-            assertNotNull(rollbackA);
-            assertEquals(TEST_APP_A, rollbackA.targetPackage.getPackageName());
+            RollbackInfo rollbackA = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollbackA);
 
-            RollbackInfo rollbackB = rm.getAvailableRollback(TEST_APP_B);
-            assertNotNull(rollbackB);
-            assertEquals(TEST_APP_B, rollbackB.targetPackage.getPackageName());
+            RollbackInfo rollbackB = getUniqueRollbackInfoForPackage(
+                    rm.getAvailableRollbacks(), TEST_APP_B);
+            assertRollbackInfoEquals(TEST_APP_B, 2, 1, rollbackB);
 
             // Start apps PackageWatchdog#TRIGGER_FAILURE_COUNT times so TEST_APP_A crashes
             for (int i = 0; i < 5; i++) {
@@ -724,4 +686,47 @@
             RollbackTestUtils.dropShellPermissionIdentity();
         }
     }
+
+    // Helper function to test the value of a RollbackInfo with single package
+    private void assertRollbackInfoEquals(String packageName,
+            long versionRolledBackFrom, long versionRolledBackTo,
+            RollbackInfo info) {
+        assertNotNull(info);
+        assertEquals(1, info.getPackages().size());
+        assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom, versionRolledBackTo,
+                info.getPackages().get(0));
+    }
+
+    // Helper function to test that the given rollback info is a rollback for
+    // the atomic set {A2, B2} -> {A1, B1}.
+    private void assertRollbackInfoForAandB(RollbackInfo rollback) {
+        assertNotNull(rollback);
+        assertEquals(2, rollback.getPackages().size());
+        if (TEST_APP_A.equals(rollback.getPackages().get(0).getPackageName())) {
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(0));
+            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(1));
+        } else {
+            assertPackageRollbackInfoEquals(TEST_APP_B, 2, 1, rollback.getPackages().get(0));
+            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.getPackages().get(1));
+        }
+    }
+
+    // Helper function to return the RollbackInfo with a given package in the
+    // list of rollbacks. Throws an assertion failure if there is more than
+    // one such rollback info. Returns null if there are no such rollback
+    // infos.
+    private RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks,
+            String packageName) {
+        RollbackInfo found = null;
+        for (RollbackInfo rollback : rollbacks) {
+            for (PackageRollbackInfo info : rollback.getPackages()) {
+                if (packageName.equals(info.getPackageName())) {
+                    assertNull(found);
+                    found = rollback;
+                    break;
+                }
+            }
+        }
+        return found;
+    }
 }
diff --git a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
index edb1355..1478657 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
@@ -21,7 +21,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
 import android.support.test.InstrumentationRegistry;
 
@@ -90,12 +89,12 @@
     }
 
     /**
-     * Execute the given rollback.
+     * Commit the given rollback.
      * @throws AssertionError if the rollback fails.
      */
-    static void rollback(RollbackInfo rollback) throws InterruptedException {
+    static void rollback(int rollbackId) throws InterruptedException {
         RollbackManager rm = getRollbackManager();
-        rm.executeRollback(rollback, LocalIntentSender.getIntentSender());
+        rm.commitRollback(rollbackId, LocalIntentSender.getIntentSender());
         assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
     }
 
diff --git a/tests/net/java/android/net/NetworkUtilsTest.java b/tests/net/java/android/net/NetworkUtilsTest.java
index 3452819..ba6e0f2 100644
--- a/tests/net/java/android/net/NetworkUtilsTest.java
+++ b/tests/net/java/android/net/NetworkUtilsTest.java
@@ -16,161 +16,19 @@
 
 package android.net;
 
-import static android.net.NetworkUtils.getImplicitNetmask;
-import static android.net.NetworkUtils.inet4AddressToIntHTH;
-import static android.net.NetworkUtils.inet4AddressToIntHTL;
-import static android.net.NetworkUtils.intToInet4AddressHTH;
-import static android.net.NetworkUtils.intToInet4AddressHTL;
-import static android.net.NetworkUtils.netmaskToPrefixLength;
-import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH;
-import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTL;
-import static android.net.NetworkUtils.getBroadcastAddress;
-import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
-
 import static junit.framework.Assert.assertEquals;
 
-import static org.junit.Assert.fail;
-
 import android.support.test.runner.AndroidJUnit4;
 
-import java.math.BigInteger;
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.util.TreeSet;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.math.BigInteger;
+import java.util.TreeSet;
+
 @RunWith(AndroidJUnit4.class)
 @android.support.test.filters.SmallTest
 public class NetworkUtilsTest {
-
-    private InetAddress Address(String addr) {
-        return InetAddress.parseNumericAddress(addr);
-    }
-
-    private Inet4Address IPv4Address(String addr) {
-        return (Inet4Address) Address(addr);
-    }
-
-    @Test
-    public void testGetImplicitNetmask() {
-        assertEquals(8, getImplicitNetmask(IPv4Address("4.2.2.2")));
-        assertEquals(8, getImplicitNetmask(IPv4Address("10.5.6.7")));
-        assertEquals(16, getImplicitNetmask(IPv4Address("173.194.72.105")));
-        assertEquals(16, getImplicitNetmask(IPv4Address("172.23.68.145")));
-        assertEquals(24, getImplicitNetmask(IPv4Address("192.0.2.1")));
-        assertEquals(24, getImplicitNetmask(IPv4Address("192.168.5.1")));
-        assertEquals(32, getImplicitNetmask(IPv4Address("224.0.0.1")));
-        assertEquals(32, getImplicitNetmask(IPv4Address("255.6.7.8")));
-    }
-
-    private void assertInvalidNetworkMask(Inet4Address addr) {
-        try {
-            netmaskToPrefixLength(addr);
-            fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception");
-        } catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void testInet4AddressToIntHTL() {
-        assertEquals(0, inet4AddressToIntHTL(IPv4Address("0.0.0.0")));
-        assertEquals(0x000080ff, inet4AddressToIntHTL(IPv4Address("255.128.0.0")));
-        assertEquals(0x0080ff0a, inet4AddressToIntHTL(IPv4Address("10.255.128.0")));
-        assertEquals(0x00feff0a, inet4AddressToIntHTL(IPv4Address("10.255.254.0")));
-        assertEquals(0xfeffa8c0, inet4AddressToIntHTL(IPv4Address("192.168.255.254")));
-        assertEquals(0xffffa8c0, inet4AddressToIntHTL(IPv4Address("192.168.255.255")));
-    }
-
-    @Test
-    public void testIntToInet4AddressHTL() {
-        assertEquals(IPv4Address("0.0.0.0"), intToInet4AddressHTL(0));
-        assertEquals(IPv4Address("255.128.0.0"), intToInet4AddressHTL(0x000080ff));
-        assertEquals(IPv4Address("10.255.128.0"), intToInet4AddressHTL(0x0080ff0a));
-        assertEquals(IPv4Address("10.255.254.0"), intToInet4AddressHTL(0x00feff0a));
-        assertEquals(IPv4Address("192.168.255.254"), intToInet4AddressHTL(0xfeffa8c0));
-        assertEquals(IPv4Address("192.168.255.255"), intToInet4AddressHTL(0xffffa8c0));
-    }
-
-    @Test
-    public void testInet4AddressToIntHTH() {
-        assertEquals(0, inet4AddressToIntHTH(IPv4Address("0.0.0.0")));
-        assertEquals(0xff800000, inet4AddressToIntHTH(IPv4Address("255.128.0.0")));
-        assertEquals(0x0aff8000, inet4AddressToIntHTH(IPv4Address("10.255.128.0")));
-        assertEquals(0x0afffe00, inet4AddressToIntHTH(IPv4Address("10.255.254.0")));
-        assertEquals(0xc0a8fffe, inet4AddressToIntHTH(IPv4Address("192.168.255.254")));
-        assertEquals(0xc0a8ffff, inet4AddressToIntHTH(IPv4Address("192.168.255.255")));
-    }
-
-    @Test
-    public void testIntToInet4AddressHTH() {
-        assertEquals(IPv4Address("0.0.0.0"), intToInet4AddressHTH(0));
-        assertEquals(IPv4Address("255.128.0.0"), intToInet4AddressHTH(0xff800000));
-        assertEquals(IPv4Address("10.255.128.0"), intToInet4AddressHTH(0x0aff8000));
-        assertEquals(IPv4Address("10.255.254.0"), intToInet4AddressHTH(0x0afffe00));
-        assertEquals(IPv4Address("192.168.255.254"), intToInet4AddressHTH(0xc0a8fffe));
-        assertEquals(IPv4Address("192.168.255.255"), intToInet4AddressHTH(0xc0a8ffff));
-    }
-
-    @Test
-    public void testNetmaskToPrefixLength() {
-        assertEquals(0, netmaskToPrefixLength(IPv4Address("0.0.0.0")));
-        assertEquals(9, netmaskToPrefixLength(IPv4Address("255.128.0.0")));
-        assertEquals(17, netmaskToPrefixLength(IPv4Address("255.255.128.0")));
-        assertEquals(23, netmaskToPrefixLength(IPv4Address("255.255.254.0")));
-        assertEquals(31, netmaskToPrefixLength(IPv4Address("255.255.255.254")));
-        assertEquals(32, netmaskToPrefixLength(IPv4Address("255.255.255.255")));
-
-        assertInvalidNetworkMask(IPv4Address("0.0.0.1"));
-        assertInvalidNetworkMask(IPv4Address("255.255.255.253"));
-        assertInvalidNetworkMask(IPv4Address("255.255.0.255"));
-    }
-
-    @Test
-    public void testPrefixLengthToV4NetmaskIntHTL() {
-        assertEquals(0, prefixLengthToV4NetmaskIntHTL(0));
-        assertEquals(0x000080ff /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTL(9));
-        assertEquals(0x0080ffff /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTL(17));
-        assertEquals(0x00feffff /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTL(23));
-        assertEquals(0xfeffffff /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTL(31));
-        assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTL(32));
-    }
-
-    @Test
-    public void testPrefixLengthToV4NetmaskIntHTH() {
-        assertEquals(0, prefixLengthToV4NetmaskIntHTH(0));
-        assertEquals(0xff800000 /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTH(9));
-        assertEquals(0xffff8000 /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTH(17));
-        assertEquals(0xfffffe00 /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTH(23));
-        assertEquals(0xfffffffe /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTH(31));
-        assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTH(32));
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testPrefixLengthToV4NetmaskIntHTH_NegativeLength() {
-        prefixLengthToV4NetmaskIntHTH(-1);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testPrefixLengthToV4NetmaskIntHTH_LengthTooLarge() {
-        prefixLengthToV4NetmaskIntHTH(33);
-    }
-
-    private void checkAddressMasking(String expectedAddr, String addr, int prefixLength) {
-        final int prefix = prefixLengthToV4NetmaskIntHTH(prefixLength);
-        final int addrInt = inet4AddressToIntHTH(IPv4Address(addr));
-        assertEquals(IPv4Address(expectedAddr), intToInet4AddressHTH(prefix & addrInt));
-    }
-
-    @Test
-    public void testPrefixLengthToV4NetmaskIntHTH_MaskAddr() {
-        checkAddressMasking("192.168.0.0", "192.168.128.1", 16);
-        checkAddressMasking("255.240.0.0", "255.255.255.255", 12);
-        checkAddressMasking("255.255.255.255", "255.255.255.255", 32);
-        checkAddressMasking("0.0.0.0", "255.255.255.255", 0);
-    }
-
     @Test
     public void testRoutedIPv4AddressCount() {
         final TreeSet<IpPrefix> set = new TreeSet<>(IpPrefix.lengthComparator());
@@ -267,44 +125,4 @@
         assertEquals(BigInteger.valueOf(7l - 4 + 4 + 16 + 65536),
                 NetworkUtils.routedIPv6AddressCount(set));
     }
-
-    @Test
-    public void testGetPrefixMaskAsAddress() {
-        assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress());
-        assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress());
-        assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress());
-        assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testGetPrefixMaskAsAddress_PrefixTooLarge() {
-        getPrefixMaskAsInet4Address(33);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testGetPrefixMaskAsAddress_NegativePrefix() {
-        getPrefixMaskAsInet4Address(-1);
-    }
-
-    @Test
-    public void testGetBroadcastAddress() {
-        assertEquals("192.168.15.255",
-                getBroadcastAddress(IPv4Address("192.168.0.123"), 20).getHostAddress());
-        assertEquals("192.255.255.255",
-                getBroadcastAddress(IPv4Address("192.168.0.123"), 8).getHostAddress());
-        assertEquals("192.168.0.123",
-                getBroadcastAddress(IPv4Address("192.168.0.123"), 32).getHostAddress());
-        assertEquals("255.255.255.255",
-                getBroadcastAddress(IPv4Address("192.168.0.123"), 0).getHostAddress());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testGetBroadcastAddress_PrefixTooLarge() {
-        getBroadcastAddress(IPv4Address("192.168.0.123"), 33);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testGetBroadcastAddress_NegativePrefix() {
-        getBroadcastAddress(IPv4Address("192.168.0.123"), -1);
-    }
 }
diff --git a/tests/net/java/android/net/StaticIpConfigurationTest.java b/tests/net/java/android/net/StaticIpConfigurationTest.java
index 5bb5734..2b5ad37 100644
--- a/tests/net/java/android/net/StaticIpConfigurationTest.java
+++ b/tests/net/java/android/net/StaticIpConfigurationTest.java
@@ -26,13 +26,13 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.net.InetAddress;
 import java.util.HashSet;
 import java.util.Objects;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class StaticIpConfigurationTest {
@@ -203,7 +203,7 @@
         try {
             s.writeToParcel(p, 0);
             p.setDataPosition(0);
-            s2 = StaticIpConfiguration.CREATOR.createFromParcel(p);
+            s2 = StaticIpConfiguration.readFromParcel(p);
         } finally {
             p.recycle();
         }
diff --git a/tests/net/java/android/net/ip/IpServerTest.java b/tests/net/java/android/net/ip/IpServerTest.java
index 80aac04..f7542a7 100644
--- a/tests/net/java/android/net/ip/IpServerTest.java
+++ b/tests/net/java/android/net/ip/IpServerTest.java
@@ -22,11 +22,11 @@
 import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
 import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
 import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
-import static android.net.NetworkUtils.intToInet4AddressHTH;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.ip.IpServer.STATE_AVAILABLE;
 import static android.net.ip.IpServer.STATE_TETHERED;
 import static android.net.ip.IpServer.STATE_UNAVAILABLE;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/tests/net/java/android/net/shared/Inet4AddressUtilsTest.java b/tests/net/java/android/net/shared/Inet4AddressUtilsTest.java
new file mode 100644
index 0000000..6da8514
--- /dev/null
+++ b/tests/net/java/android/net/shared/Inet4AddressUtilsTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.shared;
+
+import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
+import static android.net.shared.Inet4AddressUtils.getImplicitNetmask;
+import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
+import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
+import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTL;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTL;
+import static android.net.shared.Inet4AddressUtils.netmaskToPrefixLength;
+import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
+import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.fail;
+
+import android.net.InetAddresses;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class Inet4AddressUtilsTest {
+
+    @Test
+    public void testInet4AddressToIntHTL() {
+        assertEquals(0, inet4AddressToIntHTL(ipv4Address("0.0.0.0")));
+        assertEquals(0x000080ff, inet4AddressToIntHTL(ipv4Address("255.128.0.0")));
+        assertEquals(0x0080ff0a, inet4AddressToIntHTL(ipv4Address("10.255.128.0")));
+        assertEquals(0x00feff0a, inet4AddressToIntHTL(ipv4Address("10.255.254.0")));
+        assertEquals(0xfeffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.254")));
+        assertEquals(0xffffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.255")));
+    }
+
+    @Test
+    public void testIntToInet4AddressHTL() {
+        assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTL(0));
+        assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTL(0x000080ff));
+        assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTL(0x0080ff0a));
+        assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTL(0x00feff0a));
+        assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTL(0xfeffa8c0));
+        assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTL(0xffffa8c0));
+    }
+
+    @Test
+    public void testInet4AddressToIntHTH() {
+        assertEquals(0, inet4AddressToIntHTH(ipv4Address("0.0.0.0")));
+        assertEquals(0xff800000, inet4AddressToIntHTH(ipv4Address("255.128.0.0")));
+        assertEquals(0x0aff8000, inet4AddressToIntHTH(ipv4Address("10.255.128.0")));
+        assertEquals(0x0afffe00, inet4AddressToIntHTH(ipv4Address("10.255.254.0")));
+        assertEquals(0xc0a8fffe, inet4AddressToIntHTH(ipv4Address("192.168.255.254")));
+        assertEquals(0xc0a8ffff, inet4AddressToIntHTH(ipv4Address("192.168.255.255")));
+    }
+
+    @Test
+    public void testIntToInet4AddressHTH() {
+        assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTH(0));
+        assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTH(0xff800000));
+        assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTH(0x0aff8000));
+        assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTH(0x0afffe00));
+        assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTH(0xc0a8fffe));
+        assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTH(0xc0a8ffff));
+    }
+
+
+    @Test
+    public void testPrefixLengthToV4NetmaskIntHTL() {
+        assertEquals(0, prefixLengthToV4NetmaskIntHTL(0));
+        assertEquals(0x000080ff /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTL(9));
+        assertEquals(0x0080ffff /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTL(17));
+        assertEquals(0x00feffff /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTL(23));
+        assertEquals(0xfeffffff /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTL(31));
+        assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTL(32));
+    }
+
+    @Test
+    public void testPrefixLengthToV4NetmaskIntHTH() {
+        assertEquals(0, prefixLengthToV4NetmaskIntHTH(0));
+        assertEquals(0xff800000 /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTH(9));
+        assertEquals(0xffff8000 /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTH(17));
+        assertEquals(0xfffffe00 /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTH(23));
+        assertEquals(0xfffffffe /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTH(31));
+        assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTH(32));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrefixLengthToV4NetmaskIntHTH_NegativeLength() {
+        prefixLengthToV4NetmaskIntHTH(-1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPrefixLengthToV4NetmaskIntHTH_LengthTooLarge() {
+        prefixLengthToV4NetmaskIntHTH(33);
+    }
+
+    private void checkAddressMasking(String expectedAddr, String addr, int prefixLength) {
+        final int prefix = prefixLengthToV4NetmaskIntHTH(prefixLength);
+        final int addrInt = inet4AddressToIntHTH(ipv4Address(addr));
+        assertEquals(ipv4Address(expectedAddr), intToInet4AddressHTH(prefix & addrInt));
+    }
+
+    @Test
+    public void testPrefixLengthToV4NetmaskIntHTH_MaskAddr() {
+        checkAddressMasking("192.168.0.0", "192.168.128.1", 16);
+        checkAddressMasking("255.240.0.0", "255.255.255.255", 12);
+        checkAddressMasking("255.255.255.255", "255.255.255.255", 32);
+        checkAddressMasking("0.0.0.0", "255.255.255.255", 0);
+    }
+
+    @Test
+    public void testGetImplicitNetmask() {
+        assertEquals(8, getImplicitNetmask(ipv4Address("4.2.2.2")));
+        assertEquals(8, getImplicitNetmask(ipv4Address("10.5.6.7")));
+        assertEquals(16, getImplicitNetmask(ipv4Address("173.194.72.105")));
+        assertEquals(16, getImplicitNetmask(ipv4Address("172.23.68.145")));
+        assertEquals(24, getImplicitNetmask(ipv4Address("192.0.2.1")));
+        assertEquals(24, getImplicitNetmask(ipv4Address("192.168.5.1")));
+        assertEquals(32, getImplicitNetmask(ipv4Address("224.0.0.1")));
+        assertEquals(32, getImplicitNetmask(ipv4Address("255.6.7.8")));
+    }
+
+    private void assertInvalidNetworkMask(Inet4Address addr) {
+        try {
+            netmaskToPrefixLength(addr);
+            fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testNetmaskToPrefixLength() {
+        assertEquals(0, netmaskToPrefixLength(ipv4Address("0.0.0.0")));
+        assertEquals(9, netmaskToPrefixLength(ipv4Address("255.128.0.0")));
+        assertEquals(17, netmaskToPrefixLength(ipv4Address("255.255.128.0")));
+        assertEquals(23, netmaskToPrefixLength(ipv4Address("255.255.254.0")));
+        assertEquals(31, netmaskToPrefixLength(ipv4Address("255.255.255.254")));
+        assertEquals(32, netmaskToPrefixLength(ipv4Address("255.255.255.255")));
+
+        assertInvalidNetworkMask(ipv4Address("0.0.0.1"));
+        assertInvalidNetworkMask(ipv4Address("255.255.255.253"));
+        assertInvalidNetworkMask(ipv4Address("255.255.0.255"));
+    }
+
+    @Test
+    public void testGetPrefixMaskAsAddress() {
+        assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress());
+        assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress());
+        assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress());
+        assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress());
+    }
+
+    @Test
+    public void testGetBroadcastAddress() {
+        assertEquals("192.168.15.255",
+                getBroadcastAddress(ipv4Address("192.168.0.123"), 20).getHostAddress());
+        assertEquals("192.255.255.255",
+                getBroadcastAddress(ipv4Address("192.168.0.123"), 8).getHostAddress());
+        assertEquals("192.168.0.123",
+                getBroadcastAddress(ipv4Address("192.168.0.123"), 32).getHostAddress());
+        assertEquals("255.255.255.255",
+                getBroadcastAddress(ipv4Address("192.168.0.123"), 0).getHostAddress());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetBroadcastAddress_PrefixTooLarge() {
+        getBroadcastAddress(ipv4Address("192.168.0.123"), 33);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetBroadcastAddress_NegativePrefix() {
+        getBroadcastAddress(ipv4Address("192.168.0.123"), -1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetPrefixMaskAsAddress_PrefixTooLarge() {
+        getPrefixMaskAsInet4Address(33);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetPrefixMaskAsAddress_NegativePrefix() {
+        getPrefixMaskAsInet4Address(-1);
+    }
+
+    private Inet4Address ipv4Address(String addr) {
+        return (Inet4Address) InetAddresses.parseNumericAddress(addr);
+    }
+}
diff --git a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
index 14df392..fb4d43c 100644
--- a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
+++ b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
@@ -62,7 +62,7 @@
         mDhcpResults.leaseDuration = 3600;
         mDhcpResults.mtu = 1450;
         // Any added DhcpResults field must be included in equals() to be tested properly
-        assertFieldCountEquals(4, DhcpResults.class);
+        assertFieldCountEquals(8, DhcpResults.class);
     }
 
     @Test
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 7783e10..8f75287 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -182,7 +182,8 @@
     defaults: ["aapt2_defaults"],
     data: [
          "integration-tests/CompileTest/**/*",
-         "integration-tests/CommandTests/**/*"
+         "integration-tests/CommandTests/**/*",
+         "integration-tests/ConvertTest/**/*"
     ],
 }
 
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 85f9080..7a74ba9 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -284,6 +284,8 @@
     // The table might be modified by below code.
     auto converted_table = apk->GetResourceTable();
 
+    std::unordered_set<std::string> files_written;
+
     // Resources
     for (const auto& package : converted_table->packages) {
       for (const auto& type : package->types) {
@@ -297,10 +299,14 @@
                 return 1;
               }
 
-              if (!serializer->SerializeFile(file, output_writer)) {
-                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                                 << "failed to serialize file " << *file->path);
-                return 1;
+              // Only serialize if we haven't seen this file before
+              if (files_written.insert(*file->path).second) {
+                if (!serializer->SerializeFile(file, output_writer)) {
+                  context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+                                                       << "failed to serialize file "
+                                                       << *file->path);
+                  return 1;
+                }
               }
             } // file
           } // config_value
diff --git a/tools/aapt2/cmd/Convert_test.cpp b/tools/aapt2/cmd/Convert_test.cpp
index 8da5bb8..3c0fe37 100644
--- a/tools/aapt2/cmd/Convert_test.cpp
+++ b/tools/aapt2/cmd/Convert_test.cpp
@@ -18,6 +18,7 @@
 
 #include "LoadedApk.h"
 #include "test/Test.h"
+#include "ziparchive/zip_archive.h"
 
 using testing::Eq;
 using testing::Ne;
@@ -103,4 +104,45 @@
   EXPECT_THAT(util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), Eq("007"));
 }
 
+TEST_F(ConvertTest, DuplicateEntriesWrittenOnce) {
+  StdErrDiagnostics diag;
+  const std::string apk_path =
+      file::BuildPath({android::base::GetExecutableDirectory(),
+                       "integration-tests", "ConvertTest", "duplicate_entries.apk"});
+
+  const std::string out_convert_apk = GetTestPath("out_convert.apk");
+  std::vector<android::StringPiece> convert_args = {
+      "-o", out_convert_apk,
+      "--output-format", "proto",
+      apk_path
+  };
+  ASSERT_THAT(ConvertCommand().Execute(convert_args, &std::cerr), Eq(0));
+
+  ZipArchiveHandle handle;
+  ASSERT_THAT(OpenArchive(out_convert_apk.c_str(), &handle), Eq(0));
+
+  void* cookie = nullptr;
+
+  ZipString prefix("res/theme/10");
+  int32_t result = StartIteration(handle, &cookie, &prefix, nullptr);
+
+  // If this is -5, that means we've found a duplicate entry and this test has failed
+  EXPECT_THAT(result, Eq(0));
+
+  // But if read succeeds, verify only one res/theme/10 entry
+  int count = 0;
+
+  // Can't pass nullptrs into Next()
+  ZipString zip_name;
+  ZipEntry zip_data;
+
+  while ((result = Next(cookie, &zip_data, &zip_name)) == 0) {
+    count++;
+  }
+
+  EndIteration(cookie);
+
+  EXPECT_THAT(count, Eq(1));
+}
+
 }  // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk b/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk
new file mode 100644
index 0000000..c558a33
--- /dev/null
+++ b/tools/aapt2/integration-tests/ConvertTest/duplicate_entries.apk
Binary files differ
diff --git a/tools/bit/aapt.cpp b/tools/bit/aapt.cpp
index 961b47c..cee0cd5 100644
--- a/tools/bit/aapt.cpp
+++ b/tools/bit/aapt.cpp
@@ -159,10 +159,11 @@
 inspect_apk(Apk* apk, const string& filename)
 {
     // Load the manifest xml
-    Command cmd("aapt");
+    Command cmd("aapt2");
     cmd.AddArg("dump");
     cmd.AddArg("xmltree");
     cmd.AddArg(filename);
+    cmd.AddArg("--file");
     cmd.AddArg("AndroidManifest.xml");
 
     int err;
@@ -217,11 +218,11 @@
             if (current != NULL) {
                 Attribute attr;
                 string str = match[2];
-                size_t colon = str.find(':');
+                size_t colon = str.rfind(':');
                 if (colon == string::npos) {
                     attr.name = str;
                 } else {
-                    attr.ns = scope->namespaces[string(str, 0, colon)];
+                    attr.ns.assign(str, 0, colon);
                     attr.name.assign(str, colon+1, string::npos);
                 }
                 attr.value = match[3];
diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp
index a71cea1..860094ae 100644
--- a/tools/bit/main.cpp
+++ b/tools/bit/main.cpp
@@ -623,12 +623,13 @@
     const string buildProduct = get_required_env("TARGET_PRODUCT", false);
     const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false);
     const string buildType = get_required_env("TARGET_BUILD_TYPE", false);
-
+    const string buildOut = get_out_dir();
     chdir_or_exit(buildTop.c_str());
 
-    const string buildDevice = get_build_var("TARGET_DEVICE", false);
-    const string buildId = get_build_var("BUILD_ID", false);
-    const string buildOut = get_out_dir();
+    BuildVars buildVars(buildOut, buildProduct, buildVariant, buildType);
+
+    const string buildDevice = buildVars.GetBuildVar("TARGET_DEVICE", false);
+    const string buildId = buildVars.GetBuildVar("BUILD_ID", false);
 
     // Get the modules for the targets
     map<string,Module> modules;
@@ -661,6 +662,7 @@
     string dataPath = buildOut + "/target/product/" + buildDevice + "/data/";
     bool syncSystem = false;
     bool alwaysSyncSystem = false;
+    vector<string> systemFiles;
     vector<InstallApk> installApks;
     for (size_t i=0; i<targets.size(); i++) {
         Target* target = targets[i];
@@ -670,6 +672,7 @@
                 // System partition
                 if (starts_with(file, systemPath)) {
                     syncSystem = true;
+                    systemFiles.push_back(file);
                     if (!target->build) {
                         // If a system partition target didn't get built then
                         // it won't change we will always need to do adb sync
@@ -692,6 +695,19 @@
         get_directory_contents(systemPath, &systemFilesBefore);
     }
 
+    if (systemFiles.size() > 0){
+        print_info("System files:");
+        for (size_t i=0; i<systemFiles.size(); i++) {
+            printf("  %s\n", systemFiles[i].c_str());
+        }
+    }
+    if (installApks.size() > 0){
+        print_info("APKs to install:");
+        for (size_t i=0; i<installApks.size(); i++) {
+            printf("  %s\n", installApks[i].file.filename.c_str());
+        }
+    }
+
     //
     // Build
     //
@@ -798,7 +814,8 @@
             for (size_t j=0; j<target->module.installed.size(); j++) {
                 string filename = target->module.installed[j];
 
-                if (!ends_with(filename, ".apk")) {
+                // Apk in the data partition
+                if (!starts_with(filename, dataPath) || !ends_with(filename, ".apk")) {
                     continue;
                 }
 
@@ -1004,13 +1021,16 @@
 void
 run_tab_completion(const string& word)
 {
-    const string buildTop = get_required_env("ANDROID_BUILD_TOP", true);
+    const string buildTop = get_required_env("ANDROID_BUILD_TOP", false);
     const string buildProduct = get_required_env("TARGET_PRODUCT", false);
+    const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false);
+    const string buildType = get_required_env("TARGET_BUILD_TYPE", false);
     const string buildOut = get_out_dir();
-
     chdir_or_exit(buildTop.c_str());
 
-    string buildDevice = sniff_device_name(buildOut, buildProduct);
+    BuildVars buildVars(buildOut, buildProduct, buildVariant, buildType);
+
+    string buildDevice = buildVars.GetBuildVar("TARGET_DEVICE", false);
 
     map<string,Module> modules;
     read_modules(buildOut, buildDevice, &modules, true);
diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp
index 5a9ab22..6270913 100644
--- a/tools/bit/make.cpp
+++ b/tools/bit/make.cpp
@@ -21,6 +21,7 @@
 #include "util.h"
 
 #include <json/reader.h>
+#include <json/writer.h>
 #include <json/value.h>
 
 #include <fstream>
@@ -34,22 +35,118 @@
 
 using namespace std;
 
-map<string,string> g_buildVars;
+static bool
+map_contains(const map<string,string>& m, const string& k, const string& v) {
+    map<string,string>::const_iterator it = m.find(k);
+    if (it == m.end()) {
+        return false;
+    }
+    return it->second == v;
+}
+
+static string
+make_cache_filename(const string& outDir)
+{
+    string filename(outDir);
+    return filename + "/.bit_cache";
+}
+
+BuildVars::BuildVars(const string& outDir, const string& buildProduct,
+        const string& buildVariant, const string& buildType)
+    :m_filename(),
+     m_cache()
+{
+    m_cache["TARGET_PRODUCT"] = buildProduct;
+    m_cache["TARGET_BUILD_VARIANT"] = buildVariant;
+    m_cache["TARGET_BUILD_TYPE"] = buildType;
+
+    // If we have any problems reading the file, that's ok, just do
+    // uncached calls to make / soong.
+
+    if (outDir == "") {
+        return;
+    }
+
+
+    m_filename = make_cache_filename(outDir);
+
+    std::ifstream stream(m_filename, std::ifstream::binary);
+
+    if (stream.fail()) {
+        return;
+    }
+
+    Json::Value json;
+    Json::Reader reader;
+    if (!reader.parse(stream, json)) {
+        return;
+    }
+
+    if (!json.isObject()) {
+        return;
+    }
+
+    map<string,string> cache;
+
+    vector<string> names = json.getMemberNames();
+    const int N = names.size();
+    for (int i=0; i<N; i++) {
+        const string& name = names[i];
+        const Json::Value& value = json[name];
+        if (!value.isString()) {
+            continue;
+        }
+        cache[name] = value.asString();
+    }
+
+    // If all of the base variables match, then we can use this cache.  Otherwise, use our
+    // base one.  The next time someone reads a value, the new one, with our base varaibles
+    // will be saved.
+    if (map_contains(cache, "TARGET_PRODUCT", buildProduct)
+            && map_contains(cache, "TARGET_BUILD_VARIANT", buildVariant)
+            && map_contains(cache, "TARGET_BUILD_TYPE", buildType)) {
+        m_cache = cache;
+    }
+}
+
+BuildVars::~BuildVars()
+{
+}
+
+void
+BuildVars::save()
+{
+    if (m_filename == "") {
+        return;
+    }
+
+    Json::StyledStreamWriter writer("  ");
+
+    Json::Value json(Json::objectValue);
+
+    for (map<string,string>::const_iterator it = m_cache.begin(); it != m_cache.end(); it++) {
+        json[it->first] = it->second;
+    }
+
+    std::ofstream stream(m_filename, std::ofstream::binary);
+    writer.write(stream, json);
+}
 
 string
-get_build_var(const string& name, bool quiet)
+BuildVars::GetBuildVar(const string& name, bool quiet)
 {
     int err;
 
-    map<string,string>::iterator it = g_buildVars.find(name);
-    if (it == g_buildVars.end()) {
+    map<string,string>::iterator it = m_cache.find(name);
+    if (it == m_cache.end()) {
         Command cmd("build/soong/soong_ui.bash");
         cmd.AddArg("--dumpvar-mode");
         cmd.AddArg(name);
 
         string output = trim(get_command_output(cmd, &err, quiet));
         if (err == 0) {
-            g_buildVars[name] = output;
+            m_cache[name] = output;
+            save();
             return output;
         } else {
             return string();
@@ -59,38 +156,6 @@
     }
 }
 
-string
-sniff_device_name(const string& buildOut, const string& product)
-{
-    string match("ro.build.product=" + product);
-
-    string base(buildOut + "/target/product");
-    DIR* dir = opendir(base.c_str());
-    if (dir == NULL) {
-        return string();
-    }
-
-    dirent* entry;
-    while ((entry = readdir(dir)) != NULL) {
-        if (entry->d_name[0] == '.') {
-            continue;
-        }
-        if (entry->d_type == DT_DIR) {
-            string filename(base + "/" + entry->d_name + "/system/build.prop");
-            vector<string> lines;
-            split_lines(&lines, read_file(filename));
-            for (size_t i=0; i<lines.size(); i++) {
-                if (lines[i] == match) {
-                    return entry->d_name;
-                }
-            }
-        }
-    }
-
-    closedir(dir);
-    return string();
-}
-
 void
 json_error(const string& filename, const char* error, bool quiet)
 {
diff --git a/tools/bit/make.h b/tools/bit/make.h
index 1c9504d..db0b69f 100644
--- a/tools/bit/make.h
+++ b/tools/bit/make.h
@@ -31,16 +31,26 @@
     vector<string> installed;
 };
 
-string get_build_var(const string& name, bool quiet);
-
 /**
- * Poke around in the out directory and try to find a device name that matches
- * our product. This is faster than running get_build_var and good enough for
- * tab completion.
- *
- * Returns the empty string if we can't find one.
+ * Class to encapsulate getting build variables. Caches the
+ * results if possible.
  */
-string sniff_device_name(const string& buildOut, const string& product);
+class BuildVars
+{
+public:
+    BuildVars(const string& outDir, const string& buildProduct,
+            const string& buildVariant, const string& buildType);
+    ~BuildVars();
+
+    string GetBuildVar(const string& name, bool quiet);
+
+private:
+    void save();
+
+    string m_filename;
+
+    map<string,string> m_cache;
+};
 
 void read_modules(const string& buildOut, const string& buildDevice,
         map<string,Module>* modules, bool quiet);
diff --git a/tools/bit/print.cpp b/tools/bit/print.cpp
index 790e0b4..35feda1 100644
--- a/tools/bit/print.cpp
+++ b/tools/bit/print.cpp
@@ -116,6 +116,20 @@
 }
 
 void
+print_info(const char* format, ...)
+{
+    fputs(g_escapeBold, stdout);
+
+    va_list args;
+    va_start(args, format);
+    vfprintf(stdout, format, args);
+    va_end(args);
+
+    fputs(g_escapeEndColor, stdout);
+    fputc('\n', stdout);
+}
+
+void
 print_one_line(const char* format, ...)
 {
     if (g_stdoutIsTty) {
diff --git a/tools/bit/print.h b/tools/bit/print.h
index b6c3e9a..db6cf5f 100644
--- a/tools/bit/print.h
+++ b/tools/bit/print.h
@@ -33,6 +33,7 @@
 void print_command(const Command& command);
 void print_error(const char* format, ...);
 void print_warning(const char* format, ...);
+void print_info(const char* format, ...);
 void print_one_line(const char* format, ...);
 void check_error(int err);