Merge "[DPP] Add metrics"
diff --git a/Android.bp b/Android.bp
index a5cc89c..7ce8b31 100644
--- a/Android.bp
+++ b/Android.bp
@@ -49,9 +49,8 @@
         "rs/java/**/*.java",
 
         ":framework-javastream-protos",
-        // TODO: Resolve circular library dependency and remove media1-srcs and mediasession2-srcs
+        // TODO: Resolve circular library dependency and remove media1-srcs
         ":media1-srcs",
-        ":mediasession2-srcs",
 
         "core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl",
         "core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl",
@@ -271,6 +270,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",
@@ -375,6 +375,7 @@
         "core/java/android/view/IApplicationToken.aidl",
         "core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl",
         "core/java/android/view/IDockedStackListener.aidl",
+        "core/java/android/view/IDisplayFoldListener.aidl",
         "core/java/android/view/IGraphicsStats.aidl",
         "core/java/android/view/IGraphicsStatsCallback.aidl",
         "core/java/android/view/IInputFilter.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/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
index a7a81f2..ec46a75 100644
--- a/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
+++ b/apct-tests/perftests/core/src/android/textclassifier/TextClassifierPerfTest.java
@@ -47,7 +47,7 @@
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameterized.Parameters(name = "size={0}")
+    @Parameterized.Parameters(name = "size{0}")
     public static Collection<Object[]> data() {
         return Arrays.asList(new Object[][]{{ACTUAL_REQUEST}, {10}, {100}, {1000}});
     }
@@ -91,7 +91,7 @@
     private static ConversationActions.Request createConversationActionsRequest(CharSequence text) {
         ConversationActions.Message message =
                 new ConversationActions.Message.Builder(
-                        ConversationActions.Message.PERSON_USER_REMOTE)
+                        ConversationActions.Message.PERSON_USER_OTHERS)
                         .setText(text)
                         .build();
         return new ConversationActions.Request.Builder(Collections.singletonList(message))
diff --git a/api/current.txt b/api/current.txt
index 014ba9d..d431e39 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3802,6 +3802,7 @@
     method @Deprecated public void onStateNotSaved();
     method @CallSuper protected void onStop();
     method protected void onTitleChanged(CharSequence, int);
+    method public void onTopResumedActivityChanged(boolean);
     method public boolean onTouchEvent(android.view.MotionEvent);
     method public boolean onTrackballEvent(android.view.MotionEvent);
     method public void onTrimMemory(int);
@@ -5101,7 +5102,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();
@@ -5466,9 +5467,11 @@
 
   public static final class Notification.BubbleMetadata implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean getAutoExpandBubble();
     method public int getDesiredHeight();
     method public android.graphics.drawable.Icon getIcon();
     method public android.app.PendingIntent getIntent();
+    method public boolean getSuppressInitialNotification();
     method public CharSequence getTitle();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.Notification.BubbleMetadata> CREATOR;
@@ -5477,9 +5480,11 @@
   public static class Notification.BubbleMetadata.Builder {
     ctor public Notification.BubbleMetadata.Builder();
     method public android.app.Notification.BubbleMetadata build();
+    method public android.app.Notification.BubbleMetadata.Builder setAutoExpandBubble(boolean);
     method public android.app.Notification.BubbleMetadata.Builder setDesiredHeight(int);
     method public android.app.Notification.BubbleMetadata.Builder setIcon(android.graphics.drawable.Icon);
     method public android.app.Notification.BubbleMetadata.Builder setIntent(android.app.PendingIntent);
+    method public android.app.Notification.BubbleMetadata.Builder setSuppressInitialNotification(boolean);
     method public android.app.Notification.BubbleMetadata.Builder setTitle(CharSequence);
   }
 
@@ -5772,6 +5777,7 @@
     method public String addAutomaticZenRule(android.app.AutomaticZenRule);
     method public boolean areBubblesAllowed();
     method public boolean areNotificationsEnabled();
+    method public boolean areNotificationsPaused();
     method public boolean canNotifyAsPackage(String);
     method public void cancel(int);
     method public void cancel(String, int);
@@ -11431,6 +11437,7 @@
     method public int getSessionId();
     method public long getSize();
     method public int getStagedSessionErrorCode();
+    method public String getStagedSessionErrorMessage();
     method public boolean isActive();
     method public boolean isMultiPackage();
     method public boolean isSealed();
@@ -12507,6 +12514,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);
@@ -12530,6 +12538,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
@@ -13942,8 +13951,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);
@@ -14162,6 +14169,7 @@
     ctor public ImageFormat();
     method public static int getBitsPerPixel(int);
     field public static final int DEPTH16 = 1144402265; // 0x44363159
+    field public static final int DEPTH_JPEG = 1768253795; // 0x69656963
     field public static final int DEPTH_POINT_CLOUD = 257; // 0x101
     field public static final int FLEX_RGBA_8888 = 42; // 0x2a
     field public static final int FLEX_RGB_888 = 41; // 0x29
@@ -16166,6 +16174,7 @@
     method public long getUsage();
     method public int getWidth();
     method public boolean isClosed();
+    method public static boolean isSupported(int, int, int, int, long);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int BLOB = 33; // 0x21
     field public static final android.os.Parcelable.Creator<android.hardware.HardwareBuffer> CREATOR;
@@ -16493,6 +16502,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);
@@ -23517,6 +23527,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);
@@ -25145,6 +25157,8 @@
     field public static final int METADATA_KEY_DATE = 5; // 0x5
     field public static final int METADATA_KEY_DISC_NUMBER = 14; // 0xe
     field public static final int METADATA_KEY_DURATION = 9; // 0x9
+    field public static final int METADATA_KEY_EXIF_LENGTH = 34; // 0x22
+    field public static final int METADATA_KEY_EXIF_OFFSET = 33; // 0x21
     field public static final int METADATA_KEY_GENRE = 6; // 0x6
     field public static final int METADATA_KEY_HAS_AUDIO = 16; // 0x10
     field public static final int METADATA_KEY_HAS_IMAGE = 26; // 0x1a
@@ -27341,6 +27355,7 @@
     method public int getRatingType();
     method @Nullable public android.app.PendingIntent getSessionActivity();
     method @NonNull public android.media.session.MediaSession.Token getSessionToken();
+    method public String getTag();
     method @NonNull public android.media.session.MediaController.TransportControls getTransportControls();
     method public void registerCallback(@NonNull android.media.session.MediaController.Callback);
     method public void registerCallback(@NonNull android.media.session.MediaController.Callback, @Nullable android.os.Handler);
@@ -27471,6 +27486,7 @@
     method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens();
     method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo);
     method public void notifySession2Created(@NonNull android.media.Session2Token);
+    method public void notifySession2Destroyed(@NonNull android.media.Session2Token);
     method public void removeOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener);
     method public void removeOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener);
   }
@@ -27673,6 +27689,7 @@
     field public static final String TYPE_DVB_T2 = "TYPE_DVB_T2";
     field public static final String TYPE_ISDB_C = "TYPE_ISDB_C";
     field public static final String TYPE_ISDB_S = "TYPE_ISDB_S";
+    field public static final String TYPE_ISDB_S3 = "TYPE_ISDB_S3";
     field public static final String TYPE_ISDB_T = "TYPE_ISDB_T";
     field public static final String TYPE_ISDB_TB = "TYPE_ISDB_TB";
     field public static final String TYPE_NTSC = "TYPE_NTSC";
@@ -30579,6 +30596,7 @@
   }
 
   public final class NfcAdapter {
+    method public boolean deviceSupportsNfcSecure();
     method public void disableForegroundDispatch(android.app.Activity);
     method @Deprecated public void disableForegroundNdefPush(android.app.Activity);
     method public void disableReaderMode(android.app.Activity);
@@ -30591,6 +30609,7 @@
     method @Deprecated public boolean invokeBeam(android.app.Activity);
     method public boolean isEnabled();
     method @Deprecated public boolean isNdefPushEnabled();
+    method public boolean isNfcSecureEnabled();
     method @Deprecated public void setBeamPushUris(android.net.Uri[], android.app.Activity);
     method @Deprecated public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
     method @Deprecated public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
@@ -34400,8 +34419,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();
@@ -38637,6 +38660,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";
@@ -38670,6 +38694,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";
@@ -38744,6 +38769,7 @@
 
   public static final class Settings.Panel {
     field public static final String ACTION_INTERNET_CONNECTIVITY = "android.settings.panel.action.INTERNET_CONNECTIVITY";
+    field public static final String ACTION_NFC = "android.settings.panel.action.NFC";
     field public static final String ACTION_VOLUME = "android.settings.panel.action.VOLUME";
   }
 
@@ -41399,9 +41425,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";
   }
 
@@ -44656,12 +44682,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);
@@ -44724,6 +44752,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);
@@ -44993,6 +45026,7 @@
     method @Deprecated public void setVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle, boolean);
     method public boolean updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>);
     field public static final String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL";
+    field public static final String ACTION_NETWORK_COUNTRY_CHANGED = "android.telephony.action.NETWORK_COUNTRY_CHANGED";
     field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
     field public static final String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
     field public static final String ACTION_SECRET_CODE = "android.telephony.action.SECRET_CODE";
@@ -45031,6 +45065,7 @@
     field public static final String EXTRA_INCOMING_NUMBER = "incoming_number";
     field public static final String EXTRA_IS_REFRESH = "android.telephony.extra.IS_REFRESH";
     field public static final String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
+    field public static final String EXTRA_NETWORK_COUNTRY = "android.telephony.extra.NETWORK_COUNTRY";
     field public static final String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
     field public static final String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telephony.extra.PHONE_ACCOUNT_HANDLE";
     field public static final String EXTRA_PRECISE_CARRIER_ID = "android.telephony.extra.PRECISE_CARRIER_ID";
@@ -48562,6 +48597,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);
@@ -53340,8 +53376,8 @@
     method @Nullable public CharSequence getText();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR;
-    field public static final android.app.Person PERSON_USER_LOCAL;
-    field public static final android.app.Person PERSON_USER_REMOTE;
+    field public static final android.app.Person PERSON_USER_OTHERS;
+    field public static final android.app.Person PERSON_USER_SELF;
   }
 
   public static final class ConversationActions.Message.Builder {
@@ -55925,10 +55961,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 {
@@ -62380,20 +62416,20 @@
     method public abstract Object array();
     method public abstract int arrayOffset();
     method public final int capacity();
-    method public final java.nio.Buffer clear();
-    method public final java.nio.Buffer flip();
+    method public java.nio.Buffer clear();
+    method public java.nio.Buffer flip();
     method public abstract boolean hasArray();
     method public final boolean hasRemaining();
     method public abstract boolean isDirect();
     method public abstract boolean isReadOnly();
     method public final int limit();
-    method public final java.nio.Buffer limit(int);
-    method public final java.nio.Buffer mark();
+    method public java.nio.Buffer limit(int);
+    method public java.nio.Buffer mark();
     method public final int position();
-    method public final java.nio.Buffer position(int);
+    method public java.nio.Buffer position(int);
     method public final int remaining();
-    method public final java.nio.Buffer reset();
-    method public final java.nio.Buffer rewind();
+    method public java.nio.Buffer reset();
+    method public java.nio.Buffer rewind();
   }
 
   public class BufferOverflowException extends java.lang.RuntimeException {
diff --git a/api/system-current.txt b/api/system-current.txt
index 8ead061..da3b7f4 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,19 +1698,27 @@
 
   public final class RollbackInfo implements android.os.Parcelable {
     method public int describeContents();
+    method public java.util.List<android.content.pm.VersionedPackage> getCausePackages();
+    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 java.util.List<android.content.pm.VersionedPackage>, @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();
+    field public static final String EXTRA_STATUS = "android.content.rollback.extra.STATUS";
+    field public static final String EXTRA_STATUS_MESSAGE = "android.content.rollback.extra.STATUS_MESSAGE";
+    field public static final int STATUS_FAILURE = 1; // 0x1
+    field public static final int STATUS_FAILURE_INSTALL = 3; // 0x3
+    field public static final int STATUS_FAILURE_ROLLBACK_UNAVAILABLE = 2; // 0x2
+    field public static final int STATUS_SUCCESS = 0; // 0x0
   }
 
 }
@@ -1853,9 +1864,15 @@
   public final class HdmiControlManager {
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void addHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
     method @Nullable public android.hardware.hdmi.HdmiClient getClient(int);
+    method @Nullable public java.util.List<android.hardware.hdmi.HdmiDeviceInfo> getConnectedDevicesList();
+    method public int getPhysicalAddress();
     method @Nullable public android.hardware.hdmi.HdmiPlaybackClient getPlaybackClient();
+    method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient();
     method @Nullable public android.hardware.hdmi.HdmiTvClient getTvClient();
+    method public boolean isRemoteDeviceConnected(android.hardware.hdmi.HdmiDeviceInfo);
+    method public void powerOffRemoteDevice(android.hardware.hdmi.HdmiDeviceInfo);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void removeHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
+    method public void requestRemoteDeviceToBecomeActiveSource(android.hardware.hdmi.HdmiDeviceInfo);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setStandbyMode(boolean);
     field public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE";
     field public static final int AVR_VOLUME_MUTED = 101; // 0x65
@@ -1945,6 +1962,9 @@
     field public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 10; // 0xa
   }
 
+  @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface HdmiControlManager.ControlCallbackResult {
+  }
+
   public static interface HdmiControlManager.HotplugEventListener {
     method public void onReceived(android.hardware.hdmi.HdmiHotplugEvent);
   }
@@ -2071,6 +2091,16 @@
   public abstract static class HdmiRecordSources.RecordSource {
   }
 
+  public class HdmiSwitchClient extends android.hardware.hdmi.HdmiClient {
+    method public int getDeviceType();
+    method public void selectPort(int, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener);
+    method public void selectPort(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener);
+  }
+
+  public static interface HdmiSwitchClient.OnSelectListener {
+    method public void onSelect(@android.hardware.hdmi.HdmiControlManager.ControlCallbackResult int);
+  }
+
   public class HdmiTimerRecordSources {
     method public static boolean checkTimerRecordSource(int, byte[]);
     method public static android.hardware.hdmi.HdmiTimerRecordSources.Duration durationOf(int, int);
@@ -3395,6 +3425,15 @@
     method public void stop();
   }
 
+  public final class Session2Token implements android.os.Parcelable {
+    ctor public Session2Token(@NonNull android.content.Context, @NonNull String, @Nullable android.os.Bundle);
+    method public void destroy();
+    method @NonNull public android.os.Bundle getExtras();
+    method public int getPid();
+    method public boolean isDestroyed();
+    field public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service";
+  }
+
   public static class SubtitleData.Builder {
     ctor public SubtitleData.Builder();
     ctor public SubtitleData.Builder(@NonNull android.media.SubtitleData);
@@ -3589,6 +3628,10 @@
     method public void unregisterCallback(@NonNull android.media.session.ControllerCallbackLink);
   }
 
+  public static final class MediaController.PlaybackInfo implements android.os.Parcelable {
+    ctor public MediaController.PlaybackInfo(int, int, int, int, android.media.AudioAttributes);
+  }
+
   public abstract static class MediaSession.Callback {
     method public void onSetMediaButtonEventDelegate(@NonNull android.media.session.MediaSessionEngine.MediaButtonEventDelegate);
   }
@@ -4059,6 +4102,7 @@
 
   public final class IpPrefix implements android.os.Parcelable {
     ctor public IpPrefix(java.net.InetAddress, int);
+    ctor public IpPrefix(String);
   }
 
   public final class IpSecManager {
@@ -4073,29 +4117,15 @@
     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);
     ctor public LinkAddress(String);
+    ctor public LinkAddress(String, int, int);
     method public boolean isGlobalPreferred();
     method public boolean isIPv4();
     method public boolean isIPv6();
@@ -4106,6 +4136,7 @@
     ctor public LinkProperties();
     ctor public LinkProperties(android.net.LinkProperties);
     method public boolean addDnsServer(java.net.InetAddress);
+    method public boolean addLinkAddress(android.net.LinkAddress);
     method public boolean addRoute(android.net.RouteInfo);
     method public void clear();
     method @Nullable public android.net.IpPrefix getNat64Prefix();
@@ -4120,6 +4151,7 @@
     method public boolean isProvisioned();
     method public boolean isReachable(java.net.InetAddress);
     method public boolean removeDnsServer(java.net.InetAddress);
+    method public boolean removeLinkAddress(android.net.LinkAddress);
     method public boolean removeRoute(android.net.RouteInfo);
     method public void setDnsServers(java.util.Collection<java.net.InetAddress>);
     method public void setDomains(String);
@@ -4136,6 +4168,7 @@
   }
 
   public class Network implements android.os.Parcelable {
+    ctor public Network(android.net.Network);
     method public android.net.Network getPrivateDnsBypassingCopy();
   }
 
@@ -4221,10 +4254,31 @@
     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();
     method public static void setThreadStatsTagRestore();
+    field public static final int TAG_SYSTEM_DHCP = -192; // 0xffffff40
+    field public static final int TAG_SYSTEM_DHCP_SERVER = -186; // 0xffffff46
+    field public static final int TAG_SYSTEM_PROBE = -190; // 0xffffff42
   }
 
   public class VpnService extends android.app.Service {
@@ -4246,6 +4300,49 @@
 
 }
 
+package android.net.apf {
+
+  public class ApfCapabilities {
+    ctor public ApfCapabilities(int, int, int);
+    method public static boolean getApfDrop8023Frames(android.content.Context);
+    method public static int[] getApfEthTypeBlackList(android.content.Context);
+    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 {
@@ -4405,10 +4502,19 @@
 package android.net.util {
 
   public class SocketUtils {
+    method public static void addArpEntry(java.net.Inet4Address, android.net.MacAddress, String, java.io.FileDescriptor) throws java.io.IOException;
+    method public static void attachControlPacketFilter(java.io.FileDescriptor, int) throws java.net.SocketException;
+    method public static void attachDhcpFilter(java.io.FileDescriptor) throws java.net.SocketException;
+    method public static void attachRaFilter(java.io.FileDescriptor, int) throws java.net.SocketException;
+    method public static void bindSocket(java.io.FileDescriptor, java.net.SocketAddress) throws android.system.ErrnoException, java.net.SocketException;
     method public static void bindSocketToInterface(java.io.FileDescriptor, String) throws android.system.ErrnoException;
+    method public static void closeSocket(java.io.FileDescriptor) throws java.io.IOException;
+    method public static void connectSocket(java.io.FileDescriptor, java.net.SocketAddress) throws android.system.ErrnoException, java.net.SocketException;
     method public static java.net.SocketAddress makeNetlinkSocketAddress(int, int);
     method public static java.net.SocketAddress makePacketSocketAddress(short, int);
     method public static java.net.SocketAddress makePacketSocketAddress(int, byte[]);
+    method public static void sendTo(java.io.FileDescriptor, byte[], int, int, int, java.net.SocketAddress) throws android.system.ErrnoException, java.net.SocketException;
+    method public static void setSocketTimeValueOption(java.io.FileDescriptor, int, int, long) throws android.system.ErrnoException;
   }
 
 }
@@ -4666,7 +4772,6 @@
     method public boolean isPortableHotspotSupported();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled();
     method public boolean isWifiScannerSupported();
-    method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void registerNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback, @Nullable android.os.Handler);
     method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void removeWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.WifiUsabilityStatsListener);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void save(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission("android.permission.WIFI_SET_DEVICE_MOBILITY_STATE") public void setDeviceMobilityState(int);
@@ -4676,7 +4781,6 @@
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public boolean startScan(android.os.WorkSource);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(android.net.wifi.hotspot2.OsuProvider, android.net.wifi.hotspot2.ProvisioningCallback, @Nullable android.os.Handler);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession();
-    method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void unregisterNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback);
     method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void updateWifiUsabilityScore(int, int, int);
     field public static final int CHANGE_REASON_ADDED = 0; // 0x0
     field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2
@@ -4713,19 +4817,6 @@
     method public void onSuccess();
   }
 
-  public static interface WifiManager.NetworkRequestMatchCallback {
-    method public void onAbort();
-    method public void onMatch(@NonNull java.util.List<android.net.wifi.ScanResult>);
-    method public void onUserSelectionCallbackRegistration(@NonNull android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback);
-    method public void onUserSelectionConnectFailure(@NonNull android.net.wifi.WifiConfiguration);
-    method public void onUserSelectionConnectSuccess(@NonNull android.net.wifi.WifiConfiguration);
-  }
-
-  public static interface WifiManager.NetworkRequestUserSelectionCallback {
-    method public void reject();
-    method public void select(@NonNull android.net.wifi.WifiConfiguration);
-  }
-
   public static interface WifiManager.WifiUsabilityStatsListener {
     method public void onStatsUpdated(int, boolean, android.net.wifi.WifiUsabilityStatsEntry);
   }
@@ -5017,6 +5108,7 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
     method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setNfcSecure(boolean);
     field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
   }
 
@@ -5029,6 +5121,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";
   }
@@ -5046,6 +5139,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;
   }
@@ -5474,8 +5597,9 @@
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract int onCountPermissionApps(@NonNull java.util.List<java.lang.String>, boolean, boolean);
     method @NonNull public abstract java.util.List<android.permission.RuntimePermissionPresentationInfo> onGetAppPermissions(@NonNull String);
+    method @NonNull public abstract java.util.List<android.permission.RuntimePermissionUsageInfo> onGetPermissionUsages(boolean, long);
     method public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream);
-    method @NonNull public abstract java.util.List<android.permission.RuntimePermissionUsageInfo> onPermissionUsageResult(boolean, long);
+    method public abstract boolean onIsApplicationQualifiedForRole(@NonNull String, @NonNull String);
     method public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String);
     method @NonNull public abstract java.util.Map<java.lang.String,java.util.List<java.lang.String>> onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String);
     field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
@@ -5519,7 +5643,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";
   }
 
@@ -5651,6 +5774,12 @@
     field public static final String NAMESPACE = "activity_manager";
   }
 
+  public static interface DeviceConfig.AttentionManagerService {
+    field public static final String NAMESPACE = "attention_manager_service";
+    field public static final String PROPERTY_COMPONENT_NAME = "component_name";
+    field public static final String PROPERTY_SERVICE_ENABLED = "service_enabled";
+  }
+
   public static interface DeviceConfig.FsiBoot {
     field public static final String NAMESPACE = "fsi_boot";
     field public static final String OOB_ENABLED = "oob_enabled";
@@ -5659,14 +5788,19 @@
 
   public static interface DeviceConfig.IntelligenceAttention {
     field public static final String NAMESPACE = "intelligence_attention";
-    field public static final String PROPERTY_ATTENTION_CHECK_ENABLED = "attention_check_enabled";
-    field public static final String PROPERTY_ATTENTION_CHECK_SETTINGS = "attention_check_settings";
+    field public static final String PROPERTY_ATTENTION_ENABLED = "attention_enabled";
+    field public static final String PROPERTY_ATTENTION_SETTINGS = "attention_settings";
   }
 
   public static interface DeviceConfig.OnPropertyChangedListener {
     method public void onPropertyChanged(String, String, String);
   }
 
+  public static interface DeviceConfig.Storage {
+    field public static final String ISOLATED_STORAGE_ENABLED = "isolated_storage_enabled";
+    field public static final String NAMESPACE = "storage";
+  }
+
   public static interface DeviceConfig.Telephony {
     field public static final String NAMESPACE = "telephony";
     field public static final String PROPERTY_ENABLE_RAMPING_RINGER = "enable_ramping_ringer";
@@ -7424,7 +7558,7 @@
   }
 
   public class PhoneStateListener {
-    method public void onCallAttributesChanged(android.telephony.CallAttributes);
+    method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
     method public void onCallDisconnectCauseChanged(int, int);
     method public void onPreciseCallStateChanged(android.telephony.PreciseCallState);
     method public void onPreciseDataConnectionStateChanged(android.telephony.PreciseDataConnectionState);
@@ -7830,7 +7964,7 @@
 package android.telephony.data {
 
   public final class DataCallResponse implements android.os.Parcelable {
-    ctor public DataCallResponse(int, int, int, int, @Nullable String, @Nullable String, @Nullable java.util.List<android.net.LinkAddress>, @Nullable java.util.List<java.net.InetAddress>, @Nullable java.util.List<java.net.InetAddress>, @Nullable java.util.List<java.lang.String>, int);
+    ctor public DataCallResponse(int, int, int, int, int, @Nullable String, @Nullable java.util.List<android.net.LinkAddress>, @Nullable java.util.List<java.net.InetAddress>, @Nullable java.util.List<java.net.InetAddress>, @Nullable java.util.List<java.lang.String>, int);
     ctor public DataCallResponse(android.os.Parcel);
     method public int describeContents();
     method public int getActive();
@@ -7841,9 +7975,9 @@
     method @NonNull public String getIfname();
     method public int getMtu();
     method @NonNull public java.util.List<java.lang.String> getPcscfs();
+    method public int getProtocolType();
     method public int getStatus();
     method public int getSuggestedRetryTime();
-    method @NonNull public String getType();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.telephony.data.DataCallResponse> CREATOR;
   }
@@ -7857,8 +7991,8 @@
     method public int getMtu();
     method public String getPassword();
     method public int getProfileId();
-    method public String getProtocol();
-    method public String getRoamingProtocol();
+    method public int getProtocol();
+    method public int getRoamingProtocol();
     method public int getSupportedApnTypesBitmap();
     method public int getType();
     method public String getUserName();
@@ -8225,6 +8359,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();
@@ -8242,7 +8386,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();
@@ -8251,8 +8395,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);
@@ -8687,13 +8831,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 8e638fd..9c27535 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6,11 +6,13 @@
     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";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
+    field public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
     field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
   }
 
@@ -514,6 +516,7 @@
 
   public final class Bitmap implements android.os.Parcelable {
     method public void eraseColor(@ColorLong long);
+    method public void setColorSpace(@NonNull android.graphics.ColorSpace);
   }
 
   public final class ImageDecoder implements java.lang.AutoCloseable {
@@ -809,6 +812,7 @@
 
   public final class IpPrefix implements android.os.Parcelable {
     ctor public IpPrefix(java.net.InetAddress, int);
+    ctor public IpPrefix(String);
   }
 
   public final class IpSecManager {
@@ -817,6 +821,9 @@
 
   public class LinkAddress implements android.os.Parcelable {
     ctor public LinkAddress(java.net.InetAddress, int, int, int);
+    ctor public LinkAddress(java.net.InetAddress, int);
+    ctor public LinkAddress(String);
+    ctor public LinkAddress(String, int, int);
     method public boolean isGlobalPreferred();
     method public boolean isIPv4();
     method public boolean isIPv6();
@@ -826,6 +833,7 @@
   public final class LinkProperties implements android.os.Parcelable {
     ctor public LinkProperties(android.net.LinkProperties);
     method public boolean addDnsServer(java.net.InetAddress);
+    method public boolean addLinkAddress(android.net.LinkAddress);
     method @Nullable public android.net.IpPrefix getNat64Prefix();
     method public java.util.List<java.net.InetAddress> getPcscfServers();
     method public String getTcpBufferSizes();
@@ -838,6 +846,7 @@
     method public boolean isProvisioned();
     method public boolean isReachable(java.net.InetAddress);
     method public boolean removeDnsServer(java.net.InetAddress);
+    method public boolean removeLinkAddress(android.net.LinkAddress);
     method public boolean removeRoute(android.net.RouteInfo);
     method public void setNat64Prefix(android.net.IpPrefix);
     method public void setPcscfServers(java.util.Collection<java.net.InetAddress>);
@@ -848,6 +857,7 @@
   }
 
   public class Network implements android.os.Parcelable {
+    ctor public Network(android.net.Network);
     method public android.net.Network getPrivateDnsBypassingCopy();
   }
 
@@ -865,11 +875,75 @@
     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();
     method public static long getLoopbackTxBytes();
     method public static long getLoopbackTxPackets();
+    field public static final int TAG_SYSTEM_DHCP = -192; // 0xffffff40
+    field public static final int TAG_SYSTEM_DHCP_SERVER = -186; // 0xffffff46
+    field public static final int TAG_SYSTEM_PROBE = -190; // 0xffffff42
+  }
+
+}
+
+package android.net.apf {
+
+  public class ApfCapabilities {
+    ctor public ApfCapabilities(int, int, int);
+    method public static boolean getApfDrop8023Frames(android.content.Context);
+    method public static int[] getApfEthTypeBlackList(android.content.Context);
+    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);
   }
 
 }
@@ -1030,6 +1104,26 @@
 
 }
 
+package android.net.util {
+
+  public class SocketUtils {
+    method public static void addArpEntry(java.net.Inet4Address, android.net.MacAddress, String, java.io.FileDescriptor) throws java.io.IOException;
+    method public static void attachControlPacketFilter(java.io.FileDescriptor, int) throws java.net.SocketException;
+    method public static void attachDhcpFilter(java.io.FileDescriptor) throws java.net.SocketException;
+    method public static void attachRaFilter(java.io.FileDescriptor, int) throws java.net.SocketException;
+    method public static void bindSocket(java.io.FileDescriptor, java.net.SocketAddress) throws android.system.ErrnoException, java.net.SocketException;
+    method public static void bindSocketToInterface(java.io.FileDescriptor, String) throws android.system.ErrnoException;
+    method public static void closeSocket(java.io.FileDescriptor) throws java.io.IOException;
+    method public static void connectSocket(java.io.FileDescriptor, java.net.SocketAddress) throws android.system.ErrnoException, java.net.SocketException;
+    method public static java.net.SocketAddress makeNetlinkSocketAddress(int, int);
+    method public static java.net.SocketAddress makePacketSocketAddress(short, int);
+    method public static java.net.SocketAddress makePacketSocketAddress(int, byte[]);
+    method public static void sendTo(java.io.FileDescriptor, byte[], int, int, int, java.net.SocketAddress) throws android.system.ErrnoException, java.net.SocketException;
+    method public static void setSocketTimeValueOption(java.io.FileDescriptor, int, int, long) throws android.system.ErrnoException;
+  }
+
+}
+
 package android.os {
 
   public class Build {
@@ -1052,6 +1146,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);
@@ -1217,6 +1315,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);
@@ -1484,17 +1586,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 {
@@ -1554,6 +1676,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 {
@@ -1828,10 +1954,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 8fb01b4..812a2f2 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -24,14 +24,18 @@
 import "frameworks/base/core/proto/android/app/enums.proto";
 import "frameworks/base/core/proto/android/app/settings_enums.proto";
 import "frameworks/base/core/proto/android/app/job/enums.proto";
+import "frameworks/base/core/proto/android/bluetooth/a2dp/enums.proto";
 import "frameworks/base/core/proto/android/bluetooth/enums.proto";
 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/bluetooth/smp/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";
 import "frameworks/base/core/proto/android/server/enums.proto";
+import "frameworks/base/core/proto/android/server/job/enums.proto";
 import "frameworks/base/core/proto/android/server/location/enums.proto";
 import "frameworks/base/core/proto/android/service/procstats_enum.proto";
 import "frameworks/base/core/proto/android/service/usb.proto";
@@ -146,9 +150,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 +167,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 +213,31 @@
         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;
+        ScheduledJobConstraintChanged scheduled_job_constraint_changed = 150;
+        BluetoothActiveDeviceChanged bluetooth_active_device_changed = 151;
+        BluetoothA2dpPlaybackStateChanged bluetooth_a2dp_playback_state_changed = 152;
+        BluetoothA2dpCodecConfigChanged bluetooth_a2dp_codec_config_changed = 153;
+        BluetoothA2dpCodecCapabilityChanged bluetooth_a2dp_codec_capability_changed = 154;
+        BluetoothA2dpAudioUnderrunReported bluetooth_a2dp_audio_underrun_reported = 155;
+        BluetoothA2dpAudioOverrunReported bluetooth_a2dp_audio_overrun_reported = 156;
+        BluetoothDeviceRssiReported bluetooth_device_rssi_reported = 157;
+        BluetoothDeviceFailedContactCounterReported bluetooth_device_failed_contact_counter_reported = 158;
+        BluetoothDeviceTxPowerLevelReported bluetooth_device_tx_power_level_reported = 159;
+        BluetoothHciTimeoutReported bluetooth_hci_timeout_reported = 160;
+        BluetoothQualityReportReported bluetooth_quality_report_reported = 161;
+        BluetoothManufacturerInfoReported bluetooth_device_info_reported = 162;
+        BluetoothRemoteVersionInfoReported bluetooth_remote_version_info_reported = 163;
+        BluetoothSdpAttributeReported bluetooth_sdp_attribute_reported = 164;
+        BluetoothBondStateChanged bluetooth_bond_state_changed = 165;
+        BluetoothClassicPairingEventReported bluetooth_classic_pairing_event_reported = 166;
+        BluetoothSmpPairingEventReported bluetooth_smp_pairing_event_reported = 167;
     }
 
     // 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 +270,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 +285,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.
@@ -1400,6 +1428,27 @@
     optional android.bluetooth.hfp.ScoCodec codec = 3;
 }
 
+/**
+ * Logged when active device of a profile changes
+ *
+ * Logged from:
+ *     packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java
+ *     packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetService.java
+ *     packages/apps/Bluetooth/src/com/android/bluetooth/hearingaid/HearingAidService.java
+ */
+message BluetoothActiveDeviceChanged {
+    // The profile whose active device has changed. Eg. A2DP, HEADSET, HEARING_AID
+    // From android.bluetooth.BluetoothProfile
+    optional int32 bt_profile = 1;
+    // An identifier that can be used to match events for this new active device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if there is no active device for this profile
+    optional bytes obfuscated_id = 2 [(android.os.statsd.log_mode) = MODE_BYTES];
+}
+
 // Logs when there is an event affecting Bluetooth device's link layer connection.
 // - This event is triggered when there is a related HCI command or event
 // - Users of this metrics can deduce Bluetooth device's connection state from these events
@@ -1483,6 +1532,535 @@
     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 there is a change in Bluetooth A2DP playback state
+ *
+ * Logged from:
+ *     packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java
+ */
+message BluetoothA2dpPlaybackStateChanged {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if the device identifier is not known
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Current playback state
+    // Default: PLAYBACK_STATE_UNKNOWN
+    optional android.bluetooth.a2dp.PlaybackStateEnum playback_state = 2;
+    // Current audio coding mode
+    // Default: AUDIO_CODING_MODE_UNKNOWN
+    optional android.bluetooth.a2dp.AudioCodingModeEnum audio_coding_mode = 3;
+}
+
+/**
+ * Logs when there is a change in A2DP codec config for a particular remote device
+ *
+ * Logged from:
+ *     frameworks/base/core/java/android/bluetooth/BluetoothCodecConfig.java
+ *     packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java
+ */
+message BluetoothA2dpCodecConfigChanged {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if the device identifier is not known
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Type of codec as defined by various SOURCE_CODEC_TYPE_* constants in BluetoothCodecConfig
+    // Default SOURCE_CODEC_TYPE_INVALID
+    optional int32 codec_type = 2;
+    // Codec priroity, the higher the more preferred, -1 for disabled
+    // Default: CODEC_PRIORITY_DEFAULT
+    optional int32 codec_priority = 3;
+    // Sample rate in Hz as defined by various SAMPLE_RATE_* constants in BluetoothCodecConfig
+    // Default: SAMPLE_RATE_NONE
+    optional int32 sample_rate = 4;
+    // Bits per sample as defined by various BITS_PER_SAMPLE_* constants in BluetoothCodecConfig
+    // Default: BITS_PER_SAMPLE_NONE
+    optional int32 bits_per_sample = 5;
+    // Channel mode as defined by various CHANNEL_MODE_* constants in BluetoothCodecConfig
+    // Default: CHANNEL_MODE_NONE
+    optional int32 channel_mode = 6;
+    // Codec specific values
+    // Default 0
+    optional int64 codec_specific_1 = 7;
+    optional int64 codec_specific_2 = 8;
+    optional int64 codec_specific_3 = 9;
+    optional int64 codec_specific_4 = 10;
+}
+
+/**
+ * Logs when there is a change in selectable A2DP codec capability for a paricular remote device
+ * Each codec's capability is logged separately due to statsd restriction
+ *
+ * Logged from:
+ *     frameworks/base/core/java/android/bluetooth/BluetoothCodecConfig.java
+ *     packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java
+ */
+message BluetoothA2dpCodecCapabilityChanged {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if the device identifier is not known
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Type of codec as defined by various SOURCE_CODEC_TYPE_* constants in BluetoothCodecConfig
+    // Default SOURCE_CODEC_TYPE_INVALID
+    optional int32 codec_type = 2;
+    // Codec priroity, the higher the more preferred, -1 for disabled
+    // Default: CODEC_PRIORITY_DEFAULT
+    optional int32 codec_priority = 3;
+    // A bit field of supported sample rates as defined by various SAMPLE_RATE_* constants
+    // in BluetoothCodecConfig
+    // Default: empty and SAMPLE_RATE_NONE for individual item
+    optional int32 sample_rate = 4;
+    // A bit field of supported bits per sample as defined by various BITS_PER_SAMPLE_* constants
+    // in BluetoothCodecConfig
+    // Default: empty and BITS_PER_SAMPLE_NONE for individual item
+    optional int32 bits_per_sample = 5;
+    // A bit field of supported channel mode as defined by various CHANNEL_MODE_* constants in
+    // BluetoothCodecConfig
+    // Default: empty and CHANNEL_MODE_NONE for individual item
+    optional int32 channel_mode = 6;
+    // Codec specific values
+    // Default 0
+    optional int64 codec_specific_1 = 7;
+    optional int64 codec_specific_2 = 8;
+    optional int64 codec_specific_3 = 9;
+    optional int64 codec_specific_4 = 10;
+}
+
+/**
+ * Logs when A2DP failed to read from PCM source.
+ * This typically happens when audio HAL cannot supply A2DP with data fast enough for encoding.
+ *
+ * Logged from:
+ *     system/bt
+ */
+message BluetoothA2dpAudioUnderrunReported {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if the device identifier is not known
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Encoding interval in nanoseconds
+    // Default: 0
+    optional int64 encoding_interval_nanos = 2;
+    // Number of bytes of PCM data that could not be read from the source
+    // Default: 0
+    optional int32 num_missing_pcm_bytes = 3;
+}
+
+/**
+ * Logs when A2DP failed send encoded data to the remote device fast enough such that the transmit
+ * buffer queue is full and we have to drop data
+ *
+ * Logged from:
+ *     system/bt
+ */
+message BluetoothA2dpAudioOverrunReported {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if the device identifier is not known
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Encoding interval in nanoseconds
+    // Default: 0
+    optional int64 encoding_interval_nanos = 2;
+    // Number of buffers dropped in this event
+    // Each buffer is encoded in one encoding interval and consists of multiple encoded frames
+    // Default: 0
+    optional int32 num_dropped_buffers = 3;
+    // Number of encoded buffers dropped in this event
+    // Default 0
+    optional int32 num_dropped_encoded_frames = 4;
+    // Number of encoded bytes dropped in this event
+    // Default: 0
+    optional int32 num_dropped_encoded_bytes = 5;
+}
+
+/**
+ * Logs when we receive reports regarding a device's RSSI value
+ *
+ * Logged from:
+ *     system/bt
+ */
+message BluetoothDeviceRssiReported {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if the device identifier is not known
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Connection handle of this connection if available
+    // Range: 0x0000 - 0x0EFF (12 bits)
+    // Default: 0xFFFF if the handle is unknown
+    optional int32 connection_handle = 2;
+    // HCI command status code if this is triggerred by hci_cmd
+    // Default: STATUS_UNKNOWN
+    optional android.bluetooth.hci.StatusEnum hci_status = 3;
+    // BR/EDR
+    //   Range: -128 ≤ N ≤ 127 (signed integer)
+    //   Units: dB
+    // LE:
+    //   Range: -127 to 20, 127 (signed integer)
+    //   Units: dBm
+    // Invalid when an out of range value is reported
+    optional int32 rssi = 4;
+}
+
+/**
+ * Logs when we receive reports regarding how many consecutive failed contacts for a connection
+ *
+ * Logged from:
+ *     system/bt
+ */
+message BluetoothDeviceFailedContactCounterReported {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if the device identifier is not known
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Connection handle of this connection if available
+    // Range: 0x0000 - 0x0EFF (12 bits)
+    // Default: 0xFFFF if the handle is unknown
+    optional int32 connection_handle = 2;
+    // HCI command status code if this is triggerred by hci_cmd
+    // Default: STATUS_UNKNOWN
+    optional android.bluetooth.hci.StatusEnum cmd_status = 3;
+    // Number of consecutive failed contacts for a connection corresponding to the Handle
+    // Range: uint16_t, 0-0xFFFF
+    // Default: 0xFFFFF
+    optional int32 failed_contact_counter = 4;
+}
+
+/**
+ * Logs when we receive reports regarding the tranmit power level used for a specific connection
+ *
+ * Logged from:
+ *     system/bt
+ */
+message BluetoothDeviceTxPowerLevelReported {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if the device identifier is not known
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Connection handle of this connection if available
+    // Range: 0x0000 - 0x0EFF (12 bits)
+    // Default: 0xFFFF if the handle is unknown
+    optional int32 connection_handle = 2;
+    // HCI command status code if this is triggered by hci_cmd
+    // Default: STATUS_UNKNOWN
+    optional android.bluetooth.hci.StatusEnum hci_status = 3;
+    // Range: -30 ≤ N ≤ 20
+    // Units: dBm
+    // Invalid when an out of range value is reported
+    optional int32 transmit_power_level = 4;
+}
+
+/**
+ * Logs when Bluetooth controller failed to reply with command status within a timeout period after
+ * receiving an HCI command from the host
+ *
+ * Logged from: system/bt
+ */
+message BluetoothHciTimeoutReported {
+    // HCI command associated with this event
+    // Default: CMD_UNKNOWN
+    optional android.bluetooth.hci.CommandEnum hci_command = 1;
+}
+
+/**
+ * Logs when we receive Bluetooth Link Quality Report event from the controller
+ * See Android Bluetooth HCI specification for more details
+ *
+ * Note: all count and bytes field are counted since last event
+ *
+ * Logged from: system/bt
+ */
+message BluetoothQualityReportReported {
+    // Quality report ID
+    // Original type: uint8_t
+    // Default: BQR_ID_UNKNOWN
+    optional android.bluetooth.hci.BqrIdEnum quality_report_id = 1;
+    // Packet type of the connection
+    // Original type: uint8_t
+    // Default: BQR_PACKET_TYPE_UNKNOWN
+    optional android.bluetooth.hci.BqrPacketTypeEnum packet_types = 2;
+    // Connection handle of the connection
+    // Original type: uint16_t
+    optional int32 connection_handle = 3;
+    // Performing Role for the connection
+    // Original type: uint8_t
+    optional int32 connection_role = 4;
+    // Current Transmit Power Level for the connection. This value is the same as the controller's
+    // response to the HCI_Read_Transmit_Power_Level HCI command
+    // Original type: uint8_t
+    optional int32 tx_power_level = 5;
+    // Received Signal Strength Indication (RSSI) value for the connection. This value is an
+    // absolute receiver signal strength value
+    // Original type: int8_t
+    optional int32 rssi = 6;
+    // Signal-to-Noise Ratio (SNR) value for the connection. It is the average SNR of all the
+    // channels used by the link currently
+    // Original type: uint8_t
+    optional int32 snr = 7;
+    // Indicates the number of unused channels in AFH_channel_map
+    // Original type: uint8_t
+    optional int32 unused_afh_channel_count = 8;
+    // Indicates the number of the channels which are interfered and quality is bad but are still
+    // selected for AFH
+    // Original type: uint8_t
+    optional int32 afh_select_unideal_channel_count = 9;
+    // Current Link Supervision Timeout Setting
+    // Unit: N * 0.3125 ms (1 Bluetooth Clock)
+    // Original type: uint16_t
+    optional int32 lsto = 10;
+    // Piconet Clock for the specified Connection_Handle. This value is the same as the controller's
+    // response to HCI_Read_Clock HCI command with the parameter "Which_Clock" of
+    // 0x01 (Piconet Clock)
+    // Unit: N * 0.3125 ms (1 Bluetooth Clock)
+    // Original type: uint32_t
+    optional int64 connection_piconet_clock = 11;
+    // The count of retransmission
+    // Original type: uint32_t
+    optional int64 retransmission_count = 12;
+    // The count of no RX
+    // Original type: uint32_t
+    optional int64 no_rx_count = 13;
+    // The count of NAK (Negative Acknowledge)
+    // Original type: uint32_t
+    optional int64 nak_count = 14;
+    // Controller timestamp of last TX ACK
+    // Unit: N * 0.3125 ms (1 Bluetooth Clock)
+    // Original type: uint32_t
+    optional int64 last_tx_ack_timestamp = 15;
+    // The count of Flow-off (STOP)
+    // Original type: uint32_t
+    optional int64 flow_off_count = 16;
+    // Controller timestamp of last Flow-on (GO)
+    // Unit: N * 0.3125 ms (1 Bluetooth Clock)
+    // Original type: uint32_t
+    optional int64 last_flow_on_timestamp = 17;
+    // Buffer overflow count (how many bytes of TX data are dropped) since the last event
+    // Original type: uint32_t
+    optional int64 buffer_overflow_bytes = 18;
+    // Buffer underflow count (in byte) since last event
+    // Original type: uint32_t
+    optional int64 buffer_underflow_bytes = 19;
+}
+
+/**
+ * Logs when a Bluetooth device's manufacturer information is learnt by the Bluetooth stack
+ *
+ * Notes:
+ * - Each event can be partially filled as we might learn different pieces of device
+ *   information at different time
+ * - Multiple device info events can be combined to give more complete picture
+ * - When multiple device info events tries to describe the same information, the
+ *   later one wins
+ *
+ * Logged from:
+ *     packages/apps/Bluetooth
+ */
+message BluetoothManufacturerInfoReported {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if the device identifier is not known
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Where is this device info obtained from
+    optional android.bluetooth.DeviceInfoSrcEnum source_type = 2;
+    // Name of the data source
+    // For EXTERNAL: package name of the data source
+    // For INTERNAL: null for general case, component name otherwise
+    optional string source_name = 3;
+    // Name of the manufacturer of this device
+    optional string manufacturer = 4;
+    // Model of this device
+    optional string model = 5;
+    // Hardware version of this device
+    optional string hardware_version = 6;
+    // Software version of this device
+    optional string software_version = 7;
+}
+
+/**
+ * Logs when we receive Bluetooth Read Remote Version Information Complete Event from the remote
+ * device, as documented by the Bluetooth Core HCI specification
+ * Reference: https://www.bluetooth.com/specifications/bluetooth-core-specification
+ * Vol 2, Part E, Page 1118
+ *
+ * Logged from:
+ *     system/bt
+ */
+message BluetoothRemoteVersionInfoReported {
+    // Connection handle of the connection
+    // Original type: uint16_t
+    optional int32 connection_handle = 1;
+    // HCI command status code
+    // Default: STATUS_UNKNOWN
+    optional android.bluetooth.hci.StatusEnum hci_status = 2;
+    // 1 byte Version of current LMP in the remote controller
+    optional int32 lmp_version = 3;
+    // 2 bytes LMP manufacturer code of the remote controller
+    // https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers
+    optional int32 lmp_manufacturer_code = 4;
+    // 4 bytes subversion of the LMP in the remote controller
+    optional int32 lmp_subversion = 5;
+}
+
+/**
+ * Logs when certain Bluetooth SDP attributes are discovered
+ * Constant definitions are from:
+ *     https://www.bluetooth.com/specifications/assigned-numbers/service-discovery
+ *
+ * Current logged attributes:
+ * - BluetoothProfileDescriptorList
+ * - Supported Features Bitmask
+ *
+ * Logged from:
+ *     system/bt
+ */
+message BluetoothSdpAttributeReported {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Short form UUIDs used to identify Bluetooth protocols, profiles, and service classes
+    // Original type: uint16_t
+    optional int32 protocol_uuid = 2;
+    // Short form UUIDs used to identify Bluetooth SDP attribute types
+    // Original type: uint16_t
+    optional int32 attribute_id = 3;
+    // Attribute value for the particular attribute
+    optional bytes attribute_value = 4 [(android.os.statsd.log_mode) = MODE_BYTES];
+}
+
+/**
+ * Logs when bond state of a Bluetooth device changes
+ *
+ * Logged from:
+ *     frameworks/base/core/java/android/bluetooth/BluetoothDevice.java
+ *     packages/apps/Bluetooth/src/com/android/bluetooth/btservice/BondStateMachine.java
+ */
+message BluetoothBondStateChanged {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if the device identifier is not known
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Preferred transport type to remote dual mode device
+    // Default: TRANSPORT_AUTO means no preference
+    optional android.bluetooth.TransportTypeEnum transport = 2;
+    // The type of this Bluetooth device (Classic, LE, or Dual mode)
+    // Default: UNKNOWN
+    optional android.bluetooth.DeviceTypeEnum type = 3;
+    // Current bond state (NONE, BONDING, BONDED)
+    // Default: BOND_STATE_UNKNOWN
+    optional android.bluetooth.BondStateEnum bond_state = 4;
+    // Bonding sub state
+    // Default: BOND_SUB_STATE_UNKNOWN
+    optional android.bluetooth.BondSubStateEnum bonding_sub_state = 5;
+    // Unbond Reason
+    // Default: UNBOND_REASON_UNKNOWN
+    optional android.bluetooth.UnbondReasonEnum unbond_reason = 6;
+}
+
+/**
+ * Logs there is an event related Bluetooth classic pairing
+ *
+ * Logged from:
+ *     system/bt
+ */
+message BluetoothClassicPairingEventReported {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if the device identifier is not known
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Connection handle of this connection if available
+    // Range: 0x0000 - 0x0EFF (12 bits)
+    // Default: 0xFFFF if the handle is unknown
+    optional int32 connection_handle = 2;
+    // HCI command associated with this event
+    // Default: CMD_UNKNOWN
+    optional android.bluetooth.hci.CommandEnum hci_cmd = 3;
+    // HCI event associated with this event
+    // Default: EVT_UNKNOWN
+    optional android.bluetooth.hci.EventEnum hci_event = 4;
+    // HCI command status code if this is triggerred by hci_cmd
+    // Default: STATUS_UNKNOWN
+    optional android.bluetooth.hci.StatusEnum cmd_status = 5;
+    // HCI reason code associated with this event
+    // Default: STATUS_UNKNOWN
+    optional android.bluetooth.hci.StatusEnum reason_code = 6;
+}
+
+/**
+ * Logs when there is an event related to Bluetooth Security Manager Protocol (SMP)
+ *
+ * Logged from:
+ *     system/bt
+ */
+message BluetoothSmpPairingEventReported {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if the device identifier is not known
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // SMP command sent or received over L2CAP
+    // Default: CMD_UNKNOWN
+    optional android.bluetooth.smp.CommandEnum smp_command = 2;
+    // Whether this command is sent or received
+    // Default: DIRECTION_UNKNOWN
+    optional android.bluetooth.DirectionEnum direction = 3;
+    // SMP failure reason code
+    // Default: PAIRING_FAIL_REASON_DEFAULT
+    optional android.bluetooth.smp.PairingFailReasonEnum smp_fail_reason = 4;
+}
 
 /**
  * Logs when something is plugged into or removed from the USB-C connector.
@@ -2351,58 +2929,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,40 +3091,44 @@
 }
 
 /*
- * 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;
 }
 
 /*
  * Logs when a binary push state changes.
- * Logged in Play store
+ * Logged by the installer via public api.
  */
 message BinaryPushStateChanged {
-    // Binary package name.
-    optional string binary_name = 1;
-    // Version code.
-    optional int64 version = 2;
-    // State
+    // Name of the train.
+    optional string train_name = 1;
+    // Version code for a "train" of packages that need to be installed atomically
+    optional int64 train_version_code = 2;
+    // After installation of this package, device requires a restart.
+    optional bool requires_staging = 3;
+    // Rollback should be enabled for this install.
+    optional bool rollback_enabled = 4;
+    // Requires low latency monitoring if possible.
+    optional bool requires_low_latency_monitor = 5;
+
     enum State {
-        STATE_UNKNOWN = 0;
-        DOWNLOAD_START = 1;
-        DOWNLOAD_DONE = 2;
-        INSTALL_START = 3;
-        INSTALL_DONE = 4;
-        REBOOT_START = 5;
-        REBOOT_DONE = 6;
+        UNKNOWN = 0;
+        INSTALL_REQUESTED = 1;
+        INSTALL_STARTED = 2;
+        INSTALL_STAGED_NOT_READY = 3;
+        INSTALL_STAGED_READY = 4;
+        INSTALL_SUCCESS = 5;
+        INSTALL_FAILURE = 6;
+        INSTALL_CANCELLED = 7;
+        INSTALLER_ROLLBACK_REQUESTED = 8;
     }
-    optional State state = 3;
+    optional State state = 6;
 }
 
 /** Represents USB port overheat event. */
@@ -3341,14 +3960,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 {
@@ -4549,3 +5168,107 @@
     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;
+}
+
+/**
+ * Logs that a constraint for a scheduled job has changed.
+ *
+ * Logged from:
+ *     frameworks/base/services/core/java/com/android/server/job/controllers/JobStatus.java
+ */
+message ScheduledJobConstraintChanged {
+    repeated AttributionNode attribution_node = 1;
+
+    // Name of the job.
+    optional string job_name = 2;
+
+    optional com.android.server.job.ConstraintEnum constraint = 3;
+
+    enum State {
+        UNSATISFIED = 0;
+        SATISFIED = 1;
+    }
+    optional State state = 4;
+}
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/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 4122d84..5645461 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -524,6 +524,14 @@
         multiIntervals.resize(mFieldMatchers.size());
     }
 
+    // We only use anomaly detection under certain cases.
+    // N.B.: The anomaly detection cases were modified in order to fix an issue with value metrics
+    // containing multiple values. We tried to retain all previous behaviour, but we are unsure the
+    // previous behaviour was correct. At the time of the fix, anomaly detection had no owner.
+    // Whoever next works on it should look into the cases where it is triggered in this function.
+    // Discussion here: http://ag/6124370.
+    bool useAnomalyDetection = true;
+
     for (int i = 0; i < (int)mFieldMatchers.size(); i++) {
         const Matcher& matcher = mFieldMatchers[i];
         Interval& interval = multiIntervals[i];
@@ -546,7 +554,11 @@
                     // no base. just update base and return.
                     interval.base = value;
                     interval.hasBase = true;
-                    return;
+                    // If we're missing a base, do not use anomaly detection on incomplete data
+                    useAnomalyDetection = false;
+                    // Continue (instead of return) here in order to set interval.base and
+                    // interval.hasBase for other intervals
+                    continue;
                 }
             }
             Value diff;
@@ -560,7 +572,9 @@
                         VLOG("Unexpected decreasing value");
                         StatsdStats::getInstance().notePullDataError(mPullTagId);
                         interval.base = value;
-                        return;
+                        // If we've got bad data, do not use anomaly detection
+                        useAnomalyDetection = false;
+                        continue;
                     }
                     break;
                 case ValueMetric::DECREASING:
@@ -572,7 +586,9 @@
                         VLOG("Unexpected increasing value");
                         StatsdStats::getInstance().notePullDataError(mPullTagId);
                         interval.base = value;
-                        return;
+                        // If we've got bad data, do not use anomaly detection
+                        useAnomalyDetection = false;
+                        continue;
                     }
                     break;
                 case ValueMetric::ANY:
@@ -608,14 +624,18 @@
         interval.sampleSize += 1;
     }
 
-    // TODO: propgate proper values down stream when anomaly support doubles
-    long wholeBucketVal = multiIntervals[0].value.long_value;
-    auto prev = mCurrentFullBucket.find(eventKey);
-    if (prev != mCurrentFullBucket.end()) {
-        wholeBucketVal += prev->second;
-    }
-    for (auto& tracker : mAnomalyTrackers) {
-        tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, wholeBucketVal);
+    // Only trigger the tracker if all intervals are correct
+    if (useAnomalyDetection) {
+        // TODO: propgate proper values down stream when anomaly support doubles
+        long wholeBucketVal = multiIntervals[0].value.long_value;
+        auto prev = mCurrentFullBucket.find(eventKey);
+        if (prev != mCurrentFullBucket.end()) {
+            wholeBucketVal += prev->second;
+        }
+        for (auto& tracker : mAnomalyTrackers) {
+            tracker->detectAndDeclareAnomaly(
+                eventTimeNs, mCurrentBucketNum, eventKey, wholeBucketVal);
+        }
     }
 }
 
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 69eb0af..a8dfc5b 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -212,6 +212,7 @@
     FRIEND_TEST(ValueMetricProducerTest, TestFirstBucket);
     FRIEND_TEST(ValueMetricProducerTest, TestCalcPreviousBucketEndTime);
     FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput);
+    FRIEND_TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue);
     FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase);
     FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures);
     FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey);
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 9cfe343..c0648ee 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -1508,6 +1508,113 @@
     EXPECT_EQ(5, valueProducer.mPastBuckets.begin()->second.back().values[0].long_value);
 }
 
+TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) {
+    ValueMetric metric;
+    metric.set_id(metricId);
+    metric.set_bucket(ONE_MINUTE);
+    metric.mutable_value_field()->set_field(tagId);
+    metric.mutable_value_field()->add_child()->set_field(2);
+    metric.mutable_value_field()->add_child()->set_field(3);
+    metric.set_aggregation_type(ValueMetric::MIN);
+    metric.set_use_diff(true);
+
+    UidMap uidMap;
+    SimpleAtomMatcher atomMatcher;
+    atomMatcher.set_atom_id(tagId);
+    sp<EventMatcherWizard> eventMatcherWizard =
+            new EventMatcherWizard({new SimpleLogMatchingTracker(
+                    atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
+    sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+    ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
+                                      eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
+                                      pullerManager);
+
+    shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+    event1->write(1);
+    event1->write(10);
+    event1->write(20);
+    event1->init();
+    shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 15);
+    event2->write(1);
+    event2->write(15);
+    event2->write(22);
+    event2->init();
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1);
+    // has one slice
+    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    ValueMetricProducer::Interval curInterval0 =
+        valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    ValueMetricProducer::Interval curInterval1 =
+        valueProducer.mCurrentSlicedBucket.begin()->second[1];
+    EXPECT_EQ(true, curInterval0.hasBase);
+    EXPECT_EQ(10, curInterval0.base.long_value);
+    EXPECT_EQ(false, curInterval0.hasValue);
+    EXPECT_EQ(true, curInterval1.hasBase);
+    EXPECT_EQ(20, curInterval1.base.long_value);
+    EXPECT_EQ(false, curInterval1.hasValue);
+
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2);
+
+    // has one slice
+    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    curInterval0 = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval1 = valueProducer.mCurrentSlicedBucket.begin()->second[1];
+    EXPECT_EQ(true, curInterval0.hasValue);
+    EXPECT_EQ(5, curInterval0.value.long_value);
+    EXPECT_EQ(true, curInterval1.hasValue);
+    EXPECT_EQ(2, curInterval1.value.long_value);
+
+    // no change in first value field
+    shared_ptr<LogEvent> event3 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10);
+    event3->write(1);
+    event3->write(15);
+    event3->write(25);
+    event3->init();
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event3);
+    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    curInterval0 = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval1 = valueProducer.mCurrentSlicedBucket.begin()->second[1];
+    EXPECT_EQ(true, curInterval0.hasBase);
+    EXPECT_EQ(15, curInterval0.base.long_value);
+    EXPECT_EQ(true, curInterval0.hasValue);
+    EXPECT_EQ(true, curInterval1.hasBase);
+    EXPECT_EQ(25, curInterval1.base.long_value);
+    EXPECT_EQ(true, curInterval1.hasValue);
+
+    shared_ptr<LogEvent> event4 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 15);
+    event4->write(1);
+    event4->write(15);
+    event4->write(29);
+    event4->init();
+    valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event4);
+    EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+    curInterval0 = valueProducer.mCurrentSlicedBucket.begin()->second[0];
+    curInterval1 = valueProducer.mCurrentSlicedBucket.begin()->second[1];
+    EXPECT_EQ(true, curInterval0.hasBase);
+    EXPECT_EQ(15, curInterval0.base.long_value);
+    EXPECT_EQ(true, curInterval0.hasValue);
+    EXPECT_EQ(true, curInterval1.hasBase);
+    EXPECT_EQ(29, curInterval1.base.long_value);
+    EXPECT_EQ(true, curInterval1.hasValue);
+
+    valueProducer.flushIfNeededLocked(bucket3StartTimeNs);
+
+    EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
+    EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size());
+    EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second[0].values.size());
+    EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second[1].values.size());
+
+    EXPECT_EQ(5, valueProducer.mPastBuckets.begin()->second[0].values[0].long_value);
+    EXPECT_EQ(0, valueProducer.mPastBuckets.begin()->second[0].valueIndex[0]);
+    EXPECT_EQ(2, valueProducer.mPastBuckets.begin()->second[0].values[1].long_value);
+    EXPECT_EQ(1, valueProducer.mPastBuckets.begin()->second[0].valueIndex[1]);
+
+    EXPECT_EQ(3, valueProducer.mPastBuckets.begin()->second[1].values[0].long_value);
+    EXPECT_EQ(1, valueProducer.mPastBuckets.begin()->second[1].valueIndex[0]);
+}
+
 /*
  * Tests zero default base.
  */
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/Activity.java b/core/java/android/app/Activity.java
index 92f47e7..e77e212 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1808,6 +1808,29 @@
         mCalled = true;
     }
 
+    /**
+     * Called when activity gets or looses the top resumed position in the system.
+     *
+     * <p>Starting with {@link android.os.Build.VERSION_CODES#Q} multiple activities can be resumed
+     * at the same time in multi-window and multi-display modes. This callback should be used
+     * instead of {@link #onResume()} as an indication that the activity can try to open
+     * exclusive-access devices like camera.</p>
+     *
+     * <p>It will always be delivered after the activity was resumed and before it is paused. In
+     * some cases it might be skipped and activity can go straight from {@link #onResume()} to
+     * {@link #onPause()} without receiving the top resumed state.</p>
+     *
+     * @param isTopResumedActivity {@code true} if it's the topmost resumed activity in the system,
+     *                             {@code false} otherwise. A call with this as {@code true} will
+     *                             always be followed by another one with {@code false}.
+     *
+     * @see #onResume()
+     * @see #onPause()
+     * @see #onWindowFocusChanged(boolean)
+     */
+    public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
+    }
+
     void setVoiceInteractor(IVoiceInteractor voiceInteractor) {
         if (mVoiceInteractor != null) {
             for (Request activeRequest: mVoiceInteractor.getActiveRequests()) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ee3d27c..03a09ee 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -449,6 +449,14 @@
         ViewRootImpl.ActivityConfigCallback configCallback;
         ActivityClientRecord nextIdle;
 
+        // Indicates whether this activity is currently the topmost resumed one in the system.
+        // This holds the last reported value from server.
+        boolean isTopResumedActivity;
+        // This holds the value last sent to the activity. This is needed, because an update from
+        // server may come at random time, but we always need to report changes between ON_RESUME
+        // and ON_PAUSE to the app.
+        boolean lastReportedTopResumedState;
+
         ProfilerInfo profilerInfo;
 
         @UnsupportedAppUsage
@@ -3295,16 +3303,14 @@
         final boolean resumed = !r.paused;
         if (resumed) {
             r.activity.mTemporaryPause = true;
-            mInstrumentation.callActivityOnPause(r.activity);
+            performPauseActivityIfNeeded(r, "performNewIntents");
         }
         checkAndBlockForNetworkAccess();
         deliverNewIntents(r, intents);
         if (resumed) {
-            r.activity.performResume(false, "performNewIntents");
+            performResumeActivity(token, false, "performNewIntents");
             r.activity.mTemporaryPause = false;
-        }
-
-        if (r.paused && andPause) {
+        } else if (andPause) {
             // In this case the activity was in the paused state when we delivered the intent,
             // to guarantee onResume gets called after onNewIntent we temporarily resume the
             // activity and pause again as the caller wanted.
@@ -3957,6 +3963,8 @@
             r.state = null;
             r.persistentState = null;
             r.setState(ON_RESUME);
+
+            reportTopResumedActivityChanged(r, r.isTopResumedActivity);
         } catch (Exception e) {
             if (!mInstrumentation.onException(r.activity, e)) {
                 throw new RuntimeException("Unable to resume activity "
@@ -4111,6 +4119,45 @@
         Looper.myQueue().addIdleHandler(new Idler());
     }
 
+
+    @Override
+    public void handleTopResumedActivityChanged(IBinder token, boolean onTop, String reason) {
+        ActivityClientRecord r = mActivities.get(token);
+        if (r == null || r.activity == null) {
+            Slog.w(TAG, "Not found target activity to report position change for token: " + token);
+            return;
+        }
+
+        if (DEBUG_ORDER) {
+            Slog.d(TAG, "Received position change to top: " + onTop + " for activity: " + r);
+        }
+
+        if (r.isTopResumedActivity == onTop) {
+            throw new IllegalStateException("Activity top position already set to onTop=" + onTop);
+        }
+
+        r.isTopResumedActivity = onTop;
+
+        if (r.getLifecycleState() == ON_RESUME) {
+            reportTopResumedActivityChanged(r, onTop);
+        } else {
+            if (DEBUG_ORDER) {
+                Slog.d(TAG, "Won't deliver top position change in state=" + r.getLifecycleState());
+            }
+        }
+    }
+
+    /**
+     * Call {@link Activity#onTopResumedActivityChanged(boolean)} if its top resumed state changed
+     * since the last report.
+     */
+    private void reportTopResumedActivityChanged(ActivityClientRecord r, boolean onTop) {
+        if (r.lastReportedTopResumedState != onTop) {
+            r.lastReportedTopResumedState = onTop;
+            r.activity.onTopResumedActivityChanged(onTop);
+        }
+    }
+
     @Override
     public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
             int configChanges, PendingTransactionActions pendingActions, String reason) {
@@ -4202,6 +4249,10 @@
             return;
         }
 
+        // Always reporting top resumed position loss when pausing an activity. If necessary, it
+        // will be restored in performResumeActivity().
+        reportTopResumedActivityChanged(r, false /* onTop */);
+
         try {
             r.activity.mCalled = false;
             mInstrumentation.callActivityOnPause(r.activity);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ab2430c..d2630d5 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -39,6 +39,7 @@
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.ArrayMap;
@@ -554,9 +555,11 @@
     public static final int OP_WRITE_MEDIA_IMAGES = 86;
     /** @hide Has a legacy (non-isolated) view of storage. */
     public static final int OP_LEGACY_STORAGE = 87;
+    /** @hide Accessing accessibility features */
+    public static final int OP_ACCESS_ACCESSIBILITY = 88;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int _NUM_OP = 88;
+    public static final int _NUM_OP = 89;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -825,6 +828,8 @@
     public static final String OPSTR_WRITE_MEDIA_IMAGES = "android:write_media_images";
     /** @hide Has a legacy (non-isolated) view of storage. */
     public static final String OPSTR_LEGACY_STORAGE = "android:legacy_storage";
+    /** @hide Interact with accessibility. */
+    public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
 
     // Warning: If an permission is added here it also has to be added to
     // com.android.packageinstaller.permission.utils.EventLogger
@@ -984,6 +989,7 @@
             OP_READ_MEDIA_IMAGES,               // READ_MEDIA_IMAGES
             OP_WRITE_MEDIA_IMAGES,              // WRITE_MEDIA_IMAGES
             OP_LEGACY_STORAGE,                  // LEGACY_STORAGE
+            OP_ACCESS_ACCESSIBILITY,            // ACCESS_ACCESSIBILITY
     };
 
     /**
@@ -1078,6 +1084,7 @@
             OPSTR_READ_MEDIA_IMAGES,
             OPSTR_WRITE_MEDIA_IMAGES,
             OPSTR_LEGACY_STORAGE,
+            OPSTR_ACCESS_ACCESSIBILITY,
     };
 
     /**
@@ -1173,6 +1180,7 @@
             "READ_MEDIA_IMAGES",
             "WRITE_MEDIA_IMAGES",
             "LEGACY_STORAGE",
+            "ACCESS_ACCESSIBILITY",
     };
 
     /**
@@ -1269,6 +1277,7 @@
             Manifest.permission.READ_MEDIA_IMAGES,
             null, // no permission for OP_WRITE_MEDIA_IMAGES
             null, // no permission for OP_LEGACY_STORAGE
+            null, // no permission for OP_ACCESS_ACCESSIBILITY
     };
 
     /**
@@ -1365,6 +1374,7 @@
             null, // READ_MEDIA_IMAGES
             null, // WRITE_MEDIA_IMAGES
             null, // LEGACY_STORAGE
+            null, // ACCESS_ACCESSIBILITY
     };
 
     /**
@@ -1460,6 +1470,7 @@
             false, // READ_MEDIA_IMAGES
             false, // WRITE_MEDIA_IMAGES
             false, // LEGACY_STORAGE
+            false, // ACCESS_ACCESSIBILITY
     };
 
     /**
@@ -1554,6 +1565,7 @@
             AppOpsManager.MODE_ALLOWED, // READ_MEDIA_IMAGES
             AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_IMAGES
             AppOpsManager.MODE_DEFAULT, // LEGACY_STORAGE
+            AppOpsManager.MODE_ALLOWED, // ACCESS_ACCESSIBILITY
     };
 
     /**
@@ -1652,6 +1664,7 @@
             false, // READ_MEDIA_IMAGES
             false, // WRITE_MEDIA_IMAGES
             false, // LEGACY_STORAGE
+            false, // ACCESS_ACCESSIBILITY
     };
 
     /**
@@ -1712,6 +1725,12 @@
     /** @hide */
     public static final String KEY_HISTORICAL_OPS = "historical_ops";
 
+    /** System properties for debug logging of noteOp call sites */
+    private static final String DEBUG_LOGGING_ENABLE_PROP = "appops.logging_enabled";
+    private static final String DEBUG_LOGGING_PACKAGES_PROP = "appops.logging_packages";
+    private static final String DEBUG_LOGGING_OPS_PROP = "appops.logging_ops";
+    private static final String DEBUG_LOGGING_TAG = "AppOpsManager";
+
     /**
      * Retrieve the op switch that controls the given operation.
      * @hide
@@ -4469,6 +4488,7 @@
      */
     @UnsupportedAppUsage
     public int noteOpNoThrow(int op, int uid, String packageName) {
+        logNoteOpIfNeeded(op, packageName);
         try {
             return mService.noteOperation(op, uid, packageName);
         } catch (RemoteException e) {
@@ -4834,4 +4854,45 @@
 
         return AppOpsManager.MODE_DEFAULT;
     }
+
+    private static void logNoteOpIfNeeded(int op, String callingPackage) {
+        // Check if debug logging propety is enabled.
+        if (!SystemProperties.getBoolean(DEBUG_LOGGING_ENABLE_PROP, false)) {
+            return;
+        }
+        // Check if this package should be logged.
+        String packages = SystemProperties.get(DEBUG_LOGGING_PACKAGES_PROP, "");
+        if (!"".equals(packages) && callingPackage != null) {
+            boolean found = false;
+            for (String pkg : packages.split(",")) {
+                if (callingPackage.equals(pkg)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return;
+            }
+        }
+        String opStr = opToName(op);
+        // Check if this app op should be logged
+        String logOps = SystemProperties.get(DEBUG_LOGGING_OPS_PROP, "");
+        if (!"".equals(logOps)) {
+            boolean found = false;
+            for (String logOp : logOps.split(",")) {
+                if (opStr.equals(logOp)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return;
+            }
+        }
+
+        // Log a stack trace
+        Exception here = new Exception("HERE!");
+        android.util.Log.i(DEBUG_LOGGING_TAG, "Note operation package= " + callingPackage
+                + " op= " + opStr, here);
+    }
 }
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/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 9bcb36f..4491b95 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -3017,6 +3017,15 @@
     }
 
     @Override
+    public String getContentCaptureServicePackageName() {
+        try {
+            return mPM.getContentCaptureServicePackageName();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
     public boolean isPackageStateProtected(String packageName, int userId) {
         try {
             return mPM.isPackageStateProtected(packageName, userId);
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 07dbb6b..70badfa 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -105,6 +105,16 @@
             boolean isForward, String reason);
 
     /**
+     * Notify the activity about top resumed state change.
+     * @param token Target activity token.
+     * @param isTopResumedActivity Current state of the activity, {@code true} if it's the
+     *                             topmost resumed activity in the system, {@code false} otherwise.
+     * @param reason Reason for performing this operation.
+     */
+    public abstract void handleTopResumedActivityChanged(IBinder token,
+            boolean isTopResumedActivity, String reason);
+
+    /**
      * Stop the activity.
      * @param token Target activity token.
      * @param show Flag indicating whether activity is still shown.
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 853b45e..acc7094 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1067,7 +1067,12 @@
      * COLUMN_* constants.
      */
     public Cursor query(Query query) {
-        Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS, mBaseUri);
+        return query(query, UNDERLYING_COLUMNS);
+    }
+
+    /** @hide */
+    public Cursor query(Query query, String[] projection) {
+        Cursor underlyingCursor = query.runQuery(mResolver, projection, mBaseUri);
         if (underlyingCursor == null) {
             return null;
         }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index fb65da1..412d7f4 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -277,6 +277,7 @@
     void unstableProviderDied(in IBinder connection);
     boolean isIntentSenderAnActivity(in IIntentSender sender);
     boolean isIntentSenderAForegroundService(in IIntentSender sender);
+    boolean isIntentSenderABroadcast(in IIntentSender sender);
     int startActivityAsUser(in IApplicationThread caller, in String callingPackage,
             in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho,
             int requestCode, int flags, in ProfilerInfo profilerInfo,
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 199c133..8953940 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -68,6 +68,7 @@
     void setBubblesAllowed(String pkg, int uid, boolean allowed);
     boolean areBubblesAllowed(String pkg);
     boolean areBubblesAllowedForPackage(String pkg, int uid);
+    boolean hasUserApprovedBubblesForPackage(String pkg, int uid);
 
     void createNotificationChannelGroups(String pkg, in ParceledListSlice channelGroupList);
     void createNotificationChannels(String pkg, in ParceledListSlice channelsList);
@@ -94,6 +95,7 @@
     boolean areChannelsBypassingDnd();
     int getAppsBypassingDndCount(int uid);
     ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int userId);
+    boolean isPackagePaused(String pkg);
 
     // TODO: Remove this when callers have been migrated to the equivalent
     // INotificationListener method.
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/Notification.java b/core/java/android/app/Notification.java
index b8d748d..7c550d4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -84,7 +84,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ContrastColorUtil;
-import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -5257,7 +5256,11 @@
          * @hide
          */
         public RemoteViews makeAmbientNotification() {
-            return createHeadsUpContentView(false /* increasedHeight */);
+            RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */);
+            if (headsUpContentView != null) {
+                return headsUpContentView;
+            }
+            return createContentView();
         }
 
         private void hideLine1Text(RemoteViews result) {
@@ -8399,6 +8402,30 @@
         private CharSequence mTitle;
         private Icon mIcon;
         private int mDesiredHeight;
+        private int mFlags;
+
+        /**
+         * If set and the app creating the bubble is in the foreground, the bubble will be posted
+         * in its expanded state, with the contents of {@link #getIntent()} in a floating window.
+         *
+         * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p>
+         *
+         * <p>Generally this flag should only be set if the user has performed an action to request
+         * or create a bubble.</p>
+         */
+        private static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
+
+        /**
+         * If set and the app creating the bubble is in the foreground, the bubble will be posted
+         * <b>without</b> the associated notification in the notification shade. Subsequent update
+         * notifications to this bubble will post a notification in the shade.
+         *
+         * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p>
+         *
+         * <p>Generally this flag should only be set if the user has performed an action to request
+         * or create a bubble.</p>
+         */
+        private static final int FLAG_SUPPRESS_INITIAL_NOTIFICATION = 0x00000002;
 
         private BubbleMetadata(PendingIntent intent, CharSequence title, Icon icon, int height) {
             mPendingIntent = intent;
@@ -8412,6 +8439,7 @@
             mTitle = in.readCharSequence();
             mIcon = Icon.CREATOR.createFromParcel(in);
             mDesiredHeight = in.readInt();
+            mFlags = in.readInt();
         }
 
         /**
@@ -8444,6 +8472,24 @@
             return mDesiredHeight;
         }
 
+        /**
+         * @return whether this bubble should auto expand when it is posted.
+         *
+         * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean)
+         */
+        public boolean getAutoExpandBubble() {
+            return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0;
+        }
+
+        /**
+         * @return whether this bubble should suppress the initial notification when it is posted.
+         *
+         * @see BubbleMetadata.Builder#setSuppressInitialNotification(boolean)
+         */
+        public boolean getSuppressInitialNotification() {
+            return (mFlags & FLAG_SUPPRESS_INITIAL_NOTIFICATION) != 0;
+        }
+
         public static final Parcelable.Creator<BubbleMetadata> CREATOR =
                 new Parcelable.Creator<BubbleMetadata>() {
 
@@ -8469,6 +8515,11 @@
             out.writeCharSequence(mTitle);
             mIcon.writeToParcel(out, 0);
             out.writeInt(mDesiredHeight);
+            out.writeInt(mFlags);
+        }
+
+        private void setFlags(int flags) {
+            mFlags = flags;
         }
 
         /**
@@ -8480,6 +8531,7 @@
             private CharSequence mTitle;
             private Icon mIcon;
             private int mDesiredHeight;
+            private int mFlags;
 
             /**
              * Constructs a new builder object.
@@ -8539,6 +8591,39 @@
             }
 
             /**
+             * If set and the app creating the bubble is in the foreground, the bubble will be
+             * posted in its expanded state, with the contents of {@link #getIntent()} in a
+             * floating window.
+             *
+             * <p>If the app creating the bubble is not in the foreground this flag has no effect.
+             * </p>
+             *
+             * <p>Generally this flag should only be set if the user has performed an action to
+             * request or create a bubble.</p>
+             */
+            public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) {
+                setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand);
+                return this;
+            }
+
+            /**
+             * If set and the app creating the bubble is in the foreground, the bubble will be
+             * posted <b>without</b> the associated notification in the notification shade.
+             * Subsequent update notifications to this bubble will post a notification in the shade.
+             *
+             * <p>If the app creating the bubble is not in the foreground this flag has no effect.
+             * </p>
+             *
+             * <p>Generally this flag should only be set if the user has performed an action to
+             * request or create a bubble.</p>
+             */
+            public BubbleMetadata.Builder setSuppressInitialNotification(
+                    boolean shouldSupressNotif) {
+                setFlag(FLAG_SUPPRESS_INITIAL_NOTIFICATION, shouldSupressNotif);
+                return this;
+            }
+
+            /**
              * Creates the {@link BubbleMetadata} defined by this builder.
              * <p>Will throw {@link IllegalStateException} if required fields have not been set
              * on this builder.</p>
@@ -8553,7 +8638,22 @@
                 if (mIcon == null) {
                     throw new IllegalStateException("Must supply an icon for the bubble");
                 }
-                return new BubbleMetadata(mPendingIntent, mTitle, mIcon, mDesiredHeight);
+                BubbleMetadata data = new BubbleMetadata(mPendingIntent, mTitle, mIcon,
+                        mDesiredHeight);
+                data.setFlags(mFlags);
+                return data;
+            }
+
+            /**
+             * @hide
+             */
+            public BubbleMetadata.Builder setFlag(int mask, boolean value) {
+                if (value) {
+                    mFlags |= mask;
+                } else {
+                    mFlags &= ~mask;
+                }
+                return this;
             }
         }
     }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index c4b4b40..621f134 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1094,6 +1094,22 @@
     }
 
     /**
+     * Returns whether notifications from this package are temporarily hidden. This
+     * could be done because the package was marked as distracting to the user via
+     * {@code PackageManager#setDistractingPackageRestrictions(String[], int)} or because the
+     * package is {@code PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
+     * PersistableBundle, SuspendDialogInfo) suspended}.
+     */
+    public boolean areNotificationsPaused() {
+        INotificationManager service = getService();
+        try {
+            return service.isPackagePaused(mContext.getPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Checks the ability to modify notification do not disturb policy for the calling package.
      *
      * <p>
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 75d95b2..55014eb 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -1113,6 +1113,19 @@
 
     /**
      * @hide
+     * Check whether this PendingIntent will launch an Activity.
+     */
+    public boolean isBroadcast() {
+        try {
+            return ActivityManager.getService()
+                .isIntentSenderABroadcast(mTarget);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
      * Return the Intent of this PendingIntent.
      */
     @UnsupportedAppUsage
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/app/servertransaction/TopResumedActivityChangeItem.java b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
new file mode 100644
index 0000000..4064a02
--- /dev/null
+++ b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
@@ -0,0 +1,112 @@
+/*
+ * 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.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Top resumed activity changed callback.
+ * @hide
+ */
+public class TopResumedActivityChangeItem extends ClientTransactionItem {
+
+    private boolean mOnTop;
+
+    @Override
+    public void execute(ClientTransactionHandler client, IBinder token,
+            PendingTransactionActions pendingActions) {
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "topResumedActivityChangeItem");
+        client.handleTopResumedActivityChanged(token, mOnTop, "topResumedActivityChangeItem");
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+
+    // ObjectPoolItem implementation
+
+    private TopResumedActivityChangeItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static TopResumedActivityChangeItem obtain(boolean onTop) {
+        TopResumedActivityChangeItem instance =
+                ObjectPool.obtain(TopResumedActivityChangeItem.class);
+        if (instance == null) {
+            instance = new TopResumedActivityChangeItem();
+        }
+        instance.mOnTop = onTop;
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mOnTop = false;
+        ObjectPool.recycle(this);
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBoolean(mOnTop);
+    }
+
+    /** Read from Parcel. */
+    private TopResumedActivityChangeItem(Parcel in) {
+        mOnTop = in.readBoolean();
+    }
+
+    public static final Creator<TopResumedActivityChangeItem> CREATOR =
+            new Creator<TopResumedActivityChangeItem>() {
+                public TopResumedActivityChangeItem createFromParcel(Parcel in) {
+                    return new TopResumedActivityChangeItem(in);
+                }
+
+                public TopResumedActivityChangeItem[] newArray(int size) {
+                    return new TopResumedActivityChangeItem[size];
+                }
+            };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final TopResumedActivityChangeItem other = (TopResumedActivityChangeItem) o;
+        return mOnTop == other.mOnTop;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + (mOnTop ? 1 : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "TopResumedActivityChangeItem{onTop=" + mOnTop + "}";
+    }
+}
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 3d3c03a..43ce521 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -287,19 +287,14 @@
 
     /** A class which is used to share the usage limit data for an app or a group of apps. */
     public static class AppUsageLimitData {
-        private final boolean mGroupLimit;
         private final long mTotalUsageLimit;
         private final long mUsageRemaining;
 
-        public AppUsageLimitData(boolean groupLimit, long totalUsageLimit, long usageRemaining) {
-            this.mGroupLimit = groupLimit;
+        public AppUsageLimitData(long totalUsageLimit, long usageRemaining) {
             this.mTotalUsageLimit = totalUsageLimit;
             this.mUsageRemaining = usageRemaining;
         }
 
-        public boolean isGroupLimit() {
-            return mGroupLimit;
-        }
         public long getTotalUsageLimit() {
             return mTotalUsageLimit;
         }
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 edd765b..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
@@ -9976,9 +9975,21 @@
     }
 
     /** @hide */
+    public void writeToProto(ProtoOutputStream proto) {
+        // Same input parameters that toString() gives to toShortString().
+        writeToProtoWithoutFieldId(proto, true, true, true, false);
+    }
+
+    /** @hide */
     public void writeToProto(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp,
             boolean extras, boolean clip) {
         long token = proto.start(fieldId);
+        writeToProtoWithoutFieldId(proto, secure, comp, extras, clip);
+        proto.end(token);
+    }
+
+    private void writeToProtoWithoutFieldId(ProtoOutputStream proto, boolean secure, boolean comp,
+            boolean extras, boolean clip) {
         if (mAction != null) {
             proto.write(IntentProto.ACTION, mAction);
         }
@@ -10023,7 +10034,6 @@
         if (mSelector != null) {
             proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip));
         }
-        proto.end(token);
     }
 
     /**
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index b7366f1..e897b91 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -18,7 +18,6 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
@@ -77,18 +76,6 @@
     }
 
     /**
-     * @deprecated use {@link #startActivity(ComponentName, UserHandle)} instead.
-     *
-     * @removed
-     * @hide
-     */
-    @Deprecated
-    @UnsupportedAppUsage
-    public void startAnyActivity(@NonNull ComponentName component, @NonNull UserHandle targetUser) {
-        startActivity(component, targetUser);
-    }
-
-    /**
      * Starts the specified activity of the caller package in the specified profile. Unlike
      * {@link #startMainActivity}, this can start any activity of the caller package, not just
      * the main activity.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 4b130b2..7bd8c4e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -686,6 +686,8 @@
 
     String getWellbeingPackageName();
 
+    String getContentCaptureServicePackageName();
+
     boolean isPackageStateProtected(String packageName, int userId);
 
     void sendDeviceCustomizationReadyBroadcast();
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index c702b16..276853d 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -76,4 +76,6 @@
 
     // System API used by framework's ShareSheet (ChooserActivity)
     ParceledListSlice getShareTargets(String packageName, in IntentFilter filter, int userId);
+
+    boolean hasShareTargets(String packageName, String packageToCheck, int userId);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 89630e1..36bb5d4 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1658,35 +1658,23 @@
      * A class that encapsulates information about the usage limit set for an app or
      * a group of apps.
      *
-     * <p>The launcher can query specifics about the usage limit such as if it is a group limit,
-     * how much usage time the limit has, and how much of the total usage time is remaining
-     * via the APIs available in this class.
+     * <p>The launcher can query specifics about the usage limit such as how much usage time
+     * the limit has and how much of the total usage time is remaining via the APIs available
+     * in this class.
      *
      * @see #getAppUsageLimit(String, UserHandle)
      */
     public static final class AppUsageLimit implements Parcelable {
-        private final boolean mGroupLimit;
         private final long mTotalUsageLimit;
         private final long mUsageRemaining;
 
         /** @hide */
-        public AppUsageLimit(boolean groupLimit, long totalUsageLimit, long usageRemaining) {
-            this.mGroupLimit = groupLimit;
+        public AppUsageLimit(long totalUsageLimit, long usageRemaining) {
             this.mTotalUsageLimit = totalUsageLimit;
             this.mUsageRemaining = usageRemaining;
         }
 
         /**
-         * Returns whether this limit refers to a group of apps.
-         *
-         * @return {@code TRUE} if the limit refers to a group of apps, {@code FALSE} otherwise.
-         * @hide
-         */
-        public boolean isGroupLimit() {
-            return mGroupLimit;
-        }
-
-        /**
          * Returns the total usage limit in milliseconds set for an app or a group of apps.
          *
          * @return the total usage limit in milliseconds
@@ -1706,7 +1694,6 @@
         }
 
         private AppUsageLimit(Parcel source) {
-            mGroupLimit = source.readBoolean();
             mTotalUsageLimit = source.readLong();
             mUsageRemaining = source.readLong();
         }
@@ -1730,7 +1717,6 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeBoolean(mGroupLimit);
             dest.writeLong(mTotalUsageLimit);
             dest.writeLong(mUsageRemaining);
         }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 73b1f4e..2dc014c 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1710,6 +1710,7 @@
         /** {@hide} */
         public boolean isSessionFailed;
         private int mStagedSessionErrorCode;
+        private String mStagedSessionErrorMessage;
 
         /** {@hide} */
         @UnsupportedAppUsage
@@ -1749,6 +1750,7 @@
             isSessionReady = source.readBoolean();
             isSessionFailed = source.readBoolean();
             mStagedSessionErrorCode = source.readInt();
+            mStagedSessionErrorMessage = source.readString();
         }
 
         /**
@@ -2066,9 +2068,19 @@
             return mStagedSessionErrorCode;
         }
 
+        /**
+         * Text description of the error code returned by {@code getStagedSessionErrorCode}, or
+         * empty string if no error was encountered.
+         */
+        public String getStagedSessionErrorMessage() {
+            return mStagedSessionErrorMessage;
+        }
+
         /** {@hide} */
-        public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode) {
+        public void setStagedSessionErrorCode(@StagedSessionErrorCode int errorCode,
+                                              String errorMessage) {
             mStagedSessionErrorCode = errorCode;
+            mStagedSessionErrorMessage = errorMessage;
         }
 
         @Override
@@ -2106,6 +2118,7 @@
             dest.writeBoolean(isSessionReady);
             dest.writeBoolean(isSessionFailed);
             dest.writeInt(mStagedSessionErrorCode);
+            dest.writeString(mStagedSessionErrorMessage);
         }
 
         public static final Parcelable.Creator<SessionInfo>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 783ee64..0aa33c8 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -6734,6 +6734,16 @@
     }
 
     /**
+     * @return the system defined content capture service package name, or null if there's none.
+     *
+     * @hide
+     */
+    public String getContentCaptureServicePackageName() {
+        throw new UnsupportedOperationException(
+                "getContentCaptureServicePackageName not implemented in subclass");
+    }
+
+    /**
      * @return whether a given package's state is protected, e.g. package cannot be disabled,
      *         suspended, hidden or force stopped.
      *
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 4f7acd9..849fd03 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -635,4 +635,21 @@
                     }
                 };
     }
+
+    /**
+     * Used by framework's ShareSheet (ChooserActivity.java) to check if a given package has share
+     * target definitions in it's resources.
+     *
+     * @param packageName Package to check for share targets.
+     * @return True if the package has any share target definitions, False otherwise.
+     * @hide
+     */
+    public boolean hasShareTargets(@NonNull String packageName) {
+        try {
+            return mService.hasShareTargets(mContext.getPackageName(), packageName,
+                    injectMyUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
new file mode 100644
index 0000000..a2e98cc
--- /dev/null
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "system/apex/tests"
+    }
+  ]
+}
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..32e2198 100644
--- a/core/java/android/content/rollback/IRollbackManager.aidl
+++ b/core/java/android/content/rollback/IRollbackManager.aidl
@@ -17,21 +17,17 @@
 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,
-            in IntentSender statusReceiver);
+    void commitRollback(int rollbackId, in ParceledListSlice causePackages,
+            String callerPackageName, in IntentSender statusReceiver);
 
     // Exposed for use from the system server only. Callback from the package
     // manager during the install flow when user data can be restored for a given
diff --git a/core/java/android/content/rollback/RollbackInfo.java b/core/java/android/content/rollback/RollbackInfo.java
index 0803a7c..1111b43 100644
--- a/core/java/android/content/rollback/RollbackInfo.java
+++ b/core/java/android/content/rollback/RollbackInfo.java
@@ -17,9 +17,13 @@
 package android.content.rollback;
 
 import android.annotation.SystemApi;
+import android.content.pm.PackageInstaller;
+import android.content.pm.VersionedPackage;
 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 +38,22 @@
      */
     private final int mRollbackId;
 
-    /**
-     * The package that needs to be rolled back.
-     */
-    public final PackageRollbackInfo targetPackage;
+    private final List<PackageRollbackInfo> mPackages;
 
-    // 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<VersionedPackage> mCausePackages;
 
     /** @hide */
-    public RollbackInfo(int rollbackId, PackageRollbackInfo targetPackage) {
+    public RollbackInfo(int rollbackId, List<PackageRollbackInfo> packages,
+            List<VersionedPackage> causePackages) {
         this.mRollbackId = rollbackId;
-        this.targetPackage = targetPackage;
+        this.mPackages = packages;
+        this.mCausePackages = causePackages;
     }
 
     private RollbackInfo(Parcel in) {
         mRollbackId = in.readInt();
-        targetPackage = PackageRollbackInfo.CREATOR.createFromParcel(in);
+        mPackages = in.createTypedArrayList(PackageRollbackInfo.CREATOR);
+        mCausePackages = in.createTypedArrayList(VersionedPackage.CREATOR);
     }
 
     /**
@@ -62,6 +63,40 @@
         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;
+    }
+
+    /**
+     * Gets the list of package versions that motivated this rollback.
+     * As provided to {@link #commitRollback} when the rollback was committed.
+     * This is only applicable for rollbacks that have been committed.
+     */
+    public List<VersionedPackage> getCausePackages() {
+        return mCausePackages;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -70,7 +105,8 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mRollbackId);
-        targetPackage.writeToParcel(out, flags);
+        out.writeTypedList(mPackages);
+        out.writeTypedList(mCausePackages);
     }
 
     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..2788f82 100644
--- a/core/java/android/content/rollback/RollbackManager.java
+++ b/core/java/android/content/rollback/RollbackManager.java
@@ -17,12 +17,13 @@
 package android.content.rollback;
 
 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.content.IntentSender;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.VersionedPackage;
 import android.os.RemoteException;
 
 import java.util.List;
@@ -50,55 +51,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 +79,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 +93,74 @@
     }
 
     /**
-     * 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)}.
-     * <p>
-     * TODO: Specify the returns status codes.
-     * TODO: What happens in case reboot is required for the rollback to take
-     * effect for staged installs?
+     * Status of a rollback commit. Will be one of
+     * {@link #STATUS_SUCCESS}, {@link #STATUS_FAILURE},
+     * {@link #STATUS_FAILURE_ROLLBACK_UNAVAILABLE}, {@link #STATUS_FAILURE_INSTALL}
      *
-     * @param rollback to execute
-     * @param statusReceiver where to deliver the results
+     * @see Intent#getIntExtra(String, int)
+     */
+    public static final String EXTRA_STATUS = "android.content.rollback.extra.STATUS";
+
+    /**
+     * Detailed string representation of the status, including raw details that
+     * are useful for debugging.
+     *
+     * @see Intent#getStringExtra(String)
+     */
+    public static final String EXTRA_STATUS_MESSAGE =
+            "android.content.rollback.extra.STATUS_MESSAGE";
+
+    /**
+     * The rollback was successfully committed.
+     */
+    public static final int STATUS_SUCCESS = 0;
+
+    /**
+     * The rollback could not be committed due to some generic failure.
+     *
+     * @see #EXTRA_STATUS_MESSAGE
+     */
+    public static final int STATUS_FAILURE = 1;
+
+    /**
+     * The rollback could not be committed because it was no longer available.
+     *
+     * @see #EXTRA_STATUS_MESSAGE
+     */
+    public static final int STATUS_FAILURE_ROLLBACK_UNAVAILABLE = 2;
+
+    /**
+     * The rollback failed to install successfully.
+     *
+     * @see #EXTRA_STATUS_MESSAGE
+     */
+    public static final int STATUS_FAILURE_INSTALL = 3;
+
+    /**
+     * 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()}.
+     *
+     * @param rollbackId ID of the rollback to commit
+     * @param causePackages package versions to record as the motivation for this
+     *                      rollback.
+     * @param statusReceiver where to deliver the results. Intents sent to
+     *                       this receiver contain {@link #EXTRA_STATUS}
+     *                       and {@link #EXTRA_STATUS_MESSAGE}.
      * @throws SecurityException if the caller does not have the
      *            MANAGE_ROLLBACKS permission.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ROLLBACKS)
-    public void executeRollback(@NonNull RollbackInfo rollback,
+    public void commitRollback(int rollbackId, @NonNull List<VersionedPackage> causePackages,
             @NonNull IntentSender statusReceiver) {
         try {
-            mBinder.executeRollback(rollback, mCallerPackageName, statusReceiver);
+            mBinder.commitRollback(rollbackId, new ParceledListSlice(causePackages),
+                    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/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index d2c0e7b..5d4928c 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -182,6 +182,38 @@
     }
 
     /**
+     * Queries whether the given buffer description is supported by the system. If this returns
+     * true, then the allocation may succeed until resource exhaustion occurs. If this returns
+     * false then this combination will never succeed.
+     *
+     * @param width The width in pixels of the buffer
+     * @param height The height in pixels of the buffer
+     * @param format The @Format of each pixel
+     * @param layers The number of layers in the buffer
+     * @param usage The @Usage flags describing how the buffer will be used
+     * @return True if the combination is supported, false otherwise.
+     */
+    public static boolean isSupported(int width, int height, @Format int format, int layers,
+            @Usage long usage) {
+        if (!HardwareBuffer.isSupportedFormat(format)) {
+            throw new IllegalArgumentException("Invalid pixel format " + format);
+        }
+        if (width <= 0) {
+            throw new IllegalArgumentException("Invalid width " + width);
+        }
+        if (height <= 0) {
+            throw new IllegalArgumentException("Invalid height " + height);
+        }
+        if (layers <= 0) {
+            throw new IllegalArgumentException("Invalid layer count " + layers);
+        }
+        if (format == BLOB && height != 1) {
+            throw new IllegalArgumentException("Height must be 1 when using the BLOB format");
+        }
+        return nIsSupported(width, height, format, layers, usage);
+    }
+
+    /**
      * Private use only. See {@link #create(int, int, int, int, long)}. May also be
      * called from JNI using an already allocated native <code>HardwareBuffer</code>.
      */
@@ -386,4 +418,6 @@
     private static native int nGetLayers(long nativeObject);
     @FastNative
     private static native long nGetUsage(long nativeObject);
+    private static native boolean nIsSupported(int width, int height, int format, int layers,
+            long usage);
 }
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index 9d37d99..125dabe 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -252,12 +252,65 @@
     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;
+
+    /**
+     * This message represents the earliest message sent at the beginning of the authentication
+     * pipeline. It is expected to be used to measure latency. For example, in a camera-based
+     * authentication system it's expected to be sent prior to camera initialization. Note this
+     * should be sent whenever authentication is restarted (see IBiometricsFace#userActivity).
+     * The framework will measure latency based on the time between the last START message and the
+     * onAuthenticated callback.
+     */
+    public static final int FACE_ACQUIRED_START = 20;
+
+    /**
      * 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 = 21;
 
     /**
      * @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/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 9758308..425b956 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3721,6 +3721,59 @@
             new Key<android.hardware.camera2.params.RecommendedStreamConfiguration[]>("android.depth.availableRecommendedDepthStreamConfigurations", android.hardware.camera2.params.RecommendedStreamConfiguration[].class);
 
     /**
+     * <p>The available dynamic depth dataspace stream
+     * configurations that this camera device supports
+     * (i.e. format, width, height, output/input stream).</p>
+     * <p>These are output stream configurations for use with
+     * dataSpace DYNAMIC_DEPTH. The configurations are
+     * listed as <code>(format, width, height, input?)</code> tuples.</p>
+     * <p>Only devices that support depth output for at least
+     * the HAL_PIXEL_FORMAT_Y16 dense depth map along with
+     * HAL_PIXEL_FORMAT_BLOB with the same size or size with
+     * the same aspect ratio can have dynamic depth dataspace
+     * stream configuration. {@link CameraCharacteristics#DEPTH_DEPTH_IS_EXCLUSIVE android.depth.depthIsExclusive} also
+     * needs to be set to FALSE.</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     *
+     * @see CameraCharacteristics#DEPTH_DEPTH_IS_EXCLUSIVE
+     * @hide
+     */
+    public static final Key<android.hardware.camera2.params.StreamConfiguration[]> DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS =
+            new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.depth.availableDynamicDepthStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class);
+
+    /**
+     * <p>This lists the minimum frame duration for each
+     * format/size combination for dynamic depth output streams.</p>
+     * <p>This should correspond to the frame duration when only that
+     * stream is active, with all processing (typically in android.*.mode)
+     * set to either OFF or FAST.</p>
+     * <p>When multiple streams are used in a request, the minimum frame
+     * duration will be max(individual stream min durations).</p>
+     * <p>The minimum frame duration of a stream (of a particular format, size)
+     * is the same regardless of whether the stream is input or output.</p>
+     * <p><b>Units</b>: (format, width, height, ns) x n</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     * @hide
+     */
+    public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS =
+            new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDynamicDepthMinFrameDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+    /**
+     * <p>This lists the maximum stall duration for each
+     * output format/size combination for dynamic depth streams.</p>
+     * <p>A stall duration is how much extra time would get added
+     * to the normal minimum frame duration for a repeating request
+     * that has streams with non-zero stall.</p>
+     * <p>All dynamic depth output streams may have a nonzero stall
+     * duration.</p>
+     * <p><b>Units</b>: (format, width, height, ns) x n</p>
+     * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+     * @hide
+     */
+    public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS =
+            new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDynamicDepthStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+    /**
      * <p>String containing the ids of the underlying physical cameras.</p>
      * <p>For a logical camera, this is concatenation of all underlying physical camera IDs.
      * The null terminator for physical camera ID must be preserved so that the whole string
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index c527ab4..7877a4d 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1119,6 +1119,8 @@
                 continue;
             }
 
+            // Dynamic depth streams involve alot of SW processing and currently cannot be
+            // recommended.
             StreamConfigurationMap map = null;
             switch (i) {
                 case RecommendedStreamConfigurationMap.USECASE_PREVIEW:
@@ -1127,28 +1129,44 @@
                     map = new StreamConfigurationMap(scData.streamConfigurationArray,
                             scData.minDurationArray, scData.stallDurationArray,
                             /*depthconfiguration*/ null, /*depthminduration*/ null,
-                            /*depthstallduration*/ null, /*highspeedvideoconfigurations*/ null,
+                            /*depthstallduration*/ null,
+                            /*dynamicDepthConfigurations*/ null,
+                            /*dynamicDepthMinFrameDurations*/ null,
+                            /*dynamicDepthStallDurations*/ null,
+                            /*highspeedvideoconfigurations*/ null,
                             /*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]);
                     break;
                 case RecommendedStreamConfigurationMap.USECASE_RECORD:
                     map = new StreamConfigurationMap(scData.streamConfigurationArray,
                             scData.minDurationArray, scData.stallDurationArray,
                             /*depthconfiguration*/ null, /*depthminduration*/ null,
-                            /*depthstallduration*/ null, highSpeedVideoConfigurations,
+                            /*depthstallduration*/ null,
+                            /*dynamicDepthConfigurations*/ null,
+                            /*dynamicDepthMinFrameDurations*/ null,
+                            /*dynamicDepthStallDurations*/ null,
+                            highSpeedVideoConfigurations,
                             /*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]);
                     break;
                 case RecommendedStreamConfigurationMap.USECASE_ZSL:
                     map = new StreamConfigurationMap(scData.streamConfigurationArray,
                             scData.minDurationArray, scData.stallDurationArray,
                             depthScData.streamConfigurationArray, depthScData.minDurationArray,
-                            depthScData.stallDurationArray, /*highSpeedVideoConfigurations*/ null,
+                            depthScData.stallDurationArray,
+                            /*dynamicDepthConfigurations*/ null,
+                            /*dynamicDepthMinFrameDurations*/ null,
+                            /*dynamicDepthStallDurations*/ null,
+                            /*highSpeedVideoConfigurations*/ null,
                             inputOutputFormatsMap, listHighResolution, supportsPrivate[i]);
                     break;
                 default:
                     map = new StreamConfigurationMap(scData.streamConfigurationArray,
                             scData.minDurationArray, scData.stallDurationArray,
                             depthScData.streamConfigurationArray, depthScData.minDurationArray,
-                            depthScData.stallDurationArray, /*highSpeedVideoConfigurations*/ null,
+                            depthScData.stallDurationArray,
+                            /*dynamicDepthConfigurations*/ null,
+                            /*dynamicDepthMinFrameDurations*/ null,
+                            /*dynamicDepthStallDurations*/ null,
+                            /*highSpeedVideoConfigurations*/ null,
                             /*inputOutputFormatsMap*/ null, listHighResolution, supportsPrivate[i]);
             }
 
@@ -1206,6 +1224,12 @@
                 CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS);
         StreamConfigurationDuration[] depthStallDurations = getBase(
                 CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS);
+        StreamConfiguration[] dynamicDepthConfigurations = getBase(
+                CameraCharacteristics.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STREAM_CONFIGURATIONS);
+        StreamConfigurationDuration[] dynamicDepthMinFrameDurations = getBase(
+                CameraCharacteristics.DEPTH_AVAILABLE_DYNAMIC_DEPTH_MIN_FRAME_DURATIONS);
+        StreamConfigurationDuration[] dynamicDepthStallDurations = getBase(
+                CameraCharacteristics.DEPTH_AVAILABLE_DYNAMIC_DEPTH_STALL_DURATIONS);
         HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase(
                 CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS);
         ReprocessFormatsMap inputOutputFormatsMap = getBase(
@@ -1214,7 +1238,8 @@
         return new StreamConfigurationMap(
                 configurations, minFrameDurations, stallDurations,
                 depthConfigurations, depthMinFrameDurations, depthStallDurations,
-                highSpeedVideoConfigurations, inputOutputFormatsMap,
+                dynamicDepthConfigurations, dynamicDepthMinFrameDurations,
+                dynamicDepthStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap,
                 listHighResolution);
     }
 
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index dd052a8..a22e008 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -95,13 +95,17 @@
             StreamConfiguration[] depthConfigurations,
             StreamConfigurationDuration[] depthMinFrameDurations,
             StreamConfigurationDuration[] depthStallDurations,
+            StreamConfiguration[] dynamicDepthConfigurations,
+            StreamConfigurationDuration[] dynamicDepthMinFrameDurations,
+            StreamConfigurationDuration[] dynamicDepthStallDurations,
             HighSpeedVideoConfiguration[] highSpeedVideoConfigurations,
             ReprocessFormatsMap inputOutputFormatsMap,
             boolean listHighResolution) {
         this(configurations, minFrameDurations, stallDurations,
                     depthConfigurations, depthMinFrameDurations, depthStallDurations,
-                    highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution,
-                    /*enforceImplementationDefined*/ true);
+                    dynamicDepthConfigurations, dynamicDepthMinFrameDurations,
+                    dynamicDepthStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap,
+                    listHighResolution, /*enforceImplementationDefined*/ true);
     }
 
     /**
@@ -131,6 +135,9 @@
             StreamConfiguration[] depthConfigurations,
             StreamConfigurationDuration[] depthMinFrameDurations,
             StreamConfigurationDuration[] depthStallDurations,
+            StreamConfiguration[] dynamicDepthConfigurations,
+            StreamConfigurationDuration[] dynamicDepthMinFrameDurations,
+            StreamConfigurationDuration[] dynamicDepthStallDurations,
             HighSpeedVideoConfiguration[] highSpeedVideoConfigurations,
             ReprocessFormatsMap inputOutputFormatsMap,
             boolean listHighResolution,
@@ -163,6 +170,19 @@
                     "depthStallDurations");
         }
 
+        if (dynamicDepthConfigurations == null) {
+            mDynamicDepthConfigurations = new StreamConfiguration[0];
+            mDynamicDepthMinFrameDurations = new StreamConfigurationDuration[0];
+            mDynamicDepthStallDurations = new StreamConfigurationDuration[0];
+        } else {
+            mDynamicDepthConfigurations = checkArrayElementsNotNull(dynamicDepthConfigurations,
+                    "dynamicDepthConfigurations");
+            mDynamicDepthMinFrameDurations = checkArrayElementsNotNull(
+                    dynamicDepthMinFrameDurations, "dynamicDepthMinFrameDurations");
+            mDynamicDepthStallDurations = checkArrayElementsNotNull(dynamicDepthStallDurations,
+                    "dynamicDepthStallDurations");
+        }
+
         if (highSpeedVideoConfigurations == null) {
             mHighSpeedVideoConfigurations = new HighSpeedVideoConfiguration[0];
         } else {
@@ -205,6 +225,15 @@
             mDepthOutputFormats.put(config.getFormat(),
                     mDepthOutputFormats.get(config.getFormat()) + 1);
         }
+        for (StreamConfiguration config : mDynamicDepthConfigurations) {
+            if (!config.isOutput()) {
+                // Ignoring input configs
+                continue;
+            }
+
+            mDynamicDepthOutputFormats.put(config.getFormat(),
+                    mDynamicDepthOutputFormats.get(config.getFormat()) + 1);
+        }
 
         if (configurations != null && enforceImplementationDefined &&
                 mOutputFormats.indexOfKey(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED) < 0) {
@@ -335,6 +364,8 @@
         int dataspace = imageFormatToDataspace(format);
         if (dataspace == HAL_DATASPACE_DEPTH) {
             return mDepthOutputFormats.indexOfKey(internalFormat) >= 0;
+        } else if (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) {
+            return mDynamicDepthOutputFormats.indexOfKey(internalFormat) >= 0;
         } else {
             return getFormatsMap(/*output*/true).indexOfKey(internalFormat) >= 0;
         }
@@ -446,7 +477,9 @@
         boolean isFlexible = SurfaceUtils.isFlexibleConsumer(surface);
 
         StreamConfiguration[] configs =
-                surfaceDataspace != HAL_DATASPACE_DEPTH ? mConfigurations : mDepthConfigurations;
+                surfaceDataspace == HAL_DATASPACE_DEPTH ? mDepthConfigurations :
+                surfaceDataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthConfigurations :
+                mConfigurations;
         for (StreamConfiguration config : configs) {
             if (config.getFormat() == surfaceFormat && config.isOutput()) {
                 // Matching format, either need exact size match, or a flexible consumer
@@ -479,7 +512,9 @@
         int dataspace = imageFormatToDataspace(format);
 
         StreamConfiguration[] configs =
-            dataspace != HAL_DATASPACE_DEPTH ? mConfigurations : mDepthConfigurations;
+            dataspace == HAL_DATASPACE_DEPTH ? mDepthConfigurations :
+            dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthConfigurations :
+            mConfigurations;
         for (StreamConfiguration config : configs) {
             if ((config.getFormat() == internalFormat) && config.isOutput() &&
                     config.getSize().equals(size)) {
@@ -992,6 +1027,12 @@
                     Arrays.equals(mMinFrameDurations, other.mMinFrameDurations) &&
                     Arrays.equals(mStallDurations, other.mStallDurations) &&
                     Arrays.equals(mDepthConfigurations, other.mDepthConfigurations) &&
+                    Arrays.equals(mDepthMinFrameDurations, other.mDepthMinFrameDurations) &&
+                    Arrays.equals(mDepthStallDurations, other.mDepthStallDurations) &&
+                    Arrays.equals(mDynamicDepthConfigurations, other.mDynamicDepthConfigurations) &&
+                    Arrays.equals(mDynamicDepthMinFrameDurations,
+                            other.mDynamicDepthMinFrameDurations) &&
+                    Arrays.equals(mDynamicDepthStallDurations, other.mDynamicDepthStallDurations) &&
                     Arrays.equals(mHighSpeedVideoConfigurations,
                             other.mHighSpeedVideoConfigurations);
         }
@@ -1005,9 +1046,10 @@
     public int hashCode() {
         // XX: do we care about order?
         return HashCodeHelpers.hashCodeGeneric(
-                mConfigurations, mMinFrameDurations,
-                mStallDurations,
-                mDepthConfigurations, mHighSpeedVideoConfigurations);
+                mConfigurations, mMinFrameDurations, mStallDurations,
+                mDepthConfigurations, mDepthMinFrameDurations, mDepthStallDurations,
+                mDynamicDepthConfigurations, mDynamicDepthMinFrameDurations,
+                mDynamicDepthStallDurations, mHighSpeedVideoConfigurations);
     }
 
     // Check that the argument is supported by #getOutputFormats or #getInputFormats
@@ -1022,6 +1064,10 @@
                 if (mDepthOutputFormats.indexOfKey(internalFormat) >= 0) {
                     return format;
                 }
+            } else if (internalDataspace == HAL_DATASPACE_DYNAMIC_DEPTH) {
+                if (mDynamicDepthOutputFormats.indexOfKey(internalFormat) >= 0) {
+                    return format;
+                }
             } else {
                 if (mAllOutputFormats.indexOfKey(internalFormat) >= 0) {
                     return format;
@@ -1245,6 +1291,7 @@
         switch (format) {
             case ImageFormat.JPEG:
             case ImageFormat.DEPTH_POINT_CLOUD:
+            case ImageFormat.DEPTH_JPEG:
                 return HAL_PIXEL_FORMAT_BLOB;
             case ImageFormat.DEPTH16:
                 return HAL_PIXEL_FORMAT_Y16;
@@ -1264,6 +1311,7 @@
      * <li>ImageFormat.JPEG => HAL_DATASPACE_V0_JFIF
      * <li>ImageFormat.DEPTH_POINT_CLOUD => HAL_DATASPACE_DEPTH
      * <li>ImageFormat.DEPTH16 => HAL_DATASPACE_DEPTH
+     * <li>ImageFormat.DEPTH_JPEG => HAL_DATASPACE_DYNAMIC_DEPTH
      * <li>others => HAL_DATASPACE_UNKNOWN
      * </ul>
      * </p>
@@ -1293,6 +1341,8 @@
             case ImageFormat.DEPTH16:
             case ImageFormat.RAW_DEPTH:
                 return HAL_DATASPACE_DEPTH;
+            case ImageFormat.DEPTH_JPEG:
+                return HAL_DATASPACE_DYNAMIC_DEPTH;
             default:
                 return HAL_DATASPACE_UNKNOWN;
         }
@@ -1343,12 +1393,16 @@
         SparseIntArray formatsMap =
                 !output ? mInputFormats :
                 dataspace == HAL_DATASPACE_DEPTH ? mDepthOutputFormats :
+                dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ? mDynamicDepthOutputFormats :
                 highRes ? mHighResOutputFormats :
                 mOutputFormats;
 
         int sizesCount = formatsMap.get(format);
-        if ( ((!output || dataspace == HAL_DATASPACE_DEPTH) && sizesCount == 0) ||
-                (output && dataspace != HAL_DATASPACE_DEPTH && mAllOutputFormats.get(format) == 0)) {
+        if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH ||
+                            dataspace == HAL_DATASPACE_DYNAMIC_DEPTH)) && sizesCount == 0) ||
+                (output && (dataspace != HAL_DATASPACE_DEPTH &&
+                            dataspace != HAL_DATASPACE_DYNAMIC_DEPTH) &&
+                 mAllOutputFormats.get(format) == 0)) {
             // Only throw if this is really not supported at all
             throw new IllegalArgumentException("format not available");
         }
@@ -1357,9 +1411,13 @@
         int sizeIndex = 0;
 
         StreamConfiguration[] configurations =
-                (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations : mConfigurations;
+                (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations :
+                (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations :
+                mConfigurations;
         StreamConfigurationDuration[] minFrameDurations =
-                (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations : mMinFrameDurations;
+                (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations :
+                (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthMinFrameDurations :
+                mMinFrameDurations;
 
         for (StreamConfiguration config : configurations) {
             int fmt = config.getFormat();
@@ -1386,7 +1444,21 @@
             }
         }
 
-        if (sizeIndex != sizesCount) {
+        // Dynamic depth streams can have both fast and also high res modes.
+        if ((sizeIndex != sizesCount) && (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH)) {
+
+            if (sizeIndex > sizesCount) {
+                throw new AssertionError(
+                        "Too many dynamic depth sizes (expected " + sizesCount + ", actual " +
+                        sizeIndex + ")");
+            }
+
+            if (sizeIndex <= 0) {
+                sizes = new Size[0];
+            } else {
+                sizes = Arrays.copyOf(sizes, sizeIndex);
+            }
+        } else if (sizeIndex != sizesCount) {
             throw new AssertionError(
                     "Too few sizes (expected " + sizesCount + ", actual " + sizeIndex + ")");
         }
@@ -1409,6 +1481,10 @@
             for (int j = 0; j < mDepthOutputFormats.size(); j++) {
                 formats[i++] = depthFormatToPublic(mDepthOutputFormats.keyAt(j));
             }
+            if (mDynamicDepthOutputFormats.size() > 0) {
+                // Only one publicly dynamic depth format is available.
+                formats[i++] = ImageFormat.DEPTH_JPEG;
+            }
         }
         if (formats.length != i) {
             throw new AssertionError("Too few formats " + i + ", expected " + formats.length);
@@ -1451,11 +1527,13 @@
     private StreamConfigurationDuration[] getDurations(int duration, int dataspace) {
         switch (duration) {
             case DURATION_MIN_FRAME:
-                return (dataspace == HAL_DATASPACE_DEPTH) ?
-                        mDepthMinFrameDurations : mMinFrameDurations;
+                return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations :
+                        (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ?
+                        mDynamicDepthMinFrameDurations : mMinFrameDurations;
             case DURATION_STALL:
-                return (dataspace == HAL_DATASPACE_DEPTH) ?
-                        mDepthStallDurations : mStallDurations;
+                return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthStallDurations :
+                        (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthStallDurations :
+                        mStallDurations;
             default:
                 throw new IllegalArgumentException("duration was invalid");
         }
@@ -1467,6 +1545,7 @@
         int size = formatsMap.size();
         if (output) {
             size += mDepthOutputFormats.size();
+            size += mDynamicDepthOutputFormats.size();
         }
 
         return size;
@@ -1486,10 +1565,11 @@
         return false;
     }
 
-    private boolean isSupportedInternalConfiguration(int format, int dataspace,
-            Size size) {
+    private boolean isSupportedInternalConfiguration(int format, int dataspace, Size size) {
         StreamConfiguration[] configurations =
-                (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations : mConfigurations;
+                (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations :
+                (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations :
+                mConfigurations;
 
         for (int i = 0; i < configurations.length; i++) {
             if (configurations[i].getFormat() == format &&
@@ -1681,6 +1761,8 @@
                 return "DEPTH16";
             case ImageFormat.DEPTH_POINT_CLOUD:
                 return "DEPTH_POINT_CLOUD";
+            case ImageFormat.DEPTH_JPEG:
+                return "DEPTH_JPEG";
             case ImageFormat.RAW_DEPTH:
                 return "RAW_DEPTH";
             case ImageFormat.PRIVATE:
@@ -1712,6 +1794,7 @@
             (1 << HAL_DATASPACE_RANGE_SHIFT);
 
     private static final int HAL_DATASPACE_DEPTH = 0x1000;
+    private static final int HAL_DATASPACE_DYNAMIC_DEPTH = 0x1002;
 
     private static final long DURATION_20FPS_NS = 50000000L;
     /**
@@ -1728,6 +1811,10 @@
     private final StreamConfigurationDuration[] mDepthMinFrameDurations;
     private final StreamConfigurationDuration[] mDepthStallDurations;
 
+    private final StreamConfiguration[] mDynamicDepthConfigurations;
+    private final StreamConfigurationDuration[] mDynamicDepthMinFrameDurations;
+    private final StreamConfigurationDuration[] mDynamicDepthStallDurations;
+
     private final HighSpeedVideoConfiguration[] mHighSpeedVideoConfigurations;
     private final ReprocessFormatsMap mInputOutputFormatsMap;
 
@@ -1745,6 +1832,8 @@
     private final SparseIntArray mInputFormats = new SparseIntArray();
     /** internal format -> num depth output sizes mapping, for HAL_DATASPACE_DEPTH */
     private final SparseIntArray mDepthOutputFormats = new SparseIntArray();
+    /** internal format -> num dynamic depth output sizes mapping, for HAL_DATASPACE_DYNAMIC_DEPTH */
+    private final SparseIntArray mDynamicDepthOutputFormats = new SparseIntArray();
     /** High speed video Size -> FPS range count mapping*/
     private final HashMap</*HighSpeedVideoSize*/Size, /*Count*/Integer> mHighSpeedVideoSizeMap =
             new HashMap<Size, Integer>();
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/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index a98b31a..56020b2 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
@@ -33,6 +34,8 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.List;
 
 /**
@@ -106,9 +109,24 @@
     public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
     public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3;
 
+    @IntDef ({
+        RESULT_SUCCESS,
+        RESULT_TIMEOUT,
+        RESULT_SOURCE_NOT_AVAILABLE,
+        RESULT_TARGET_NOT_AVAILABLE,
+        RESULT_ALREADY_IN_PROGRESS,
+        RESULT_EXCEPTION,
+        RESULT_INCORRECT_MODE,
+        RESULT_COMMUNICATION_FAILED,
+    })
+    public @interface ControlCallbackResult {}
+
+    /** Control operation is successfully handled by the framework. */
     public static final int RESULT_SUCCESS = 0;
     public static final int RESULT_TIMEOUT = 1;
+    /** Source device that the application is using is not available. */
     public static final int RESULT_SOURCE_NOT_AVAILABLE = 2;
+    /** Target device that the application is controlling is not available. */
     public static final int RESULT_TARGET_NOT_AVAILABLE = 3;
 
     @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4;
@@ -394,16 +412,15 @@
     /**
      * Gets an object that represents an HDMI-CEC logical device of type switch on the system.
      *
-     * <p>Used to send HDMI control messages to other devices like TV through HDMI bus. It is also
-     * possible to communicate with other logical devices hosted in the same system if the system is
-     * configured to host more than one type of HDMI-CEC logical devices.
+     * <p>Used to send HDMI control messages to other devices (e.g. TVs) through HDMI bus.
+     * It is also possible to communicate with other logical devices hosted in the same
+     * system if the system is configured to host more than one type of HDMI-CEC logical device.
      *
      * @return {@link HdmiSwitchClient} instance. {@code null} on failure.
-     *
-     * TODO(b/110094868): unhide for Q
      * @hide
      */
     @Nullable
+    @SystemApi
     @SuppressLint("Doclava125")
     public HdmiSwitchClient getSwitchClient() {
         return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
@@ -412,11 +429,15 @@
     /**
      * Get a snapshot of the real-time status of the remote devices.
      *
-     * @return a list of {@link HdmiDeviceInfo} of the devices connected to the current device.
+     * <p>This only applies to devices with multiple HDMI inputs.
      *
-     * TODO(b/110094868): unhide for Q
+     * @return a list of {@link HdmiDeviceInfo} of the connected CEC devices. An empty
+     * list will be returned if there is none.
+     *
      * @hide
      */
+    @SystemApi
+    @Nullable
     public List<HdmiDeviceInfo> getConnectedDevicesList() {
         try {
             return mService.getDeviceList();
@@ -426,14 +447,17 @@
     }
 
     /**
-     * Power off the target device.
+     * Power off the target device by sending CEC commands.
      *
-     * @param deviceInfo HdmiDeviceInfo of the device to be powered off
+     * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
      *
-     * TODO(b/110094868): unhide for Q
+     * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered off.
+     *
      * @hide
      */
+    @SystemApi
     public void powerOffRemoteDevice(HdmiDeviceInfo deviceInfo) {
+        Preconditions.checkNotNull(deviceInfo);
         try {
             mService.powerOffRemoteDevice(
                     deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
@@ -443,14 +467,16 @@
     }
 
     /**
-     * Power on the target device.
+     * Power on the target device by sending CEC commands.
      *
-     * @param deviceInfo HdmiDeviceInfo of the device to be powered on
+     * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
      *
-     * TODO(b/110094868): unhide for Q
+     * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered on.
+     *
      * @hide
      */
     public void powerOnRemoteDevice(HdmiDeviceInfo deviceInfo) {
+        Preconditions.checkNotNull(deviceInfo);
         try {
             mService.powerOnRemoteDevice(
                     deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
@@ -460,14 +486,17 @@
     }
 
     /**
-     * Ask the target device to be the new Active Source.
+     * Request the target device to be the new Active Source by sending CEC commands.
+     *
+     * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
      *
      * @param deviceInfo HdmiDeviceInfo of the target device
      *
-     * TODO(b/110094868): unhide for Q
      * @hide
      */
-    public void askRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) {
+    @SystemApi
+    public void requestRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) {
+        Preconditions.checkNotNull(deviceInfo);
         try {
             mService.askRemoteDeviceToBecomeActiveSource(deviceInfo.getPhysicalAddress());
         } catch (RemoteException e) {
@@ -506,8 +535,13 @@
     /**
      * Get the physical address of the device.
      *
+     * <p>Physical address needs to be automatically adjusted when devices are phyiscally or
+     * electrically added or removed from the device tree. Please see HDMI Specification Version
+     * 1.4b 8.7 Physical Address for more details on the address discovery proccess.
+     *
      * @hide
      */
+    @SystemApi
     public int getPhysicalAddress() {
         if (mPhysicalAddress != INVALID_PHYSICAL_ADDRESS) {
             return mPhysicalAddress;
@@ -521,16 +555,19 @@
     }
 
     /**
-     * Check if the target device is connected to the current device. The
-     * API also returns true if the current device is the target.
+     * Check if the target remote device is connected to the current device.
+     *
+     * <p>The API also returns true if the current device is the target.
      *
      * @param targetDevice {@link HdmiDeviceInfo} of the target device.
-     * @return true if {@code device} is directly or indirectly connected to the
+     * @return true if {@code targetDevice} is directly or indirectly
+     * connected to the current device.
      *
-     * TODO(b/110094868): unhide for Q
      * @hide
      */
-    public boolean isTargetDeviceConnected(HdmiDeviceInfo targetDevice) {
+    @SystemApi
+    public boolean isRemoteDeviceConnected(HdmiDeviceInfo targetDevice) {
+        Preconditions.checkNotNull(targetDevice);
         mPhysicalAddress = getPhysicalAddress();
         if (mPhysicalAddress == INVALID_PHYSICAL_ADDRESS) {
             return false;
diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
index 1ac2973..a036512 100644
--- a/core/java/android/hardware/hdmi/HdmiSwitchClient.java
+++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
@@ -15,24 +15,30 @@
  */
 package android.hardware.hdmi;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.hardware.hdmi.HdmiControlManager.ControlCallbackResult;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
- * HdmiSwitchClient represents HDMI-CEC logical device of type Switch in the Android system which
- * acts as switch.
+ * An {@code HdmiSwitchClient} represents a HDMI-CEC switch device.
  *
- * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH,
- * but it is used by all Android TV devices that have multiple HDMI inputs,
- * even if it is not a "pure" swicth and has another device type like TV or Player.
+ * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH, but it is
+ * used by all Android devices that have multiple HDMI inputs, even if it is not a "pure" swicth
+ * and has another device type like TV or Player.
  *
  * @hide
- * TODO(b/110094868): unhide and add @SystemApi for Q
  */
+@SystemApi
 public class HdmiSwitchClient extends HdmiClient {
 
     private static final String TAG = "HdmiSwitchClient";
@@ -41,17 +47,15 @@
         super(service);
     }
 
-    private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) {
+    private static IHdmiControlCallback getCallbackWrapper(final OnSelectListener listener) {
         return new IHdmiControlCallback.Stub() {
             @Override
             public void onComplete(int result) {
-                callback.onComplete(result);
+                listener.onSelect(result);
             }
         };
     }
 
-    /** @hide */
-    // TODO(b/110094868): unhide for Q
     @Override
     public int getDeviceType() {
         return HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH;
@@ -61,20 +65,17 @@
      * Selects a CEC logical device to be a new active source.
      *
      * @param logicalAddress logical address of the device to select
-     * @param callback callback to get the result with
-     * @throws {@link IllegalArgumentException} if the {@code callback} is null
+     * @param listener listener to get the result with
      *
      * @hide
-     * TODO(b/110094868): unhide and add @SystemApi for Q
      */
-    public void deviceSelect(int logicalAddress, @NonNull SelectCallback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("callback must not be null.");
-        }
+    public void selectDevice(int logicalAddress, @NonNull OnSelectListener listener) {
+        Preconditions.checkNotNull(listener);
         try {
-            mService.deviceSelect(logicalAddress, getCallbackWrapper(callback));
+            mService.deviceSelect(logicalAddress, getCallbackWrapper(listener));
         } catch (RemoteException e) {
             Log.e(TAG, "failed to select device: ", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -82,20 +83,83 @@
      * Selects a HDMI port to be a new route path.
      *
      * @param portId HDMI port to select
-     * @param callback callback to get the result with
-     * @throws {@link IllegalArgumentException} if the {@code callback} is null
+     * @see {@link android.media.tv.TvInputHardwareInfo#getHdmiPortId()}
+     *     to get portId of a specific TV Input.
+     * @param listener listener to get the result with
      *
      * @hide
-     * TODO(b/110094868): unhide and add @SystemApi for Q
      */
-    public void portSelect(int portId, @NonNull SelectCallback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("Callback must not be null");
-        }
+    @SystemApi
+    public void selectPort(int portId, @NonNull OnSelectListener listener) {
+        Preconditions.checkNotNull(listener);
         try {
-            mService.portSelect(portId, getCallbackWrapper(callback));
+            mService.portSelect(portId, getCallbackWrapper(listener));
         } catch (RemoteException e) {
             Log.e(TAG, "failed to select port: ", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Selects a CEC logical device to be a new active source.
+     *
+     * @param logicalAddress logical address of the device to select
+     * @param executor executor to allow the develper to specify the thread upon which the listeners
+     *     will be invoked
+     * @param listener listener to get the result with
+     *
+     * @hide
+     */
+    public void selectDevice(
+            int logicalAddress,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnSelectListener listener) {
+        Preconditions.checkNotNull(listener);
+        try {
+            mService.deviceSelect(logicalAddress,
+                    new IHdmiControlCallback.Stub() {
+                            @Override
+                            public void onComplete(int result) {
+                                Binder.withCleanCallingIdentity(
+                                        () -> executor.execute(() -> listener.onSelect(result)));
+                            }
+                    }
+            );
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to select device: ", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Selects a HDMI port to be a new route path.
+     *
+     * @param portId HDMI port to select
+     * @param executor executor to allow the develper to specify the thread upon which the listeners
+     *     will be invoked
+     * @param listener listener to get the result with
+     *
+     * @hide
+     */
+    @SystemApi
+    public void selectPort(
+            int portId,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnSelectListener listener) {
+        Preconditions.checkNotNull(listener);
+        try {
+            mService.portSelect(portId,
+                    new IHdmiControlCallback.Stub() {
+                            @Override
+                            public void onComplete(int result) {
+                                Binder.withCleanCallingIdentity(
+                                        () -> executor.execute(() -> listener.onSelect(result)));
+                            }
+                    }
+            );
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to select port: ", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -105,10 +169,9 @@
      * <p>This only applies to device with multiple HDMI inputs
      *
      * @return list of {@link HdmiDeviceInfo} for connected CEC devices. Empty list is returned if
-     * there is none.
+     *     there is none.
      *
      * @hide
-     * TODO(b/110094868): unhide and add @SystemApi for Q
      */
     public List<HdmiDeviceInfo> getDeviceList() {
         try {
@@ -120,18 +183,19 @@
     }
 
     /**
-     * Callback interface used to get the result of {@link #deviceSelect} or {@link #portSelect}.
+     * Listener interface used to get the result of {@link #deviceSelect} or {@link #portSelect}.
      *
      * @hide
-     * TODO(b/110094868): unhide and add @SystemApi for Q
      */
-    public interface SelectCallback {
+    @SystemApi
+    public interface OnSelectListener {
 
         /**
          * Called when the operation is finished.
          *
-         * @param result the result value of {@link #deviceSelect} or {@link #portSelect}.
+         * @param result callback result.
+         * @see {@link ControlCallbackResult}
          */
-        void onComplete(int result);
+        void onSelect(@ControlCallbackResult int result);
     }
 }
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 1030694..37b25c8 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -179,19 +179,24 @@
                 return;
             case DO_START_INPUT: {
                 final SomeArgs args = (SomeArgs) msg.obj;
-                final int missingMethods = msg.arg1;
-                final boolean restarting = msg.arg2 != 0;
                 final IBinder startInputToken = (IBinder) args.arg1;
                 final IInputContext inputContext = (IInputContext) args.arg2;
                 final EditorInfo info = (EditorInfo) args.arg3;
                 final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
+                SomeArgs moreArgs = (SomeArgs) args.arg5;
                 final InputConnection ic = inputContext != null
                         ? new InputConnectionWrapper(
-                                mTarget, inputContext, missingMethods, isUnbindIssued) : null;
+                                mTarget, inputContext, moreArgs.argi3, isUnbindIssued)
+                        : null;
                 info.makeCompatible(mTargetSdkVersion);
-                inputMethod.dispatchStartInputWithToken(ic, info, restarting /* restarting */,
-                        startInputToken);
+                inputMethod.dispatchStartInputWithToken(
+                        ic,
+                        info,
+                        moreArgs.argi1 == 1 /* restarting */,
+                        startInputToken,
+                        moreArgs.argi2 == 1 /* shouldPreRenderIme */);
                 args.recycle();
+                moreArgs.recycle();
                 return;
             }
             case DO_CREATE_SESSION: {
@@ -291,14 +296,17 @@
     @Override
     public void startInput(IBinder startInputToken, IInputContext inputContext,
             @InputConnectionInspector.MissingMethodFlags final int missingMethods,
-            EditorInfo attribute, boolean restarting) {
+            EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) {
         if (mIsUnbindIssued == null) {
             Log.e(TAG, "startInput must be called after bindInput.");
             mIsUnbindIssued = new AtomicBoolean();
         }
-        mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOOO(DO_START_INPUT,
-                missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute,
-                mIsUnbindIssued));
+        SomeArgs args = SomeArgs.obtain();
+        args.argi1 = restarting ? 1 : 0;
+        args.argi2 = shouldPreRenderIme ? 1 : 0;
+        args.argi3 = missingMethods;
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(
+                DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args));
     }
 
     @BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 38ddc16..333cfbd 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -346,6 +346,13 @@
      */
     public static final int IME_VISIBLE = 0x2;
 
+    /**
+     * @hide
+     * The IME is active and ready with views but set invisible.
+     * This flag cannot be combined with {@link #IME_VISIBLE}.
+     */
+    public static final int IME_INVISIBLE = 0x4;
+
     // Min and max values for back disposition.
     private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT;
     private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_ADJUST_NOTHING;
@@ -362,10 +369,19 @@
     View mRootView;
     SoftInputWindow mWindow;
     boolean mInitialized;
-    boolean mWindowCreated;
-    boolean mWindowVisible;
-    boolean mWindowWasVisible;
+    boolean mViewsCreated;
+    // IME views visibility.
+    boolean mDecorViewVisible;
+    boolean mDecorViewWasVisible;
     boolean mInShowWindow;
+    // True if pre-rendering of IME views/window is supported.
+    boolean mCanPreRender;
+    // If IME is pre-rendered.
+    boolean mIsPreRendered;
+    // IME window visibility.
+    // Use (mDecorViewVisible && mWindowVisible) to check if IME is visible to the user.
+    boolean mWindowVisible;
+
     ViewGroup mFullscreenArea;
     FrameLayout mExtractFrame;
     FrameLayout mCandidatesFrame;
@@ -552,15 +568,18 @@
          */
         @MainThread
         @Override
-        public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
+        public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
                 @NonNull EditorInfo editorInfo, boolean restarting,
-                @NonNull IBinder startInputToken) {
+                @NonNull IBinder startInputToken, boolean shouldPreRenderIme) {
             mPrivOps.reportStartInput(startInputToken);
-            // This needs to be dispatched to interface methods rather than doStartInput().
-            // Otherwise IME developers who have overridden those interface methods will lose
-            // notifications.
-            super.dispatchStartInputWithToken(inputConnection, editorInfo, restarting,
-                    startInputToken);
+            mCanPreRender = shouldPreRenderIme;
+            if (DEBUG) Log.v(TAG, "Will Pre-render IME: " + mCanPreRender);
+
+            if (restarting) {
+                restartInput(inputConnection, editorInfo);
+            } else {
+                startInput(inputConnection, editorInfo);
+            }
         }
 
         /**
@@ -570,14 +589,28 @@
         @Override
         public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
             if (DEBUG) Log.v(TAG, "hideSoftInput()");
-            boolean wasVis = isInputViewShown();
-            mShowInputFlags = 0;
-            mShowInputRequested = false;
-            doHideWindow();
+            final boolean wasVisible = mIsPreRendered
+                    ? mDecorViewVisible && mWindowVisible : isInputViewShown();
+            if (mIsPreRendered) {
+                // TODO: notify visibility to insets consumer.
+                if (DEBUG) {
+                    Log.v(TAG, "Making IME window invisible");
+                }
+                setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
+                applyVisibilityInInsetsConsumer(false /* setVisible */);
+                onPreRenderedWindowVisibilityChanged(false /* setVisible */);
+            } else {
+                mShowInputFlags = 0;
+                mShowInputRequested = false;
+                doHideWindow();
+            }
+            final boolean isVisible = mIsPreRendered
+                    ? mDecorViewVisible && mWindowVisible : isInputViewShown();
+            final boolean visibilityChanged = isVisible != wasVisible;
             if (resultReceiver != null) {
-                resultReceiver.send(wasVis != isInputViewShown()
+                resultReceiver.send(visibilityChanged
                         ? InputMethodManager.RESULT_HIDDEN
-                        : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+                        : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
                                 : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
             }
         }
@@ -589,17 +622,28 @@
         @Override
         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
             if (DEBUG) Log.v(TAG, "showSoftInput()");
-            boolean wasVis = isInputViewShown();
+            final boolean wasVisible = mIsPreRendered
+                    ? mDecorViewVisible && mWindowVisible : isInputViewShown();
             if (dispatchOnShowInputRequested(flags, false)) {
-                showWindow(true);
+                if (mIsPreRendered) {
+                    if (DEBUG) {
+                        Log.v(TAG, "Making IME window visible");
+                    }
+                    applyVisibilityInInsetsConsumer(true /* setVisible */);
+                    onPreRenderedWindowVisibilityChanged(true /* setVisible */);
+                } else {
+                    showWindow(true);
+                }
             }
             // If user uses hard keyboard, IME button should always be shown.
-            setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
-
+            setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
+            final boolean isVisible = mIsPreRendered
+                    ? mDecorViewVisible && mWindowVisible : isInputViewShown();
+            final boolean visibilityChanged = isVisible != wasVisible;
             if (resultReceiver != null) {
-                resultReceiver.send(wasVis != isInputViewShown()
+                resultReceiver.send(visibilityChanged
                         ? InputMethodManager.RESULT_SHOWN
-                        : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+                        : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
                                 : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
             }
         }
@@ -972,7 +1016,7 @@
 
     void initViews() {
         mInitialized = false;
-        mWindowCreated = false;
+        mViewsCreated = false;
         mShowInputRequested = false;
         mShowInputFlags = 0;
 
@@ -1046,7 +1090,7 @@
     }
 
     private void resetStateForNewConfiguration() {
-        boolean visible = mWindowVisible;
+        boolean visible = mDecorViewVisible;
         int showFlags = mShowInputFlags;
         boolean showingInput = mShowInputRequested;
         CompletionInfo[] completions = mCurCompletions;
@@ -1129,7 +1173,7 @@
             return;
         }
         mBackDisposition = disposition;
-        setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
+        setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
     }
 
     /**
@@ -1380,7 +1424,7 @@
             mExtractFrame.setVisibility(View.GONE);
         }
         updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE);
-        if (mWindowWasVisible && mFullscreenArea.getVisibility() != vis) {
+        if (mDecorViewWasVisible && mFullscreenArea.getVisibility() != vis) {
             int animRes = mThemeAttrs.getResourceId(vis == View.VISIBLE
                     ? com.android.internal.R.styleable.InputMethodService_imeExtractEnterAnimation
                     : com.android.internal.R.styleable.InputMethodService_imeExtractExitAnimation,
@@ -1439,7 +1483,7 @@
      */
     public void updateInputViewShown() {
         boolean isShown = mShowInputRequested && onEvaluateInputViewShown();
-        if (mIsInputViewShown != isShown && mWindowVisible) {
+        if (mIsInputViewShown != isShown && mDecorViewVisible) {
             mIsInputViewShown = isShown;
             mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE);
             if (mInputView == null) {
@@ -1458,14 +1502,14 @@
     public boolean isShowInputRequested() {
         return mShowInputRequested;
     }
-    
+
     /**
      * Return whether the soft input view is <em>currently</em> shown to the
      * user.  This is the state that was last determined and
      * applied by {@link #updateInputViewShown()}.
      */
     public boolean isInputViewShown() {
-        return mIsInputViewShown && mWindowVisible;
+        return mCanPreRender ? mWindowVisible : mIsInputViewShown && mDecorViewVisible;
     }
 
     /**
@@ -1499,7 +1543,7 @@
      */
     public void setCandidatesViewShown(boolean shown) {
         updateCandidatesVisibility(shown);
-        if (!mShowInputRequested && mWindowVisible != shown) {
+        if (!mShowInputRequested && mDecorViewVisible != shown) {
             // If we are being asked to show the candidates view while the app
             // has not asked for the input view to be shown, then we need
             // to update whether the window is shown.
@@ -1804,7 +1848,8 @@
     public void showWindow(boolean showInput) {
         if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput
                 + " mShowInputRequested=" + mShowInputRequested
-                + " mWindowCreated=" + mWindowCreated
+                + " mViewsCreated=" + mViewsCreated
+                + " mDecorViewVisible=" + mDecorViewVisible
                 + " mWindowVisible=" + mWindowVisible
                 + " mInputStarted=" + mInputStarted
                 + " mShowInputFlags=" + mShowInputFlags);
@@ -1814,18 +1859,55 @@
             return;
         }
 
-        mWindowWasVisible = mWindowVisible;
+        mDecorViewWasVisible = mDecorViewVisible;
         mInShowWindow = true;
-        showWindowInner(showInput);
-        mWindowWasVisible = true;
+        boolean isPreRenderedAndInvisible = mIsPreRendered && !mWindowVisible;
+        final int previousImeWindowStatus =
+                (mDecorViewVisible ? IME_ACTIVE : 0) | (isInputViewShown()
+                        ? (isPreRenderedAndInvisible ? IME_INVISIBLE : IME_VISIBLE) : 0);
+        startViews(prepareWindow(showInput));
+        final int nextImeWindowStatus = mapToImeWindowStatus();
+        if (previousImeWindowStatus != nextImeWindowStatus) {
+            setImeWindowStatus(nextImeWindowStatus, mBackDisposition);
+        }
+
+        // compute visibility
+        onWindowShown();
+        mIsPreRendered = mCanPreRender;
+        if (mIsPreRendered) {
+            onPreRenderedWindowVisibilityChanged(true /* setVisible */);
+        } else {
+            // Pre-rendering not supported.
+            if (DEBUG) Log.d(TAG, "No pre-rendering supported");
+            mWindowVisible = true;
+        }
+
+        // request draw for the IME surface.
+        // When IME is not pre-rendered, this will actually show the IME.
+        if ((previousImeWindowStatus & IME_ACTIVE) == 0) {
+            if (DEBUG) Log.v(TAG, "showWindow: draw decorView!");
+            mWindow.show();
+        }
+        maybeNotifyPreRendered();
+        mDecorViewWasVisible = true;
         mInShowWindow = false;
     }
 
-    void showWindowInner(boolean showInput) {
+    /**
+     * Notify {@link android.view.ImeInsetsSourceConsumer} if IME has been pre-rendered
+     * for current EditorInfo, when pre-rendering is enabled.
+     */
+    private void maybeNotifyPreRendered() {
+        if (!mCanPreRender || !mIsPreRendered) {
+            return;
+        }
+        mPrivOps.reportPreRendered(getCurrentInputEditorInfo());
+    }
+
+
+    private boolean prepareWindow(boolean showInput) {
         boolean doShowInput = false;
-        final int previousImeWindowStatus =
-                (mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0);
-        mWindowVisible = true;
+        mDecorViewVisible = true;
         if (!mShowInputRequested && mInputStarted && showInput) {
             doShowInput = true;
             mShowInputRequested = true;
@@ -1836,8 +1918,8 @@
         updateFullscreenMode();
         updateInputViewShown();
 
-        if (!mWindowCreated) {
-            mWindowCreated = true;
+        if (!mViewsCreated) {
+            mViewsCreated = true;
             initialize();
             if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView");
             View v = onCreateCandidatesView();
@@ -1846,6 +1928,10 @@
                 setCandidatesView(v);
             }
         }
+        return doShowInput;
+    }
+
+    private void startViews(boolean doShowInput) {
         if (mShowInputRequested) {
             if (!mInputViewStarted) {
                 if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
@@ -1857,29 +1943,38 @@
             mCandidatesViewStarted = true;
             onStartCandidatesView(mInputEditorInfo, false);
         }
-        
-        if (doShowInput) {
-            startExtractingText(false);
-        }
+        if (doShowInput) startExtractingText(false);
+    }
 
-        final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown());
-        if (previousImeWindowStatus != nextImeWindowStatus) {
-            setImeWindowStatus(nextImeWindowStatus, mBackDisposition);
-        }
-        if ((previousImeWindowStatus & IME_ACTIVE) == 0) {
-            if (DEBUG) Log.v(TAG, "showWindow: showing!");
+    private void onPreRenderedWindowVisibilityChanged(boolean setVisible) {
+        mWindowVisible = setVisible;
+        mShowInputFlags = setVisible ? mShowInputFlags : 0;
+        mShowInputRequested = setVisible;
+        mDecorViewVisible = setVisible;
+        if (setVisible) {
             onWindowShown();
-            mWindow.show();
         }
     }
 
-    private void finishViews() {
+    /**
+     * Apply the IME visibility in {@link android.view.ImeInsetsSourceConsumer} when
+     * pre-rendering is enabled.
+     * @param setVisible {@code true} to make it visible, false to hide it.
+     */
+    private void applyVisibilityInInsetsConsumer(boolean setVisible) {
+        if (!mIsPreRendered) {
+            return;
+        }
+        mPrivOps.applyImeVisibility(setVisible);
+    }
+
+    private void finishViews(boolean finishingInput) {
         if (mInputViewStarted) {
             if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
-            onFinishInputView(false);
+            onFinishInputView(finishingInput);
         } else if (mCandidatesViewStarted) {
             if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
-            onFinishCandidatesView(false);
+            onFinishCandidatesView(finishingInput);
         }
         mInputViewStarted = false;
         mCandidatesViewStarted = false;
@@ -1891,12 +1986,15 @@
     }
 
     public void hideWindow() {
-        finishViews();
-        if (mWindowVisible) {
+        if (DEBUG) Log.v(TAG, "CALL: hideWindow");
+        mIsPreRendered = false;
+        mWindowVisible = false;
+        finishViews(false /* finishingInput */);
+        if (mDecorViewVisible) {
             mWindow.hide();
-            mWindowVisible = false;
+            mDecorViewVisible = false;
             onWindowHidden();
-            mWindowWasVisible = false;
+            mDecorViewWasVisible = false;
         }
         updateFullscreenMode();
     }
@@ -1956,15 +2054,8 @@
     }
     
     void doFinishInput() {
-        if (mInputViewStarted) {
-            if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
-            onFinishInputView(true);
-        } else if (mCandidatesViewStarted) {
-            if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView");
-            onFinishCandidatesView(true);
-        }
-        mInputViewStarted = false;
-        mCandidatesViewStarted = false;
+        if (DEBUG) Log.v(TAG, "CALL: doFinishInput");
+        finishViews(true /* finishingInput */);
         if (mInputStarted) {
             if (DEBUG) Log.v(TAG, "CALL: onFinishInput");
             onFinishInput();
@@ -1984,7 +2075,7 @@
         initialize();
         if (DEBUG) Log.v(TAG, "CALL: onStartInput");
         onStartInput(attribute, restarting);
-        if (mWindowVisible) {
+        if (mDecorViewVisible) {
             if (mShowInputRequested) {
                 if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                 mInputViewStarted = true;
@@ -1995,6 +2086,32 @@
                 mCandidatesViewStarted = true;
                 onStartCandidatesView(mInputEditorInfo, restarting);
             }
+        } else if (mCanPreRender && mInputEditorInfo != null && mStartedInputConnection != null) {
+            // Pre-render IME views and window when real EditorInfo is available.
+            // pre-render IME window and keep it invisible.
+            if (DEBUG) Log.v(TAG, "Pre-Render IME for " + mInputEditorInfo.fieldName);
+            if (mInShowWindow) {
+                Log.w(TAG, "Re-entrance in to showWindow");
+                return;
+            }
+
+            mDecorViewWasVisible = mDecorViewVisible;
+            mInShowWindow = true;
+            startViews(prepareWindow(true /* showInput */));
+
+            // compute visibility
+            mIsPreRendered = true;
+            onPreRenderedWindowVisibilityChanged(false /* setVisible */);
+
+            // request draw for the IME surface.
+            // When IME is not pre-rendered, this will actually show the IME.
+            if (DEBUG) Log.v(TAG, "showWindow: draw decorView!");
+            mWindow.show();
+            maybeNotifyPreRendered();
+            mDecorViewWasVisible = true;
+            mInShowWindow = false;
+        } else {
+            mIsPreRendered = false;
         }
     }
     
@@ -2153,7 +2270,7 @@
             // consume the back key.
             if (doIt) requestHideSelf(0);
             return true;
-        } else if (mWindowVisible) {
+        } else if (mDecorViewVisible) {
             if (mCandidatesVisibility == View.VISIBLE) {
                 // If we are showing candidates even if no input area, then
                 // hide them.
@@ -2180,7 +2297,6 @@
         return mExtractEditText;
     }
 
-
     /**
      * Called back when a {@link KeyEvent} is forwarded from the target application.
      *
@@ -2893,8 +3009,11 @@
         inputContentInfo.setUriToken(uriToken);
     }
 
-    private static int mapToImeWindowStatus(boolean isInputViewShown) {
-        return IME_ACTIVE | (isInputViewShown ? IME_VISIBLE : 0);
+    private int mapToImeWindowStatus() {
+        return IME_ACTIVE
+                | (isInputViewShown()
+                        ? (mCanPreRender ? (mWindowVisible ? IME_VISIBLE : IME_INVISIBLE)
+                        : IME_VISIBLE) : 0);
     }
 
     /**
@@ -2904,9 +3023,10 @@
     @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
         final Printer p = new PrintWriterPrinter(fout);
         p.println("Input method service state for " + this + ":");
-        p.println("  mWindowCreated=" + mWindowCreated);
-        p.println("  mWindowVisible=" + mWindowVisible
-                + " mWindowWasVisible=" + mWindowWasVisible
+        p.println("  mViewsCreated=" + mViewsCreated);
+        p.println("  mDecorViewVisible=" + mDecorViewVisible
+                + " mDecorViewWasVisible=" + mDecorViewWasVisible
+                + " mWindowVisible=" + mWindowVisible
                 + " mInShowWindow=" + mInShowWindow);
         p.println("  Configuration=" + getResources().getConfiguration());
         p.println("  mToken=" + mToken);
@@ -2926,6 +3046,8 @@
         
         p.println("  mShowInputRequested=" + mShowInputRequested
                 + " mLastShowInputRequested=" + mLastShowInputRequested
+                + " mCanPreRender=" + mCanPreRender
+                + " mIsPreRendered=" + mIsPreRendered
                 + " mShowInputFlags=0x" + Integer.toHexString(mShowInputFlags));
         p.println("  mCandidatesVisibility=" + mCandidatesVisibility
                 + " mFullscreenApplied=" + mFullscreenApplied
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 5bb24ba..3bae12e 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1014,20 +1014,54 @@
      *                   to remove an existing always-on VPN configuration.
      * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
      *        {@code false} otherwise.
+     * @param lockdownWhitelist The list of packages that are allowed to access network directly
+     *         when VPN is in lockdown mode but is not running. Non-existent packages are ignored so
+     *         this method must be called when a package that should be whitelisted is installed or
+     *         uninstalled.
      * @return {@code true} if the package is set as always-on VPN controller;
      *         {@code false} otherwise.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
     public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
-            boolean lockdownEnabled) {
+            boolean lockdownEnabled, @Nullable List<String> lockdownWhitelist) {
         try {
-            return mService.setAlwaysOnVpnPackage(userId, vpnPackage, lockdownEnabled);
+            return mService.setAlwaysOnVpnPackage(
+                    userId, vpnPackage, lockdownEnabled, lockdownWhitelist);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
+     * Configures an always-on VPN connection through a specific application.
+     * This connection is automatically granted and persisted after a reboot.
+     *
+     * <p>The designated package should declare a {@link VpnService} in its
+     *    manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
+     *    otherwise the call will fail.
+     *
+     * @param userId The identifier of the user to set an always-on VPN for.
+     * @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
+     *                   to remove an existing always-on VPN configuration.
+     * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
+     *        {@code false} otherwise.
+     * @return {@code true} if the package is set as always-on VPN controller;
+     *         {@code false} otherwise.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage,
+            boolean lockdownEnabled) {
+        try {
+            return mService.setAlwaysOnVpnPackage(
+                    userId, vpnPackage, lockdownEnabled, /* whitelist */ null);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+   /**
      * Returns the package name of the currently set always-on VPN application.
      * If there is no always-on VPN set, or the VPN is provided by the system instead
      * of by an app, {@code null} will be returned.
@@ -1036,6 +1070,7 @@
      *         or {@code null} if none is set.
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
     public String getAlwaysOnVpnPackageForUser(int userId) {
         try {
             return mService.getAlwaysOnVpnPackage(userId);
@@ -1045,6 +1080,36 @@
     }
 
     /**
+     * @return whether always-on VPN is in lockdown mode.
+     *
+     * @hide
+     **/
+    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    public boolean isVpnLockdownEnabled(int userId) {
+        try {
+            return mService.isVpnLockdownEnabled(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+    }
+
+    /**
+     * @return the list of packages that are allowed to access network when always-on VPN is in
+     * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active.
+     *
+     * @hide
+     **/
+    @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN)
+    public List<String> getVpnLockdownWhitelist(int userId) {
+        try {
+            return mService.getVpnLockdownWhitelist(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns details about the currently active default data network
      * for a given uid.  This is for internal use only to avoid spying
      * other apps.
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/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index e97060a..fd7360f 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -125,8 +125,11 @@
 
     boolean updateLockdownVpn();
     boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
-    boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown);
+    boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown,
+            in List<String> lockdownWhitelist);
     String getAlwaysOnVpnPackage(int userId);
+    boolean isVpnLockdownEnabled(int userId);
+    List<String> getVpnLockdownWhitelist(int userId);
 
     int checkMobileProvisioning(int suggestedTimeOutMs);
 
diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java
index b996cda..175263f 100644
--- a/core/java/android/net/IpPrefix.java
+++ b/core/java/android/net/IpPrefix.java
@@ -104,6 +104,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @TestApi
     public IpPrefix(String prefix) {
         // We don't reuse the (InetAddress, int) constructor because "error: call to this must be
         // first statement in constructor". We could factor out setting the member variables to an
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/LinkAddress.java b/core/java/android/net/LinkAddress.java
index fbd602c..8d779aa 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -176,6 +176,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public LinkAddress(InetAddress address, int prefixLength) {
         this(address, prefixLength, 0, 0);
         this.scope = scopeForUnicastAddress(address);
@@ -199,6 +200,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public LinkAddress(String address) {
         this(address, 0, 0);
         this.scope = scopeForUnicastAddress(this.address);
@@ -212,6 +214,8 @@
      * @param scope The address scope.
      * @hide
      */
+    @SystemApi
+    @TestApi
     public LinkAddress(String address, int flags, int scope) {
         // This may throw an IllegalArgumentException; catching it is the caller's responsibility.
         // TODO: consider rejecting mapped IPv4 addresses such as "::ffff:192.0.2.5/24".
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 6628701..42db0fd 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -287,7 +287,8 @@
      * @return true if {@code address} was added or updated, false otherwise.
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
+    @TestApi
     public boolean addLinkAddress(LinkAddress address) {
         if (address == null) {
             return false;
@@ -315,6 +316,8 @@
      * @return true if the address was removed, false if it did not exist.
      * @hide
      */
+    @SystemApi
+    @TestApi
     public boolean removeLinkAddress(LinkAddress toRemove) {
         int i = findLinkAddressIndex(toRemove);
         if (i >= 0) {
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 2c831de..e04b5fc 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -123,6 +123,8 @@
     /**
      * @hide
      */
+    @SystemApi
+    @TestApi
     public Network(Network that) {
         this(that.netId, that.mPrivateDnsBypass);
     }
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/TrafficStats.java b/core/java/android/net/TrafficStats.java
index bbf8f97..49c6f74 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -128,10 +128,14 @@
     public static final int TAG_SYSTEM_APP = 0xFFFFFF05;
 
     /** @hide */
+    @SystemApi
+    @TestApi
     public static final int TAG_SYSTEM_DHCP = 0xFFFFFF40;
     /** @hide */
     public static final int TAG_SYSTEM_NTP = 0xFFFFFF41;
     /** @hide */
+    @SystemApi
+    @TestApi
     public static final int TAG_SYSTEM_PROBE = 0xFFFFFF42;
     /** @hide */
     public static final int TAG_SYSTEM_NEIGHBOR = 0xFFFFFF43;
@@ -140,6 +144,8 @@
     /** @hide */
     public static final int TAG_SYSTEM_PAC = 0xFFFFFF45;
     /** @hide */
+    @SystemApi
+    @TestApi
     public static final int TAG_SYSTEM_DHCP_SERVER = 0xFFFFFF46;
 
     private static INetworkStatsService sStatsService;
diff --git a/core/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java
index f28cdc9..e09fa8f 100644
--- a/core/java/android/net/apf/ApfCapabilities.java
+++ b/core/java/android/net/apf/ApfCapabilities.java
@@ -16,11 +16,19 @@
 
 package android.net.apf;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.Context;
+
+import com.android.internal.R;
+
 /**
  * APF program support capabilities.
  *
  * @hide
  */
+@SystemApi
+@TestApi
 public class ApfCapabilities {
     /**
      * Version of APF instruction set supported for packet filtering. 0 indicates no support for
@@ -69,4 +77,18 @@
     public boolean hasDataAccess() {
         return apfVersionSupported >= 4;
     }
+
+    /**
+     * @return Whether the APF Filter in the device should filter out IEEE 802.3 Frames.
+     */
+    public static boolean getApfDrop8023Frames(Context context) {
+        return context.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
+    }
+
+    /**
+     * @return An array of blacklisted EtherType, packets with EtherTypes within it will be dropped.
+     */
+    public static int[] getApfEthTypeBlackList(Context context) {
+        return context.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
+    }
 }
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/packages/NetworkStack/src/android/net/util/FdEventsReader.java b/core/java/android/net/shared/FdEventsReader.java
similarity index 93%
rename from packages/NetworkStack/src/android/net/util/FdEventsReader.java
rename to core/java/android/net/shared/FdEventsReader.java
index 8bbf449..5ccc560 100644
--- a/packages/NetworkStack/src/android/net/util/FdEventsReader.java
+++ b/core/java/android/net/shared/FdEventsReader.java
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package android.net.util;
+package android.net.shared;
 
-import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -63,6 +63,7 @@
  * All public methods MUST only be called from the same thread with which
  * the Handler constructor argument is associated.
  *
+ * @param <BufferType> the type of the buffer used to read data.
  * @hide
  */
 public abstract class FdEventsReader<BufferType> {
@@ -89,6 +90,7 @@
         mBuffer = buffer;
     }
 
+    /** Start this FdEventsReader. */
     public void start() {
         if (onCorrectThread()) {
             createAndRegisterFd();
@@ -100,6 +102,7 @@
         }
     }
 
+    /** Stop this FdEventsReader and destroy the file descriptor. */
     public void stop() {
         if (onCorrectThread()) {
             unregisterAndDestroyFd();
@@ -112,18 +115,25 @@
     }
 
     @NonNull
-    public Handler getHandler() { return mHandler; }
+    public Handler getHandler() {
+        return mHandler;
+    }
 
     protected abstract int recvBufSize(@NonNull BufferType buffer);
 
-    public int recvBufSize() { return recvBufSize(mBuffer); }
+    /** Returns the size of the receive buffer. */
+    public int recvBufSize() {
+        return recvBufSize(mBuffer);
+    }
 
     /**
      * Get the number of successful calls to {@link #readPacket(FileDescriptor, Object)}.
      *
      * <p>A call was successful if {@link #readPacket(FileDescriptor, Object)} returned a value > 0.
      */
-    public final long numPacketsReceived() { return mPacketsReceived; }
+    public final long numPacketsReceived() {
+        return mPacketsReceived;
+    }
 
     /**
      * Subclasses MUST create the listening socket here, including setting
@@ -199,7 +209,9 @@
         onStart();
     }
 
-    private boolean isRunning() { return (mFd != null) && mFd.valid(); }
+    private boolean isRunning() {
+        return (mFd != null) && mFd.valid();
+    }
 
     // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error.
     private boolean handleInput() {
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/net/util/SocketUtils.java b/core/java/android/net/util/SocketUtils.java
index de67cf5..fbb15ed 100644
--- a/core/java/android/net/util/SocketUtils.java
+++ b/core/java/android/net/util/SocketUtils.java
@@ -20,20 +20,29 @@
 import static android.system.OsConstants.SO_BINDTODEVICE;
 
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.net.MacAddress;
 import android.net.NetworkUtils;
 import android.system.ErrnoException;
 import android.system.NetlinkSocketAddress;
 import android.system.Os;
 import android.system.PacketSocketAddress;
+import android.system.StructTimeval;
+
+import libcore.io.IoBridge;
 
 import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet4Address;
 import java.net.SocketAddress;
+import java.net.SocketException;
 
 /**
  * Collection of utilities to interact with raw sockets.
  * @hide
  */
 @SystemApi
+@TestApi
 public class SocketUtils {
     /**
      * Create a raw datagram socket that is bound to an interface.
@@ -57,18 +66,94 @@
     }
 
     /**
-     * Make a socket address to bind to packet sockets.
+     * Make socket address that packet sockets can bind to.
      */
     public static SocketAddress makePacketSocketAddress(short protocol, int ifIndex) {
         return new PacketSocketAddress(protocol, ifIndex);
     }
 
     /**
-     * Make a socket address to send raw packets.
+     * Make a socket address that packet socket can send packets to.
      */
     public static SocketAddress makePacketSocketAddress(int ifIndex, byte[] hwAddr) {
         return new PacketSocketAddress(ifIndex, hwAddr);
     }
 
+    /**
+     * Set an option on a socket that takes a time value argument.
+     */
+    public static void setSocketTimeValueOption(
+            FileDescriptor fd, int level, int option, long millis) throws ErrnoException {
+        Os.setsockoptTimeval(fd, level, option, StructTimeval.fromMillis(millis));
+    }
+
+    /**
+     * Bind a socket to the specified address.
+     */
+    public static void bindSocket(FileDescriptor fd, SocketAddress addr)
+            throws ErrnoException, SocketException {
+        Os.bind(fd, addr);
+    }
+
+    /**
+     * Connect a socket to the specified address.
+     */
+    public static void connectSocket(FileDescriptor fd, SocketAddress addr)
+            throws ErrnoException, SocketException {
+        Os.connect(fd, addr);
+    }
+
+    /**
+     * Send a message on a socket, using the specified SocketAddress.
+     */
+    public static void sendTo(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount,
+            int flags, SocketAddress addr) throws ErrnoException, SocketException {
+        Os.sendto(fd, bytes, byteOffset, byteCount, flags, addr);
+    }
+
+    /**
+     * @see IoBridge#closeAndSignalBlockedThreads(FileDescriptor)
+     */
+    public static void closeSocket(FileDescriptor fd) throws IOException {
+        IoBridge.closeAndSignalBlockedThreads(fd);
+    }
+
+    /**
+     * Attaches a socket filter that accepts DHCP packets to the given socket.
+     */
+    public static void attachDhcpFilter(FileDescriptor fd) throws SocketException {
+        NetworkUtils.attachDhcpFilter(fd);
+    }
+
+    /**
+     * Attaches a socket filter that accepts ICMPv6 router advertisements to the given socket.
+     * @param fd the socket's {@link FileDescriptor}.
+     * @param packetType the hardware address type, one of ARPHRD_*.
+     */
+    public static void attachRaFilter(FileDescriptor fd, int packetType) throws SocketException {
+        NetworkUtils.attachRaFilter(fd, packetType);
+    }
+
+    /**
+     * Attaches a socket filter that accepts L2-L4 signaling traffic required for IP connectivity.
+     *
+     * This includes: all ARP, ICMPv6 RS/RA/NS/NA messages, and DHCPv4 exchanges.
+     *
+     * @param fd the socket's {@link FileDescriptor}.
+     * @param packetType the hardware address type, one of ARPHRD_*.
+     */
+    public static void attachControlPacketFilter(FileDescriptor fd, int packetType)
+            throws SocketException {
+        NetworkUtils.attachControlPacketFilter(fd, packetType);
+    }
+
+    /**
+     * Add an entry into the ARP cache.
+     */
+    public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname,
+            FileDescriptor fd) throws IOException {
+        NetworkUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd);
+    }
+
     private SocketUtils() {}
 }
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 6801618..0b2cfdd 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -68,4 +68,8 @@
     void removeNfcUnlockHandler(INfcUnlockHandler unlockHandler);
 
     void verifyNfcPermission();
+    boolean isNfcSecureEnabled();
+    boolean deviceSupportsNfcSecure();
+    boolean setNfcSecure(boolean enable);
+
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index e55e036..a7d2ee9 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -1702,6 +1702,63 @@
     }
 
     /**
+     * Sets Secure NFC feature.
+     * <p>This API is for the Settings application.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public boolean setNfcSecure(boolean enable) {
+        if (!sHasNfcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.setNfcSecure(enable);
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        }
+    }
+
+    /**
+     * Checks if the device supports Secure NFC functionality.
+     *
+     * @return True if device supports Secure NFC, false otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     */
+    public boolean deviceSupportsNfcSecure() {
+        if (!sHasNfcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.deviceSupportsNfcSecure();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        }
+    }
+
+    /**
+     * Checks Secure NFC feature is enabled.
+     *
+     * @return True if device supports Secure NFC is enabled, false otherwise
+     * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+     * @throws UnsupportedOperationException if device doesn't support
+     *         Secure NFC functionality. {@link #deviceSupportsNfcSecure}
+     */
+    public boolean isNfcSecureEnabled() {
+        if (!sHasNfcFeature) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return sService.isNfcSecureEnabled();
+        } catch (RemoteException e) {
+            attemptDeadServiceRecovery(e);
+            return false;
+        }
+    }
+
+    /**
      * Enable NDEF Push feature.
      * <p>This API is for the Settings application.
      * @hide
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/DumpstateOptions.java b/core/java/android/os/DumpstateOptions.java
deleted file mode 100644
index 53037b24..0000000
--- a/core/java/android/os/DumpstateOptions.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-/**
- * Options passed to dumpstate service.
- *
- * @hide
- */
-public final class DumpstateOptions implements Parcelable {
-    // If true the caller can get callbacks with per-section
-    // progress details.
-    private final boolean mGetSectionDetails;
-    // Name of the caller.
-    private final String mName;
-
-    public DumpstateOptions(Parcel in) {
-        mGetSectionDetails = in.readBoolean();
-        mName = in.readString();
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-     public void writeToParcel(Parcel out, int flags) {
-        out.writeBoolean(mGetSectionDetails);
-        out.writeString(mName);
-    }
-
-    public static final Parcelable.Creator<DumpstateOptions> CREATOR =
-            new Parcelable.Creator<DumpstateOptions>() {
-        public DumpstateOptions createFromParcel(Parcel in) {
-            return new DumpstateOptions(in);
-        }
-
-        public DumpstateOptions[] newArray(int size) {
-            return new DumpstateOptions[size];
-        }
-    };
-}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index a236300..3feccf5 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -50,6 +50,7 @@
 
     /** {@hide} */
     public static final String DIR_ANDROID = "Android";
+    private static final String DIR_SANDBOX = "sandbox";
     private static final String DIR_DATA = "data";
     private static final String DIR_MEDIA = "media";
     private static final String DIR_OBB = "obb";
@@ -117,6 +118,10 @@
             return buildPaths(getExternalDirs(), type);
         }
 
+        public File[] buildExternalStorageAndroidSandboxDirs() {
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_SANDBOX);
+        }
+
         public File[] buildExternalStorageAndroidDataDirs() {
             return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA);
         }
@@ -824,6 +829,15 @@
      * Returns the path for android-specific data on the SD card.
      * @hide
      */
+    public static File[] buildExternalStorageAndroidSandboxDirs() {
+        throwIfUserRequired();
+        return sCurrentUser.buildExternalStorageAndroidSandboxDirs();
+    }
+
+    /**
+     * Returns the path for android-specific data on the SD card.
+     * @hide
+     */
     public static File[] buildExternalStorageAndroidDataDirs() {
         throwIfUserRequired();
         return sCurrentUser.buildExternalStorageAndroidDataDirs();
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/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index efcad3e..93360a5 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -63,7 +63,7 @@
     private static final boolean DEBUG = false;
     private static final String TAG = "GraphicsEnvironment";
     private static final String PROPERTY_GFX_DRIVER = "ro.gfx.driver.0";
-    private static final String GUP_WHITELIST_FILENAME = "whitelist.txt";
+    private static final String GAME_DRIVER_WHITELIST_FILENAME = "whitelist.txt";
     private static final String ANGLE_RULES_FILE = "a4a_rules.json";
     private static final String ANGLE_TEMP_RULES = "debug.angle.rules";
     private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID";
@@ -529,44 +529,45 @@
             return;
         }
 
-        // GUP_DEV_ALL_APPS
+        // GAME_DRIVER_ALL_APPS
         // 0: Default (Invalid values fallback to default as well)
         // 1: All apps use Game Driver
         // 2: All apps use system graphics driver
-        int gupDevAllApps = coreSettings.getInt(Settings.Global.GUP_DEV_ALL_APPS, 0);
-        if (gupDevAllApps == 2) {
+        int gameDriverAllApps = coreSettings.getInt(Settings.Global.GAME_DRIVER_ALL_APPS, 0);
+        if (gameDriverAllApps == 2) {
             if (DEBUG) {
-                Log.w(TAG, "GUP is turned off on this device");
+                Log.w(TAG, "Game Driver is turned off on this device");
             }
             return;
         }
 
-        if (gupDevAllApps != 1) {
-            // GUP_DEV_OPT_OUT_APPS has higher priority than GUP_DEV_OPT_IN_APPS
-            if (getGlobalSettingsString(coreSettings, Settings.Global.GUP_DEV_OPT_OUT_APPS)
+        if (gameDriverAllApps != 1) {
+            // GAME_DRIVER_OPT_OUT_APPS has higher priority than GAME_DRIVER_OPT_IN_APPS
+            if (getGlobalSettingsString(coreSettings, Settings.Global.GAME_DRIVER_OPT_OUT_APPS)
                             .contains(ai.packageName)) {
                 if (DEBUG) {
-                    Log.w(TAG, ai.packageName + " opts out from GUP.");
+                    Log.w(TAG, ai.packageName + " opts out from Game Driver.");
                 }
                 return;
             }
-            boolean isDevOptIn = getGlobalSettingsString(coreSettings,
-                                                         Settings.Global.GUP_DEV_OPT_IN_APPS)
-                              .contains(ai.packageName);
+            boolean isOptIn =
+                    getGlobalSettingsString(coreSettings, Settings.Global.GAME_DRIVER_OPT_IN_APPS)
+                            .contains(ai.packageName);
 
-            if (!isDevOptIn && !onWhitelist(context, driverPackageName, ai.packageName)) {
+            if (!isOptIn && !onWhitelist(context, driverPackageName, ai.packageName)) {
                 if (DEBUG) {
                     Log.w(TAG, ai.packageName + " is not on the whitelist.");
                 }
                 return;
             }
 
-            if (!isDevOptIn) {
+            if (!isOptIn) {
                 // At this point, the application is on the whitelist only, check whether it's
                 // on the blacklist, terminate early when it's on the blacklist.
                 try {
                     // TODO(b/121350991) Switch to DeviceConfig with property listener.
-                    String base64String = coreSettings.getString(Settings.Global.GUP_BLACKLIST);
+                    String base64String =
+                            coreSettings.getString(Settings.Global.GAME_DRIVER_BLACKLIST);
                     if (base64String != null && !base64String.isEmpty()) {
                         Blacklists blacklistsProto = Blacklists.parseFrom(
                                 Base64.decode(base64String, BASE64_FLAGS));
@@ -652,7 +653,7 @@
             Context driverContext = context.createPackageContext(driverPackageName,
                                                                  Context.CONTEXT_RESTRICTED);
             AssetManager assets = driverContext.getAssets();
-            InputStream stream = assets.open(GUP_WHITELIST_FILENAME);
+            InputStream stream = assets.open(GAME_DRIVER_WHITELIST_FILENAME);
             BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
             for (String packageName; (packageName = reader.readLine()) != null; ) {
                 if (packageName.equals(applicationPackageName)) {
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/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 9e47179..e94ad2b 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -74,6 +74,19 @@
     public static final String ZYGOTE_SECONDARY_SOCKET_NAME = "zygote_secondary";
 
     /**
+     * @hide for internal use only.
+     */
+    public static final int ZYGOTE_CONNECT_TIMEOUT_MS = 20000;
+
+    /**
+     * @hide for internal use only.
+     *
+     * Use a relatively short delay, because for app zygote, this is in the critical path of
+     * service launch.
+     */
+    public static final int ZYGOTE_CONNECT_RETRY_DELAY_MS = 50;
+
+    /**
      * @hide for internal use only
      */
     public static final String BLASTULA_POOL_SOCKET_NAME = "blastula_pool";
@@ -933,7 +946,8 @@
      * @param address The name of the socket to connect to.
      */
     public static void waitForConnectionToZygote(LocalSocketAddress zygoteSocketAddress) {
-        for (int n = 20; n >= 0; n--) {
+        int numRetries = ZYGOTE_CONNECT_TIMEOUT_MS / ZYGOTE_CONNECT_RETRY_DELAY_MS;
+        for (int n = numRetries; n >= 0; n--) {
             try {
                 final ZygoteState zs =
                         ZygoteState.connect(zygoteSocketAddress, null);
@@ -945,7 +959,7 @@
             }
 
             try {
-                Thread.sleep(1000);
+                Thread.sleep(ZYGOTE_CONNECT_RETRY_DELAY_MS);
             } catch (InterruptedException ie) {
             }
         }
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 249b622..5dd869f 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -35,4 +35,6 @@
     void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted,
             boolean countSystem, in RemoteCallback callback);
     void getPermissionUsages(boolean countSystem, long numMillis, in RemoteCallback callback);
+    void isApplicationQualifiedForRole(String roleName, String packageName,
+            in RemoteCallback callback);
 }
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index bfcca7c..b59d0c7 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -21,6 +21,7 @@
 import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkStringNotEmpty;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
@@ -343,6 +344,28 @@
     }
 
     /**
+     * Check whether an application is qualified for a role.
+     *
+     * @param roleName name of the role to check for
+     * @param packageName package name of the application to check for
+     * @param executor Executor on which to invoke the callback
+     * @param callback Callback to receive the result
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+    public void isApplicationQualifiedForRole(@NonNull String roleName, @NonNull String packageName,
+            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+        checkStringNotEmpty(roleName);
+        checkStringNotEmpty(packageName);
+        checkNotNull(executor);
+        checkNotNull(callback);
+
+        sRemoteService.scheduleRequest(new PendingIsApplicationQualifiedForRoleRequest(
+                sRemoteService, roleName, packageName, executor, callback));
+    }
+
+    /**
      * A connection to the remote service
      */
     static final class RemoteService extends
@@ -810,4 +833,58 @@
             }
         }
     }
+
+    /**
+     * Request for {@link #isApplicationQualifiedForRole}.
+     */
+    private static final class PendingIsApplicationQualifiedForRoleRequest extends
+            AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
+
+        private final @NonNull String mRoleName;
+        private final @NonNull String mPackageName;
+        private final @NonNull Consumer<Boolean> mCallback;
+
+        private final @NonNull RemoteCallback mRemoteCallback;
+
+        private PendingIsApplicationQualifiedForRoleRequest(@NonNull RemoteService service,
+                @NonNull String roleName, @NonNull String packageName,
+                @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+            super(service);
+
+            mRoleName = roleName;
+            mPackageName = packageName;
+            mCallback = callback;
+
+            mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    boolean qualified;
+                    if (result != null) {
+                        qualified = result.getBoolean(KEY_RESULT);
+                    } else {
+                        qualified = false;
+                    }
+                    callback.accept(qualified);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                    finish();
+                }
+            }), null);
+        }
+
+        @Override
+        protected void onTimeout(RemoteService remoteService) {
+            mCallback.accept(false);
+        }
+
+        @Override
+        public void run() {
+            try {
+                getService().getServiceInterface().isApplicationQualifiedForRole(mRoleName,
+                        mPackageName, mRemoteCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error checking whether application qualifies for role", e);
+            }
+        }
+    }
 }
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 10e8c8d..9a58b97 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -20,6 +20,7 @@
 import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
 import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
 import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkStringNotEmpty;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.Manifest;
@@ -136,8 +137,19 @@
      *
      * @return descriptions of the users of permissions
      */
-    public abstract @NonNull List<RuntimePermissionUsageInfo>
-            onPermissionUsageResult(boolean countSystem, long numMillis);
+    public abstract @NonNull List<RuntimePermissionUsageInfo> onGetPermissionUsages(
+            boolean countSystem, long numMillis);
+
+    /**
+     * Check whether an application is qualified for a role.
+     *
+     * @param roleName name of the role to check for
+     * @param packageName package name of the application to check for
+     *
+     * @return whether the application is qualified for the role.
+     */
+    public abstract boolean onIsApplicationQualifiedForRole(@NonNull String roleName,
+            @NonNull String packageName);
 
     @Override
     public final IBinder onBind(Intent intent) {
@@ -240,6 +252,20 @@
                                 PermissionControllerService.this, countSystem, numMillis,
                                 callback));
             }
+
+            @Override
+            public void isApplicationQualifiedForRole(String roleName, String packageName,
+                    RemoteCallback callback) {
+                checkStringNotEmpty(roleName);
+                checkStringNotEmpty(packageName);
+                checkNotNull(callback, "callback");
+
+                enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);
+
+                mHandler.sendMessage(obtainMessage(
+                        PermissionControllerService::isApplicationQualifiedForRole,
+                        PermissionControllerService.this, roleName, packageName, callback));
+            }
         };
     }
 
@@ -296,7 +322,7 @@
     private void getPermissionUsages(boolean countSystem, long numMillis,
             @NonNull RemoteCallback callback) {
         List<RuntimePermissionUsageInfo> users =
-                onPermissionUsageResult(countSystem, numMillis);
+                onGetPermissionUsages(countSystem, numMillis);
         if (users != null && !users.isEmpty()) {
             Bundle result = new Bundle();
             result.putParcelableList(PermissionControllerManager.KEY_RESULT, users);
@@ -305,4 +331,12 @@
             callback.sendResult(null);
         }
     }
+
+    private void isApplicationQualifiedForRole(@NonNull String roleName,
+            @NonNull String packageName, @NonNull RemoteCallback callback) {
+        boolean qualified = onIsApplicationQualifiedForRole(roleName, packageName);
+        Bundle result = new Bundle();
+        result.putBoolean(PermissionControllerManager.KEY_RESULT, qualified);
+        callback.sendResult(result);
+    }
 }
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/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index cd823a9..d58e00a 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -112,14 +112,12 @@
     @SystemApi
     public interface IntelligenceAttention {
         String NAMESPACE = "intelligence_attention";
-        /**
-         * If {@code true}, enables the attention check.
-         */
-        String PROPERTY_ATTENTION_CHECK_ENABLED = "attention_check_enabled";
-        /**
-         * Settings for performing the attention check.
-         */
-        String PROPERTY_ATTENTION_CHECK_SETTINGS = "attention_check_settings";
+
+        /** If {@code true}, enables the attention features. */
+        String PROPERTY_ATTENTION_ENABLED = "attention_enabled";
+
+        /** Settings for the attention features. */
+        String PROPERTY_ATTENTION_SETTINGS = "attention_settings";
     }
 
     /**
@@ -181,6 +179,39 @@
         String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
     }
 
+    /**
+     * Namespace for {@link AttentionManagerService} related features.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface AttentionManagerService {
+        String NAMESPACE = "attention_manager_service";
+
+        /** If {@code true}, enables {@link AttentionManagerService} features. */
+        String PROPERTY_SERVICE_ENABLED = "service_enabled";
+
+        /** Allows a CTS to inject a fake implementation. */
+        String PROPERTY_COMPONENT_NAME = "component_name";
+    }
+
+    /**
+     * Namespace for storage-related features.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface Storage {
+        String NAMESPACE = "storage";
+
+        /**
+         * If {@code 1}, enables the isolated storage feature. If {@code -1},
+         * disables the isolated storage feature. If {@code 0}, uses the default
+         * value from the build system.
+         */
+        String ISOLATED_STORAGE_ENABLED = "isolated_storage_enabled";
+    }
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index b348da4..63bbb9c 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -837,6 +837,14 @@
         }
     }
 
+    /** @hide */
+    public static final String MEDIASTORE_DOWNLOADS_DELETED_CALL = "mediastore_downloads_deleted";
+
+    /** @hide */
+    public static final String EXTRA_IDS = "ids";
+    /** @hide */
+    public static final String EXTRA_MIME_TYPES = "mime_types";
+
     /**
      * Query where clause for general querying.
      */
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index f5c442f..124c50a 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
@@ -153,6 +158,8 @@
     public static final String PARAM_PROGRESS = "progress";
     /** {@hide} */
     public static final String PARAM_REQUIRE_ORIGINAL = "requireOriginal";
+    /** {@hide} */
+    public static final String PARAM_LIMIT = "limit";
 
     /**
      * Activity Action: Launch a music player.
@@ -508,7 +515,12 @@
      * @see MediaStore#createPending(Context, PendingParams)
      */
     public static @NonNull Uri setIncludePending(@NonNull Uri uri) {
-        return uri.buildUpon().appendQueryParameter(PARAM_INCLUDE_PENDING, "1").build();
+        return setIncludePending(uri.buildUpon()).build();
+    }
+
+    /** @hide */
+    public static @NonNull Uri.Builder setIncludePending(@NonNull Uri.Builder uriBuilder) {
+        return uriBuilder.appendQueryParameter(PARAM_INCLUDE_PENDING, "1");
     }
 
     /**
@@ -977,6 +989,11 @@
      * work with multiple media file types in a single query.
      */
     public static final class Files {
+        /** @hide */
+        public static final String TABLE = "files";
+
+        /** @hide */
+        public static final Uri EXTERNAL_CONTENT_URI = getContentUri(VOLUME_EXTERNAL);
 
         /**
          * Get the content:// style URI for the files table on the
@@ -2992,6 +3009,7 @@
      *
      * @hide
      */
+    @TestApi
     public static @NonNull File getVolumePath(@NonNull String volumeName)
             throws FileNotFoundException {
         if (TextUtils.isEmpty(volumeName)) {
@@ -3022,6 +3040,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 d840e3c..a7af5d1 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
@@ -5800,6 +5831,16 @@
         public static final String ALWAYS_ON_VPN_LOCKDOWN = "always_on_vpn_lockdown";
 
         /**
+         * Comma separated list of packages that are allowed to access the network when VPN is in
+         * lockdown mode but not running.
+         * @see #ALWAYS_ON_VPN_LOCKDOWN
+         *
+         * @hide
+         */
+        public static final String ALWAYS_ON_VPN_LOCKDOWN_WHITELIST =
+                "always_on_vpn_lockdown_whitelist";
+
+        /**
          * Whether applications can be installed for this user via the system's
          * {@link Intent#ACTION_INSTALL_PACKAGE} mechanism.
          *
@@ -8786,6 +8827,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 +11764,7 @@
          * entity_list_default                      (String[])
          * entity_list_not_editable                 (String[])
          * entity_list_editable                     (String[])
+         * lang_id_threshold_override               (float)
          * </pre>
          *
          * <p>
@@ -11766,6 +11809,44 @@
         public static final String SYNC_MANAGER_CONSTANTS = "sync_manager_constants";
 
         /**
+         * Broadcast dispatch tuning parameters specific to foreground broadcasts.
+         *
+         * This is encoded as a key=value list, separated by commas. Ex: "foo=1,bar=true"
+         *
+         * The following keys are supported:
+         * <pre>
+         * bcast_timeout                (long)
+         * bcast_slow_time              (long)
+         * bcast_deferral               (long)
+         * bcast_deferral_decay_factor  (float)
+         * bcast_deferral_floor         (long)
+         * </pre>
+         *
+         * @hide
+         */
+        public static final String BROADCAST_FG_CONSTANTS = "bcast_fg_constants";
+
+        /**
+         * Broadcast dispatch tuning parameters specific to background broadcasts.
+         *
+         * This is encoded as a key=value list, separated by commas. Ex: "foo=1,bar=true".
+         * See {@link #BROADCAST_FG_CONSTANTS} for the list of supported keys.
+         *
+         * @hide
+         */
+        public static final String BROADCAST_BG_CONSTANTS = "bcast_bg_constants";
+
+        /**
+         * Broadcast dispatch tuning parameters specific to specific "offline" broadcasts.
+         *
+         * This is encoded as a key=value list, separated by commas. Ex: "foo=1,bar=true".
+         * See {@link #BROADCAST_FG_CONSTANTS} for the list of supported keys.
+         *
+         * @hide
+         */
+        public static final String BROADCAST_OFFLOAD_CONSTANTS = "bcast_offload_constants";
+
+        /**
          * Whether or not App Standby feature is enabled by system. This controls throttling of apps
          * based on usage patterns and predictions. Platform will turn on this feature if both this
          * flag and {@link #ADAPTIVE_BATTERY_MANAGEMENT_ENABLED} is on.
@@ -12160,33 +12241,33 @@
                 "angle_whitelist";
 
         /**
-         * Game Update Package global preference for all Apps.
+         * Game Driver global preference for all Apps.
          * 0 = Default
-         * 1 = All Apps use Game Update Package
+         * 1 = All Apps use Game Driver
          * 2 = All Apps use system graphics driver
          * @hide
          */
-        public static final String GUP_DEV_ALL_APPS = "gup_dev_all_apps";
+        public static final String GAME_DRIVER_ALL_APPS = "game_driver_all_apps";
 
         /**
-         * List of Apps selected to use Game Update Package.
+         * List of Apps selected to use Game Driver.
          * i.e. <pkg1>,<pkg2>,...,<pkgN>
          * @hide
          */
-        public static final String GUP_DEV_OPT_IN_APPS = "gup_dev_opt_in_apps";
+        public static final String GAME_DRIVER_OPT_IN_APPS = "game_driver_opt_in_apps";
 
         /**
-         * List of Apps selected not to use Game Update Package.
+         * List of Apps selected not to use Game Driver.
          * i.e. <pkg1>,<pkg2>,...,<pkgN>
          * @hide
          */
-        public static final String GUP_DEV_OPT_OUT_APPS = "gup_dev_opt_out_apps";
+        public static final String GAME_DRIVER_OPT_OUT_APPS = "game_driver_opt_out_apps";
 
         /**
-         * Apps on the blacklist that are forbidden to use Game Update Package.
+         * Apps on the blacklist that are forbidden to use Game Driver.
          * @hide
          */
-        public static final String GUP_BLACKLIST = "gup_blacklist";
+        public static final String GAME_DRIVER_BLACKLIST = "game_driver_blacklist";
 
         /**
          * Apps on the whitelist that are allowed to use Game Driver.
@@ -14280,6 +14361,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";
     }
 
     /**
@@ -14603,6 +14695,17 @@
                 "android.settings.panel.action.INTERNET_CONNECTIVITY";
 
         /**
+         * Activity Action: Show a settings dialog containing NFC-related settings.
+         * <p>
+         * Input: Nothing.
+         * <p>
+         * Output: Nothing.
+         */
+        @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+        public static final String ACTION_NFC =
+                "android.settings.panel.action.NFC";
+
+        /**
          * Activity Action: Show a settings dialog containing all volume streams.
          * <p>
          * Input: Nothing.
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/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 3a644d4..551bb8a 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -28,6 +28,7 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 
+import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
 /**
@@ -410,7 +411,9 @@
             .clearSubtype()
             .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
             .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
-                getNotification().isGroupSummary() ? 1 : 0);
+                getNotification().isGroupSummary() ? 1 : 0)
+            .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CATEGORY,
+                    getNotification().category);
     }
 
     private String getGroupLogTag() {
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 0edcb3d..db9351b 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -40,6 +40,7 @@
     public static final String SAFETY_HUB = "settings_safety_hub";
     public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
     public static final String AOD_IMAGEWALLPAPER_ENABLED = "settings_aod_imagewallpaper_enabled";
+    public static final String GLOBAL_ACTIONS_GRID_ENABLED = "settings_global_actions_grid_enabled";
 
     private static final Map<String, String> DEFAULT_FLAGS;
     private static final Set<String> OBSERVABLE_FLAGS;
@@ -51,15 +52,16 @@
         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", "false");
-        DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "false");
-        DEFAULT_FLAGS.put("settings_wifi_sharing", "false");
+        DEFAULT_FLAGS.put("settings_wifi_dpp", "true");
+        DEFAULT_FLAGS.put("settings_wifi_mac_randomization", "true");
+        DEFAULT_FLAGS.put("settings_wifi_sharing", "true");
         DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
         DEFAULT_FLAGS.put(SAFETY_HUB, "false");
         DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false");
         DEFAULT_FLAGS.put(AOD_IMAGEWALLPAPER_ENABLED, "false");
+        DEFAULT_FLAGS.put(GLOBAL_ACTIONS_GRID_ENABLED, "false");
 
         OBSERVABLE_FLAGS = new HashSet<>();
         OBSERVABLE_FLAGS.add(AOD_IMAGEWALLPAPER_ENABLED);
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/services/net/java/android/net/dhcp/DhcpClient.java b/core/java/android/view/IDisplayFoldListener.aidl
similarity index 62%
rename from services/net/java/android/net/dhcp/DhcpClient.java
rename to core/java/android/view/IDisplayFoldListener.aidl
index cddb91f..2c91149 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/core/java/android/view/IDisplayFoldListener.aidl
@@ -14,16 +14,13 @@
  * limitations under the License.
  */
 
-package android.net.dhcp;
+package android.view;
 
 /**
- * TODO: remove this class after migrating clients.
+ * {@hide}
  */
-public class DhcpClient {
-    public static final int CMD_PRE_DHCP_ACTION = 1003;
-    public static final int CMD_POST_DHCP_ACTION = 1004;
-    public static final int CMD_PRE_DHCP_ACTION_COMPLETE = 1006;
-
-    public static final int DHCP_SUCCESS = 1;
-    public static final int DHCP_FAILURE = 2;
+oneway interface IDisplayFoldListener
+{
+    /** Called when the foldedness of a display changes */
+    void onDisplayFoldChanged(int displayId, boolean folded);
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 42ac880..8ae4757 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -34,6 +34,7 @@
 import android.view.IApplicationToken;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IDockedStackListener;
+import android.view.IDisplayFoldListener;
 import android.view.IOnKeyguardExitResult;
 import android.view.IPinnedStackListener;
 import android.view.RemoteAnimationAdapter;
@@ -403,6 +404,16 @@
     Region getCurrentImeTouchRegion();
 
     /**
+     * Registers an IDisplayFoldListener.
+     */
+    void registerDisplayFoldListener(IDisplayFoldListener listener);
+
+    /**
+     * Unregisters an IDisplayFoldListener.
+     */
+    void unregisterDisplayFoldListener(IDisplayFoldListener listener);
+
+    /**
      * Starts a window trace.
      */
     void startWindowTrace();
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
new file mode 100644
index 0000000..7026d2b
--- /dev/null
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -0,0 +1,157 @@
+/*
+ * 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.view;
+
+import static android.view.InsetsState.TYPE_IME;
+
+import android.os.Parcel;
+import android.text.TextUtils;
+import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets.Type;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+import java.util.function.Supplier;
+
+/**
+ * Controls the visibility and animations of IME window insets source.
+ * @hide
+ */
+public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
+    private EditorInfo mFocusedEditor;
+    private EditorInfo mPreRenderedEditor;
+    /**
+     * Determines if IME would be shown next time IME is pre-rendered for currently focused
+     * editor {@link #mFocusedEditor} if {@link #isServedEditorRendered} is {@code true}.
+     */
+    private boolean mShowOnNextImeRender;
+    private boolean mHasWindowFocus;
+
+    public ImeInsetsSourceConsumer(
+            InsetsState state, Supplier<Transaction> transactionSupplier,
+            InsetsController controller) {
+        super(TYPE_IME, state, transactionSupplier, controller);
+    }
+
+    public void onPreRendered(EditorInfo info) {
+        mPreRenderedEditor = info;
+        if (mShowOnNextImeRender) {
+            mShowOnNextImeRender = false;
+            if (isServedEditorRendered()) {
+                applyImeVisibility(true /* setVisible */);
+            }
+        }
+    }
+
+    public void onServedEditorChanged(EditorInfo info) {
+        if (isDummyOrEmptyEditor(info)) {
+            mShowOnNextImeRender = false;
+        }
+        mFocusedEditor = info;
+    }
+
+    public void applyImeVisibility(boolean setVisible) {
+        if (!mHasWindowFocus) {
+            // App window doesn't have focus, any visibility changes would be no-op.
+            return;
+        }
+
+        if (setVisible) {
+            mController.show(Type.IME);
+        } else {
+            mController.hide(Type.IME);
+        }
+    }
+
+    @Override
+    public void onWindowFocusGained() {
+        mHasWindowFocus = true;
+        getImm().registerImeConsumer(this);
+    }
+
+    @Override
+    public void onWindowFocusLost() {
+        mHasWindowFocus = false;
+    }
+
+    private boolean isDummyOrEmptyEditor(EditorInfo info) {
+        // TODO(b/123044812): Handle dummy input gracefully in IME Insets API
+        return info == null || (info.fieldId <= 0 && info.inputType <= 0);
+    }
+
+    private boolean isServedEditorRendered() {
+        if (mFocusedEditor == null || mPreRenderedEditor == null
+                || isDummyOrEmptyEditor(mFocusedEditor)
+                || isDummyOrEmptyEditor(mPreRenderedEditor)) {
+            // No view is focused or ready.
+            return false;
+        }
+        return areEditorsSimilar(mFocusedEditor, mPreRenderedEditor);
+    }
+
+    @VisibleForTesting
+    public static boolean areEditorsSimilar(EditorInfo info1, EditorInfo info2) {
+        // We don't need to compare EditorInfo.fieldId (View#id) since that shouldn't change
+        // IME views.
+        boolean areOptionsSimilar =
+                info1.imeOptions == info2.imeOptions
+                && info1.inputType == info2.inputType
+                && TextUtils.equals(info1.packageName, info2.packageName);
+        areOptionsSimilar &= info1.privateImeOptions != null
+                ? info1.privateImeOptions.equals(info2.privateImeOptions) : true;
+
+        if (!areOptionsSimilar) {
+            return false;
+        }
+
+        // compare bundle extras.
+        if ((info1.extras == null && info2.extras == null) || info1.extras == info2.extras) {
+            return true;
+        }
+        if ((info1.extras == null && info2.extras != null)
+                || (info1.extras == null && info2.extras != null)) {
+            return false;
+        }
+        if (info1.extras.hashCode() == info2.extras.hashCode()
+                || info1.extras.equals(info1)) {
+            return true;
+        }
+        if (info1.extras.size() != info2.extras.size()) {
+            return false;
+        }
+        if (info1.extras.toString().equals(info2.extras.toString())) {
+            return true;
+        }
+
+        // Compare bytes
+        Parcel parcel1 = Parcel.obtain();
+        info1.extras.writeToParcel(parcel1, 0);
+        parcel1.setDataPosition(0);
+        Parcel parcel2 = Parcel.obtain();
+        info2.extras.writeToParcel(parcel2, 0);
+        parcel2.setDataPosition(0);
+
+        return Arrays.equals(parcel1.createByteArray(), parcel2.createByteArray());
+    }
+
+    private InputMethodManager getImm() {
+        return mController.getViewRoot().mDisplayContext.getSystemService(InputMethodManager.class);
+    }
+}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index dd6231d..4f9ecd5 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -16,17 +16,24 @@
 
 package android.view;
 
+import static android.view.InsetsState.TYPE_IME;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TypeEvaluator;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Property;
 import android.util.SparseArray;
+import android.view.InsetsState.InternalInsetType;
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets.Type.InsetType;
-import android.view.InsetsState.InternalInsetType;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -39,6 +46,41 @@
  */
 public class InsetsController implements WindowInsetsController {
 
+    // TODO: Use animation scaling and more optimal duration.
+    private static final int ANIMATION_DURATION_MS = 400;
+    private static final int DIRECTION_NONE = 0;
+    private static final int DIRECTION_SHOW = 1;
+    private static final int DIRECTION_HIDE = 2;
+    @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
+    private @interface AnimationDirection{}
+
+    /**
+     * Translation animation evaluator.
+     */
+    private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of(
+            0,
+            (int) (startValue.top + fraction * (endValue.top - startValue.top)),
+            0,
+            (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
+
+    /**
+     * Linear animation property
+     */
+    private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> {
+        InsetsProperty() {
+            super(Insets.class, "Insets");
+        }
+
+        @Override
+        public Insets get(WindowInsetsAnimationController object) {
+            return object.getCurrentInsets();
+        }
+        @Override
+        public void set(WindowInsetsAnimationController object, Insets value) {
+            object.changeInsets(value);
+        }
+    }
+
     private final String TAG = "InsetsControllerImpl";
 
     private final InsetsState mState = new InsetsState();
@@ -58,6 +100,8 @@
 
     private final Rect mLastLegacyContentInsets = new Rect();
     private final Rect mLastLegacyStableInsets = new Rect();
+    private ObjectAnimator mAnimator;
+    private @AnimationDirection int mAnimationDirection;
 
     public InsetsController(ViewRootImpl viewRoot) {
         mViewRoot = viewRoot;
@@ -122,7 +166,10 @@
     public void onControlsChanged(InsetsSourceControl[] activeControls) {
         if (activeControls != null) {
             for (InsetsSourceControl activeControl : activeControls) {
-                mTmpControlArray.put(activeControl.getType(), activeControl);
+                if (activeControl != null) {
+                    // TODO(b/122982984): Figure out why it can be null.
+                    mTmpControlArray.put(activeControl.getType(), activeControl);
+                }
             }
         }
 
@@ -146,18 +193,40 @@
 
     @Override
     public void show(@InsetType int types) {
+        int typesReady = 0;
         final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            getSourceConsumer(internalTypes.valueAt(i)).show();
+            InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
+            if (mAnimationDirection == DIRECTION_HIDE) {
+                // Only one animator (with multiple InsetType) can run at a time.
+                // previous one should be cancelled for simplicity.
+                cancelExistingAnimation();
+            } else if (consumer.isVisible() || mAnimationDirection == DIRECTION_SHOW) {
+                // no-op: already shown or animating in.
+                // TODO: When we have more than one types: handle specific case when
+                // show animation is going on, but the current type is not becoming visible.
+                continue;
+            }
+            typesReady |= InsetsState.toPublicType(consumer.getType());
         }
+        applyAnimation(typesReady, true /* show */);
     }
 
     @Override
     public void hide(@InsetType int types) {
+        int typesReady = 0;
         final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            getSourceConsumer(internalTypes.valueAt(i)).hide();
+            InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
+            if (mAnimationDirection == DIRECTION_SHOW) {
+                cancelExistingAnimation();
+            } else if (!consumer.isVisible() || mAnimationDirection == DIRECTION_HIDE) {
+                // no-op: already hidden or animating out.
+                continue;
+            }
+            typesReady |= InsetsState.toPublicType(consumer.getType());
         }
+        applyAnimation(typesReady, false /* show */);
     }
 
     @Override
@@ -195,7 +264,7 @@
         if (controller != null) {
             return controller;
         }
-        controller = new InsetsSourceConsumer(type, mState, Transaction::new, this);
+        controller = createConsumerOfType(type);
         mSourceConsumers.put(type, controller);
         return controller;
     }
@@ -207,6 +276,32 @@
     }
 
     /**
+     * Called when current window gains focus.
+     */
+    public void onWindowFocusGained() {
+        getSourceConsumer(TYPE_IME).onWindowFocusGained();
+    }
+
+    /**
+     * Called when current window loses focus.
+     */
+    public void onWindowFocusLost() {
+        getSourceConsumer(TYPE_IME).onWindowFocusLost();
+    }
+
+    ViewRootImpl getViewRoot() {
+        return mViewRoot;
+    }
+
+    private InsetsSourceConsumer createConsumerOfType(int type) {
+        if (type == TYPE_IME) {
+            return new ImeInsetsSourceConsumer(mState, Transaction::new, this);
+        } else {
+            return new InsetsSourceConsumer(type, mState, Transaction::new, this);
+        }
+    }
+
+    /**
      * Sends the local visibility state back to window manager.
      */
     private void sendStateToWindowManager() {
@@ -226,6 +321,79 @@
         }
     }
 
+    private void applyAnimation(@InsetType final int types, boolean show) {
+        if (types == 0) {
+            // nothing to animate.
+            return;
+        }
+        WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {
+            @Override
+            public void onReady(WindowInsetsAnimationController controller, int types) {
+                mAnimator = ObjectAnimator.ofObject(
+                        controller,
+                        new InsetsProperty(),
+                        sEvaluator,
+                        show ? controller.getHiddenStateInsets() : controller.getShownStateInsets(),
+                        show ? controller.getShownStateInsets() : controller.getHiddenStateInsets()
+                );
+                mAnimator.setDuration(ANIMATION_DURATION_MS);
+                mAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                        onAnimationFinish();
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        onAnimationFinish();
+                    }
+                });
+                mAnimator.start();
+            }
+
+            @Override
+            public void onCancelled() {}
+
+            private void onAnimationFinish() {
+                mAnimationDirection = DIRECTION_NONE;
+                if (show) {
+                    showOnAnimationEnd(types);
+                } else {
+                    hideOnAnimationEnd(types);
+                }
+            }
+        };
+        // TODO: Instead of clearing this here, properly wire up
+        // InsetsAnimationControlImpl.finish() to remove this from mAnimationControls.
+        mAnimationControls.clear();
+        controlWindowInsetsAnimation(types, listener);
+    }
+
+    private void hideOnAnimationEnd(@InsetType int types) {
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            getSourceConsumer(internalTypes.valueAt(i)).hide();
+        }
+    }
+
+    private void showOnAnimationEnd(@InsetType int types) {
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            getSourceConsumer(internalTypes.valueAt(i)).show();
+        }
+    }
+
+    /**
+     * Cancel on-going animation to show/hide {@link InsetType}.
+     */
+    @VisibleForTesting
+    public void cancelExistingAnimation() {
+        mAnimationDirection = DIRECTION_NONE;
+        if (mAnimator != null) {
+            mAnimator.cancel();
+        }
+    }
+
     void dump(String prefix, PrintWriter pw) {
         pw.println(prefix); pw.println("InsetsController:");
         mState.dump(prefix + "  ", pw);
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 145b097..f48318c 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -17,8 +17,8 @@
 package android.view;
 
 import android.annotation.Nullable;
-import android.view.SurfaceControl.Transaction;
 import android.view.InsetsState.InternalInsetType;
+import android.view.SurfaceControl.Transaction;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -30,12 +30,12 @@
  */
 public class InsetsSourceConsumer {
 
+    protected final InsetsController mController;
+    protected boolean mVisible;
     private final Supplier<Transaction> mTransactionSupplier;
     private final @InternalInsetType int mType;
     private final InsetsState mState;
-    private final InsetsController mController;
     private @Nullable InsetsSourceControl mSourceControl;
-    private boolean mVisible;
 
     public InsetsSourceConsumer(@InternalInsetType int type, InsetsState state,
             Supplier<Transaction> transactionSupplier, InsetsController controller) {
@@ -43,7 +43,7 @@
         mState = state;
         mTransactionSupplier = transactionSupplier;
         mController = controller;
-        mVisible = InsetsState.getDefaultVisibly(type);
+        mVisible = InsetsState.getDefaultVisibility(type);
     }
 
     public void setControl(@Nullable InsetsSourceControl control) {
@@ -76,6 +76,16 @@
         setVisible(false);
     }
 
+    /**
+     * Called when current window gains focus
+     */
+    public void onWindowFocusGained() {}
+
+    /**
+     * Called when current window loses focus.
+     */
+    public void onWindowFocusLost() {}
+
     boolean applyLocalVisibilityOverride() {
 
         // If we don't have control, we are not able to change the visibility.
@@ -89,6 +99,11 @@
         return true;
     }
 
+    @VisibleForTesting
+    public boolean isVisible() {
+        return mVisible;
+    }
+
     private void setVisible(boolean visible) {
         if (mVisible == visible) {
             return;
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 529776e..4f809fe6 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,7 +16,10 @@
 
 package android.view;
 
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.WindowInsets.Type.SIZE;
 import static android.view.WindowInsets.Type.indexOf;
 
 import android.annotation.IntDef;
@@ -124,9 +127,10 @@
             @Nullable @InsetSide SparseIntArray typeSideMap) {
         Insets[] typeInsetsMap = new Insets[Type.SIZE];
         Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
+        boolean[] typeVisibilityMap = new boolean[SIZE];
         final Rect relativeFrame = new Rect(frame);
         final Rect relativeFrameMax = new Rect(frame);
-        if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_IME
+        if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
                 && legacyContentInsets != null && legacyStableInsets != null) {
             WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets);
             WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets);
@@ -136,22 +140,29 @@
             if (source == null) {
                 continue;
             }
+            if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
+                    && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR)) {
+                typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
+                continue;
+            }
+
             processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
-                    typeSideMap);
+                    typeSideMap, typeVisibilityMap);
 
             // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
             // target.
             if (source.getType() != TYPE_IME) {
                 processSource(source, relativeFrameMax, true /* ignoreVisibility */,
-                        typeMaxInsetsMap, null /* typeSideMap */);
+                        typeMaxInsetsMap, null /* typeSideMap */, null /* typeVisibilityMap */);
             }
         }
-        return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, isScreenRound,
+        return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
                 alwaysConsumeNavBar, cutout);
     }
 
     private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
-            Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap) {
+            Insets[] typeInsetsMap, @Nullable @InsetSide SparseIntArray typeSideMap,
+            @Nullable boolean[] typeVisibilityMap) {
         Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
 
         int index = indexOf(toPublicType(source.getType()));
@@ -162,6 +173,10 @@
             typeInsetsMap[index] = Insets.max(existing, insets);
         }
 
+        if (typeVisibilityMap != null) {
+            typeVisibilityMap[index] = source.isVisible();
+        }
+
         if (typeSideMap != null && !Insets.NONE.equals(insets)) {
             @InsetSide int insetSide = getInsetSide(insets);
             if (insetSide != INSET_SIDE_UNKNWON) {
@@ -263,7 +278,7 @@
         }
     }
 
-    public static boolean getDefaultVisibly(@InsetType int type) {
+    public static boolean getDefaultVisibility(@InsetType int type) {
         switch (type) {
             case TYPE_TOP_BAR:
             case TYPE_SIDE_BAR_1:
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/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 45e6c50..ecbec65 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -492,7 +492,7 @@
         if (mBackgroundControl == null) {
             return;
         }
-        if ((mSurfaceFlags & PixelFormat.OPAQUE) == 0) {
+        if ((mSurfaceFlags & PixelFormat.OPAQUE) != 0) {
             mBackgroundControl.show();
             mBackgroundControl.setLayer(Integer.MIN_VALUE);
         } else {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 991b385..cd3decf 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9067,35 +9067,43 @@
                     Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'appeared' on " + this + ": laid="
                             + isLaidOut() + ", visibleToUser=" + isVisibleToUser()
                             + ", visible=" + (getVisibility() == VISIBLE)
-                            + ": alreadyNotifiedAppeared="
-                            + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0));
-                }
-                return;
-            }
-            // All good: notify it...
-            final ViewStructure structure = session.newViewStructure(this);
-            onProvideContentCaptureStructure(structure, /* flags= */ 0);
-            session.notifyViewAppeared(structure);
-            // ...and set the flags
-            mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
-            mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
-        } else {
-            if ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) == 0
-                    || (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0) {
-                if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
-                    Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this
-                            + ": notifiedAppeared="
-                            + ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)
+                            + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4
+                                    & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)
                             + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4
                                     & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0));
                 }
                 return;
             }
-            // All good: notify it...
-            session.notifyViewDisappeared(getAutofillId());
-            // ...and set the flags
+            mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
+            mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
+
+            // The code below doesn't take much for a unique view, but it's called for all views
+            // the first time the view hiearchy is laid off, which could acccumulative delay the
+            // initial layout. Hence, we're postponing it to a later stage - it might still cost a
+            // lost frame (or more), but that jank cost would only happen after the 1st layout.
+            Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
+                final ViewStructure structure = session.newViewStructure(this);
+                onProvideContentCaptureStructure(structure, /* flags= */ 0);
+                session.notifyViewAppeared(structure);
+            }, /* token= */ null);
+        } else {
+            if ((mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) == 0
+                    || (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0) {
+                if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
+                    Log.v(CONTENT_CAPTURE_LOG_TAG, "Ignoring 'disappeared' on " + this + ": laid="
+                            + isLaidOut() + ", visibleToUser=" + isVisibleToUser()
+                            + ", visible=" + (getVisibility() == VISIBLE)
+                            + ": alreadyNotifiedAppeared=" + ((mPrivateFlags4
+                                    & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0)
+                            + ", alreadyNotifiedDisappeared=" + ((mPrivateFlags4
+                                    & PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED) != 0));
+                }
+                return;
+            }
             mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
             mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
+            Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT,
+                    () -> session.notifyViewDisappeared(getAutofillId()), /* token= */ null);
         }
     }
 
@@ -9163,7 +9171,7 @@
 
         ContentCaptureSession session = null;
         if (mParent instanceof View) {
-            session = ((View) mParent).getContentCaptureSession();
+            session = ((View) mParent).getContentCaptureSession(ccm);
         }
 
         return session != null ? session : ccm.getMainContentCaptureSession();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a031b70..f47eb10 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2802,6 +2802,11 @@
             hasWindowFocus = mUpcomingWindowFocus;
             inTouchMode = mUpcomingInTouchMode;
         }
+        if (hasWindowFocus) {
+            mInsetsController.onWindowFocusGained();
+        } else {
+            mInsetsController.onWindowFocusLost();
+        }
 
         if (mAdded) {
             profileRendering(hasWindowFocus);
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index e808830..c1536ae 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -66,6 +66,7 @@
 
     private final Insets[] mTypeInsetsMap;
     private final Insets[] mTypeMaxInsetsMap;
+    private final boolean[] mTypeVisibilityMap;
 
     @Nullable private Rect mTempRect;
     private final boolean mIsRound;
@@ -106,6 +107,7 @@
     public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect,
             boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
         this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
+                createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)),
                 isRound, alwaysConsumeNavBar, displayCutout);
     }
 
@@ -122,7 +124,9 @@
      * @hide
      */
     public WindowInsets(@Nullable Insets[] typeInsetsMap,
-            @Nullable Insets[] typeMaxInsetsMap, boolean isRound,
+            @Nullable Insets[] typeMaxInsetsMap,
+            boolean[] typeVisibilityMap,
+            boolean isRound,
             boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
         mSystemWindowInsetsConsumed = typeInsetsMap == null;
         mTypeInsetsMap = mSystemWindowInsetsConsumed
@@ -134,6 +138,7 @@
                 ? new Insets[SIZE]
                 : typeMaxInsetsMap.clone();
 
+        mTypeVisibilityMap = typeVisibilityMap;
         mIsRound = isRound;
         mAlwaysConsumeNavBar = alwaysConsumeNavBar;
 
@@ -148,8 +153,8 @@
      * @param src Source to copy insets from
      */
     public WindowInsets(WindowInsets src) {
-        this(src.mTypeInsetsMap, src.mTypeMaxInsetsMap, src.mIsRound, src.mAlwaysConsumeNavBar,
-                displayCutoutCopyConstructorArgument(src));
+        this(src.mTypeInsetsMap, src.mTypeMaxInsetsMap, src.mTypeVisibilityMap, src.mIsRound,
+                src.mAlwaysConsumeNavBar, displayCutoutCopyConstructorArgument(src));
     }
 
     private static DisplayCutout displayCutoutCopyConstructorArgument(WindowInsets w) {
@@ -200,7 +205,7 @@
     /** @hide */
     @UnsupportedAppUsage
     public WindowInsets(Rect systemWindowInsets) {
-        this(createCompatTypeMap(systemWindowInsets), null, false, false, null);
+        this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null);
     }
 
     /**
@@ -225,6 +230,20 @@
         typeInsetMap[indexOf(SIDE_BARS)] = Insets.of(insets.left, 0, insets.right, insets.bottom);
     }
 
+    private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetMap) {
+        boolean[] typeVisibilityMap = new boolean[SIZE];
+        if (typeInsetMap == null) {
+            return typeVisibilityMap;
+        }
+        for (int i = FIRST; i <= LAST; i = i << 1) {
+            int index = indexOf(i);
+            if (!Insets.NONE.equals(typeInsetMap[index])) {
+                typeVisibilityMap[index] = true;
+            }
+        }
+        return typeVisibilityMap;
+    }
+
     /**
      * Used to provide a safe copy of the system window insets to pass through
      * to the existing fitSystemWindows method and other similar internals.
@@ -297,6 +316,27 @@
     }
 
     /**
+     * Returns whether a set of windows that may cause insets is currently visible on screen,
+     * regardless of whether it actually overlaps with this window.
+     *
+     * @param typeMask Bit mask of {@link InsetType}s to query visibility status.
+     * @return {@code true} if and only if all windows included in {@code typeMask} are currently
+     *         visible on screen.
+     * @hide pending unhide
+     */
+    public boolean isVisible(@InsetType int typeMask) {
+        for (int i = FIRST; i <= LAST; i = i << 1) {
+            if ((typeMask & i) == 0) {
+                continue;
+            }
+            if (!mTypeVisibilityMap[indexOf(i)]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Returns the left system window inset in pixels.
      *
      * <p>The system window inset represents the area of a full-screen window that is
@@ -392,6 +432,7 @@
     public WindowInsets consumeDisplayCutout() {
         return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
                 mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
+                mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeNavBar,
                 null /* displayCutout */);
     }
@@ -437,6 +478,7 @@
     @NonNull
     public WindowInsets consumeSystemWindowInsets() {
         return new WindowInsets(null, mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
+                mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeNavBar,
                 displayCutoutCopyConstructorArgument(this));
     }
@@ -594,7 +636,7 @@
     @NonNull
     public WindowInsets consumeStableInsets() {
         return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, null,
-                mIsRound, mAlwaysConsumeNavBar,
+                mTypeVisibilityMap, mIsRound, mAlwaysConsumeNavBar,
                 displayCutoutCopyConstructorArgument(this));
     }
 
@@ -671,6 +713,7 @@
                 mStableInsetsConsumed
                         ? null
                         : insetInsets(mTypeMaxInsetsMap, left, top, right, bottom),
+                mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeNavBar,
                 mDisplayCutoutConsumed
                         ? null
@@ -692,14 +735,15 @@
                 && mDisplayCutoutConsumed == that.mDisplayCutoutConsumed
                 && Arrays.equals(mTypeInsetsMap, that.mTypeInsetsMap)
                 && Arrays.equals(mTypeMaxInsetsMap, that.mTypeMaxInsetsMap)
+                && Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap)
                 && Objects.equals(mDisplayCutout, that.mDisplayCutout);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
-                mIsRound, mDisplayCutout, mAlwaysConsumeNavBar, mSystemWindowInsetsConsumed,
-                mStableInsetsConsumed, mDisplayCutoutConsumed);
+                Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mAlwaysConsumeNavBar,
+                mSystemWindowInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed);
     }
 
 
@@ -754,6 +798,7 @@
 
         private final Insets[] mTypeInsetsMap;
         private final Insets[] mTypeMaxInsetsMap;
+        private final boolean[] mTypeVisibilityMap;
         private boolean mSystemInsetsConsumed = true;
         private boolean mStableInsetsConsumed = true;
 
@@ -768,6 +813,7 @@
         public Builder() {
             mTypeInsetsMap = new Insets[SIZE];
             mTypeMaxInsetsMap = new Insets[SIZE];
+            mTypeVisibilityMap = new boolean[SIZE];
         }
 
         /**
@@ -778,6 +824,7 @@
         public Builder(WindowInsets insets) {
             mTypeInsetsMap = insets.mTypeInsetsMap.clone();
             mTypeMaxInsetsMap = insets.mTypeMaxInsetsMap.clone();
+            mTypeVisibilityMap = insets.mTypeVisibilityMap.clone();
             mSystemInsetsConsumed = insets.mSystemWindowInsetsConsumed;
             mStableInsetsConsumed = insets.mStableInsetsConsumed;
             mDisplayCutout = displayCutoutCopyConstructorArgument(insets);
@@ -862,6 +909,29 @@
         }
 
         /**
+         * Sets whether windows that can cause insets are currently visible on screen.
+         *
+         *
+         * @see #isVisible(int)
+         *
+         * @param typeMask The bitmask of {@link InsetType} to set the visibility for.
+         * @param visible Whether to mark the windows as visible or not.
+         *
+         * @return itself
+         * @hide pending unhide
+         */
+        @NonNull
+        public Builder setVisible(@InsetType int typeMask, boolean visible) {
+            for (int i = FIRST; i <= LAST; i = i << 1) {
+                if ((typeMask & i) == 0) {
+                    continue;
+                }
+                mTypeVisibilityMap[indexOf(i)] = visible;
+            }
+            return this;
+        }
+
+        /**
          * Sets the stable insets in pixels.
          *
          * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
@@ -916,8 +986,8 @@
         @NonNull
         public WindowInsets build() {
             return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
-                    mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mIsRound,
-                    mAlwaysConsumeNavBar, mDisplayCutout);
+                    mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
+                    mIsRound, mAlwaysConsumeNavBar, mDisplayCutout);
         }
     }
 
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index b9017b3..d7f1b9f 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -156,8 +156,7 @@
         // Wait for system server to return the component name.
         final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
         mHandler.sendMessage(obtainMessage(
-                ContentCaptureManager::handleReceiverServiceComponentName,
-                this, mContext.getUserId(), resultReceiver));
+                ContentCaptureManager::handleGetComponentName, this, resultReceiver));
 
         try {
             return resultReceiver.getParcelableResult();
@@ -198,7 +197,7 @@
         Preconditions.checkNotNull(request);
 
         try {
-            mService.removeUserData(mContext.getUserId(), request);
+            mService.removeUserData(request);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
@@ -227,9 +226,9 @@
 
 
     /** Retrieves the component name of the target content capture service through system_server. */
-    private void handleReceiverServiceComponentName(int userId, IResultReceiver resultReceiver) {
+    private void handleGetComponentName(@NonNull IResultReceiver resultReceiver) {
         try {
-            mService.getReceiverServiceComponentName(userId, resultReceiver);
+            mService.getServiceComponentName(resultReceiver);
         } catch (RemoteException e) {
             Log.w(TAG, "Unable to retrieve service component name: " + e);
         }
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index 51aea16..56ed8bf 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -34,21 +34,21 @@
   */
 oneway interface IContentCaptureManager {
     /**
-     * Starts a new session for the provided {@code userId} running as part of the
+     * Starts a new session for the calling user running as part of the
      * app's activity identified by {@code activityToken}/{@code componentName}.
      *
      * @param sessionId Unique session id as provided by the app.
      * @param flags Meta flags that enable or disable content capture (see
      *     {@link IContentCaptureContext#flags}).
      */
-    void startSession(int userId, IBinder activityToken, in ComponentName componentName,
+    void startSession(IBinder activityToken, in ComponentName componentName,
                       String sessionId, int flags, in IResultReceiver result);
 
     /**
-     * Marks the end of a session for the provided {@code userId} identified by
+     * Marks the end of a session for the calling user identified by
      * the corresponding {@code startSession}'s {@code sessionId}.
      */
-    void finishSession(int userId, String sessionId);
+    void finishSession(String sessionId);
 
     /**
      * Returns the content capture service's component name (if enabled and
@@ -56,10 +56,10 @@
      * @param Receiver of the content capture service's @{code ComponentName}
      *     provided {@code Bundle} with key "{@code EXTRA}".
      */
-    void getReceiverServiceComponentName(int userId, in IResultReceiver result);
+    void getServiceComponentName(in IResultReceiver result);
 
     /**
-     * Requests the removal of user data for the provided {@code userId}.
+     * Requests the removal of user data for the calling user.
      */
-    void removeUserData(int userId, in UserDataRemovalRequest request);
+    void removeUserData(in UserDataRemovalRequest request);
 }
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 9e99c88..83dbf2d 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -211,8 +211,8 @@
         try {
             if (mSystemServerInterface == null) return;
 
-            mSystemServerInterface.startSession(mContext.getUserId(), mApplicationToken,
-                    componentName, mId, flags, new IResultReceiver.Stub() {
+            mSystemServerInterface.startSession(mApplicationToken, componentName, mId, flags,
+                    new IResultReceiver.Stub() {
                         @Override
                         public void send(int resultCode, Bundle resultData) {
                             IBinder binder = null;
@@ -473,7 +473,7 @@
         try {
             if (mSystemServerInterface == null) return;
 
-            mSystemServerInterface.finishSession(mContext.getUserId(), mId);
+            mSystemServerInterface.finishSession(mId);
         } catch (RemoteException e) {
             Log.e(TAG, "Error destroying system-service session " + mId + " for "
                     + getDebugState() + ": " + e);
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index d09323d..112653a 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -219,7 +219,7 @@
     @MainThread
     default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
             @NonNull EditorInfo editorInfo, boolean restarting,
-            @NonNull IBinder startInputToken) {
+            @NonNull IBinder startInputToken, boolean shouldPreRenderIme) {
         if (restarting) {
             restartInput(inputConnection, editorInfo);
         } else {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0cb1800..7fee3ef 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -55,6 +55,7 @@
 import android.util.Printer;
 import android.util.SparseArray;
 import android.view.Display;
+import android.view.ImeInsetsSourceConsumer;
 import android.view.InputChannel;
 import android.view.InputEvent;
 import android.view.InputEventSender;
@@ -441,6 +442,13 @@
      */
     private int mRequestUpdateCursorAnchorInfoMonitorMode = REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE;
 
+    /**
+     * When {@link ViewRootImpl#sNewInsetsMode} is set to
+     * >= {@link ViewRootImpl#NEW_INSETS_MODE_IME}, {@link ImeInsetsSourceConsumer} applies the
+     * IME visibility and listens for other state changes.
+     */
+    private ImeInsetsSourceConsumer mImeInsetsConsumer;
+
     final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20);
     final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
 
@@ -454,6 +462,8 @@
     static final int MSG_TIMEOUT_INPUT_EVENT = 6;
     static final int MSG_FLUSH_INPUT_EVENT = 7;
     static final int MSG_REPORT_FULLSCREEN_MODE = 10;
+    static final int MSG_REPORT_PRE_RENDERED = 15;
+    static final int MSG_APPLY_IME_VISIBILITY = 20;
 
     private static boolean isAutofillUIShowing(View servedView) {
         AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
@@ -650,6 +660,23 @@
                     }
                     return;
                 }
+                case MSG_REPORT_PRE_RENDERED: {
+                    synchronized (mH) {
+                        if (mImeInsetsConsumer != null) {
+                            mImeInsetsConsumer.onPreRendered((EditorInfo) msg.obj);
+                        }
+                    }
+                    return;
+
+                }
+                case MSG_APPLY_IME_VISIBILITY: {
+                    synchronized (mH) {
+                        if (mImeInsetsConsumer != null) {
+                            mImeInsetsConsumer.applyImeVisibility(msg.arg1 != 0);
+                        }
+                    }
+                    return;
+                }
             }
         }
     }
@@ -729,6 +756,18 @@
                     .sendToTarget();
         }
 
+        @Override
+        public void reportPreRendered(EditorInfo info) {
+            mH.obtainMessage(MSG_REPORT_PRE_RENDERED, 0, 0, info)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void applyImeVisibility(boolean setVisible) {
+            mH.obtainMessage(MSG_APPLY_IME_VISIBILITY, setVisible ? 1 : 0, 0)
+                    .sendToTarget();
+        }
+
     };
 
     final InputConnection mDummyInputConnection = new BaseInputConnection(this, false);
@@ -1515,6 +1554,7 @@
 
             // Hook 'em up and let 'er rip.
             mCurrentTextBoxAttribute = tba;
+            maybeCallServedViewChangedLocked(tba);
             mServedConnecting = false;
             if (mServedInputConnectionWrapper != null) {
                 mServedInputConnectionWrapper.deactivate();
@@ -1730,6 +1770,10 @@
             mCurrentTextBoxAttribute = null;
             mCompletions = null;
             mServedConnecting = true;
+            // servedView has changed and it's not editable.
+            if (!mServedView.onCheckIsTextEditor()) {
+                maybeCallServedViewChangedLocked(null);
+            }
         }
 
         if (ic != null) {
@@ -1828,6 +1872,21 @@
     }
 
     /**
+     * Register for IME state callbacks and applying visibility in
+     * {@link android.view.ImeInsetsSourceConsumer}.
+     * @hide
+     */
+    public void registerImeConsumer(@NonNull ImeInsetsSourceConsumer imeInsetsConsumer) {
+        if (imeInsetsConsumer == null) {
+            throw new IllegalStateException("ImeInsetsSourceConsumer cannot be null.");
+        }
+
+        synchronized (mH) {
+            mImeInsetsConsumer = imeInsetsConsumer;
+        }
+    }
+
+    /**
      * Report the current selection range.
      *
      * <p><strong>Editor authors</strong>, you need to call this method whenever
@@ -2705,6 +2764,12 @@
         }
     }
 
+    private void maybeCallServedViewChangedLocked(EditorInfo tba) {
+        if (mImeInsetsConsumer != null) {
+            mImeInsetsConsumer.onServedEditorChanged(tba);
+        }
+    }
+
     void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
         final Printer p = new PrintWriterPrinter(fout);
         p.println("Input method client state for " + this + ":");
diff --git a/core/java/android/view/inputmethod/InputMethodSystemProperty.java b/core/java/android/view/inputmethod/InputMethodSystemProperty.java
index 57ed7f9..7c79d44 100644
--- a/core/java/android/view/inputmethod/InputMethodSystemProperty.java
+++ b/core/java/android/view/inputmethod/InputMethodSystemProperty.java
@@ -93,8 +93,14 @@
      * {@code true} when per-profile IME is enabled.
      * @hide
      */
-    public static final boolean PER_PROFILE_IME_ENABLED = MULTI_CLIENT_IME_ENABLED
-            || Build.IS_DEBUGGABLE && SystemProperties.getBoolean(
-                    PROP_DEBUG_PER_PROFILE_IME, false);
-
+    public static final boolean PER_PROFILE_IME_ENABLED;
+    static {
+        if (MULTI_CLIENT_IME_ENABLED) {
+            PER_PROFILE_IME_ENABLED = true;
+        } else if (Build.IS_DEBUGGABLE) {
+            PER_PROFILE_IME_ENABLED = SystemProperties.getBoolean(PROP_DEBUG_PER_PROFILE_IME, true);
+        } else {
+            PER_PROFILE_IME_ENABLED = true;
+        }
+    }
 }
diff --git a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
index fdc34b3..4d917a1 100644
--- a/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
+++ b/core/java/android/view/textclassifier/ActionsSuggestionsHelper.java
@@ -115,7 +115,7 @@
         private int mNextUserId = FIRST_NON_LOCAL_USER;
 
         private int encode(Person person) {
-            if (ConversationActions.Message.PERSON_USER_LOCAL.equals(person)) {
+            if (ConversationActions.Message.PERSON_USER_SELF.equals(person)) {
                 return USER_LOCAL;
             }
             Integer result = mMapping.get(person);
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index f7c1a26..502181f 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -109,9 +109,9 @@
          *
          * @see Builder#Builder(Person)
          */
-        public static final Person PERSON_USER_LOCAL =
+        public static final Person PERSON_USER_SELF =
                 new Person.Builder()
-                        .setKey("text-classifier-conversation-actions-local-user")
+                        .setKey("text-classifier-conversation-actions-user-self")
                         .build();
 
         /**
@@ -123,9 +123,9 @@
          *
          * @see Builder#Builder(Person)
          */
-        public static final Person PERSON_USER_REMOTE =
+        public static final Person PERSON_USER_OTHERS =
                 new Person.Builder()
-                        .setKey("text-classifier-conversation-actions-remote-user")
+                        .setKey("text-classifier-conversation-actions-user-others")
                         .build();
 
         @Nullable
@@ -235,10 +235,10 @@
             /**
              * Constructs a builder.
              *
-             * @param author the person that composed the message, use {@link #PERSON_USER_LOCAL}
+             * @param author the person that composed the message, use {@link #PERSON_USER_SELF}
              *               to represent the local user. If it is not possible to identify the
              *               remote user that the local user is conversing with, use
-             *               {@link #PERSON_USER_REMOTE} to represent a remote user.
+             *               {@link #PERSON_USER_OTHERS} to represent a remote user.
              */
             public Builder(@NonNull Person author) {
                 mAuthor = Preconditions.checkNotNull(author);
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 30137e38..299801a 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -18,6 +18,12 @@
 
 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.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -98,6 +104,21 @@
 
     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";
+    public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_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 +330,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 +389,10 @@
         }
         unbindRemainingServices();
         mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
+        if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+            mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
+            mAppPredictor.destroy();
+        }
     }
 
     @Override
@@ -513,6 +567,7 @@
 
     void queryTargetServices(ChooserListAdapter adapter) {
         final PackageManager pm = getPackageManager();
+        ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class);
         int targetsToQuery = 0;
         for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
             final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
@@ -522,6 +577,11 @@
                 continue;
             }
             final ActivityInfo ai = dri.getResolveInfo().activityInfo;
+            if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
+                    && sm.hasShareTargets(ai.packageName)) {
+                // Share targets will be queried from ShortcutManager
+                continue;
+            }
             final Bundle md = ai.metaData;
             final String serviceName = md != null ? convertServiceName(ai.packageName,
                     md.getString(ChooserTargetService.META_DATA_NAME)) : null;
@@ -600,15 +660,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);
@@ -628,42 +683,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();
@@ -720,6 +792,9 @@
 
     void updateModelAndChooserCounts(TargetInfo info) {
         if (info != null) {
+            if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+                sendClickToAppPredictor(info);
+            }
             final ResolveInfo ri = info.getResolveInfo();
             Intent targetIntent = getTargetIntent();
             if (ri != null && ri.activityInfo != null && targetIntent != null) {
@@ -732,13 +807,39 @@
                     Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
                     Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
                 }
-            } else if(DEBUG) {
+            } else if (DEBUG) {
                 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
             }
         }
         mIsSuccessfullySelected = true;
     }
 
+    private void sendClickToAppPredictor(TargetInfo targetInfo) {
+        if (!(targetInfo instanceof ChooserTargetInfo)) {
+            return;
+        }
+        ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
+        ComponentName componentName = chooserTarget.getComponentName();
+        Bundle extras = chooserTarget.getIntentExtras();
+        if (extras == null) {
+            return;
+        }
+        String shortcutId = extras.getString(Intent.EXTRA_SHORTCUT_ID);
+        if (shortcutId == null) {
+            return;
+        }
+        mAppPredictor.notifyAppTargetEvent(
+                new AppTargetEvent.Builder(
+                    new AppTarget(
+                        new AppTargetId(shortcutId),
+                        componentName.getPackageName(),
+                        componentName.getClassName(),
+                        getUser()),
+                    AppTargetEvent.ACTION_LAUNCH
+                ).setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
+                .build());
+    }
+
     void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
         if (mRefinementResultReceiver != null) {
             mRefinementResultReceiver.destroy();
@@ -1058,6 +1159,10 @@
             return mBadgeContentDescription;
         }
 
+        public ChooserTarget getChooserTarget() {
+            return mChooserTarget;
+        }
+
         @Override
         public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
             return new ChooserTargetInfo(this, fillInIntent, flags);
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/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index d0272e0..e27ff00 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -17,6 +17,7 @@
 package com.android.internal.inputmethod;
 
 import android.net.Uri;
+import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.inputmethod.IInputContentUriToken;
@@ -39,4 +40,6 @@
     boolean switchToNextInputMethod(boolean onlyCurrentIme);
     boolean shouldOfferSwitchingToNextInputMethod();
     void notifyUserAction();
+    void reportPreRendered(in EditorInfo info);
+    void applyImeVisibility(boolean setVisible);
 }
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 7600dc9..d42c607 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -23,6 +23,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.GuardedBy;
@@ -100,6 +101,7 @@
      * @param backDisposition disposition flags
      * @see android.inputmethodservice.InputMethodService#IME_ACTIVE
      * @see android.inputmethodservice.InputMethodService#IME_VISIBLE
+     * @see android.inputmethodservice.InputMethodService#IME_INVISIBLE
      * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT
      * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_ADJUST_NOTHING
      */
@@ -346,4 +348,40 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Calls {@link IInputMethodPrivilegedOperations#reportPreRendered(info)}.
+     *
+     * @param info {@link EditorInfo} of the currently rendered {@link TextView}.
+     */
+    @AnyThread
+    public void reportPreRendered(EditorInfo info) {
+        final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
+        if (ops == null) {
+            return;
+        }
+        try {
+            ops.reportPreRendered(info);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibility(boolean)}.
+     *
+     * @param setVisible {@code true} to set IME visible, else hidden.
+     */
+    @AnyThread
+    public void applyImeVisibility(boolean setVisible) {
+        final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
+        if (ops == null) {
+            return;
+        }
+        try {
+            ops.applyImeVisibility(setVisible);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
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/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index ffbe8eb..9ba56b8 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -27,6 +27,7 @@
 import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
 
 import android.content.pm.ApplicationInfo;
+import android.metrics.LogMaker;
 import android.net.Credentials;
 import android.net.LocalSocket;
 import android.os.Parcel;
@@ -37,6 +38,9 @@
 import android.system.StructPollfd;
 import android.util.Log;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 import dalvik.system.VMRuntime;
 
 import libcore.io.IoUtils;
@@ -330,9 +334,43 @@
         }
     }
 
-    private void handleHiddenApiAccessLogSampleRate(int percent) {
+    private class HiddenApiUsageLogger implements VMRuntime.HiddenApiUsageLogger {
+
+        private final MetricsLogger mMetricsLogger = new MetricsLogger();
+
+        public void hiddenApiUsed(String packageName, String signature,
+                int accessMethod, boolean accessDenied) {
+            int accessMethodMetric = HiddenApiUsageLogger.ACCESS_METHOD_NONE;
+            switch(accessMethod) {
+                case HiddenApiUsageLogger.ACCESS_METHOD_NONE:
+                    accessMethodMetric = MetricsEvent.ACCESS_METHOD_NONE;
+                    break;
+                case HiddenApiUsageLogger.ACCESS_METHOD_REFLECTION:
+                    accessMethodMetric = MetricsEvent.ACCESS_METHOD_REFLECTION;
+                    break;
+                case HiddenApiUsageLogger.ACCESS_METHOD_JNI:
+                    accessMethodMetric = MetricsEvent.ACCESS_METHOD_JNI;
+                    break;
+                case HiddenApiUsageLogger.ACCESS_METHOD_LINKING:
+                    accessMethodMetric = MetricsEvent.ACCESS_METHOD_LINKING;
+                    break;
+            }
+            LogMaker logMaker = new LogMaker(MetricsEvent.ACTION_HIDDEN_API_ACCESSED)
+                    .setPackageName(packageName)
+                    .addTaggedData(MetricsEvent.FIELD_HIDDEN_API_SIGNATURE, signature)
+                    .addTaggedData(MetricsEvent.FIELD_HIDDEN_API_ACCESS_METHOD,
+                        accessMethodMetric);
+            if (accessDenied) {
+                logMaker.addTaggedData(MetricsEvent.FIELD_HIDDEN_API_ACCESS_DENIED, 1);
+            }
+            mMetricsLogger.write(logMaker);
+        }
+    }
+
+    private void handleHiddenApiAccessLogSampleRate(int samplingRate) {
         try {
-            ZygoteInit.setHiddenApiAccessLogSampleRate(percent);
+            ZygoteInit.setHiddenApiAccessLogSampleRate(samplingRate);
+            ZygoteInit.setHiddenApiUsageLogger(new HiddenApiUsageLogger());
             mSocketOutStream.writeInt(0);
         } catch (IOException ioe) {
             throw new IllegalStateException("Error writing to command socket", ioe);
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index e3e55ed..9f23797 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -533,6 +533,14 @@
     }
 
     /**
+     * Sets the implementation to be used for logging hidden API accesses
+     * @param logger the implementation of the VMRuntime.HiddenApiUsageLogger interface
+     */
+    public static void setHiddenApiUsageLogger(VMRuntime.HiddenApiUsageLogger logger) {
+        VMRuntime.getRuntime().setHiddenApiUsageLogger(logger);
+    }
+
+    /**
      * Creates a PathClassLoader for the given class path that is associated with a shared
      * namespace, i.e., this classloader can access platform-private native libraries. The
      * classloader will use java.library.path as the native library path.
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/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 97d5a65..2ee902a 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -40,7 +40,7 @@
     void unbindInput();
 
     void startInput(in IBinder startInputToken, in IInputContext inputContext, int missingMethods,
-            in EditorInfo attribute, boolean restarting);
+            in EditorInfo attribute, boolean restarting, boolean preRenderImeViews);
 
     void createSession(in InputChannel channel, IInputSessionCallback callback);
 
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index 17b2bc4..2cfdaaa 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -16,6 +16,8 @@
 
 package com.android.internal.view;
 
+import android.view.inputmethod.EditorInfo;
+
 import com.android.internal.view.InputBindResult;
 
 /**
@@ -27,4 +29,6 @@
     void onUnbindMethod(int sequence, int unbindReason);
     void setActive(boolean active, boolean fullscreen);
     void reportFullscreenMode(boolean fullscreen);
+    void reportPreRendered(in EditorInfo info);
+    void applyImeVisibility(boolean setVisible);
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index be12700..9afd5d0 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -166,6 +166,7 @@
         "android_media_AudioRecord.cpp",
         "android_media_AudioSystem.cpp",
         "android_media_AudioTrack.cpp",
+        "android_media_AudioAttributes.cpp",
         "android_media_DeviceCallback.cpp",
         "android_media_JetPlayer.cpp",
         "android_media_MediaMetricsJNI.cpp",
@@ -275,6 +276,7 @@
         "libselinux",
         "libandroidicu",
         "libmedia",
+        "libmedia_helper",
         "libmediametrics",
         "libmeminfo",
         "libaudioclient",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f458299..c90071a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -107,6 +107,7 @@
 extern int register_android_media_AudioRecord(JNIEnv *env);
 extern int register_android_media_AudioSystem(JNIEnv *env);
 extern int register_android_media_AudioTrack(JNIEnv *env);
+extern int register_android_media_AudioAttributes(JNIEnv *env);
 extern int register_android_media_MicrophoneInfo(JNIEnv *env);
 extern int register_android_media_JetPlayer(JNIEnv *env);
 extern int register_android_media_ToneGenerator(JNIEnv *env);
@@ -1470,6 +1471,7 @@
     REG_JNI(register_android_media_AudioSystem),
     REG_JNI(register_android_media_AudioRecord),
     REG_JNI(register_android_media_AudioTrack),
+    REG_JNI(register_android_media_AudioAttributes),
     REG_JNI(register_android_media_JetPlayer),
     REG_JNI(register_android_media_MicrophoneInfo),
     REG_JNI(register_android_media_RemoteDisplay),
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 5de0883..c74797b 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -93,6 +93,11 @@
         mBitmap->setAlphaType(alphaType);
     }
 
+    void setColorSpace(sk_sp<SkColorSpace> colorSpace) {
+        assertValid();
+        mBitmap->setColorSpace(colorSpace);
+    }
+
     const SkImageInfo& info() {
         if (mBitmap) {
             return mBitmap->info();
@@ -959,6 +964,12 @@
     return JNI_TRUE;
 }
 
+static void Bitmap_setColorSpace(JNIEnv* env, jobject, jlong bitmapHandle, jlong colorSpacePtr) {
+    LocalScopedBitmap bitmapHolder(bitmapHandle);
+    sk_sp<SkColorSpace> cs = GraphicsJNI::getNativeColorSpace(colorSpacePtr);
+    bitmapHolder->setColorSpace(cs);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static jint Bitmap_getPixel(JNIEnv* env, jobject, jlong bitmapHandle,
@@ -1239,6 +1250,7 @@
     {   "nativeCreateGraphicBufferHandle", "(J)Landroid/graphics/GraphicBuffer;",
         (void*) Bitmap_createGraphicBufferHandle },
     {   "nativeGetColorSpace",      "(J[F[F)Z", (void*)Bitmap_getColorSpace },
+    {   "nativeSetColorSpace",      "(JJ)V", (void*)Bitmap_setColorSpace },
     {   "nativeIsSRGB",             "(J)Z", (void*)Bitmap_isSRGB },
     {   "nativeIsSRGBLinear",       "(J)Z", (void*)Bitmap_isSRGBLinear},
     {   "nativeCopyColorSpace",     "(JJ)V",
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 7d0d3d8..3f9ec45 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -549,7 +549,7 @@
     // if we only close the file descriptor and not the file object it is used to
     // create.  If we don't explicitly clean up the file (which in turn closes the
     // descriptor) the buffers allocated internally by fseek will be leaked.
-    int dupDescriptor = dup(descriptor);
+    int dupDescriptor = fcntl(descriptor, F_DUPFD_CLOEXEC, 0);
 
     FILE* file = fdopen(dupDescriptor, "r");
     if (file == NULL) {
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index 5f12665..72e14e7 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -132,7 +132,7 @@
                                "broken file descriptor; fstat returned -1", nullptr, source);
     }
 
-    int dupDescriptor = dup(descriptor);
+    int dupDescriptor = fcntl(descriptor, F_DUPFD_CLOEXEC, 0);
     FILE* file = fdopen(dupDescriptor, "r");
     if (file == NULL) {
         close(dupDescriptor);
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 7679c5b..85f1159 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -28,6 +28,8 @@
 
 #include "SkBlurDrawLooper.h"
 #include "SkColorFilter.h"
+#include "SkFont.h"
+#include "SkFontMetrics.h"
 #include "SkFontTypes.h"
 #include "SkMaskFilter.h"
 #include "SkPath.h"
@@ -69,9 +71,22 @@
 static jclass   gFontMetricsInt_class;
 static JMetricsID gFontMetricsInt_fieldID;
 
-static void defaultSettingsForAndroid(Paint* paint) {
-    // GlyphID encoding is required because we are using Harfbuzz shaping
-    paint->setTextEncoding(kGlyphID_SkTextEncoding);
+static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count,
+                           const SkPoint pos[], SkPath* dst) {
+    dst->reset();
+    struct Rec {
+        SkPath* fDst;
+        const SkPoint* fPos;
+    } rec = { dst, pos };
+    font.getPaths(glyphs, count, [](const SkPath* src, const SkMatrix& mx, void* ctx) {
+        Rec* rec = (Rec*)ctx;
+        if (src) {
+            SkMatrix tmp(mx);
+            tmp.postTranslate(rec->fPos->fX, rec->fPos->fY);
+            rec->fDst->addPath(*src, tmp);
+        }
+        rec->fPos += 1;
+    }, &rec);
 }
 
 namespace PaintGlue {
@@ -88,18 +103,7 @@
     }
 
     static jlong init(JNIEnv* env, jobject) {
-        static_assert(1 <<  0 == SkPaint::kAntiAlias_Flag,             "paint_flags_mismatch");
-        static_assert(1 <<  2 == SkPaint::kDither_Flag,                "paint_flags_mismatch");
-        static_assert(1 <<  3 == SkPaint::kUnderlineText_ReserveFlag,  "paint_flags_mismatch");
-        static_assert(1 <<  4 == SkPaint::kStrikeThruText_ReserveFlag, "paint_flags_mismatch");
-        static_assert(1 <<  5 == SkPaint::kFakeBoldText_Flag,          "paint_flags_mismatch");
-        static_assert(1 <<  6 == SkPaint::kLinearText_Flag,            "paint_flags_mismatch");
-        static_assert(1 <<  7 == SkPaint::kSubpixelText_Flag,          "paint_flags_mismatch");
-        static_assert(1 << 10 == SkPaint::kEmbeddedBitmapText_Flag,    "paint_flags_mismatch");
-
-        Paint* obj = new Paint();
-        defaultSettingsForAndroid(obj);
-        return reinterpret_cast<jlong>(obj);
+        return reinterpret_cast<jlong>(new Paint);
     }
 
     static jlong initWithPaint(JNIEnv* env, jobject clazz, jlong paintHandle) {
@@ -288,10 +292,11 @@
                 pos[i].fX = x + layout.getX(i);
                 pos[i].fY = y + layout.getY(i);
             }
+            const SkFont& font = paint->getSkFont();
             if (start == 0) {
-                paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, path);
+                getPosTextPath(font, glyphs, end, pos, path);
             } else {
-                paint->getPosTextPath(glyphs + start, (end - start) << 1, pos + start, &tmpPath);
+                getPosTextPath(font, glyphs + start, end - start, pos + start, &tmpPath);
                 path->addPath(tmpPath);
             }
         }
@@ -321,7 +326,6 @@
         x += MinikinUtils::xOffsetForTextAlign(paint, layout);
         Paint::Align align = paint->getTextAlign();
         paint->setTextAlign(Paint::kLeft_Align);
-        paint->setTextEncoding(kGlyphID_SkTextEncoding);
         GetTextFunctor f(layout, path, x, y, paint, glyphs, pos);
         MinikinUtils::forFontRun(layout, paint, f);
         paint->setTextAlign(align);
@@ -584,20 +588,21 @@
         const int kElegantDescent = -500;
         const int kElegantLeading = 0;
         Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+        SkFont* font = &paint->getSkFont();
         const Typeface* typeface = paint->getAndroidTypeface();
         typeface = Typeface::resolveDefault(typeface);
         minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
-        float saveSkewX = paint->getTextSkewX();
-        bool savefakeBold = paint->isFakeBoldText();
-        MinikinFontSkia::populateSkPaint(paint, baseFont.font->typeface().get(), baseFont.fakery);
-        SkScalar spacing = paint->getFontMetrics(metrics);
+        float saveSkewX = font->getSkewX();
+        bool savefakeBold = font->isEmbolden();
+        MinikinFontSkia::populateSkFont(font, baseFont.font->typeface().get(), baseFont.fakery);
+        SkScalar spacing = font->getMetrics(metrics);
         // The populateSkPaint call may have changed fake bold / text skew
         // because we want to measure with those effects applied, so now
         // restore the original settings.
-        paint->setTextSkewX(saveSkewX);
-        paint->setFakeBoldText(savefakeBold);
+        font->setSkewX(saveSkewX);
+        font->setEmbolden(savefakeBold);
         if (paint->getFamilyVariant() == minikin::FamilyVariant::ELEGANT) {
-            SkScalar size = paint->getTextSize();
+            SkScalar size = font->getSize();
             metrics->fTop = -size * kElegantTop / 2048;
             metrics->fBottom = -size * kElegantBottom / 2048;
             metrics->fAscent = -size * kElegantAscent / 2048;
@@ -646,9 +651,7 @@
     // ------------------ @CriticalNative ---------------------------
 
     static void reset(jlong objHandle) {
-        Paint* obj = reinterpret_cast<Paint*>(objHandle);
-        obj->reset();
-        defaultSettingsForAndroid(obj);
+        reinterpret_cast<Paint*>(objHandle)->reset();
     }
 
     static void assign(jlong dstPaintHandle, jlong srcPaintHandle) {
@@ -657,40 +660,22 @@
         *dst = *src;
     }
 
-    // Equivalent to the Java Paint's FILTER_BITMAP_FLAG.
-    static const uint32_t sFilterBitmapFlag = 0x02;
-
     static jint getFlags(jlong paintHandle) {
-        Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle);
-        uint32_t result = nativePaint->getFlags();
-        result &= ~sFilterBitmapFlag; // Filtering no longer stored in this bit. Mask away.
-        if (nativePaint->getFilterQuality() != kNone_SkFilterQuality) {
-            result |= sFilterBitmapFlag;
-        }
-        return static_cast<jint>(result);
+        uint32_t flags = reinterpret_cast<Paint*>(paintHandle)->getJavaFlags();
+        return static_cast<jint>(flags);
     }
 
     static void setFlags(jlong paintHandle, jint flags) {
-        Paint* nativePaint = reinterpret_cast<Paint*>(paintHandle);
-        // Instead of modifying 0x02, change the filter level.
-        nativePaint->setFilterQuality(flags & sFilterBitmapFlag
-                ? kLow_SkFilterQuality
-                : kNone_SkFilterQuality);
-        // Don't pass through filter flag, which is no longer stored in paint's flags.
-        flags &= ~sFilterBitmapFlag;
-        // Use the existing value for 0x02.
-        const uint32_t existing0x02Flag = nativePaint->getFlags() & sFilterBitmapFlag;
-        flags |= existing0x02Flag;
-        nativePaint->setFlags(flags);
+        reinterpret_cast<Paint*>(paintHandle)->setJavaFlags(flags);
     }
 
     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);
     }
 
@@ -699,37 +684,23 @@
     }
 
     static void setLinearText(jlong paintHandle, jboolean linearText) {
-        reinterpret_cast<Paint*>(paintHandle)->setLinearText(linearText);
+        reinterpret_cast<Paint*>(paintHandle)->getSkFont().setLinearMetrics(linearText);
     }
 
     static void setSubpixelText(jlong paintHandle, jboolean subpixelText) {
-        reinterpret_cast<Paint*>(paintHandle)->setSubpixelText(subpixelText);
+        reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSubpixel(subpixelText);
     }
 
     static void setUnderlineText(jlong paintHandle, jboolean underlineText) {
-        Paint* paint = reinterpret_cast<Paint*>(paintHandle);
-        uint32_t flags = paint->getFlags();
-        if (underlineText) {
-            flags |= Paint::kUnderlineText_ReserveFlag;
-        } else {
-            flags &= ~Paint::kUnderlineText_ReserveFlag;
-        }
-        paint->setFlags(flags);
+        reinterpret_cast<Paint*>(paintHandle)->setUnderline(underlineText);
     }
 
     static void setStrikeThruText(jlong paintHandle, jboolean strikeThruText) {
-        Paint* paint = reinterpret_cast<Paint*>(paintHandle);
-        uint32_t flags = paint->getFlags();
-        if (strikeThruText) {
-            flags |= Paint::kStrikeThruText_ReserveFlag;
-        } else {
-            flags &= ~Paint::kStrikeThruText_ReserveFlag;
-        }
-        paint->setFlags(flags);
+        reinterpret_cast<Paint*>(paintHandle)->setStrikeThru(strikeThruText);
     }
 
     static void setFakeBoldText(jlong paintHandle, jboolean fakeBoldText) {
-        reinterpret_cast<Paint*>(paintHandle)->setFakeBoldText(fakeBoldText);
+        reinterpret_cast<Paint*>(paintHandle)->getSkFont().setEmbolden(fakeBoldText);
     }
 
     static void setFilterBitmap(jlong paintHandle, jboolean filterBitmap) {
@@ -907,27 +878,29 @@
     }
 
     static jfloat getTextSize(jlong paintHandle) {
-        return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSize());
+        return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize());
     }
 
     static void setTextSize(jlong paintHandle, jfloat textSize) {
-        reinterpret_cast<Paint*>(paintHandle)->setTextSize(textSize);
+        if (textSize >= 0) {
+            reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSize(textSize);
+        }
     }
 
     static jfloat getTextScaleX(jlong paintHandle) {
-        return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextScaleX());
+        return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getScaleX());
     }
 
     static void setTextScaleX(jlong paintHandle, jfloat scaleX) {
-        reinterpret_cast<Paint*>(paintHandle)->setTextScaleX(scaleX);
+        reinterpret_cast<Paint*>(paintHandle)->getSkFont().setScaleX(scaleX);
     }
 
     static jfloat getTextSkewX(jlong paintHandle) {
-        return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getTextSkewX());
+        return SkScalarToFloat(reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSkewX());
     }
 
     static void setTextSkewX(jlong paintHandle, jfloat skewX) {
-        reinterpret_cast<Paint*>(paintHandle)->setTextSkewX(skewX);
+        reinterpret_cast<Paint*>(paintHandle)->getSkFont().setSkewX(skewX);
     }
 
     static jfloat getLetterSpacing(jlong paintHandle) {
@@ -979,7 +952,7 @@
         if (metrics.hasUnderlinePosition(&position)) {
             return SkScalarToFloat(position);
         } else {
-            const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize();
+            const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize();
             return SkScalarToFloat(Paint::kStdUnderline_Top * textSize);
         }
     }
@@ -991,18 +964,18 @@
         if (metrics.hasUnderlineThickness(&thickness)) {
             return SkScalarToFloat(thickness);
         } else {
-            const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize();
+            const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize();
             return SkScalarToFloat(Paint::kStdUnderline_Thickness * textSize);
         }
     }
 
     static jfloat getStrikeThruPosition(jlong paintHandle) {
-        const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize();
+        const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize();
         return SkScalarToFloat(Paint::kStdStrikeThru_Top * textSize);
     }
 
     static jfloat getStrikeThruThickness(jlong paintHandle) {
-        const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getTextSize();
+        const SkScalar textSize = reinterpret_cast<Paint*>(paintHandle)->getSkFont().getSize();
         return SkScalarToFloat(Paint::kStdStrikeThru_Thickness * textSize);
     }
 
diff --git a/core/jni/android/graphics/PaintFilter.cpp b/core/jni/android/graphics/PaintFilter.cpp
index 182b22b..4fe9140 100644
--- a/core/jni/android/graphics/PaintFilter.cpp
+++ b/core/jni/android/graphics/PaintFilter.cpp
@@ -21,6 +21,7 @@
 
 #include "core_jni_helpers.h"
 
+#include "hwui/Paint.h"
 #include "hwui/PaintFilter.h"
 #include "SkPaint.h"
 
@@ -29,11 +30,15 @@
 class PaintFlagsFilter : public PaintFilter {
 public:
     PaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags) {
-        fClearFlags = static_cast<uint16_t>(clearFlags & SkPaint::kAllFlags);
-        fSetFlags = static_cast<uint16_t>(setFlags & SkPaint::kAllFlags);
+        fClearFlags = static_cast<uint16_t>(clearFlags);
+        fSetFlags = static_cast<uint16_t>(setFlags);
     }
     void filter(SkPaint* paint) override {
-        paint->setFlags((paint->getFlags() & ~fClearFlags) | fSetFlags);
+        uint32_t flags = Paint::GetSkPaintJavaFlags(*paint);
+        Paint::SetSkPaintJavaFlags(paint, (flags & ~fClearFlags) | fSetFlags);
+    }
+    void filterFullPaint(Paint* paint) override {
+        paint->setJavaFlags((paint->getJavaFlags() & ~fClearFlags) | fSetFlags);
     }
 
 private:
@@ -41,33 +46,6 @@
     uint16_t fSetFlags;
 };
 
-// Custom version of PaintFlagsDrawFilter that also calls setFilterQuality.
-class CompatPaintFlagsFilter : public PaintFlagsFilter {
-public:
-    CompatPaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags, SkFilterQuality desiredQuality)
-    : PaintFlagsFilter(clearFlags, setFlags)
-    , fDesiredQuality(desiredQuality) {
-    }
-
-    virtual void filter(SkPaint* paint) {
-        PaintFlagsFilter::filter(paint);
-        paint->setFilterQuality(fDesiredQuality);
-    }
-
-private:
-    const SkFilterQuality fDesiredQuality;
-};
-
-// Returns whether flags contains FILTER_BITMAP_FLAG. If flags does, remove it.
-static inline bool hadFiltering(jint& flags) {
-    // Equivalent to the Java Paint's FILTER_BITMAP_FLAG.
-    static const uint32_t sFilterBitmapFlag = 0x02;
-
-    const bool result = (flags & sFilterBitmapFlag) != 0;
-    flags &= ~sFilterBitmapFlag;
-    return result;
-}
-
 class PaintFilterGlue {
 public:
 
@@ -78,29 +56,11 @@
 
     static jlong CreatePaintFlagsFilter(JNIEnv* env, jobject clazz,
                                         jint clearFlags, jint setFlags) {
+        PaintFilter* filter = nullptr;
         if (clearFlags | setFlags) {
-            // Mask both groups of flags to remove FILTER_BITMAP_FLAG, which no
-            // longer has a Skia equivalent flag (instead it corresponds to
-            // calling setFilterQuality), and keep track of which group(s), if
-            // any, had the flag set.
-            const bool turnFilteringOn = hadFiltering(setFlags);
-            const bool turnFilteringOff = hadFiltering(clearFlags);
-
-            PaintFilter* filter;
-            if (turnFilteringOn) {
-                // Turning filtering on overrides turning it off.
-                filter = new CompatPaintFlagsFilter(clearFlags, setFlags,
-                        kLow_SkFilterQuality);
-            } else if (turnFilteringOff) {
-                filter = new CompatPaintFlagsFilter(clearFlags, setFlags,
-                        kNone_SkFilterQuality);
-            } else {
-                filter = new PaintFlagsFilter(clearFlags, setFlags);
-            }
-            return reinterpret_cast<jlong>(filter);
-        } else {
-            return NULL;
+            filter = new PaintFlagsFilter(clearFlags, setFlags);
         }
+        return reinterpret_cast<jlong>(filter);
     }
 };
 
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index 7738d84..f40b461 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -72,7 +72,7 @@
     return 0;
   }
 
-  unique_fd dup_fd(::dup(fd));
+  unique_fd dup_fd(::fcntl(fd, F_DUPFD_CLOEXEC, 0));
   if (dup_fd < 0) {
     jniThrowIOException(env, errno);
     return 0;
diff --git a/core/jni/android_ddm_DdmHandleNativeHeap.cpp b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
index 2f25d8f..e22f581 100644
--- a/core/jni/android_ddm_DdmHandleNativeHeap.cpp
+++ b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
@@ -54,7 +54,7 @@
 namespace android {
 
 static void ReadFile(const char* path, String8& s) {
-    int fd = open(path, O_RDONLY);
+    int fd = open(path, O_RDONLY | O_CLOEXEC);
     if (fd != -1) {
         char bytes[1024];
         ssize_t byteCount;
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index 8174a41..2d1fec8 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -104,6 +104,21 @@
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyWrapper));
 }
 
+static jboolean android_hardware_HardwareBuffer_isSupported(JNIEnv* env, jobject clazz,
+        jint width, jint height, jint format, jint layers, jlong usage) {
+
+    AHardwareBuffer_Desc desc;
+    desc.width = width;
+    desc.height = height;
+    desc.format = format;
+    desc.layers = layers;
+    desc.usage = usage;
+    desc.stride = 0;
+    desc.rfu0 = 0;
+    desc.rfu1 = 0;
+    return AHardwareBuffer_isSupported(&desc);
+}
+
 //----------------------------------------------------------------------------
 // Accessors
 // ----------------------------------------------------------------------------
@@ -234,6 +249,8 @@
             (void*) android_hardware_HardwareBuffer_write },
     { "nReadHardwareBufferFromParcel", "(Landroid/os/Parcel;)J",
             (void*) android_hardware_HardwareBuffer_read },
+    { "nIsSupported",  "(IIIIJ)Z",
+            (void*) android_hardware_HardwareBuffer_isSupported },
 
     // --------------- @FastNative ----------------------
     { "nGetWidth", "(J)I",      (void*) android_hardware_HardwareBuffer_getWidth },
diff --git a/core/jni/android_hardware_SerialPort.cpp b/core/jni/android_hardware_SerialPort.cpp
index 190ddce..3ff2446 100644
--- a/core/jni/android_hardware_SerialPort.cpp
+++ b/core/jni/android_hardware_SerialPort.cpp
@@ -134,7 +134,7 @@
 
     int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
     // duplicate the file descriptor, since ParcelFileDescriptor will eventually close its copy
-    fd = dup(fd);
+    fd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
     if (fd < 0) {
         jniThrowException(env, "java/io/IOException", "Could not open serial port");
         return;
diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp
index d953aee..b885c28 100644
--- a/core/jni/android_hardware_UsbDeviceConnection.cpp
+++ b/core/jni/android_hardware_UsbDeviceConnection.cpp
@@ -49,7 +49,7 @@
 {
     int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
     // duplicate the file descriptor, since ParcelFileDescriptor will eventually close its copy
-    fd = dup(fd);
+    fd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
     if (fd < 0)
         return JNI_FALSE;
 
diff --git a/core/jni/android_media_AudioAttributes.cpp b/core/jni/android_media_AudioAttributes.cpp
new file mode 100644
index 0000000..bfe6162
--- /dev/null
+++ b/core/jni/android_media_AudioAttributes.cpp
@@ -0,0 +1,205 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+
+#define LOG_TAG "AudioAttributes-JNI"
+
+#include <inttypes.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include "core_jni_helpers.h"
+
+#include <utils/Log.h>
+#include <vector>
+
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+
+#include "android_media_AudioAttributes.h"
+#include "android_media_AudioErrors.h"
+
+// ----------------------------------------------------------------------------
+
+using namespace android;
+
+// ----------------------------------------------------------------------------
+static const char* const kClassPathName = "android/media/AudioAttributes";
+
+static jclass gAudioAttributesClass;
+static struct {
+    jfieldID    mUsage;         // AudioAttributes.mUsage
+    jfieldID    mSource;        // AudioAttributes.mSource
+    jfieldID    mContentType;   // AudioAttributes.mContentType
+    jfieldID    mFlags;         // AudioAttributes.mFlags
+    jfieldID    mFormattedTags; // AudioAttributes.mFormattedTags
+} gAudioAttributesFields;
+
+static jclass gAudioAttributesBuilderClass;
+static jmethodID gAudioAttributesBuilderCstor;
+static struct {
+    jmethodID build;
+    jmethodID setUsage;
+    jmethodID setInternalCapturePreset;
+    jmethodID setContentType;
+    jmethodID setFlags;
+    jmethodID addTag;
+} gAudioAttributesBuilderMethods;
+
+
+static jint nativeAudioAttributesFromJavaAudioAttributes(
+        JNIEnv* env, jobject jAudioAttributes, audio_attributes_t *aa)
+{
+    if (env == nullptr) {
+        return AUDIO_JAVA_DEAD_OBJECT;
+    }
+    if (jAudioAttributes == nullptr) {
+        ALOGE("Invalid AudioAttributes java object");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    if (!env->IsInstanceOf(jAudioAttributes, gAudioAttributesClass)) {
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    const jstring jtags =
+            (jstring) env->GetObjectField(jAudioAttributes, gAudioAttributesFields.mFormattedTags);
+    if (jtags == nullptr) {
+        return AUDIO_JAVA_NO_INIT;
+    }
+    const char* tags = env->GetStringUTFChars(jtags, NULL);
+    // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it
+    strncpy(aa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
+    env->ReleaseStringUTFChars(jtags, tags);
+
+    // Record ?
+    aa->source = (audio_source_t) env->GetIntField(jAudioAttributes,
+                                                    gAudioAttributesFields.mSource);
+    // Track ?
+    aa->usage = (audio_usage_t) env->GetIntField(jAudioAttributes, gAudioAttributesFields.mUsage);
+
+    aa->content_type =
+            (audio_content_type_t) env->GetIntField(jAudioAttributes,
+                                                    gAudioAttributesFields.mContentType);
+
+    aa->flags = (audio_flags_mask_t)env->GetIntField(jAudioAttributes,
+                                                      gAudioAttributesFields.mFlags);
+
+    ALOGV("AudioAttributes for usage=%d content=%d source=%d tags=%s flags=%08x tags=%s",
+          aa->usage, aa->content_type, aa->source, aa->tags, aa->flags, aa->tags);
+    return (jint)AUDIO_JAVA_SUCCESS;
+}
+
+static jint nativeAudioAttributesToJavaAudioAttributes(
+        JNIEnv* env, jobject *jAudioAttributes, const audio_attributes_t &attributes)
+{
+    ScopedLocalRef<jobject> jAttributeBuilder(env, env->NewObject(gAudioAttributesBuilderClass,
+                                                                  gAudioAttributesBuilderCstor));
+    if (jAttributeBuilder.get() == nullptr) {
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+    env->CallObjectMethod(jAttributeBuilder.get(),
+                          gAudioAttributesBuilderMethods.setUsage,
+                          attributes.usage);
+    env->CallObjectMethod(jAttributeBuilder.get(),
+                          gAudioAttributesBuilderMethods.setInternalCapturePreset,
+                          attributes.source);
+    env->CallObjectMethod(jAttributeBuilder.get(),
+                          gAudioAttributesBuilderMethods.setContentType,
+                          attributes.content_type);
+    env->CallObjectMethod(jAttributeBuilder.get(),
+                          gAudioAttributesBuilderMethods.setFlags,
+                          attributes.flags);
+    env->CallObjectMethod(jAttributeBuilder.get(),
+                          gAudioAttributesBuilderMethods.addTag,
+                          env->NewStringUTF(attributes.tags));
+
+    *jAudioAttributes = env->CallObjectMethod(jAttributeBuilder.get(),
+                                              gAudioAttributesBuilderMethods.build);
+    return (jint)AUDIO_JAVA_SUCCESS;
+}
+
+// ----------------------------------------------------------------------------
+JNIAudioAttributeHelper::UniqueAaPtr JNIAudioAttributeHelper::makeUnique()
+{
+    audio_attributes_t *aa = new (calloc(1, sizeof(audio_attributes_t)))
+                audio_attributes_t{AUDIO_ATTRIBUTES_INITIALIZER};
+    return UniqueAaPtr{aa, free};
+}
+
+jint JNIAudioAttributeHelper::nativeFromJava(JNIEnv* env, jobject jAudioAttributes,
+                                             audio_attributes_t *paa)
+{
+    return nativeAudioAttributesFromJavaAudioAttributes(env, jAudioAttributes, paa);
+}
+
+jint JNIAudioAttributeHelper::nativeToJava(
+        JNIEnv* env, jobject *jAudioAttributes, const audio_attributes_t &attributes)
+{
+    return nativeAudioAttributesToJavaAudioAttributes(env, jAudioAttributes, attributes);
+}
+
+jint JNIAudioAttributeHelper::getJavaArray(
+        JNIEnv* env, jobjectArray *jAudioAttributeArray, jint numAudioAttributes)
+{
+    *jAudioAttributeArray = env->NewObjectArray(numAudioAttributes, gAudioAttributesClass, NULL);
+    return jAudioAttributeArray == NULL? (jint)AUDIO_JAVA_ERROR : (jint)AUDIO_JAVA_SUCCESS;
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+    // n/a
+};
+
+int register_android_media_AudioAttributes(JNIEnv *env)
+{
+    jclass audioAttributesClass = FindClassOrDie(env, kClassPathName);
+    gAudioAttributesClass = MakeGlobalRefOrDie(env, audioAttributesClass);
+    gAudioAttributesFields.mUsage = GetFieldIDOrDie(env, audioAttributesClass, "mUsage", "I");
+    gAudioAttributesFields.mSource = GetFieldIDOrDie(env, audioAttributesClass, "mSource", "I");
+    gAudioAttributesFields.mContentType =
+            GetFieldIDOrDie(env, audioAttributesClass, "mContentType", "I");
+    gAudioAttributesFields.mFlags = GetFieldIDOrDie(env, audioAttributesClass, "mFlags", "I");
+    gAudioAttributesFields.mFormattedTags =
+            GetFieldIDOrDie(env, audioAttributesClass, "mFormattedTags", "Ljava/lang/String;");
+
+    jclass audioAttributesBuilderClass = FindClassOrDie(
+                env, "android/media/AudioAttributes$Builder");
+    gAudioAttributesBuilderClass = MakeGlobalRefOrDie(env, audioAttributesBuilderClass);
+    gAudioAttributesBuilderCstor = GetMethodIDOrDie(
+                env, audioAttributesBuilderClass, "<init>", "()V");
+    gAudioAttributesBuilderMethods.build = GetMethodIDOrDie(
+                env, audioAttributesBuilderClass, "build", "()Landroid/media/AudioAttributes;");
+    gAudioAttributesBuilderMethods.setUsage = GetMethodIDOrDie(
+                env, audioAttributesBuilderClass, "setUsage",
+                "(I)Landroid/media/AudioAttributes$Builder;");
+    gAudioAttributesBuilderMethods.setInternalCapturePreset = GetMethodIDOrDie(
+                env, audioAttributesBuilderClass, "setInternalCapturePreset",
+                "(I)Landroid/media/AudioAttributes$Builder;");
+    gAudioAttributesBuilderMethods.setContentType = GetMethodIDOrDie(
+                env, audioAttributesBuilderClass, "setContentType",
+                "(I)Landroid/media/AudioAttributes$Builder;");
+    gAudioAttributesBuilderMethods.setFlags = GetMethodIDOrDie(
+                env, audioAttributesBuilderClass, "setFlags",
+                "(I)Landroid/media/AudioAttributes$Builder;");
+    gAudioAttributesBuilderMethods.addTag = GetMethodIDOrDie(
+                env, audioAttributesBuilderClass, "addTag",
+                "(Ljava/lang/String;)Landroid/media/AudioAttributes$Builder;");
+
+    env->DeleteLocalRef(audioAttributesClass);
+
+    return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
+}
diff --git a/core/jni/android_media_AudioAttributes.h b/core/jni/android_media_AudioAttributes.h
new file mode 100644
index 0000000..c558352
--- /dev/null
+++ b/core/jni/android_media_AudioAttributes.h
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "jni.h"
+
+#include <memory.h>
+
+#include <system/audio.h>
+
+namespace android {
+
+class JNIAudioAttributeHelper
+{
+public:
+    using UniqueAaPtr = std::unique_ptr<audio_attributes_t, decltype(free)*>;
+
+    /**
+     * @brief makeUnique helper to prevent leak
+     * @return a unique ptr of 0-initialized native audio attributes structure
+     */
+    static UniqueAaPtr makeUnique();
+
+    /**
+     * @brief nativeFromJava Gets the underlying AudioAttributes from an AudioAttributes Java
+     * object.
+     * @param env
+     * @param jAudioAttributes JAVA AudioAttribute object
+     * @param paa native AudioAttribute pointer
+     * @return AUDIO_JAVA_SUCCESS on success, error code otherwise
+     */
+    static jint nativeFromJava(
+            JNIEnv* env, jobject jAudioAttributes, audio_attributes_t *attributes);
+
+    /**
+     * @brief nativeToJava AudioAttributes Java object from a native AudioAttributes.
+     * @param env
+     * @param jAudioAttributes JAVA AudioAttribute object
+     * @param attributes native AudioAttribute
+     * @return AUDIO_JAVA_SUCCESS on success, error code otherwise
+     */
+    static jint nativeToJava(
+            JNIEnv* env, jobject *jAudioAttributes, const audio_attributes_t &attributes);
+
+    /**
+     * @brief getJavaArray: creates an array of JAVA AudioAttributes objects
+     * @param env
+     * @param jAudioAttributeArray
+     * @param numAudioAttributes
+     * @return Array of AudioAttributes objects
+     */
+    static jint getJavaArray(
+            JNIEnv* env, jobjectArray *jAudioAttributeArray, jint numAudioAttributes);
+};
+
+}; // namespace android
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 67c3064..02dffdc2 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -35,6 +35,7 @@
 #include "android_media_DeviceCallback.h"
 #include "android_media_MediaMetricsJNI.h"
 #include "android_media_MicrophoneInfo.h"
+#include "android_media_AudioAttributes.h"
 
 // ----------------------------------------------------------------------------
 
@@ -42,7 +43,6 @@
 
 // ----------------------------------------------------------------------------
 static const char* const kClassPathName = "android/media/AudioRecord";
-static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes";
 
 static jclass gArrayListClass;
 static struct {
@@ -56,12 +56,6 @@
     jfieldID  nativeCallbackCookie;    // provides access to the AudioRecord callback data
     jfieldID  nativeDeviceCallback;    // provides access to the JNIDeviceCallback instance
 };
-struct audio_attributes_fields_t {
-    jfieldID  fieldRecSource;    // AudioAttributes.mSource
-    jfieldID  fieldFlags;        // AudioAttributes.mFlags
-    jfieldID  fieldFormattedTags;// AudioAttributes.mFormattedTags
-};
-static audio_attributes_fields_t javaAudioAttrFields;
 static audio_record_fields_t     javaAudioRecordFields;
 static struct {
     jfieldID  fieldFramePosition;     // AudioTimestamp.framePosition
@@ -213,7 +207,6 @@
     env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
     nSession = NULL;
 
-    audio_attributes_t *paa = NULL;
     sp<AudioRecord> lpRecorder = 0;
     audiorecord_callback_cookie *lpCallbackData = NULL;
 
@@ -275,15 +268,11 @@
         lpRecorder = new AudioRecord(String16(opPackageNameStr.c_str()));
 
         // read the AudioAttributes values
-        paa = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t));
-        const jstring jtags =
-                (jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldFormattedTags);
-        const char* tags = env->GetStringUTFChars(jtags, NULL);
-        // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it
-        strncpy(paa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
-        env->ReleaseStringUTFChars(jtags, tags);
-        paa->source = (audio_source_t) env->GetIntField(jaa, javaAudioAttrFields.fieldRecSource);
-        paa->flags = (audio_flags_mask_t)env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);
+        auto paa = JNIAudioAttributeHelper::makeUnique();
+        jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
+        if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+            return jStatus;
+        }
         ALOGV("AudioRecord_setup for source=%d tags=%s flags=%08x", paa->source, paa->tags, paa->flags);
 
         audio_input_flags_t flags = AUDIO_INPUT_FLAG_NONE;
@@ -311,7 +300,7 @@
             AudioRecord::TRANSFER_DEFAULT,
             flags,
             -1, -1,        // default uid, pid
-            paa);
+            paa.get());
 
         if (status != NO_ERROR) {
             ALOGE("Error creating AudioRecord instance: initialization check failed with status %d.",
@@ -368,19 +357,10 @@
     // of the Java object (in mNativeCallbackCookie) so we can free the memory in finalize()
     env->SetLongField(thiz, javaAudioRecordFields.nativeCallbackCookie, (jlong)lpCallbackData);
 
-    if (paa != NULL) {
-        // audio attributes were copied in AudioRecord creation
-        free(paa);
-        paa = NULL;
-    }
-
     return (jint) AUDIO_JAVA_SUCCESS;
 
     // failure:
 native_init_failure:
-    if (paa != NULL) {
-        free(paa);
-    }
     env->DeleteGlobalRef(lpCallbackData->audioRecord_class);
     env->DeleteGlobalRef(lpCallbackData->audioRecord_ref);
     delete lpCallbackData;
@@ -972,13 +952,6 @@
     javaAudioRecordFields.nativeDeviceCallback = GetFieldIDOrDie(env,
             audioRecordClass, JAVA_NATIVEDEVICECALLBACK_FIELD_NAME, "J");
 
-    // Get the AudioAttributes class and fields
-    jclass audioAttrClass = FindClassOrDie(env, kAudioAttributesClassPathName);
-    javaAudioAttrFields.fieldRecSource = GetFieldIDOrDie(env, audioAttrClass, "mSource", "I");
-    javaAudioAttrFields.fieldFlags = GetFieldIDOrDie(env, audioAttrClass, "mFlags", "I");
-    javaAudioAttrFields.fieldFormattedTags = GetFieldIDOrDie(env,
-            audioAttrClass, "mFormattedTags", "Ljava/lang/String;");
-
     // Get the RecordTimestamp class and fields
     jclass audioTimestampClass = FindClassOrDie(env, "android/media/AudioTimestamp");
     javaAudioTimestampFields.fieldFramePosition =
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 0d8ede7..34abcd8 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -36,6 +36,7 @@
 #include "android_media_AudioFormat.h"
 #include "android_media_AudioErrors.h"
 #include "android_media_MicrophoneInfo.h"
+#include "android_media_AudioAttributes.h"
 
 // ----------------------------------------------------------------------------
 
@@ -153,15 +154,6 @@
     jfieldID    mRule;
 } gAudioMixMatchCriterionFields;
 
-static jclass gAudioAttributesClass;
-static struct {
-    jfieldID    mUsage;
-    jfieldID    mSource;
-    jfieldID    mContentType;
-    jfieldID    mFlags;
-    jfieldID    mFormattedTags;
-} gAudioAttributesFields;
-
 static const char* const kEventHandlerClassPathName =
         "android/media/AudioPortEventHandler";
 static struct {
@@ -705,27 +697,6 @@
     env->DeleteLocalRef(jValues);
 }
 
-static jint convertAudioAttributesToNative(JNIEnv *env,
-                                           audio_attributes_t *nAudioAttributes,
-                                           const jobject jAudioAttributes)
-{
-    nAudioAttributes->usage = (audio_usage_t)env->GetIntField(jAudioAttributes,
-            gAudioAttributesFields.mUsage);
-    nAudioAttributes->source = (audio_source_t)env->GetIntField(jAudioAttributes,
-            gAudioAttributesFields.mSource);
-    nAudioAttributes->content_type = (audio_content_type_t)env->GetIntField(jAudioAttributes,
-            gAudioAttributesFields.mContentType);
-    nAudioAttributes->flags = env->GetIntField(jAudioAttributes,
-            gAudioAttributesFields.mFlags);
-    const jstring jtags = (jstring)env->GetObjectField(jAudioAttributes,
-            gAudioAttributesFields.mFormattedTags);
-    const char *tags = env->GetStringUTFChars(jtags, NULL);
-    strncpy(nAudioAttributes->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
-    env->ReleaseStringUTFChars(jtags, tags);
-    return (jint)AUDIO_JAVA_SUCCESS;
-}
-
-
 static jint convertAudioPortConfigToNative(JNIEnv *env,
                                                struct audio_port_config *nAudioPortConfig,
                                                const jobject jAudioPortConfig,
@@ -1705,22 +1676,19 @@
     if (!env->IsInstanceOf(jAudioPortConfig, gAudioPortConfigClass)) {
         return AUDIO_JAVA_BAD_VALUE;
     }
-    if (!env->IsInstanceOf(jAudioAttributes, gAudioAttributesClass)) {
-        return AUDIO_JAVA_BAD_VALUE;
-    }
     struct audio_port_config nAudioPortConfig = {};
     jint jStatus = convertAudioPortConfigToNativeWithDevicePort(env,
             &nAudioPortConfig, jAudioPortConfig, false);
     if (jStatus != AUDIO_JAVA_SUCCESS) {
         return jStatus;
     }
-    audio_attributes_t nAudioAttributes = {};
-    jStatus = convertAudioAttributesToNative(env, &nAudioAttributes, jAudioAttributes);
-    if (jStatus != AUDIO_JAVA_SUCCESS) {
+    auto paa = JNIAudioAttributeHelper::makeUnique();
+    jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get());
+    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
         return jStatus;
     }
     audio_port_handle_t handle;
-    status_t status = AudioSystem::startAudioSource(&nAudioPortConfig, &nAudioAttributes, &handle);
+    status_t status = AudioSystem::startAudioSource(&nAudioPortConfig, paa.get(), &handle);
     ALOGV("AudioSystem::startAudioSource() returned %d handle %d", status, handle);
     return handle > 0 ? handle : nativeToJavaStatus(status);
 }
@@ -1833,12 +1801,16 @@
         case RULE_MATCH_ATTRIBUTE_USAGE:
         case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: {
             jobject jAttributes = env->GetObjectField(jCriterion, gAudioMixMatchCriterionFields.mAttr);
+
+            auto paa = JNIAudioAttributeHelper::makeUnique();
+            jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAttributes, paa.get());
+            if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+                return jStatus;
+            }
             if (match_rule == RULE_MATCH_ATTRIBUTE_USAGE) {
-                nCriterion.mValue.mUsage = (audio_usage_t)env->GetIntField(jAttributes,
-                        gAudioAttributesFields.mUsage);
+                nCriterion.mValue.mUsage = paa->usage;
             } else {
-                nCriterion.mValue.mSource = (audio_source_t)env->GetIntField(jAttributes,
-                        gAudioAttributesFields.mSource);
+                nCriterion.mValue.mSource = paa->source;
             }
             env->DeleteLocalRef(jAttributes);
             }
@@ -2395,17 +2367,6 @@
                                                        "I");
     gAudioMixMatchCriterionFields.mRule = GetFieldIDOrDie(env, audioMixMatchCriterionClass, "mRule",
                                                        "I");
-
-    jclass audioAttributesClass = FindClassOrDie(env, "android/media/AudioAttributes");
-    gAudioAttributesClass = MakeGlobalRefOrDie(env, audioAttributesClass);
-    gAudioAttributesFields.mUsage = GetFieldIDOrDie(env, audioAttributesClass, "mUsage", "I");
-    gAudioAttributesFields.mSource = GetFieldIDOrDie(env, audioAttributesClass, "mSource", "I");
-    gAudioAttributesFields.mContentType = GetFieldIDOrDie(env,
-            audioAttributesClass, "mContentType", "I");
-    gAudioAttributesFields.mFlags = GetFieldIDOrDie(env, audioAttributesClass, "mFlags", "I");
-    gAudioAttributesFields.mFormattedTags = GetFieldIDOrDie(env,
-            audioAttributesClass, "mFormattedTags", "Ljava/lang/String;");
-
     // AudioTrackRoutingProxy methods
     gClsAudioTrackRoutingProxy =
             android::FindClassOrDie(env, "android/media/AudioTrackRoutingProxy");
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 1065738..f9f28da 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>
 
@@ -36,6 +37,7 @@
 #include "android_media_PlaybackParams.h"
 #include "android_media_DeviceCallback.h"
 #include "android_media_VolumeShaper.h"
+#include "android_media_AudioAttributes.h"
 
 #include <cinttypes>
 
@@ -47,7 +49,6 @@
 
 // ----------------------------------------------------------------------------
 static const char* const kClassPathName = "android/media/AudioTrack";
-static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes";
 
 struct audio_track_fields_t {
     // these fields provide access from C++ to the...
@@ -56,14 +57,7 @@
     jfieldID  jniData;      // stores in Java additional resources used by the native AudioTrack
     jfieldID  fieldStreamType; // ... mStreamType field in the AudioTrack Java object
 };
-struct audio_attributes_fields_t {
-    jfieldID  fieldUsage;        // AudioAttributes.mUsage
-    jfieldID  fieldContentType;  // AudioAttributes.mContentType
-    jfieldID  fieldFlags;        // AudioAttributes.mFlags
-    jfieldID  fieldFormattedTags;// AudioAttributes.mFormattedTags
-};
 static audio_track_fields_t      javaAudioTrackFields;
-static audio_attributes_fields_t javaAudioAttrFields;
 static PlaybackParams::fields_t gPlaybackParamsFields;
 static VolumeShaperHelper::fields_t gVolumeShaperFields;
 
@@ -248,8 +242,6 @@
 
     AudioTrackJniStorage* lpJniStorage = NULL;
 
-    audio_attributes_t *paa = NULL;
-
     jclass clazz = env->GetObjectClass(thiz);
     if (clazz == NULL) {
         ALOGE("Can't find %s when setting up callback.", kClassPathName);
@@ -303,18 +295,11 @@
         lpTrack = new AudioTrack();
 
         // read the AudioAttributes values
-        paa = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t));
-        const jstring jtags =
-                (jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldFormattedTags);
-        const char* tags = env->GetStringUTFChars(jtags, NULL);
-        // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it
-        strncpy(paa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
-        env->ReleaseStringUTFChars(jtags, tags);
-        paa->usage = (audio_usage_t) env->GetIntField(jaa, javaAudioAttrFields.fieldUsage);
-        paa->content_type =
-                (audio_content_type_t) env->GetIntField(jaa, javaAudioAttrFields.fieldContentType);
-        paa->flags = env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);
-
+        auto paa = JNIAudioAttributeHelper::makeUnique();
+        jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
+        if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+            return jStatus;
+        }
         ALOGV("AudioTrack_setup for usage=%d content=%d flags=0x%#x tags=%s",
                 paa->usage, paa->content_type, paa->flags, paa->tags);
 
@@ -356,7 +341,7 @@
                     offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK : AudioTrack::TRANSFER_SYNC,
                     offload ? &offloadInfo : NULL,
                     -1, -1,                       // default uid, pid values
-                    paa);
+                    paa.get());
 
             break;
 
@@ -383,7 +368,7 @@
                     AudioTrack::TRANSFER_SHARED,
                     NULL,                         // default offloadInfo
                     -1, -1,                       // default uid, pid values
-                    paa);
+                    paa.get());
             break;
 
         default:
@@ -451,20 +436,11 @@
     // since we had audio attributes, the stream type was derived from them during the
     // creation of the native AudioTrack: push the same value to the Java object
     env->SetIntField(thiz, javaAudioTrackFields.fieldStreamType, (jint) lpTrack->streamType());
-    if (paa != NULL) {
-        // audio attributes were copied in AudioTrack creation
-        free(paa);
-        paa = NULL;
-    }
-
 
     return (jint) AUDIO_JAVA_SUCCESS;
 
     // failures:
 native_init_failure:
-    if (paa != NULL) {
-        free(paa);
-    }
     if (nSession != NULL) {
         env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
     }
@@ -1311,6 +1287,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 +1388,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},
 };
 
 
@@ -1440,17 +1445,6 @@
 
     env->DeleteLocalRef(audioTrackClass);
 
-    // Get the AudioAttributes class and fields
-    jclass audioAttrClass = FindClassOrDie(env, kAudioAttributesClassPathName);
-    javaAudioAttrFields.fieldUsage = GetFieldIDOrDie(env, audioAttrClass, "mUsage", "I");
-    javaAudioAttrFields.fieldContentType = GetFieldIDOrDie(env,
-            audioAttrClass, "mContentType", "I");
-    javaAudioAttrFields.fieldFlags = GetFieldIDOrDie(env, audioAttrClass, "mFlags", "I");
-    javaAudioAttrFields.fieldFormattedTags = GetFieldIDOrDie(env,
-            audioAttrClass, "mFormattedTags", "Ljava/lang/String;");
-
-    env->DeleteLocalRef(audioAttrClass);
-
     // initialize PlaybackParams field info
     gPlaybackParamsFields.init(env);
 
diff --git a/core/jni/android_media_MediaMetricsJNI.cpp b/core/jni/android_media_MediaMetricsJNI.cpp
deleted file mode 100644
index 38f7a7e..0000000
--- a/core/jni/android_media_MediaMetricsJNI.cpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2017, 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.
- */
-
-#include <android_runtime/AndroidRuntime.h>
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-
-#include "android_media_MediaMetricsJNI.h"
-#include <media/MediaAnalyticsItem.h>
-
-
-namespace android {
-
-// place the attributes into a java PersistableBundle object
-jobject MediaMetricsJNI::writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle) {
-
-    jclass clazzBundle = env->FindClass("android/os/PersistableBundle");
-    if (clazzBundle==NULL) {
-        ALOGD("can't find android/os/PersistableBundle");
-        return NULL;
-    }
-    // sometimes the caller provides one for us to fill
-    if (mybundle == NULL) {
-        // create the bundle
-        jmethodID constructID = env->GetMethodID(clazzBundle, "<init>", "()V");
-        mybundle = env->NewObject(clazzBundle, constructID);
-        if (mybundle == NULL) {
-            return NULL;
-        }
-    }
-
-    // grab methods that we can invoke
-    jmethodID setIntID = env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V");
-    jmethodID setLongID = env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V");
-    jmethodID setDoubleID = env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V");
-    jmethodID setStringID = env->GetMethodID(clazzBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V");
-
-    // env, class, method, {parms}
-    //env->CallVoidMethod(env, mybundle, setIntID, jstr, jint);
-
-    // iterate through my attributes
-    // -- get name, get type, get value
-    // -- insert appropriately into the bundle
-    for (size_t i = 0 ; i < item->mPropCount; i++ ) {
-            MediaAnalyticsItem::Prop *prop = &item->mProps[i];
-            // build the key parameter from prop->mName
-            jstring keyName = env->NewStringUTF(prop->mName);
-            // invoke the appropriate method to insert
-            switch (prop->mType) {
-                case MediaAnalyticsItem::kTypeInt32:
-                    env->CallVoidMethod(mybundle, setIntID,
-                                        keyName, (jint) prop->u.int32Value);
-                    break;
-                case MediaAnalyticsItem::kTypeInt64:
-                    env->CallVoidMethod(mybundle, setLongID,
-                                        keyName, (jlong) prop->u.int64Value);
-                    break;
-                case MediaAnalyticsItem::kTypeDouble:
-                    env->CallVoidMethod(mybundle, setDoubleID,
-                                        keyName, (jdouble) prop->u.doubleValue);
-                    break;
-                case MediaAnalyticsItem::kTypeCString:
-                    env->CallVoidMethod(mybundle, setStringID, keyName,
-                                        env->NewStringUTF(prop->u.CStringValue));
-                    break;
-                default:
-                        ALOGE("to_String bad item type: %d for %s",
-                              prop->mType, prop->mName);
-                        break;
-            }
-    }
-
-    return mybundle;
-}
-
-};  // namespace android
-
diff --git a/core/jni/android_media_MediaMetricsJNI.cpp b/core/jni/android_media_MediaMetricsJNI.cpp
new file mode 120000
index 0000000..3204317c
--- /dev/null
+++ b/core/jni/android_media_MediaMetricsJNI.cpp
@@ -0,0 +1 @@
+../../media/jni/android_media_MediaMetricsJNI.cpp
\ No newline at end of file
diff --git a/core/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h
deleted file mode 100644
index b3cb4d2..0000000
--- a/core/jni/android_media_MediaMetricsJNI.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2017, 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.
- */
-
-#ifndef _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
-#define _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
-
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-#include <media/MediaAnalyticsItem.h>
-
-namespace android {
-
-class MediaMetricsJNI {
-public:
-    static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle);
-};
-
-};  // namespace android
-
-#endif //  _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
diff --git a/core/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h
new file mode 120000
index 0000000..c7a685b
--- /dev/null
+++ b/core/jni/android_media_MediaMetricsJNI.h
@@ -0,0 +1 @@
+../../media/jni/android_media_MediaMetricsJNI.h
\ No newline at end of file
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 7837248..e9035ed 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -646,7 +646,7 @@
     }
 
     /* dup() the descriptor so we don't close the original with fclose() */
-    int fd = dup(origFd);
+    int fd = fcntl(origFd, F_DUPFD_CLOEXEC, 0);
     if (fd < 0) {
         ALOGW("dup(%d) failed: %s\n", origFd, strerror(errno));
         jniThrowRuntimeException(env, "dup() failed");
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index 6f9cc22..de30773 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -37,7 +37,7 @@
 
 static const uint8_t* mmapPatternFile(const std::string& locale) {
     const std::string hyFilePath = buildFileName(locale);
-    const int fd = open(hyFilePath.c_str(), O_RDONLY);
+    const int fd = open(hyFilePath.c_str(), O_RDONLY | O_CLOEXEC);
     if (fd == -1) {
         return nullptr;  // Open failed.
     }
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index f201ceb..df98cdc 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -1184,7 +1184,7 @@
     FILE *f;
 
     snprintf(filename, sizeof(filename), "/proc/%d/cmdline", pid);
-    f = fopen(filename, "r");
+    f = fopen(filename, "re");
     if (!f) {
         *buf = '\0';
         return 1;
diff --git a/core/jni/android_util_FileObserver.cpp b/core/jni/android_util_FileObserver.cpp
index 6f975b2..c64c212 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"
@@ -40,7 +42,7 @@
 static jint android_os_fileobserver_init(JNIEnv* env, jobject object)
 {
 #if defined(__linux__)
-    return (jint)inotify_init();
+    return (jint)inotify_init1(IN_CLOEXEC);
 #else
     return -1;
 #endif
@@ -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_Surface.cpp b/core/jni/android_view_Surface.cpp
index b8139a7..f9407f4 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -131,6 +131,7 @@
     switch(f) {
         case PublicFormat::JPEG:
         case PublicFormat::DEPTH_POINT_CLOUD:
+        case PublicFormat::DEPTH_JPEG:
             return HAL_PIXEL_FORMAT_BLOB;
         case PublicFormat::DEPTH16:
             return HAL_PIXEL_FORMAT_Y16;
@@ -161,6 +162,8 @@
         case PublicFormat::NV21:
         case PublicFormat::YV12:
             return HAL_DATASPACE_V0_JFIF;
+        case PublicFormat::DEPTH_JPEG:
+            return static_cast<android_dataspace> (HAL_DATASPACE_DYNAMIC_DEPTH);
         default:
             // Most formats map to UNKNOWN
             return HAL_DATASPACE_UNKNOWN;
@@ -223,8 +226,12 @@
                 case HAL_DATASPACE_V0_JFIF:
                     return PublicFormat::JPEG;
                 default:
-                    // Assume otherwise-marked blobs are also JPEG
-                    return PublicFormat::JPEG;
+                    if (dataSpace == static_cast<android_dataspace>(HAL_DATASPACE_DYNAMIC_DEPTH)) {
+                        return PublicFormat::DEPTH_JPEG;
+                    } else {
+                        // Assume otherwise-marked blobs are also JPEG
+                        return PublicFormat::JPEG;
+                    }
             }
             break;
         case HAL_PIXEL_FORMAT_BGRA_8888:
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_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 5eefc81..dc536b2 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -125,7 +125,7 @@
         return true;
     }
 
-    int fd = TEMP_FAILURE_RETRY(open(filePath, O_RDONLY));
+    int fd = TEMP_FAILURE_RETRY(open(filePath, O_RDONLY | O_CLOEXEC));
     if (fd < 0) {
         ALOGV("Couldn't open file %s: %s", filePath, strerror(errno));
         return true;
@@ -565,7 +565,7 @@
         return 0;
     }
 
-    int dupedFd = dup(fd);
+    int dupedFd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
     if (dupedFd == -1) {
         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
                              "Failed to dup FileDescriptor: %s", strerror(errno));
diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
index 24bafca..8259ffc 100644
--- a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
+++ b/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
@@ -95,7 +95,7 @@
 static int legacyReadNetworkStatsDetail(std::vector<stats_line>* lines,
                                         const std::vector<std::string>& limitIfaces,
                                         int limitTag, int limitUid, const char* path) {
-    FILE* fp = fopen(path, "r");
+    FILE* fp = fopen(path, "re");
     if (fp == NULL) {
         return -1;
     }
diff --git a/core/jni/com_android_internal_os_AtomicDirectory.cpp b/core/jni/com_android_internal_os_AtomicDirectory.cpp
index 50b2288..76b0fc1 100644
--- a/core/jni/com_android_internal_os_AtomicDirectory.cpp
+++ b/core/jni/com_android_internal_os_AtomicDirectory.cpp
@@ -29,7 +29,7 @@
         return -1;
     }
     int fd;
-    if ((fd = TEMP_FAILURE_RETRY(open(path8.c_str(), O_DIRECTORY | O_RDONLY))) == -1) {
+    if ((fd = TEMP_FAILURE_RETRY(open(path8.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC))) == -1) {
         ALOGE("Cannot open directory %s, error: %s\n", path8.c_str(), strerror(errno));
         return -1;
     }
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/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h
index 36487b3..984e942 100644
--- a/core/jni/include/android_runtime/android_view_Surface.h
+++ b/core/jni/include/android_runtime/android_view_Surface.h
@@ -57,7 +57,8 @@
     YV12              = 0x32315659,
     Y8                = 0x20203859, // @hide
     Y16               = 0x20363159, // @hide
-    DEPTH16           = 0x44363159
+    DEPTH16           = 0x44363159,
+    DEPTH_JPEG        = 0x69656963,
 };
 
 /* Gets the underlying ANativeWindow for a Surface. */
diff --git a/core/proto/Android.bp b/core/proto/Android.bp
index 80cc2d4..3b891d6 100644
--- a/core/proto/Android.bp
+++ b/core/proto/Android.bp
@@ -21,7 +21,10 @@
         type: "lite",
     },
     srcs: [
+        "android/bluetooth/a2dp/enums.proto",
         "android/bluetooth/enums.proto",
         "android/bluetooth/hci/enums.proto",
+        "android/bluetooth/hfp/enums.proto",
+        "android/bluetooth/smp/enums.proto",
     ],
 }
diff --git a/core/proto/android/app/job/enums.proto b/core/proto/android/app/job/enums.proto
index bba8328..f702b3e 100644
--- a/core/proto/android/app/job/enums.proto
+++ b/core/proto/android/app/job/enums.proto
@@ -18,6 +18,9 @@
 
 package android.app.job;
 
+// This file is for JobScheduler enums inside the app directory. If you're
+// adding enums for system-server-side code, use the file in
+// frameworks/base/core/proto/android/server/job.
 option java_outer_classname = "JobProtoEnums";
 option java_multiple_files = true;
 
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index eb716ac..94a6734 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2157,10 +2157,10 @@
     // OS: Q
     ZEN_CUSTOM_SETTINGS_DIALOG = 1612;
 
-    // OPEN: Settings > Developer Options > Game Update Packages
+    // OPEN: Settings > Developer Options > Game Driver Preferences
     // CATEGORY: SETTINGS
     // OS: Q
-    SETTINGS_GUP_DASHBOARD = 1613;
+    SETTINGS_GAME_DRIVER_DASHBOARD = 1613;
 
     // OPEN: Settings > Accessibility > Vibration > Ring vibration
     // CATEGORY: SETTINGS
diff --git a/core/proto/android/bluetooth/a2dp/enums.proto b/core/proto/android/bluetooth/a2dp/enums.proto
new file mode 100644
index 0000000..5a025bd
--- /dev/null
+++ b/core/proto/android/bluetooth/a2dp/enums.proto
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.bluetooth.a2dp;
+
+option java_outer_classname = "BluetoothA2dpProtoEnums";
+option java_multiple_files = true;
+
+// A2dp playback state enum, defined from:
+// frameworks/base/core/java/android/bluetooth/BluetoothA2dp.java
+enum PlaybackStateEnum {
+    PLAYBACK_STATE_UNKNOWN = 0;
+    PLAYBACK_STATE_PLAYING = 10;
+    PLAYBACK_STATE_NOT_PLAYING = 11;
+}
+
+enum AudioCodingModeEnum {
+    AUDIO_CODING_MODE_UNKNOWN = 0;
+    AUDIO_CODING_MODE_HARDWARE = 1;
+    AUDIO_CODING_MODE_SOFTWARE = 2;
+}
diff --git a/core/proto/android/bluetooth/enums.proto b/core/proto/android/bluetooth/enums.proto
index 76c240e..a88a06c 100644
--- a/core/proto/android/bluetooth/enums.proto
+++ b/core/proto/android/bluetooth/enums.proto
@@ -56,3 +56,57 @@
     LINK_TYPE_ACL = 0x01;
     LINK_TYPE_ESCO = 0x02;
 }
+
+enum DeviceInfoSrcEnum {
+    DEVICE_INFO_SRC_UNKNOWN = 0;
+    // Within Android Bluetooth stack
+    DEVICE_INFO_INTERNAL = 1;
+    // Outside Android Bluetooth stack
+    DEVICE_INFO_EXTERNAL = 2;
+}
+
+enum DeviceTypeEnum {
+    DEVICE_TYPE_UNKNOWN = 0;
+    DEVICE_TYPE_CLASSIC = 1;
+    DEVICE_TYPE_LE = 2;
+    DEVICE_TYPE_DUAL = 3;
+}
+
+// Defined in frameworks/base/core/java/android/bluetooth/BluetoothDevice.java
+enum TransportTypeEnum {
+    TRANSPORT_TYPE_AUTO = 0;
+    TRANSPORT_TYPE_BREDR = 1;
+    TRANSPORT_TYPE_LE = 2;
+}
+
+// Bond state enum
+// Defined in frameworks/base/core/java/android/bluetooth/BluetoothDevice.java
+enum BondStateEnum {
+    BOND_STATE_UNKNOWN = 0;
+    BOND_STATE_NONE = 10;
+    BOND_STATE_BONDING = 11;
+    BOND_STATE_BONDED = 12;
+}
+
+// Sub states within the bonding general state
+enum BondSubStateEnum {
+    BOND_SUB_STATE_UNKNOWN = 0;
+    BOND_SUB_STATE_LOCAL_OOB_DATA_PROVIDED = 1;
+    BOND_SUB_STATE_LOCAL_PIN_REQUESTED = 2;
+    BOND_SUB_STATE_LOCAL_PIN_REPLIED = 3;
+    BOND_SUB_STATE_LOCAL_SSP_REQUESTED = 4;
+    BOND_SUB_STATE_LOCAL_SSP_REPLIED = 5;
+}
+
+enum UnbondReasonEnum {
+    UNBOND_REASON_UNKNOWN = 0;
+    UNBOND_REASON_AUTH_FAILED = 1;
+    UNBOND_REASON_AUTH_REJECTED = 2;
+    UNBOND_REASON_AUTH_CANCELED = 3;
+    UNBOND_REASON_REMOTE_DEVICE_DOWN = 4;
+    UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5;
+    UNBOND_REASON_AUTH_TIMEOUT = 6;
+    UNBOND_REASON_REPEATED_ATTEMPTS = 7;
+    UNBOND_REASON_REMOTE_AUTH_CANCELED = 8;
+    UNBOND_REASON_REMOVED = 9;
+}
diff --git a/core/proto/android/bluetooth/hci/enums.proto b/core/proto/android/bluetooth/hci/enums.proto
index e1d96bb..ef894e5 100644
--- a/core/proto/android/bluetooth/hci/enums.proto
+++ b/core/proto/android/bluetooth/hci/enums.proto
@@ -351,7 +351,7 @@
     EVT_COMMAND_COMPLETE = 0x0E;
     EVT_COMMAND_STATUS = 0x0F;
     EVT_HARDWARE_ERROR = 0x10;
-    EVT_FLUSH_OCCURED = 0x11;
+    EVT_FLUSH_OCCURRED = 0x11;
     EVT_ROLE_CHANGE = 0x12;
     EVT_NUM_COMPL_DATA_PKTS = 0x13;
     EVT_MODE_CHANGE = 0x14;
@@ -517,3 +517,43 @@
     STATUS_CLB_DATA_TOO_BIG = 0x43;
     STATUS_OPERATION_CANCELED_BY_HOST = 0x44; // Not currently used in system/bt
 }
+
+enum BqrIdEnum {
+    BQR_ID_UNKNOWN = 0x00;
+    BQR_ID_MONITOR_MODE = 0x01;
+    BQR_ID_APPROACH_LSTO = 0x02;
+    BQR_ID_A2DP_AUDIO_CHOPPY = 0x03;
+    BQR_ID_SCO_VOICE_CHOPPY = 0x04;
+}
+
+enum BqrPacketTypeEnum {
+    BQR_PACKET_TYPE_UNKNOWN = 0x00;
+    BQR_PACKET_TYPE_ID = 0x01;
+    BQR_PACKET_TYPE_NULL = 0x02;
+    BQR_PACKET_TYPE_POLL = 0x03;
+    BQR_PACKET_TYPE_FHS = 0x04;
+    BQR_PACKET_TYPE_HV1 = 0x05;
+    BQR_PACKET_TYPE_HV2 = 0x06;
+    BQR_PACKET_TYPE_HV3 = 0x07;
+    BQR_PACKET_TYPE_DV = 0x08;
+    BQR_PACKET_TYPE_EV3 = 0x09;
+    BQR_PACKET_TYPE_EV4 = 0x0A;
+    BQR_PACKET_TYPE_EV5 = 0x0B;
+    BQR_PACKET_TYPE_2EV3 = 0x0C;
+    BQR_PACKET_TYPE_2EV5 = 0x0D;
+    BQR_PACKET_TYPE_3EV3 = 0x0E;
+    BQR_PACKET_TYPE_3EV5 = 0x0F;
+    BQR_PACKET_TYPE_DM1 = 0x10;
+    BQR_PACKET_TYPE_DH1 = 0x11;
+    BQR_PACKET_TYPE_DM3 = 0x12;
+    BQR_PACKET_TYPE_DH3 = 0x13;
+    BQR_PACKET_TYPE_DM5 = 0x14;
+    BQR_PACKET_TYPE_DH5 = 0x15;
+    BQR_PACKET_TYPE_AUX1 = 0x16;
+    BQR_PACKET_TYPE_2DH1 = 0x17;
+    BQR_PACKET_TYPE_2DH3 = 0x18;
+    BQR_PACKET_TYPE_2DH5 = 0x19;
+    BQR_PACKET_TYPE_3DH1 = 0x1A;
+    BQR_PACKET_TYPE_3DH3 = 0x1B;
+    BQR_PACKET_TYPE_3DH5 = 0x1C;
+}
diff --git a/core/proto/android/bluetooth/smp/enums.proto b/core/proto/android/bluetooth/smp/enums.proto
new file mode 100644
index 0000000..c6747b7
--- /dev/null
+++ b/core/proto/android/bluetooth/smp/enums.proto
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.bluetooth.smp;
+
+option java_outer_classname = "BluetoothSmpProtoEnums";
+option java_multiple_files = true;
+
+// SMP Pairing command codes
+enum CommandEnum {
+    CMD_UNKNOWN = 0x00;
+    CMD_PAIRING_REQUEST = 0x01;
+    CMD_PAIRING_RESPONSE = 0x02;
+    CMD_PAIRING_CONFIRM = 0x03;
+    CMD_PAIRING_RANDOM = 0x04;
+    CMD_PAIRING_FAILED = 0x05;
+    CMD_ENCRYPTION_INFON = 0x06;
+    CMD_MASTER_IDENTIFICATION = 0x07;
+    CMD_IDENTITY_INFO = 0x08;
+    CMD_IDENTITY_ADDR_INFO = 0x09;
+    CMD_SIGNING_INFO = 0x0A;
+    CMD_SECURITY_REQUEST = 0x0B;
+    CMD_PAIRING_PUBLIC_KEY = 0x0C;
+    CMD_PAIRING_DHKEY_CHECK = 0x0D;
+    CMD_PAIRING_KEYPRESS_INFO = 0x0E;
+}
+
+enum PairingFailReasonEnum {
+    PAIRING_FAIL_REASON_RESERVED = 0x00;
+    PAIRING_FAIL_REASON_PASSKEY_ENTRY = 0x01;
+    PAIRING_FAIL_REASON_OOB = 0x02;
+    PAIRING_FAIL_REASON_AUTH_REQ = 0x03;
+    PAIRING_FAIL_REASON_CONFIRM_VALUE = 0x04;
+    PAIRING_FAIL_REASON_PAIR_NOT_SUPPORT = 0x05;
+    PAIRING_FAIL_REASON_ENC_KEY_SIZE = 0x06;
+    PAIRING_FAIL_REASON_INVALID_CMD = 0x07;
+    PAIRING_FAIL_REASON_UNSPECIFIED = 0x08;
+    PAIRING_FAIL_REASON_REPEATED_ATTEMPTS = 0x09;
+    PAIRING_FAIL_REASON_INVALID_PARAMETERS = 0x0A;
+    PAIRING_FAIL_REASON_DHKEY_CHK = 0x0B;
+    PAIRING_FAIL_REASON_NUMERIC_COMPARISON = 0x0C;
+    PAIRING_FAIL_REASON_CLASSIC_PAIRING_IN_PROGR = 0x0D;
+    PAIRING_FAIL_REASON_XTRANS_DERIVE_NOT_ALLOW = 0x0E;
+}
\ No newline at end of file
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..66cd109 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -436,20 +436,20 @@
         // Ordered GPU debug layer list for GLES
         // i.e. <layer1>:<layer2>:...:<layerN>
         optional SettingProto debug_layers_gles = 7;
-        // GUP - Game Update Package global preference for all Apps
+        // Game Driver - global preference for all Apps
         // 0 = Default
-        // 1 = All Apps use Game Update Package
+        // 1 = All Apps use Game Driver
         // 2 = All Apps use system graphics driver
-        optional SettingProto gup_dev_all_apps = 8;
-        // GUP - List of Apps selected to use Game Update Package
+        optional SettingProto game_driver_all_apps = 8;
+        // Game Driver - List of Apps selected to use Game Driver
         // i.e. <pkg1>,<pkg2>,...,<pkgN>
-        optional SettingProto gup_dev_opt_in_apps = 9;
-        // GUP - List of Apps selected not to use Game Update Package
+        optional SettingProto game_driver_opt_in_apps = 9;
+        // Game Driver - List of Apps selected not to use Game Driver
         // i.e. <pkg1>,<pkg2>,...,<pkgN>
-        optional SettingProto gup_dev_opt_out_apps = 10;
-        // GUP - List of Apps that are forbidden to use Game Update Package
-        optional SettingProto gup_blacklist = 11;
-        // List of Apps that are allowed to use Game Driver package.
+        optional SettingProto game_driver_opt_out_apps = 10;
+        // Game Driver - List of Apps that are forbidden to use Game Driver
+        optional SettingProto game_driver_blacklist = 11;
+        // Game Driver - List of Apps that are allowed to use Game Driver
         optional SettingProto game_driver_whitelist = 12;
         // ANGLE - List of Apps that can check ANGLE rules
         optional SettingProto angle_whitelist = 13;
@@ -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/proto/android/server/job/enums.proto b/core/proto/android/server/job/enums.proto
new file mode 100644
index 0000000..50fc031
--- /dev/null
+++ b/core/proto/android/server/job/enums.proto
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.server.job;
+
+// This file is for JobScheduler enums inside the server directory. If you're
+// adding enums for app-side code, use the file in
+// frameworks/base/core/proto/android/app/job.
+option java_outer_classname = "JobServerProtoEnums";
+option java_multiple_files = true;
+
+// Set of constraints that a job potentially needs satisfied before it can run.
+// Defined in
+// frameworks/base/services/core/java/com/android/server/job/controllers/JobStatus.java
+enum ConstraintEnum {
+    CONSTRAINT_UNKNOWN = 0;
+    CONSTRAINT_CHARGING = 1;
+    CONSTRAINT_BATTERY_NOT_LOW = 2;
+    CONSTRAINT_STORAGE_NOT_LOW = 3;
+    CONSTRAINT_TIMING_DELAY = 4;
+    CONSTRAINT_DEADLINE = 5;
+    CONSTRAINT_IDLE = 6;
+    CONSTRAINT_CONNECTIVITY = 7;
+    CONSTRAINT_CONTENT_TRIGGER = 8;
+    CONSTRAINT_DEVICE_NOT_DOZING = 9;
+    CONSTRAINT_WITHIN_QUOTA = 10;
+    CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 11;
+}
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 188769d..6c9d13a 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -29,6 +29,7 @@
 import "frameworks/base/core/proto/android/os/bundle.proto";
 import "frameworks/base/core/proto/android/os/persistablebundle.proto";
 import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto";
+import "frameworks/base/core/proto/android/server/job/enums.proto";
 import "frameworks/base/libs/incident/proto/android/privacy.proto";
 
 // Next tag: 21
@@ -757,21 +758,9 @@
     }
     optional JobInfo job_info = 6;
 
-    enum Constraint {
-        CONSTRAINT_CHARGING = 1;
-        CONSTRAINT_BATTERY_NOT_LOW = 2;
-        CONSTRAINT_STORAGE_NOT_LOW = 3;
-        CONSTRAINT_TIMING_DELAY = 4;
-        CONSTRAINT_DEADLINE = 5;
-        CONSTRAINT_IDLE = 6;
-        CONSTRAINT_CONNECTIVITY = 7;
-        CONSTRAINT_CONTENT_TRIGGER = 8;
-        CONSTRAINT_DEVICE_NOT_DOZING = 9;
-        CONSTRAINT_WITHIN_QUOTA = 10;
-    }
-    repeated Constraint required_constraints = 7;
-    repeated Constraint satisfied_constraints = 8;
-    repeated Constraint unsatisfied_constraints = 9;
+    repeated ConstraintEnum required_constraints = 7;
+    repeated ConstraintEnum satisfied_constraints = 8;
+    repeated ConstraintEnum unsatisfied_constraints = 9;
     optional bool is_doze_whitelisted = 10;
     optional bool is_uid_active = 26;
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f92df6a..7184c7a 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" />
@@ -2119,7 +2119,7 @@
     <!-- ================================== -->
     <eat-comment />
 
-    <!-- @SystemApi Allows an application to write to internal media storage
+    <!-- @SystemApi @TestApi Allows an application to write to internal media storage
          @hide  -->
     <permission android:name="android.permission.WRITE_MEDIA_STORAGE"
         android:protectionLevel="signature|privileged" />
@@ -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
     -->
@@ -3528,6 +3528,12 @@
         android:protectionLevel="signature|privileged" />
     <uses-permission android:name="android.permission.CONTROL_VPN" />
 
+    <!-- Allows an application to access and modify always-on VPN configuration.
+         <p>Not for use by third-party or privileged applications.
+         @hide -->
+    <permission android:name="android.permission.CONTROL_ALWAYS_ON_VPN"
+        android:protectionLevel="signature" />
+
     <!-- Allows an application to capture audio output.
          <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"
diff --git a/packages/SettingsLib/res/drawable/btn_borderless_rect.xml b/core/res/res/drawable/btn_borderless_rect.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/btn_borderless_rect.xml
rename to core/res/res/drawable/btn_borderless_rect.xml
diff --git a/packages/SettingsLib/res/drawable/ic_bt_cellphone.xml b/core/res/res/drawable/ic_bt_cellphone.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_bt_cellphone.xml
rename to core/res/res/drawable/ic_bt_cellphone.xml
diff --git a/packages/SettingsLib/res/drawable/ic_bt_headphones_a2dp.xml b/core/res/res/drawable/ic_bt_headphones_a2dp.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_bt_headphones_a2dp.xml
rename to core/res/res/drawable/ic_bt_headphones_a2dp.xml
diff --git a/packages/SettingsLib/res/drawable/ic_bt_headset_hfp.xml b/core/res/res/drawable/ic_bt_headset_hfp.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_bt_headset_hfp.xml
rename to core/res/res/drawable/ic_bt_headset_hfp.xml
diff --git a/packages/SettingsLib/res/drawable/ic_bt_hearing_aid.xml b/core/res/res/drawable/ic_bt_hearing_aid.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_bt_hearing_aid.xml
rename to core/res/res/drawable/ic_bt_hearing_aid.xml
diff --git a/packages/SettingsLib/res/drawable/ic_bt_laptop.xml b/core/res/res/drawable/ic_bt_laptop.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_bt_laptop.xml
rename to core/res/res/drawable/ic_bt_laptop.xml
diff --git a/packages/SettingsLib/res/drawable/ic_bt_misc_hid.xml b/core/res/res/drawable/ic_bt_misc_hid.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_bt_misc_hid.xml
rename to core/res/res/drawable/ic_bt_misc_hid.xml
diff --git a/packages/SettingsLib/res/drawable/ic_bt_network_pan.xml b/core/res/res/drawable/ic_bt_network_pan.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_bt_network_pan.xml
rename to core/res/res/drawable/ic_bt_network_pan.xml
diff --git a/packages/SettingsLib/res/drawable/ic_bt_pointing_hid.xml b/core/res/res/drawable/ic_bt_pointing_hid.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_bt_pointing_hid.xml
rename to core/res/res/drawable/ic_bt_pointing_hid.xml
diff --git a/packages/SettingsLib/res/drawable/ic_expand_more.xml b/core/res/res/drawable/ic_expand_more.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_expand_more.xml
rename to core/res/res/drawable/ic_expand_more.xml
diff --git a/packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml b/core/res/res/drawable/ic_lockscreen_ime.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_lockscreen_ime.xml
rename to core/res/res/drawable/ic_lockscreen_ime.xml
diff --git a/packages/SettingsLib/res/drawable/ic_menu.xml b/core/res/res/drawable/ic_menu.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_menu.xml
rename to core/res/res/drawable/ic_menu.xml
diff --git a/packages/SettingsLib/res/drawable/ic_minus.xml b/core/res/res/drawable/ic_minus.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_minus.xml
rename to core/res/res/drawable/ic_minus.xml
diff --git a/packages/SettingsLib/res/drawable/ic_mode_edit.xml b/core/res/res/drawable/ic_mode_edit.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_mode_edit.xml
rename to core/res/res/drawable/ic_mode_edit.xml
diff --git a/packages/SettingsLib/res/drawable/ic_plus.xml b/core/res/res/drawable/ic_plus.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_plus.xml
rename to core/res/res/drawable/ic_plus.xml
diff --git a/packages/SettingsLib/res/drawable/ic_qs_night_display_on.xml b/core/res/res/drawable/ic_qs_night_display_on.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_qs_night_display_on.xml
rename to core/res/res/drawable/ic_qs_night_display_on.xml
diff --git a/packages/SettingsLib/res/drawable/ic_settings_bluetooth.xml b/core/res/res/drawable/ic_settings_bluetooth.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_settings_bluetooth.xml
rename to core/res/res/drawable/ic_settings_bluetooth.xml
diff --git a/packages/SettingsLib/res/drawable/ic_settings_print.xml b/core/res/res/drawable/ic_settings_print.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_settings_print.xml
rename to core/res/res/drawable/ic_settings_print.xml
diff --git a/packages/SettingsLib/res/drawable/ic_signal_location.xml b/core/res/res/drawable/ic_signal_location.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_signal_location.xml
rename to core/res/res/drawable/ic_signal_location.xml
diff --git a/core/res/res/values-night/themes_device_defaults.xml b/core/res/res/values-night/themes_device_defaults.xml
index 43b3552..949c12e 100644
--- a/core/res/res/values-night/themes_device_defaults.xml
+++ b/core/res/res/values-night/themes_device_defaults.xml
@@ -51,6 +51,10 @@
           -->
     <!-- DeviceDefault theme for a window that should look like the Settings app.  -->
     <style name="Theme.DeviceDefault.Settings" parent="Theme.DeviceDefault">
+        <item name="windowLightStatusBar">false</item>
+        <item name="navigationBarColor">@android:color/black</item>
+        <item name="windowLightNavigationBar">false</item>
+
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
         <item name="colorBackground">@color/primary_dark_device_default_settings</item>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 49f2c84..1db8135 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -861,6 +861,11 @@
          The default is false. -->
     <bool name="config_lidControlsSleep">false</bool>
 
+    <!-- Indicate whether closing the lid causes the device to enter the folded state which means
+         to get a smaller screen and opening the lid causes the device to enter the unfolded state
+         which means to get a larger screen. -->
+    <bool name="config_lidControlsDisplayFold">false</bool>
+
     <!-- Desk dock behavior -->
 
     <!-- The number of degrees to rotate the display when the device is in a desk dock.
@@ -2211,6 +2216,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..aefa9df 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1423,6 +1423,27 @@
   <java-symbol type="drawable" name="ic_sim_card_multi_48px_clr" />
   <java-symbol type="drawable" name="ic_signal_cellular_alt_24px" />
 
+  <java-symbol type="drawable" name="btn_borderless_rect" />
+  <java-symbol type="drawable" name="ic_bt_cellphone" />
+  <java-symbol type="drawable" name="ic_bt_headphones_a2dp" />
+  <java-symbol type="drawable" name="ic_bt_headset_hfp" />
+  <java-symbol type="drawable" name="ic_bt_hearing_aid" />
+  <java-symbol type="drawable" name="ic_bt_laptop" />
+  <java-symbol type="drawable" name="ic_bt_misc_hid" />
+  <java-symbol type="drawable" name="ic_bt_network_pan" />
+  <java-symbol type="drawable" name="ic_bt_pointing_hid" />
+  <java-symbol type="drawable" name="ic_expand_more" />
+  <java-symbol type="drawable" name="ic_lockscreen_ime" />
+  <java-symbol type="drawable" name="ic_menu" />
+  <java-symbol type="drawable" name="ic_minus" />
+  <java-symbol type="drawable" name="ic_mode_edit" />
+  <java-symbol type="drawable" name="ic_plus" />
+  <java-symbol type="drawable" name="ic_qs_night_display_on" />
+  <java-symbol type="drawable" name="ic_settings_bluetooth" />
+  <java-symbol type="drawable" name="ic_settings_print" />
+  <java-symbol type="drawable" name="ic_signal_location" />
+  <java-symbol type="drawable" name="ic_info_outline_24" />
+
   <java-symbol type="drawable" name="stat_notify_mmcc_indication_icn" />
   <java-symbol type="drawable" name="autofilled_highlight"/>
   <java-symbol type="drawable" name="ic_camera" />
@@ -3511,6 +3532,7 @@
   <java-symbol type="integer" name="config_defaultRingVibrationIntensity" />
 
   <java-symbol type="bool" name="config_maskMainBuiltInDisplayCutout" />
+  <java-symbol type="bool" name="config_lidControlsDisplayFold" />
 
   <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" />
   <java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" />
@@ -3554,4 +3576,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 0fc3bd2..9d04e63 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -49,10 +49,10 @@
 LOCAL_JAVA_LIBRARIES := \
     android.test.runner \
     telephony-common \
+    testables \
     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..ca2e3ed 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -131,6 +131,10 @@
                     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.BROADCAST_BG_CONSTANTS,
+                    Settings.Global.BROADCAST_FG_CONSTANTS,
+                    Settings.Global.BROADCAST_OFFLOAD_CONSTANTS,
                     Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
                     Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
                     Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
@@ -482,10 +486,10 @@
                     Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS,
                     Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
                     Settings.Global.GLOBAL_SETTINGS_ANGLE_WHITELIST,
-                    Settings.Global.GUP_DEV_ALL_APPS,
-                    Settings.Global.GUP_DEV_OPT_IN_APPS,
-                    Settings.Global.GUP_DEV_OPT_OUT_APPS,
-                    Settings.Global.GUP_BLACKLIST,
+                    Settings.Global.GAME_DRIVER_ALL_APPS,
+                    Settings.Global.GAME_DRIVER_OPT_IN_APPS,
+                    Settings.Global.GAME_DRIVER_OPT_OUT_APPS,
+                    Settings.Global.GAME_DRIVER_BLACKLIST,
                     Settings.Global.GAME_DRIVER_WHITELIST,
                     Settings.Global.GPU_DEBUG_LAYER_APP,
                     Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
@@ -574,6 +578,7 @@
                  Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
                  Settings.Secure.ALWAYS_ON_VPN_APP,
                  Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
+                 Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST,
                  Settings.Secure.ANDROID_ID,
                  Settings.Secure.ANR_SHOW_BACKGROUND,
                  Settings.Secure.ASSISTANT,
diff --git a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
index 4aa1000..c99777b 100644
--- a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
+++ b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
@@ -77,7 +77,6 @@
     @Test
     public void testLogMaker() {
         final LogMaker logMaker = getNotification(PKG, GROUP_ID_1, CHANNEL_ID).getLogMaker();
-
         assertEquals(CHANNEL_ID,
                 (String) logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID));
         assertEquals(PKG, logMaker.getPackageName());
@@ -85,6 +84,18 @@
         assertEquals(TAG, logMaker.getTaggedData(MetricsEvent.NOTIFICATION_TAG));
         assertEquals(GROUP_ID_1,
                 logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
+        assertEquals(0,
+                logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY));
+        assertNull(logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CATEGORY));
+    }
+
+    @Test
+    public void testLogMakerWithCategory() {
+        Notification.Builder builder = getNotificationBuilder(GROUP_ID_1, CHANNEL_ID)
+                        .setCategory(Notification.CATEGORY_MESSAGE);
+        final LogMaker logMaker = getNotification(PKG, builder).getLogMaker();
+        assertEquals(Notification.CATEGORY_MESSAGE,
+                logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_CATEGORY));
     }
 
     @Test
@@ -138,6 +149,10 @@
     }
 
     private StatusBarNotification getNotification(String pkg, String group, String channelId) {
+        return getNotification(pkg, getNotificationBuilder(group, channelId));
+    }
+
+    private Notification.Builder getNotificationBuilder(String group, String channelId) {
         final Notification.Builder builder = new Notification.Builder(mMockContext, channelId)
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
@@ -145,10 +160,13 @@
         if (group != null) {
             builder.setGroup(group);
         }
+        return builder;
+    }
 
-        Notification n = builder.build();
+    private StatusBarNotification getNotification(String pkg, Notification.Builder builder) {
+
         return new StatusBarNotification(
-                pkg, pkg, ID, TAG, UID, UID, n, USER, null, UID);
+                pkg, pkg, ID, TAG, UID, UID, builder.build(), USER, null, UID);
     }
 
 }
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
new file mode 100644
index 0000000..b07cb99
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.view;
+
+import static android.view.ImeInsetsSourceConsumer.areEditorsSimilar;
+import static android.view.InsetsState.TYPE_IME;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl.Transaction;
+import android.view.WindowManager.BadTokenException;
+import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.EditorInfo;
+import android.widget.TextView;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@Presubmit
+@FlakyTest(detail = "Promote once confirmed non-flaky")
+@RunWith(AndroidJUnit4.class)
+public class ImeInsetsSourceConsumerTest {
+
+    Context mContext = InstrumentationRegistry.getTargetContext();
+    ImeInsetsSourceConsumer mImeConsumer;
+    InsetsController mController;
+    SurfaceControl mLeash;
+
+    @Before
+    public void setup() {
+        mLeash = new SurfaceControl.Builder(new SurfaceSession())
+                .setName("testSurface")
+                .build();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            ViewRootImpl viewRootImpl = new ViewRootImpl(mContext, mContext.getDisplay());
+            try {
+                viewRootImpl.setView(new TextView(mContext), new LayoutParams(), null);
+            } catch (BadTokenException e) {
+                // activity isn't running, we will ignore BadTokenException.
+            }
+            mController = new InsetsController(viewRootImpl);
+            final Rect rect = new Rect(5, 5, 5, 5);
+            mController.calculateInsets(
+                    false,
+                    false,
+                    new DisplayCutout(
+                            Insets.of(10, 10, 10, 10), rect, rect, rect, rect),
+                    rect, rect);
+            mImeConsumer = new ImeInsetsSourceConsumer(
+                    new InsetsState(), Transaction::new, mController);
+        });
+    }
+
+    @Test
+    public void testImeVisibility() {
+        final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash);
+        mController.onControlsChanged(new InsetsSourceControl[] { ime });
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            // test if setVisibility can show IME
+            mImeConsumer.onWindowFocusGained();
+            mImeConsumer.applyImeVisibility(true);
+            mController.cancelExistingAnimation();
+            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+
+            // test if setVisibility can hide IME
+            mImeConsumer.applyImeVisibility(false);
+            mController.cancelExistingAnimation();
+            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+        });
+    }
+
+    @Test
+    public void testAreEditorsSimilar() {
+        EditorInfo info1 = new EditorInfo();
+        info1.privateImeOptions = "dummy";
+        EditorInfo info2 = new EditorInfo();
+
+        assertFalse(areEditorsSimilar(info1, info2));
+
+        info1.privateImeOptions = null;
+        assertTrue(areEditorsSimilar(info1, info2));
+
+        info1.inputType = info2.inputType = 3;
+        info1.imeOptions = info2.imeOptions = 0x4;
+        info1.packageName = info2.packageName = "dummy.package";
+        assertTrue(areEditorsSimilar(info1, info2));
+
+        Bundle extras1 = new Bundle();
+        extras1.putByteArray("key1", "value1".getBytes());
+        extras1.putChar("key2", 'c');
+        Bundle extras2 = new Bundle();
+        extras2.putByteArray("key1", "value1".getBytes());
+        extras2.putChar("key2", 'c');
+        info1.extras = extras1;
+        info2.extras = extras2;
+        assertTrue(areEditorsSimilar(info1, info2));
+
+        Bundle extraBundle = new Bundle();
+        ArrayList<Integer> list = new ArrayList<>();
+        list.add(2);
+        list.add(5);
+        extraBundle.putByteArray("key1", "value1".getBytes());
+        extraBundle.putChar("key2", 'c');
+        extraBundle.putIntegerArrayList("key3", list);
+
+        extras1.putAll(extraBundle);
+        extras2.putAll(extraBundle);
+        assertTrue(areEditorsSimilar(info1, info2));
+
+        extras2.putChar("key2", 'd');
+        assertFalse(areEditorsSimilar(info1, info2));
+
+        extras2.putChar("key2", 'c');
+        extras2.putInt("key4", 1);
+        assertFalse(areEditorsSimilar(info1, info2));
+    }
+}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index d447451..8f21096 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -16,15 +16,25 @@
 
 package android.view;
 
+import static android.view.InsetsState.TYPE_IME;
+import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.TYPE_TOP_BAR;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Mockito.mock;
-
+import android.content.Context;
+import android.graphics.Insets;
+import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.view.WindowInsets.Type;
+import android.view.WindowManager.BadTokenException;
+import android.view.WindowManager.LayoutParams;
+import android.widget.TextView;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -37,8 +47,7 @@
 @RunWith(AndroidJUnit4.class)
 public class InsetsControllerTest {
 
-    private InsetsController mController = new InsetsController(mock(ViewRootImpl.class));
-
+    private InsetsController mController;
     private SurfaceSession mSession = new SurfaceSession();
     private SurfaceControl mLeash;
 
@@ -47,6 +56,24 @@
         mLeash = new SurfaceControl.Builder(mSession)
                 .setName("testSurface")
                 .build();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            Context context = InstrumentationRegistry.getTargetContext();
+            // cannot mock ViewRootImpl since it's final.
+            ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
+            try {
+                viewRootImpl.setView(new TextView(context), new LayoutParams(), null);
+            } catch (BadTokenException e) {
+                // activity isn't running, we will ignore BadTokenException.
+            }
+            mController = new InsetsController(viewRootImpl);
+            final Rect rect = new Rect(5, 5, 5, 5);
+            mController.calculateInsets(
+                    false,
+                    false,
+                    new DisplayCutout(
+                            Insets.of(10, 10, 10, 10), rect, rect, rect, rect),
+                    rect, rect);
+        });
     }
 
     @Test
@@ -64,4 +91,39 @@
         mController.onControlsChanged(new InsetsSourceControl[0]);
         assertNull(mController.getSourceConsumer(TYPE_TOP_BAR).getControl());
     }
+
+    @Test
+    public void testAnimationEndState() {
+        final InsetsSourceControl navBar = new InsetsSourceControl(TYPE_NAVIGATION_BAR, mLeash);
+        final InsetsSourceControl topBar = new InsetsSourceControl(TYPE_TOP_BAR, mLeash);
+        final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash);
+
+        InsetsSourceControl[] controls = new InsetsSourceControl[3];
+        controls[0] = navBar;
+        controls[1] = topBar;
+        controls[2] = ime;
+        mController.onControlsChanged(controls);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mController.show(Type.all());
+            // quickly jump to final state by cancelling it.
+            mController.cancelExistingAnimation();
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(topBar.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+
+            mController.hide(Type.all());
+            mController.cancelExistingAnimation();
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+
+            mController.show(Type.ime());
+            mController.cancelExistingAnimation();
+            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+
+            mController.hide(Type.ime());
+            mController.cancelExistingAnimation();
+            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+        });
+    }
 }
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 2db2f5f..03af67d 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -155,10 +155,10 @@
 
     @Test
     public void testGetDefaultVisibility() {
-        assertTrue(InsetsState.getDefaultVisibly(TYPE_TOP_BAR));
-        assertTrue(InsetsState.getDefaultVisibly(TYPE_SIDE_BAR_1));
-        assertTrue(InsetsState.getDefaultVisibly(TYPE_SIDE_BAR_2));
-        assertTrue(InsetsState.getDefaultVisibly(TYPE_SIDE_BAR_3));
-        assertFalse(InsetsState.getDefaultVisibly(TYPE_IME));
+        assertTrue(InsetsState.getDefaultVisibility(TYPE_TOP_BAR));
+        assertTrue(InsetsState.getDefaultVisibility(TYPE_SIDE_BAR_1));
+        assertTrue(InsetsState.getDefaultVisibility(TYPE_SIDE_BAR_2));
+        assertTrue(InsetsState.getDefaultVisibility(TYPE_SIDE_BAR_3));
+        assertFalse(InsetsState.getDefaultVisibility(TYPE_IME));
     }
 }
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index d57fa8f..6a83c29b 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -20,6 +20,7 @@
 import static android.view.WindowInsets.Type.sideBars;
 import static android.view.WindowInsets.Type.topBar;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.graphics.Insets;
@@ -73,4 +74,29 @@
         assertEquals(Insets.of(0, 50, 0, 0), insets.getInsets(topBar()));
         assertEquals(Insets.of(0, 0, 30, 10), insets.getInsets(sideBars()));
     }
+
+    // TODO: Move this to CTS once API made public
+    @Test
+    public void visibility() {
+        Builder b = new WindowInsets.Builder();
+        b.setInsets(sideBars(), Insets.of(0, 0, 0, 100));
+        b.setInsets(ime(), Insets.of(0, 0, 0, 300));
+        b.setVisible(sideBars(), true);
+        b.setVisible(ime(), true);
+        WindowInsets insets = b.build();
+        assertTrue(insets.isVisible(sideBars()));
+        assertTrue(insets.isVisible(sideBars() | ime()));
+        assertFalse(insets.isVisible(sideBars() | topBar()));
+    }
+
+    // TODO: Move this to CTS once API made public
+    @Test
+    public void consume_doesntChangeVisibility() {
+        Builder b = new WindowInsets.Builder();
+        b.setInsets(ime(), Insets.of(0, 0, 0, 300));
+        b.setVisible(ime(), true);
+        WindowInsets insets = b.build();
+        insets = insets.consumeSystemWindowInsets();
+        assertTrue(insets.isVisible(ime()));
+    }
 }
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
new file mode 100644
index 0000000..68099fe
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.os.Parcel;
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Supplemental tests that cannot be covered by CTS (e.g. due to hidden API dependencies).
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class EditorInfoTest {
+    private static final int TEST_USER_ID = 42;
+
+    /**
+     * Makes sure that {@code null} {@link EditorInfo#targetInputMethodUser} can be copied via
+     * {@link Parcel}.
+     */
+    @Test
+    public void testNullTargetInputMethodUserParcelable() throws Exception {
+        final EditorInfo editorInfo = new EditorInfo();
+        editorInfo.targetInputMethodUser = null;
+        assertNull(cloneViaParcel(editorInfo).targetInputMethodUser);
+    }
+
+    /**
+     * Makes sure that non-{@code null} {@link EditorInfo#targetInputMethodUser} can be copied via
+     * {@link Parcel}.
+     */
+    @Test
+    public void testNonNullTargetInputMethodUserParcelable() throws Exception {
+        final EditorInfo editorInfo = new EditorInfo();
+        editorInfo.targetInputMethodUser = UserHandle.of(TEST_USER_ID);
+        assertEquals(UserHandle.of(TEST_USER_ID), cloneViaParcel(editorInfo).targetInputMethodUser);
+    }
+
+    private static EditorInfo cloneViaParcel(EditorInfo original) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            original.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            return EditorInfo.CREATOR.createFromParcel(parcel);
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
index 780e15a..5022e30 100644
--- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
@@ -16,8 +16,8 @@
 
 package android.view.textclassifier;
 
-import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_LOCAL;
-import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_REMOTE;
+import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_OTHERS;
+import static android.view.textclassifier.ConversationActions.Message.PERSON_USER_SELF;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -58,7 +58,7 @@
     @Test
     public void testToNativeMessages_noTextMessages() {
         ConversationActions.Message messageWithoutText =
-                new ConversationActions.Message.Builder(PERSON_USER_REMOTE).build();
+                new ConversationActions.Message.Builder(PERSON_USER_OTHERS).build();
 
         ActionsSuggestionsModel.ConversationMessage[] conversationMessages =
                 ActionsSuggestionsHelper.toNativeMessages(
@@ -81,7 +81,7 @@
                         .setText("second")
                         .build();
         ConversationActions.Message thirdMessage =
-                new ConversationActions.Message.Builder(PERSON_USER_LOCAL)
+                new ConversationActions.Message.Builder(PERSON_USER_SELF)
                         .setText("third")
                         .build();
         ConversationActions.Message fourthMessage =
@@ -104,16 +104,16 @@
     @Test
     public void testToNativeMessages_referenceTime() {
         ConversationActions.Message firstMessage =
-                new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
+                new ConversationActions.Message.Builder(PERSON_USER_OTHERS)
                         .setText("first")
                         .setReferenceTime(createZonedDateTimeFromMsUtc(1000))
                         .build();
         ConversationActions.Message secondMessage =
-                new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
+                new ConversationActions.Message.Builder(PERSON_USER_OTHERS)
                         .setText("second")
                         .build();
         ConversationActions.Message thirdMessage =
-                new ConversationActions.Message.Builder(PERSON_USER_REMOTE)
+                new ConversationActions.Message.Builder(PERSON_USER_OTHERS)
                         .setText("third")
                         .setReferenceTime(createZonedDateTimeFromMsUtc(2000))
                         .build();
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/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
index 4d78e40..5e58f82 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierTest.java
@@ -378,7 +378,7 @@
         if (isTextClassifierDisabled()) return;
         ConversationActions.Message message =
                 new ConversationActions.Message.Builder(
-                        ConversationActions.Message.PERSON_USER_REMOTE)
+                        ConversationActions.Message.PERSON_USER_OTHERS)
                         .setText("Where are you?")
                         .build();
         TextClassifier.EntityConfig typeConfig =
@@ -407,7 +407,7 @@
         if (isTextClassifierDisabled()) return;
         ConversationActions.Message message =
                 new ConversationActions.Message.Builder(
-                        ConversationActions.Message.PERSON_USER_REMOTE)
+                        ConversationActions.Message.PERSON_USER_OTHERS)
                         .setText("Where are you?")
                         .build();
         TextClassifier.EntityConfig typeConfig =
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 904c3fb..1684138 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -87,6 +87,7 @@
         <permission name="android.permission.MASTER_CLEAR"/>
         <permission name="android.permission.NETWORK_MANAGED_PROVISIONING"/>
         <permission name="android.permission.PERFORM_CDMA_PROVISIONING"/>
+        <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
         <permission name="android.permission.SET_TIME"/>
         <permission name="android.permission.SET_TIME_ZONE"/>
         <permission name="android.permission.SHUTDOWN"/>
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 8636949..f0e2361 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1751,6 +1751,50 @@
     }
 
     /**
+     * <p>Modifies the bitmap to have the specified {@link ColorSpace}, without
+     * affecting the underlying allocation backing the bitmap.</p>
+     *
+     * @throws IllegalArgumentException If the specified color space is {@code null}, not
+     *         {@link ColorSpace.Model#RGB RGB}, has a transfer function that is not an
+     *         {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or whose
+     *         components min/max values reduce the numerical range compared to the
+     *         previously assigned color space.
+     *
+     * @param colorSpace to assign to the bitmap
+     * @hide
+     */
+    @TestApi
+    public void setColorSpace(@NonNull ColorSpace colorSpace) {
+        checkRecycled("setColorSpace called on a recycled bitmap");
+        if (colorSpace == null) {
+            throw new IllegalArgumentException("The colorSpace cannot be set to null");
+        }
+        if (getColorSpace() != null) {
+            if (mColorSpace.getComponentCount() != colorSpace.getComponentCount()) {
+                throw new IllegalArgumentException("The new ColorSpace must have the same "
+                        + "component count as the current ColorSpace");
+            }
+            for (int i = 0; i < mColorSpace.getComponentCount(); i++) {
+                if (mColorSpace.getMinValue(i) < colorSpace.getMinValue(i)) {
+                    throw new IllegalArgumentException("The new ColorSpace cannot increase the "
+                            + "minimum value for any of the components compared to the current "
+                            + "ColorSpace. To perform this type of conversion create a new Bitmap "
+                            + "in the desired ColorSpace and draw this Bitmap into it.");
+                }
+                if (mColorSpace.getMaxValue(i) > colorSpace.getMaxValue(i)) {
+                    throw new IllegalArgumentException("The new ColorSpace cannot decrease the "
+                            + "maximum value for any of the components compared to the current "
+                            + "ColorSpace/ To perform this type of conversion create a new Bitmap"
+                            + "in the desired ColorSpace and draw this Bitmap into it.");
+                }
+            }
+        }
+
+        nativeSetColorSpace(mNativePtr, colorSpace.getNativeInstance());
+        mColorSpace = colorSpace;
+    }
+
+    /**
      * Fills the bitmap's pixels with the specified {@link Color}.
      *
      * @throws IllegalStateException if the bitmap is not mutable.
@@ -2174,6 +2218,7 @@
                                                                 long nativeColorSpace);
     private static native GraphicBuffer nativeCreateGraphicBufferHandle(long nativeBitmap);
     private static native boolean nativeGetColorSpace(long nativePtr, float[] xyz, float[] params);
+    private static native void nativeSetColorSpace(long nativePtr, long nativeColorSpace);
     private static native boolean nativeIsSRGB(long nativePtr);
     private static native boolean nativeIsSRGBLinear(long nativePtr);
     private static native void nativeCopyColorSpace(long srcBitmap, long dstBitmap);
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/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index c580c46..0787d85 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -183,6 +183,14 @@
     public static final int JPEG = 0x100;
 
     /**
+     * Depth augmented compressed JPEG format.
+     *
+     * <p>JPEG compressed main image along with XMP embedded depth metadata
+     * following ISO 16684-1:2011(E).</p>
+     */
+    public static final int DEPTH_JPEG = 0x69656963;
+
+    /**
      * <p>Multi-plane Android YUV 420 format</p>
      *
      * <p>This format is a generic YCbCr format, capable of describing any 4:2:0
@@ -787,6 +795,7 @@
             case PRIVATE:
             case RAW_DEPTH:
             case Y8:
+            case DEPTH_JPEG:
                 return true;
         }
 
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 25f6775..cb1f69c 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -33,6 +33,7 @@
 import android.graphics.fonts.FontVariationAxis;
 import android.graphics.fonts.SystemFonts;
 import android.os.Build;
+import android.os.Build.VERSION_CODES;
 import android.provider.FontRequest;
 import android.provider.FontsContract;
 import android.text.FontConfig;
@@ -46,6 +47,7 @@
 import com.android.internal.util.Preconditions;
 
 import dalvik.annotation.optimization.CriticalNative;
+import dalvik.system.VMRuntime;
 
 import libcore.util.NativeAllocationRegistry;
 
@@ -243,7 +245,16 @@
                 if (familyBuilder == null) {
                     familyBuilder = new FontFamily.Builder(fontBuilder.build());
                 } else {
-                    familyBuilder.addFont(fontBuilder.build());
+                    try {
+                        familyBuilder.addFont(fontBuilder.build());
+                    } catch (IllegalArgumentException e) {
+                        if (VMRuntime.getRuntime().getTargetSdkVersion() <= VERSION_CODES.P) {
+                            // Surpress the IllegalArgumentException for keeping the backward
+                            // compatibility.
+                            continue;
+                        }
+                        throw e;
+                    }
                 }
             }
             if (familyBuilder == null) {
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index 072fe73..2ae28f5 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -16,12 +16,6 @@
 
 package android.security;
 
-import android.annotation.UnsupportedAppUsage;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
 import com.android.org.bouncycastle.util.io.pem.PemObject;
 import com.android.org.bouncycastle.util.io.pem.PemReader;
 import com.android.org.bouncycastle.util.io.pem.PemWriter;
@@ -34,7 +28,6 @@
 import java.io.Reader;
 import java.io.Writer;
 import java.nio.charset.StandardCharsets;
-import java.security.KeyPair;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
@@ -53,8 +46,6 @@
 
     public static final String INSTALL_AS_USER_ACTION = "android.credentials.INSTALL_AS_USER";
 
-    public static final String UNLOCK_ACTION = "com.android.credentials.UNLOCK";
-
     /** Key prefix for CA certificates. */
     public static final String CA_CERTIFICATE = "CACERT_";
 
@@ -171,58 +162,6 @@
         }
     }
 
-    private static Credentials singleton;
-
-    @UnsupportedAppUsage
-    public static Credentials getInstance() {
-        if (singleton == null) {
-            singleton = new Credentials();
-        }
-        return singleton;
-    }
-
-    @UnsupportedAppUsage
-    public void unlock(Context context) {
-        try {
-            Intent intent = new Intent(UNLOCK_ACTION);
-            context.startActivity(intent);
-        } catch (ActivityNotFoundException e) {
-            Log.w(LOGTAG, e.toString());
-        }
-    }
-
-    public void install(Context context) {
-        try {
-            Intent intent = KeyChain.createInstallIntent();
-            context.startActivity(intent);
-        } catch (ActivityNotFoundException e) {
-            Log.w(LOGTAG, e.toString());
-        }
-    }
-
-    @UnsupportedAppUsage
-    public void install(Context context, KeyPair pair) {
-        try {
-            Intent intent = KeyChain.createInstallIntent();
-            intent.putExtra(EXTRA_PRIVATE_KEY, pair.getPrivate().getEncoded());
-            intent.putExtra(EXTRA_PUBLIC_KEY, pair.getPublic().getEncoded());
-            context.startActivity(intent);
-        } catch (ActivityNotFoundException e) {
-            Log.w(LOGTAG, e.toString());
-        }
-    }
-
-    @UnsupportedAppUsage
-    public void install(Context context, String type, byte[] value) {
-        try {
-            Intent intent = KeyChain.createInstallIntent();
-            intent.putExtra(type, value);
-            context.startActivity(intent);
-        } catch (ActivityNotFoundException e) {
-            Log.w(LOGTAG, e.toString());
-        }
-    }
-
     /**
      * Delete all types (private key, user certificate, CA certificate) for a
      * particular {@code alias}. All three can exist for any given alias.
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 3c35d9b..20303eb 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -562,7 +562,7 @@
   if (package != nullptr) {
     ToResourceName(last_resolution.type_string_ref,
                    last_resolution.entry_string_ref,
-                   package,
+                   package->GetPackageName(),
                    &resource_name);
     resource_name_string = ToFormattedResourceString(&resource_name);
   }
@@ -607,15 +607,25 @@
     return false;
   }
 
-  const LoadedPackage* package =
-      apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid));
-  if (package == nullptr) {
+  const uint8_t package_idx = package_ids_[get_package_id(resid)];
+  if (package_idx == 0xff) {
+    LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.",
+                                     get_package_id(resid), resid);
     return false;
   }
 
+  const PackageGroup& package_group = package_groups_[package_idx];
+  auto cookie_iter = std::find(package_group.cookies_.begin(),
+                               package_group.cookies_.end(), cookie);
+  if (cookie_iter == package_group.cookies_.end()) {
+    return false;
+  }
+
+  long package_pos = std::distance(package_group.cookies_.begin(), cookie_iter);
+  const LoadedPackage* package = package_group.packages_[package_pos].loaded_package_;
   return ToResourceName(entry.type_string_ref,
                         entry.entry_string_ref,
-                        package,
+                        package->GetPackageName(),
                         out_name);
 }
 
diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp
index 645984d..c63dff8 100644
--- a/libs/androidfw/ResourceUtils.cpp
+++ b/libs/androidfw/ResourceUtils.cpp
@@ -48,12 +48,12 @@
          !(has_type_separator && out_type->empty());
 }
 
-bool ToResourceName(StringPoolRef& type_string_ref,
-                    StringPoolRef& entry_string_ref,
-                    const LoadedPackage* package,
+bool ToResourceName(const StringPoolRef& type_string_ref,
+                    const StringPoolRef& entry_string_ref,
+                    const StringPiece& package_name,
                     AssetManager2::ResourceName* out_name) {
-  out_name->package = package->GetPackageName().data();
-  out_name->package_len = package->GetPackageName().size();
+  out_name->package = package_name.data();
+  out_name->package_len = package_name.size();
 
   out_name->type = type_string_ref.string8(&out_name->type_len);
   out_name->type16 = nullptr;
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index eb6eb8e..e649940 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -28,12 +28,11 @@
 bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
                          StringPiece* out_entry);
 
-// Convert a type_string_ref, entry_string_ref, and package
-// to AssetManager2::ResourceName. Useful for getting
-// resource name without re-running AssetManager2::FindEntry searches.
-bool ToResourceName(StringPoolRef& type_string_ref,
-                    StringPoolRef& entry_string_ref,
-                    const LoadedPackage* package,
+// Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName.
+// Useful for getting resource name without re-running AssetManager2::FindEntry searches.
+bool ToResourceName(const StringPoolRef& type_string_ref,
+                    const StringPoolRef& entry_string_ref,
+                    const StringPiece& package_name,
                     AssetManager2::ResourceName* out_name);
 
 // Formats a ResourceName to "package:type/entry_name".
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 105dcd2..447fdf5 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -210,6 +210,16 @@
   EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data);
 }
 
+TEST_F(AssetManager2Test, GetSharedLibraryResourceName) {
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({lib_one_assets_.get()});
+
+  AssetManager2::ResourceName name;
+  ASSERT_TRUE(assetmanager.GetResourceName(lib_one::R::string::foo, &name));
+  std::string formatted_name = ToFormattedResourceString(&name);
+  ASSERT_EQ(formatted_name, "com.android.lib_one:string/foo");
+}
+
 TEST_F(AssetManager2Test, FindsBagResourceFromSingleApkAssets) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({basic_assets_.get()});
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index cc62fdc..54a91f4 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -682,12 +682,11 @@
                             float y, float boundsLeft, float boundsTop, float boundsRight,
                             float boundsBottom, float totalAdvance) {
     if (count <= 0 || paint.nothingToDraw()) return;
-    SkPaint paintCopy(paint);
+    Paint paintCopy(paint);
     if (mPaintFilter) {
-        mPaintFilter->filter(&paintCopy);
+        mPaintFilter->filterFullPaint(&paintCopy);
     }
-    SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy);
-    SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding);
+    const SkFont& font = paintCopy.getSkFont();
     // Stroke with a hairline is drawn on HW with a fill style for compatibility with Android O and
     // older.
     if (!mCanvasOwned && sApiLevel <= 27 && paintCopy.getStrokeWidth() <= 0 &&
@@ -710,12 +709,11 @@
 void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
                                   const Paint& paint, const SkPath& path, size_t start,
                                   size_t end) {
-    SkPaint paintCopy(paint);
+    Paint paintCopy(paint);
     if (mPaintFilter) {
-        mPaintFilter->filter(&paintCopy);
+        mPaintFilter->filterFullPaint(&paintCopy);
     }
-    SkFont font = SkFont::LEGACY_ExtractFromPaint(paintCopy);
-    SkASSERT(paintCopy.getTextEncoding() == kGlyphID_SkTextEncoding);
+    const SkFont& font = paintCopy.getSkFont();
 
     const int N = end - start;
     SkTextBlobBuilder builder;
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 277148e..5231486 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -39,34 +39,28 @@
 }
 
 void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) {
-    uint32_t flags;
-    PaintFilter* paintFilter = getPaintFilter();
-    if (paintFilter) {
-        SkPaint paintCopy(paint);
-        paintFilter->filter(&paintCopy);
-        flags = paintCopy.getFlags();
-    } else {
-        flags = paint.getFlags();
-    }
-    if (flags & (SkPaint::kUnderlineText_ReserveFlag | SkPaint::kStrikeThruText_ReserveFlag)) {
+    // paint has already been filtered by our caller, so we can ignore any filter
+    const bool strikeThru = paint.isStrikeThru();
+    const bool underline = paint.isUnderline();
+    if (strikeThru || underline) {
         const SkScalar left = x;
         const SkScalar right = x + length;
-        if (flags & SkPaint::kUnderlineText_ReserveFlag) {
+        const float textSize = paint.getSkFont().getSize();
+        if (underline) {
             SkFontMetrics metrics;
-            paint.getFontMetrics(&metrics);
+            paint.getSkFont().getMetrics(&metrics);
             SkScalar position;
             if (!metrics.hasUnderlinePosition(&position)) {
-                position = paint.getTextSize() * Paint::kStdUnderline_Top;
+                position = textSize * Paint::kStdUnderline_Top;
             }
             SkScalar thickness;
             if (!metrics.hasUnderlineThickness(&thickness)) {
-                thickness = paint.getTextSize() * Paint::kStdUnderline_Thickness;
+                thickness = textSize * Paint::kStdUnderline_Thickness;
             }
             const SkScalar top = y + position;
             drawStroke(left, right, top, thickness, paint, this);
         }
-        if (flags & SkPaint::kStrikeThruText_ReserveFlag) {
-            const float textSize = paint.getTextSize();
+        if (strikeThru) {
             const float position = textSize * Paint::kStdStrikeThru_Top;
             const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness;
             const SkScalar top = y + position;
@@ -75,19 +69,19 @@
     }
 }
 
-static void simplifyPaint(int color, SkPaint* paint) {
+static void simplifyPaint(int color, Paint* paint) {
     paint->setColor(color);
     paint->setShader(nullptr);
     paint->setColorFilter(nullptr);
     paint->setLooper(nullptr);
-    paint->setStrokeWidth(4 + 0.04 * paint->getTextSize());
+    paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
     paint->setStrokeJoin(SkPaint::kRound_Join);
     paint->setLooper(nullptr);
 }
 
 class DrawTextFunctor {
 public:
-    DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const SkPaint& paint, float x,
+    DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
                     float y, minikin::MinikinRect& bounds, float totalAdvance)
             : layout(layout)
             , canvas(canvas)
@@ -123,14 +117,14 @@
             bool darken = channelSum < (128 * 3);
 
             // outline
-            SkPaint outlinePaint(paint);
+            Paint outlinePaint(paint);
             simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
             outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
             canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, bounds.mLeft, bounds.mTop,
                                bounds.mRight, bounds.mBottom, totalAdvance);
 
             // inner
-            SkPaint innerPaint(paint);
+            Paint innerPaint(paint);
             simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
             innerPaint.setStyle(SkPaint::kFill_Style);
             canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, bounds.mLeft, bounds.mTop,
@@ -145,7 +139,7 @@
 private:
     const minikin::Layout& layout;
     Canvas* canvas;
-    const SkPaint& paint;
+    const Paint& paint;
     float x;
     float y;
     minikin::MinikinRect& bounds;
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 84292c8..375f5bc 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -17,8 +17,9 @@
 #include "MinikinSkia.h"
 
 #include <SkFontDescriptor.h>
+#include <SkFont.h>
+#include <SkFontMetrics.h>
 #include <SkFontMgr.h>
-#include <SkPaint.h>
 #include <SkTypeface.h>
 #include <log/log.h>
 
@@ -40,25 +41,24 @@
         , mAxes(axes)
         , mFilePath(filePath) {}
 
-static void MinikinFontSkia_SetSkiaPaint(const minikin::MinikinFont* font, SkPaint* skPaint,
-                                         const minikin::MinikinPaint& paint,
-                                         const minikin::FontFakery& fakery) {
-    skPaint->setTextEncoding(kGlyphID_SkTextEncoding);
-    skPaint->setTextSize(paint.size);
-    skPaint->setTextScaleX(paint.scaleX);
-    skPaint->setTextSkewX(paint.skewX);
-    MinikinFontSkia::unpackPaintFlags(skPaint, paint.paintFlags);
+static void MinikinFontSkia_SetSkiaFont(const minikin::MinikinFont* font, SkFont* skFont,
+                                        const minikin::MinikinPaint& paint,
+                                        const minikin::FontFakery& fakery) {
+    skFont->setSize(paint.size);
+    skFont->setScaleX(paint.scaleX);
+    skFont->setSkewX(paint.skewX);
+    MinikinFontSkia::unpackFontFlags(skFont, paint.fontFlags);
     // Apply font fakery on top of user-supplied flags.
-    MinikinFontSkia::populateSkPaint(skPaint, font, fakery);
+    MinikinFontSkia::populateSkFont(skFont, font, fakery);
 }
 
 float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, const minikin::MinikinPaint& paint,
                                             const minikin::FontFakery& fakery) const {
-    SkPaint skPaint;
+    SkFont skFont;
     uint16_t glyph16 = glyph_id;
     SkScalar skWidth;
-    MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery);
-    skPaint.getTextWidths(&glyph16, sizeof(glyph16), &skWidth, NULL);
+    MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery);
+    skFont.getWidths(&glyph16, 1, &skWidth);
 #ifdef VERBOSE
     ALOGD("width for typeface %d glyph %d = %f", mTypeface->uniqueID(), glyph_id, skWidth);
 #endif
@@ -68,11 +68,11 @@
 void MinikinFontSkia::GetBounds(minikin::MinikinRect* bounds, uint32_t glyph_id,
                                 const minikin::MinikinPaint& paint,
                                 const minikin::FontFakery& fakery) const {
-    SkPaint skPaint;
+    SkFont skFont;
     uint16_t glyph16 = glyph_id;
     SkRect skBounds;
-    MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery);
-    skPaint.getTextWidths(&glyph16, sizeof(glyph16), NULL, &skBounds);
+    MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery);
+    skFont.getWidths(&glyph16, 1, nullptr, &skBounds);
     bounds->mLeft = skBounds.fLeft;
     bounds->mTop = skBounds.fTop;
     bounds->mRight = skBounds.fRight;
@@ -82,10 +82,10 @@
 void MinikinFontSkia::GetFontExtent(minikin::MinikinExtent* extent,
                                     const minikin::MinikinPaint& paint,
                                     const minikin::FontFakery& fakery) const {
-    SkPaint skPaint;
-    MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery);
+    SkFont skFont;
+    MinikinFontSkia_SetSkiaFont(this, &skFont, paint, fakery);
     SkFontMetrics metrics;
-    skPaint.getFontMetrics(&metrics);
+    skFont.getMetrics(&metrics);
     extent->ascent = metrics.fAscent;
     extent->descent = metrics.fDescent;
 }
@@ -137,28 +137,36 @@
                                              ttcIndex, variations);
 }
 
-uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) {
-    uint32_t flags = paint->getFlags();
-    unsigned hinting = static_cast<unsigned>(paint->getHinting());
-    // select only flags that might affect text layout
-    flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag |
-              SkPaint::kSubpixelText_Flag | SkPaint::kEmbeddedBitmapText_Flag |
-              SkPaint::kAutoHinting_Flag);
-    flags |= (hinting << 16);
+// hinting<<16 | edging<<8 | bools:5bits
+uint32_t MinikinFontSkia::packFontFlags(const SkFont& font) {
+    uint32_t flags = (unsigned)font.getHinting() << 16;
+    flags |= (unsigned)font.getEdging() << 8;
+    flags |= font.isEmbolden()          << minikin::Embolden_Shift;
+    flags |= font.isLinearMetrics()     << minikin::LinearMetrics_Shift;
+    flags |= font.isSubpixel()          << minikin::Subpixel_Shift;
+    flags |= font.isEmbeddedBitmaps()   << minikin::EmbeddedBitmaps_Shift;
+    flags |= font.isForceAutoHinting()  << minikin::ForceAutoHinting_Shift;
     return flags;
 }
 
-void MinikinFontSkia::unpackPaintFlags(SkPaint* paint, uint32_t paintFlags) {
-    paint->setFlags(paintFlags & SkPaint::kAllFlags);
-    paint->setHinting(static_cast<SkFontHinting>(paintFlags >> 16));
+void MinikinFontSkia::unpackFontFlags(SkFont* font, uint32_t flags) {
+    // We store hinting in the top 16 bits (only need 2 of them)
+    font->setHinting((SkFontHinting)(flags >> 16));
+    // We store edging in bits 8:15 (only need 2 of them)
+    font->setEdging((SkFont::Edging)((flags >> 8) & 0xFF));
+    font->setEmbolden(        (flags & minikin::Embolden_Flag) != 0);
+    font->setLinearMetrics(   (flags & minikin::LinearMetrics_Flag) != 0);
+    font->setSubpixel(        (flags & minikin::Subpixel_Flag) != 0);
+    font->setEmbeddedBitmaps( (flags & minikin::EmbeddedBitmaps_Flag) != 0);
+    font->setForceAutoHinting((flags & minikin::ForceAutoHinting_Flag) != 0);
 }
 
-void MinikinFontSkia::populateSkPaint(SkPaint* paint, const MinikinFont* font,
-                                      minikin::FontFakery fakery) {
-    paint->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface());
-    paint->setFakeBoldText(paint->isFakeBoldText() || fakery.isFakeBold());
+void MinikinFontSkia::populateSkFont(SkFont* skFont, const MinikinFont* font,
+                                     minikin::FontFakery fakery) {
+    skFont->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->RefSkTypeface());
+    skFont->setEmbolden(skFont->isEmbolden() || fakery.isFakeBold());
     if (fakery.isFakeItalic()) {
-        paint->setTextSkewX(paint->getTextSkewX() - 0.25f);
+        skFont->setSkewX(skFont->getSkewX() - 0.25f);
     }
 }
 }
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index 55576b7..ad46b23 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -21,7 +21,7 @@
 #include <cutils/compiler.h>
 #include <minikin/MinikinFont.h>
 
-class SkPaint;
+class SkFont;
 class SkTypeface;
 
 namespace android {
@@ -54,12 +54,12 @@
     std::shared_ptr<minikin::MinikinFont> createFontWithVariation(
             const std::vector<minikin::FontVariation>&) const;
 
-    static uint32_t packPaintFlags(const SkPaint* paint);
-    static void unpackPaintFlags(SkPaint* paint, uint32_t paintFlags);
+    static uint32_t packFontFlags(const SkFont&);
+    static void unpackFontFlags(SkFont*, uint32_t fontFlags);
 
     // set typeface and fake bold/italic parameters
-    static void populateSkPaint(SkPaint* paint, const minikin::MinikinFont* font,
-                                minikin::FontFakery fakery);
+    static void populateSkFont(SkFont*, const minikin::MinikinFont* font,
+                               minikin::FontFakery fakery);
 
 private:
     sk_sp<SkTypeface> mTypeface;
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index ba240fe..733f8e4 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -30,16 +30,17 @@
 minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint,
                                                         const Typeface* typeface) {
     const Typeface* resolvedFace = Typeface::resolveDefault(typeface);
+    const SkFont& font = paint->getSkFont();
 
     minikin::MinikinPaint minikinPaint(resolvedFace->fFontCollection);
     /* Prepare minikin Paint */
     minikinPaint.size =
-            paint->isLinearText() ? paint->getTextSize() : static_cast<int>(paint->getTextSize());
-    minikinPaint.scaleX = paint->getTextScaleX();
-    minikinPaint.skewX = paint->getTextSkewX();
+            font.isLinearMetrics() ? font.getSize() : static_cast<int>(font.getSize());
+    minikinPaint.scaleX = font.getScaleX();
+    minikinPaint.skewX = font.getSkewX();
     minikinPaint.letterSpacing = paint->getLetterSpacing();
     minikinPaint.wordSpacing = paint->getWordSpacing();
-    minikinPaint.paintFlags = MinikinFontSkia::packPaintFlags(paint);
+    minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font);
     minikinPaint.localeListId = paint->getMinikinLocaleListId();
     minikinPaint.familyVariant = paint->getFamilyVariant();
     minikinPaint.fontStyle = resolvedFace->fStyle;
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index d27d544..cbf4095 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -63,27 +63,29 @@
     // f is a functor of type void f(size_t start, size_t end);
     template <typename F>
     ANDROID_API static void forFontRun(const minikin::Layout& layout, Paint* paint, F& f) {
-        float saveSkewX = paint->getTextSkewX();
-        bool savefakeBold = paint->isFakeBoldText();
+        float saveSkewX = paint->getSkFont().getSkewX();
+        bool savefakeBold = paint->getSkFont().isEmbolden();
         const minikin::MinikinFont* curFont = nullptr;
         size_t start = 0;
         size_t nGlyphs = layout.nGlyphs();
         for (size_t i = 0; i < nGlyphs; i++) {
             const minikin::MinikinFont* nextFont = layout.getFont(i);
             if (i > 0 && nextFont != curFont) {
-                MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start));
+                SkFont* skfont = &paint->getSkFont();
+                MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
                 f(start, i);
-                paint->setTextSkewX(saveSkewX);
-                paint->setFakeBoldText(savefakeBold);
+                skfont->setSkewX(saveSkewX);
+                skfont->setEmbolden(savefakeBold);
                 start = i;
             }
             curFont = nextFont;
         }
         if (nGlyphs > start) {
-            MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start));
+            SkFont* skfont = &paint->getSkFont();
+            MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
             f(start, nGlyphs);
-            paint->setTextSkewX(saveSkewX);
-            paint->setFakeBoldText(savefakeBold);
+            skfont->setSkewX(saveSkewX);
+            skfont->setEmbolden(savefakeBold);
         }
     }
 };
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 92ffda9..601b3c2 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -21,6 +21,7 @@
 
 #include <cutils/compiler.h>
 
+#include <SkFont.h>
 #include <SkPaint.h>
 #include <string>
 
@@ -46,7 +47,6 @@
 
     Paint();
     Paint(const Paint& paint);
-    Paint(const SkPaint& paint);  // NOLINT(google-explicit-constructor)
     ~Paint();
 
     Paint& operator=(const Paint& other);
@@ -54,6 +54,17 @@
     friend bool operator==(const Paint& a, const Paint& b);
     friend bool operator!=(const Paint& a, const Paint& b) { return !(a == b); }
 
+    SkFont& getSkFont() { return mFont; }
+    const SkFont& getSkFont() const { return mFont; }
+
+    // These shadow the methods on SkPaint, but we need to so we can keep related
+    // attributes in-sync.
+
+    void reset();
+    void setAntiAlias(bool);
+
+    // End method shadowing
+
     void setLetterSpacing(float letterSpacing) { mLetterSpacing = letterSpacing; }
 
     float getLetterSpacing() const { return mLetterSpacing; }
@@ -94,7 +105,31 @@
     Align getTextAlign() const { return mAlign; }
     void setTextAlign(Align align) { mAlign = align; }
 
+    bool isStrikeThru() const { return mStrikeThru; }
+    void setStrikeThru(bool st) { mStrikeThru = st; }
+
+    bool isUnderline() const { return mUnderline; }
+    void setUnderline(bool u) { mUnderline = u; }
+
+    bool isDevKern() const { return mDevKern; }
+    void setDevKern(bool d) { mDevKern = d; }
+
+    // The Java flags (Paint.java) no longer fit into the native apis directly.
+    // These methods handle converting to and from them and the native representations
+    // in android::Paint.
+
+    uint32_t getJavaFlags() const;
+    void setJavaFlags(uint32_t);
+
+    // Helpers that return or apply legacy java flags to SkPaint, ignoring all flags
+    // that are meant for SkFont or Paint (e.g. underline, strikethru)
+    // The only respected flags are : [ antialias, dither, filterBitmap ]
+    static uint32_t GetSkPaintJavaFlags(const SkPaint&);
+    static void SetSkPaintJavaFlags(SkPaint*, uint32_t flags);
+ 
 private:
+    SkFont mFont;
+
     float mLetterSpacing = 0;
     float mWordSpacing = 0;
     std::string mFontFeatureSettings;
@@ -107,6 +142,9 @@
     // nullptr is valid: it means the default typeface.
     const Typeface* mTypeface = nullptr;
     Align mAlign = kLeft_Align;
+    bool mStrikeThru = false;
+    bool mUnderline = false;
+    bool mDevKern = false;
 };
 
 }  // namespace android
diff --git a/libs/hwui/hwui/PaintFilter.h b/libs/hwui/hwui/PaintFilter.h
index bf5627e..0e7b619 100644
--- a/libs/hwui/hwui/PaintFilter.h
+++ b/libs/hwui/hwui/PaintFilter.h
@@ -12,6 +12,7 @@
      *  The implementation may modify the paint as they wish.
      */
     virtual void filter(SkPaint*) = 0;
+    virtual void filterFullPaint(Paint*) = 0;
 };
 
 } // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index bdbf5ca..2f2d575 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -24,10 +24,16 @@
         , mWordSpacing(0)
         , mFontFeatureSettings()
         , mMinikinLocaleListId(0)
-        , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {}
+        , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {
+    // SkPaint::antialiasing defaults to false, but
+    // SkFont::edging defaults to kAntiAlias. To keep them
+    // insync, we manually set the font to kAilas.
+    mFont.setEdging(SkFont::Edging::kAlias);
+}
 
 Paint::Paint(const Paint& paint)
         : SkPaint(paint)
+        , mFont(paint.mFont)
         , mLetterSpacing(paint.mLetterSpacing)
         , mWordSpacing(paint.mWordSpacing)
         , mFontFeatureSettings(paint.mFontFeatureSettings)
@@ -35,20 +41,17 @@
         , mFamilyVariant(paint.mFamilyVariant)
         , mHyphenEdit(paint.mHyphenEdit)
         , mTypeface(paint.mTypeface)
-        , mAlign(paint.mAlign) {}
+        , mAlign(paint.mAlign)
+        , mStrikeThru(paint.mStrikeThru)
+        , mUnderline(paint.mUnderline)
+        , mDevKern(paint.mDevKern) {}
 
-Paint::Paint(const SkPaint& paint)
-        : SkPaint(paint)
-        , mLetterSpacing(0)
-        , mWordSpacing(0)
-        , mFontFeatureSettings()
-        , mMinikinLocaleListId(0)
-        , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {}
 
 Paint::~Paint() {}
 
 Paint& Paint::operator=(const Paint& other) {
     SkPaint::operator=(other);
+    mFont = other.mFont;
     mLetterSpacing = other.mLetterSpacing;
     mWordSpacing = other.mWordSpacing;
     mFontFeatureSettings = other.mFontFeatureSettings;
@@ -57,15 +60,136 @@
     mHyphenEdit = other.mHyphenEdit;
     mTypeface = other.mTypeface;
     mAlign = other.mAlign;
+    mStrikeThru = other.mStrikeThru;
+    mUnderline = other.mUnderline;
+    mDevKern = other.mDevKern;
     return *this;
 }
 
 bool operator==(const Paint& a, const Paint& b) {
     return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
+           a.mFont == b.mFont &&
            a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing &&
            a.mFontFeatureSettings == b.mFontFeatureSettings &&
            a.mMinikinLocaleListId == b.mMinikinLocaleListId &&
            a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
-           a.mTypeface == b.mTypeface && a.mAlign == b.mAlign;
+           a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
+           a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline &&
+           a.mDevKern == b.mDevKern;
 }
+
+void Paint::reset() {
+    SkPaint::reset();
+
+    mFont = SkFont();
+    mFont.setEdging(SkFont::Edging::kAlias);
+
+    mStrikeThru = false;
+    mUnderline = false;
+    mDevKern = false;
+}
+
+void Paint::setAntiAlias(bool aa) {
+    // Java does not support/understand subpixel(lcd) antialiasing
+    SkASSERT(mFont.getEdging() != SkFont::Edging::kSubpixelAntiAlias);
+    // JavaPaint antialiasing affects both the SkPaint and SkFont settings.
+    SkPaint::setAntiAlias(aa);
+    mFont.setEdging(aa ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias);
+}
+
+////////////////// Java flags compatibility //////////////////
+
+/*  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)
+    5 flags (merged with antialias) are for SkFont
+    2 flags are for minikin::Paint (underline and strikethru)
+*/
+
+// flags relating to SkPaint
+static const uint32_t sAntiAliasFlag    = 0x01;   // affects paint and font-edging
+static const uint32_t sFilterBitmapFlag = 0x02;   // maps to enum
+static const uint32_t sDitherFlag       = 0x04;
+// flags relating to SkFont
+static const uint32_t sFakeBoldFlag     = 0x020;
+static const uint32_t sLinearMetrics    = 0x040;
+static const uint32_t sSubpixelMetrics  = 0x080;
+static const uint32_t sEmbeddedBitmaps  = 0x400;
+static const uint32_t sForceAutoHinting = 0x800;
+// flags related to minikin::Paint
+static const uint32_t sUnderlineFlag    = 0x08;
+static const uint32_t sStrikeThruFlag   = 0x10;
+// flags no longer supported on native side (but mirrored for compatibility)
+static const uint32_t sDevKernFlag      = 0x100;
+
+static uint32_t paintToLegacyFlags(const SkPaint& paint) {
+    uint32_t flags = 0;
+    flags |= -(int)paint.isAntiAlias() & sAntiAliasFlag;
+    flags |= -(int)paint.isDither()    & sDitherFlag;
+    if (paint.getFilterQuality() != kNone_SkFilterQuality) {
+        flags |= sFilterBitmapFlag;
+    }
+    return flags;
+}
+
+static uint32_t fontToLegacyFlags(const SkFont& font) {
+    uint32_t flags = 0;
+    flags |= -(int)font.isEmbolden()         & sFakeBoldFlag;
+    flags |= -(int)font.isLinearMetrics()    & sLinearMetrics;
+    flags |= -(int)font.isSubpixel()         & sSubpixelMetrics;
+    flags |= -(int)font.isEmbeddedBitmaps()  & sEmbeddedBitmaps;
+    flags |= -(int)font.isForceAutoHinting() & sForceAutoHinting;
+    return flags;
+}
+
+static void applyLegacyFlagsToPaint(uint32_t flags, SkPaint* paint) {
+    paint->setAntiAlias((flags & sAntiAliasFlag) != 0);
+    paint->setDither   ((flags & sDitherFlag) != 0);
+
+    if (flags & sFilterBitmapFlag) {
+        paint->setFilterQuality(kLow_SkFilterQuality);
+    } else {
+        paint->setFilterQuality(kNone_SkFilterQuality);
+    }
+}
+
+static void applyLegacyFlagsToFont(uint32_t flags, SkFont* font) {
+    font->setEmbolden        ((flags & sFakeBoldFlag) != 0);
+    font->setLinearMetrics   ((flags & sLinearMetrics) != 0);
+    font->setSubpixel        ((flags & sSubpixelMetrics) != 0);
+    font->setEmbeddedBitmaps ((flags & sEmbeddedBitmaps) != 0);
+    font->setForceAutoHinting((flags & sForceAutoHinting) != 0);
+
+    if (flags & sAntiAliasFlag) {
+        font->setEdging(SkFont::Edging::kAntiAlias);
+    } else {
+        font->setEdging(SkFont::Edging::kAlias);
+    }
+}
+
+uint32_t Paint::GetSkPaintJavaFlags(const SkPaint& paint) {
+    return paintToLegacyFlags(paint);
+}
+
+void Paint::SetSkPaintJavaFlags(SkPaint* paint, uint32_t flags) {
+    applyLegacyFlagsToPaint(flags, paint);
+}
+
+uint32_t Paint::getJavaFlags() const {
+    uint32_t flags = paintToLegacyFlags(*this) | fontToLegacyFlags(mFont);
+    flags |= -(int)mStrikeThru & sStrikeThruFlag;
+    flags |= -(int)mUnderline  & sUnderlineFlag;
+    flags |= -(int)mDevKern    & sDevKernFlag;
+    return flags;
+}
+
+void Paint::setJavaFlags(uint32_t flags) {
+    applyLegacyFlagsToPaint(flags, this);
+    applyLegacyFlagsToFont(flags, &mFont);
+    mStrikeThru = (flags & sStrikeThruFlag) != 0;
+    mUnderline  = (flags & sUnderlineFlag) != 0;
+    mDevKern    = (flags & sDevKernFlag) != 0;
+}
+
 }  // namespace android
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 60c8057..a1b2b18 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -138,6 +138,7 @@
     info.width = fboSize.width();
     info.height = fboSize.height();
     mat4.asColMajorf(&info.transform[0]);
+    info.color_space_ptr = canvas->imageInfo().colorSpace();
 
     // ensure that the framebuffer that the webview will render into is bound before we clear
     // the stencil and/or draw the functor.
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index c3563db..706325f 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -132,6 +132,7 @@
         info.width = mFBInfo.width();
         info.height = mFBInfo.height();
         mat4.asColMajorf(&info.transform[0]);
+        info.color_space_ptr = canvas->imageInfo().colorSpace();
 
         glViewport(0, 0, info.width, info.height);
 
@@ -179,8 +180,8 @@
     canvas->resetMatrix();
 
     auto functorImage = SkImage::MakeFromAHardwareBuffer(
-            reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType, nullptr,
-            kBottomLeft_GrSurfaceOrigin);
+            reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType,
+            canvas->imageInfo().refColorSpace(), kBottomLeft_GrSurfaceOrigin);
     canvas->drawImage(functorImage, 0, 0, &paint);
     canvas->restore();
 }
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index 9e1bb8e..501b8df 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_HWUI_DRAW_GL_INFO_H
 #define ANDROID_HWUI_DRAW_GL_INFO_H
 
+#include <SkColorSpace.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -41,6 +43,9 @@
     // Input: current transform matrix, in OpenGL format
     float transform[16];
 
+    // Input: Color space.
+    const SkColorSpace* color_space_ptr;
+
     // Output: dirty region to redraw
     float dirtyLeft;
     float dirtyTop;
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 16a27598..a9f651d 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -78,24 +78,21 @@
     return layerUpdater;
 }
 
-void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x,
+void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x,
                                  float y) {
     auto utf16 = asciiToUtf16(text);
     uint32_t length = strlen(text);
-    Paint glyphPaint(paint);
-    glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding);
+
     canvas->drawText(utf16.get(), length,  // text buffer
                      0, length,            // draw range
                      0, length,            // context range
-                     x, y, minikin::Bidi::LTR, glyphPaint, nullptr, nullptr /* measured text */);
+                     x, y, minikin::Bidi::LTR, paint, nullptr, nullptr /* measured text */);
 }
 
-void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint,
+void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint,
                                  const SkPath& path) {
     auto utf16 = asciiToUtf16(text);
-    Paint glyphPaint(paint);
-    glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding);
-    canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, glyphPaint,
+    canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, paint,
                            nullptr);
 }
 
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 6a1ca5a..e7124df 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -278,10 +278,10 @@
 
     static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
 
-    static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x,
+    static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint, float x,
                                  float y);
 
-    static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint,
+    static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const Paint& paint,
                                  const SkPath& path);
 
     static std::unique_ptr<uint16_t[]> asciiToUtf16(const char* str);
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
index f0a5e9d..0795d13 100644
--- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -51,7 +51,7 @@
         paint.setAntiAlias(true);
         paint.setColor(Color::Black);
         for (int i = 0; i < 5; i++) {
-            paint.setTextSize(10 + (frameNr % 20) + i * 20);
+            paint.getSkFont().setSize(10 + (frameNr % 20) + i * 20);
             TestUtils::drawUtf8ToCanvas(canvas.get(), text, paint, 0, 100 * (i + 2));
         }
 
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index 58c9980..ecaaf48 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -16,7 +16,7 @@
 
 #include "TestSceneBase.h"
 #include "tests/common/TestListViewSceneBase.h"
-
+#include "hwui/Paint.h"
 #include <SkGradientShader.h>
 
 class ListOfFadedTextAnimation;
@@ -33,8 +33,8 @@
         canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
         int length = dp(100);
         canvas.saveLayer(0, 0, length, itemHeight, nullptr, SaveFlags::HasAlphaLayer);
-        SkPaint textPaint;
-        textPaint.setTextSize(dp(20));
+        Paint textPaint;
+        textPaint.getSkFont().setSize(dp(20));
         textPaint.setAntiAlias(true);
         TestUtils::drawUtf8ToCanvas(&canvas, "not that long long text", textPaint, dp(10), dp(30));
 
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index 4111bd2..feb881f 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -16,6 +16,7 @@
 
 #include "TestSceneBase.h"
 #include "tests/common/TestListViewSceneBase.h"
+#include "hwui/Paint.h"
 #include <SkFont.h>
 #include <cstdio>
 
@@ -83,14 +84,14 @@
         roundRectPaint.setColor(Color::White);
         canvas.drawRoundRect(0, 0, itemWidth, itemHeight, dp(6), dp(6), roundRectPaint);
 
-        SkPaint textPaint;
+        Paint textPaint;
         textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500);
-        textPaint.setTextSize(dp(20));
+        textPaint.getSkFont().setSize(dp(20));
         textPaint.setAntiAlias(true);
         char buf[256];
         snprintf(buf, sizeof(buf), "This card is #%d", cardId);
         TestUtils::drawUtf8ToCanvas(&canvas, buf, textPaint, itemHeight, dp(25));
-        textPaint.setTextSize(dp(15));
+        textPaint.getSkFont().setSize(dp(15));
         TestUtils::drawUtf8ToCanvas(&canvas, "This is some more text on the card", textPaint,
                                     itemHeight, dp(45));
 
diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
index aa537b4..f6cff1c 100644
--- a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
@@ -17,6 +17,7 @@
 #include "TestSceneBase.h"
 #include "renderthread/RenderProxy.h"
 #include "utils/Color.h"
+#include "hwui/Paint.h"
 
 class MagnifierAnimation;
 
@@ -37,9 +38,9 @@
         canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
         card = TestUtils::createNode(
                 0, 0, width, height, [&](RenderProperties& props, Canvas& canvas) {
-                    SkPaint paint;
+                    Paint paint;
                     paint.setAntiAlias(true);
-                    paint.setTextSize(50);
+                    paint.getSkFont().setSize(50);
 
                     paint.setColor(Color::Black);
                     TestUtils::drawUtf8ToCanvas(&canvas, "Test string", paint, 10, 400);
diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
index 3befce4..8630be8 100644
--- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
@@ -41,9 +41,9 @@
         int top = bounds.fTop;
 
         mBluePaint.setColor(SkColorSetARGB(255, 0, 0, 255));
-        mBluePaint.setTextSize(padding);
+        mBluePaint.getSkFont().setSize(padding);
         mGreenPaint.setColor(SkColorSetARGB(255, 0, 255, 0));
-        mGreenPaint.setTextSize(padding);
+        mGreenPaint.getSkFont().setSize(padding);
 
         // interleave drawText and drawRect with saveLayer ops
         for (int i = 0; i < regions; i++, top += smallRectHeight) {
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
index a16b1784..d309036 100644
--- a/libs/hwui/tests/common/scenes/TextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "TestSceneBase.h"
+#include "hwui/Paint.h"
 
 class TextAnimation;
 
@@ -28,9 +29,9 @@
         canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
         card = TestUtils::createNode(0, 0, width, height, [](RenderProperties& props,
                                                              Canvas& canvas) {
-            SkPaint paint;
+            Paint paint;
             paint.setAntiAlias(true);
-            paint.setTextSize(50);
+            paint.getSkFont().setSize(50);
 
             paint.setColor(Color::Black);
             for (int i = 0; i < 10; i++) {
diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp
index 286f5f1..229c7f3 100644
--- a/libs/hwui/tests/common/scenes/TvApp.cpp
+++ b/libs/hwui/tests/common/scenes/TvApp.cpp
@@ -17,6 +17,7 @@
 #include "SkBlendMode.h"
 #include "TestSceneBase.h"
 #include "tests/common/BitmapAllocationTestUtils.h"
+#include "hwui/Paint.h"
 
 class TvApp;
 class TvAppNoRoundedCorner;
@@ -116,13 +117,13 @@
                                      [text, text2](RenderProperties& props, Canvas& canvas) {
                                          canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver);
 
-                                         SkPaint paint;
+                                         Paint paint;
                                          paint.setAntiAlias(true);
-                                         paint.setTextSize(24);
+                                         paint.getSkFont().setSize(24);
 
                                          paint.setColor(Color::Black);
                                          TestUtils::drawUtf8ToCanvas(&canvas, text, paint, 10, 30);
-                                         paint.setTextSize(20);
+                                         paint.getSkFont().setSize(20);
                                          TestUtils::drawUtf8ToCanvas(&canvas, text2, paint, 10, 54);
 
                                      });
diff --git a/media/Android.bp b/media/Android.bp
index 88ed9c6a..753f4b7 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -25,9 +25,10 @@
 }
 
 java_library {
-    name: "updatable-mediasession2",
+    name: "updatable-media",
 
     srcs: [
+        ":mediaplayer2-srcs",
         ":mediasession2-srcs",
         ":framework-media-annotation-srcs",
     ],
@@ -37,26 +38,12 @@
             "apex/java",
         ],
 
-        // TODO: find out a way to include only the necessary aidl files instead of dirs.
         include_dirs: [
+            // For the usage of android.os.Bundle and android.os.ResultReceiver in aidl files
             "frameworks/base/core/java",
         ],
     },
 
-    installable: true,
-
-    // Make sure that the implementaion only relies on SDK or system APIs.
-    sdk_version: "system_current",
-}
-
-java_library {
-    name: "updatable-media",
-
-    srcs: [
-        ":mediaplayer2-srcs",
-        ":framework-media-annotation-srcs",
-    ],
-
     static_libs: [
         "mediaplayer2-protos",
     ],
@@ -132,7 +119,6 @@
         "apex/java/android/media/Session2Command.java",
         "apex/java/android/media/Session2CommandGroup.java",
         "apex/java/android/media/Session2Link.java",
-        "apex/java/android/media/Session2Token.java",
     ],
 }
 
diff --git a/media/apex/java/android/media/MediaConstants.java b/media/apex/java/android/media/MediaConstants.java
index 65b6f55..45ea826 100644
--- a/media/apex/java/android/media/MediaConstants.java
+++ b/media/apex/java/android/media/MediaConstants.java
@@ -24,7 +24,8 @@
     static final String KEY_PACKAGE_NAME = "android.media.key.PACKAGE_NAME";
 
     // Bundle key for Parcelable
-    static final String KEY_SESSION2LINK = "android.media.key.SESSION2LINK";
+    static final String KEY_SESSION2_TOKEN = "android.media.key.SESSION2_TOKEN";
+    static final String KEY_SESSION2_LINK = "android.media.key.SESSION2_LINK";
     static final String KEY_ALLOWED_COMMANDS = "android.media.key.ALLOWED_COMMANDS";
     static final String KEY_PLAYBACK_ACTIVE = "android.media.key.PLAYBACK_ACTIVE";
 
diff --git a/media/apex/java/android/media/MediaController2.java b/media/apex/java/android/media/MediaController2.java
index 887b447..41721f3 100644
--- a/media/apex/java/android/media/MediaController2.java
+++ b/media/apex/java/android/media/MediaController2.java
@@ -20,9 +20,11 @@
 import static android.media.MediaConstants.KEY_PACKAGE_NAME;
 import static android.media.MediaConstants.KEY_PID;
 import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE;
-import static android.media.MediaConstants.KEY_SESSION2LINK;
+import static android.media.MediaConstants.KEY_SESSION2_LINK;
+import static android.media.MediaConstants.KEY_SESSION2_TOKEN;
 import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR;
 import static android.media.Session2Command.RESULT_INFO_SKIPPED;
+import static android.media.Session2Token.SESSION_SERVICE_INTERFACE;
 import static android.media.Session2Token.TYPE_SESSION;
 
 import android.annotation.NonNull;
@@ -260,7 +262,8 @@
 
     // Called by Controller2Link.onConnected
     void onConnected(int seq, Bundle connectionResult) {
-        Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2LINK);
+        Session2Token token = connectionResult.getParcelable(KEY_SESSION2_TOKEN);
+        Session2Link sessionBinder = token.getExtras().getParcelable(KEY_SESSION2_LINK);
         Session2CommandGroup allowedCommands =
                 connectionResult.getParcelable(KEY_ALLOWED_COMMANDS);
         boolean playbackActive = connectionResult.getBoolean(KEY_PLAYBACK_ACTIVE);
@@ -281,8 +284,7 @@
             // Implementation for the local binder is no-op,
             // so can be used without worrying about deadlock.
             sessionBinder.linkToDeath(mDeathRecipient, 0);
-            mConnectedToken = new Session2Token(mSessionToken.getUid(), TYPE_SESSION,
-                    mSessionToken.getPackageName(), sessionBinder);
+            mConnectedToken = token;
         }
         mCallbackExecutor.execute(() -> {
             mCallback.onConnected(MediaController2.this, allowedCommands);
@@ -353,7 +355,7 @@
     }
 
     private boolean requestConnectToSession() {
-        Session2Link sessionBinder = mSessionToken.getSessionLink();
+        Session2Link sessionBinder = mSessionToken.getExtras().getParcelable(KEY_SESSION2_LINK);
         Bundle connectionRequest = createConnectionRequest();
         try {
             sessionBinder.connect(mControllerStub, getNextSeqNumber(), connectionRequest);
@@ -366,7 +368,7 @@
 
     private boolean requestConnectToService() {
         // Service. Needs to get fresh binder whenever connection is needed.
-        final Intent intent = new Intent(MediaSession2Service.SERVICE_INTERFACE);
+        final Intent intent = new Intent(SESSION_SERVICE_INTERFACE);
         intent.setClassName(mSessionToken.getPackageName(), mSessionToken.getServiceName());
 
         // Use bindService() instead of startForegroundService() to start session service for three
diff --git a/media/apex/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java
index a2feec2..1a1f6fb 100644
--- a/media/apex/java/android/media/MediaPlayer2.java
+++ b/media/apex/java/android/media/MediaPlayer2.java
@@ -4482,6 +4482,7 @@
     };
 
     // Modular DRM
+    private final Map<UUID, MediaDrm> mDrmObjs = new HashMap<>();
     private class DrmHandle {
 
         static final int PROVISION_TIMEOUT_MS = 60000;
@@ -4594,7 +4595,13 @@
             Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
 
             try {
-                mDrmObj = new MediaDrm(uuid);
+                mDrmObj = mDrmObjs.computeIfAbsent(uuid, scheme -> {
+                    try {
+                        return new MediaDrm(scheme);
+                    } catch (UnsupportedSchemeException e) {
+                        throw new IllegalArgumentException(e);
+                    }
+                });
                 Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
             } catch (Exception e) { // UnsupportedSchemeException
                 Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
@@ -4837,7 +4844,12 @@
                 msg = mTaskHandler.obtainMessage(
                         MEDIA_ERROR, status, MEDIA_ERROR_UNKNOWN, null);
             }
-            mTaskHandler.sendMessage(msg);
+            mTaskHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mTaskHandler.handleMessage(msg, mSrcId);
+                }
+            });
 
             sendDrmEvent(new DrmEventNotifier() {
                 @Override
diff --git a/media/apex/java/android/media/MediaSession2.java b/media/apex/java/android/media/MediaSession2.java
index fdd07fd..80c91cc 100644
--- a/media/apex/java/android/media/MediaSession2.java
+++ b/media/apex/java/android/media/MediaSession2.java
@@ -20,10 +20,10 @@
 import static android.media.MediaConstants.KEY_PACKAGE_NAME;
 import static android.media.MediaConstants.KEY_PID;
 import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE;
-import static android.media.MediaConstants.KEY_SESSION2LINK;
+import static android.media.MediaConstants.KEY_SESSION2_LINK;
+import static android.media.MediaConstants.KEY_SESSION2_TOKEN;
 import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR;
 import static android.media.Session2Command.RESULT_INFO_SKIPPED;
-import static android.media.Session2Token.TYPE_SESSION;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -34,7 +34,6 @@
 import android.media.session.MediaSessionManager.RemoteUserInfo;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Process;
 import android.os.ResultReceiver;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -108,8 +107,10 @@
         mCallbackExecutor = callbackExecutor;
         mCallback = callback;
         mSessionStub = new Session2Link(this);
-        mSessionToken = new Session2Token(Process.myUid(), TYPE_SESSION, context.getPackageName(),
-                mSessionStub);
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(KEY_SESSION2_LINK, mSessionStub);
+        mSessionToken = new Session2Token(context, id, extras);
         mSessionManager = (MediaSessionManager) mContext.getSystemService(
                 Context.MEDIA_SESSION_SERVICE);
         // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked.
@@ -141,6 +142,8 @@
             for (ControllerInfo info : controllerInfos) {
                 info.notifyDisconnected();
             }
+            mSessionToken.destroy();
+            mSessionManager.notifySession2Destroyed(mSessionToken);
         } catch (Exception e) {
             // Should not be here.
         }
@@ -328,7 +331,7 @@
                 // It's needed because we cannot call synchronous calls between
                 // session/controller.
                 Bundle connectionResult = new Bundle();
-                connectionResult.putParcelable(KEY_SESSION2LINK, mSessionStub);
+                connectionResult.putParcelable(KEY_SESSION2_TOKEN, mSessionToken);
                 connectionResult.putParcelable(KEY_ALLOWED_COMMANDS,
                         controllerInfo.mAllowedCommands);
                 connectionResult.putBoolean(KEY_PLAYBACK_ACTIVE, isPlaybackActive());
diff --git a/media/apex/java/android/media/MediaSession2Service.java b/media/apex/java/android/media/MediaSession2Service.java
index 5bb746a..54d0ed2 100644
--- a/media/apex/java/android/media/MediaSession2Service.java
+++ b/media/apex/java/android/media/MediaSession2Service.java
@@ -48,7 +48,7 @@
     /**
      * The {@link Intent} that must be declared as handled by the service.
      */
-    public static final String SERVICE_INTERFACE = "android.media.MediaSession2Service";
+    public static final String SERVICE_INTERFACE = Session2Token.SESSION_SERVICE_INTERFACE;
 
     private static final String TAG = "MediaSession2Service";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/media/apex/java/android/media/session/MediaController.java b/media/apex/java/android/media/session/MediaController.java
index d43acf4..79389a8 100644
--- a/media/apex/java/android/media/session/MediaController.java
+++ b/media/apex/java/android/media/session/MediaController.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -82,42 +83,26 @@
     private final TransportControls mTransportControls;
 
     /**
-     * Call for creating a MediaController directly from a controller link. Should only
-     * be used by framework code.
-     * @hide
-     */
-    public MediaController(Context context, ControllerLink sessionBinder) {
-        if (sessionBinder == null) {
-            throw new IllegalArgumentException("Session token cannot be null");
-        }
-        if (context == null) {
-            throw new IllegalArgumentException("Context cannot be null");
-        }
-        mSessionBinder = sessionBinder;
-        mTransportControls = new TransportControls();
-        mToken = new MediaSession.Token(sessionBinder);
-        mContext = context;
-        mCbStub = new ControllerCallbackLink(context, new CallbackStub(this));
-    }
-
-    /**
-     * Call for creating a MediaController directly from a binder. Should only
-     * be used by framework code.
-     * @hide
-     * TODO: remove this constructor
-     */
-    public MediaController(Context context, ISessionController sessionBinder) {
-        this(context, new ControllerLink(sessionBinder.asBinder()));
-    }
-
-    /**
      * Create a new MediaController from a session's token.
      *
      * @param context The caller's context.
      * @param token The token for the session.
      */
     public MediaController(@NonNull Context context, @NonNull MediaSession.Token token) {
-        this(context, token.getControllerLink());
+        if (context == null) {
+            throw new IllegalArgumentException("context shouldn't be null");
+        }
+        if (token == null) {
+            throw new IllegalArgumentException("token shouldn't be null");
+        }
+        if (token.getControllerLink() == null) {
+            throw new IllegalArgumentException("token.getControllerLink() shouldn't be null");
+        }
+        mSessionBinder = token.getControllerLink();
+        mTransportControls = new TransportControls();
+        mToken = token;
+        mContext = context;
+        mCbStub = new ControllerCallbackLink(context, new CallbackStub(this));
     }
 
     /**
@@ -501,7 +486,6 @@
      * Get the session's tag for debugging purposes.
      *
      * @return The session's tag.
-     * @hide
      */
     public String getTag() {
         if (mTag == null) {
@@ -1011,6 +995,7 @@
         /**
          * @hide
          */
+        @SystemApi
         public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs) {
             mVolumeType = type;
             mVolumeControl = control;
diff --git a/media/apex/java/android/media/session/MediaSessionEngine.java b/media/apex/java/android/media/session/MediaSessionEngine.java
index 1f5fa5f..edf283e 100644
--- a/media/apex/java/android/media/session/MediaSessionEngine.java
+++ b/media/apex/java/android/media/session/MediaSessionEngine.java
@@ -451,12 +451,24 @@
     }
 
     /**
+     * Returns the name of the package that sent the last media button, transport control, or
+     * command from controllers and the system. This is only valid while in a request callback, such
+     * as {@link MediaSession.Callback#onPlay}.
+     */
+    public String getCallingPackage() {
+        if (mCallbackHandler != null && mCallbackHandler.mCurrentControllerInfo != null) {
+            return mCallbackHandler.mCurrentControllerInfo.getPackageName();
+        }
+        return null;
+    }
+
+
+    /**
      * Notify the system that the remote volume changed.
      *
      * @param provider The provider that is handling volume changes.
-     * @hide
      */
-    public void notifyRemoteVolumeChanged(VolumeProvider provider) {
+    private void notifyRemoteVolumeChanged(VolumeProvider provider) {
         synchronized (mLock) {
             if (provider == null || provider != mVolumeProvider) {
                 Log.w(TAG, "Received update from stale volume provider");
@@ -470,18 +482,6 @@
         }
     }
 
-    /**
-     * Returns the name of the package that sent the last media button, transport control, or
-     * command from controllers and the system. This is only valid while in a request callback, such
-     * as {@link MediaSession.Callback#onPlay}.
-     */
-    public String getCallingPackage() {
-        if (mCallbackHandler != null && mCallbackHandler.mCurrentControllerInfo != null) {
-            return mCallbackHandler.mCurrentControllerInfo.getPackageName();
-        }
-        return null;
-    }
-
     private void dispatchPrepare(RemoteUserInfo caller) {
         postToCallback(caller, CallbackMessageHandler.MSG_PREPARE, null, null);
     }
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/ImageReader.java b/media/java/android/media/ImageReader.java
index 9ac147b..60ef1d9 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -17,12 +17,10 @@
 package android.media;
 
 import android.graphics.ImageFormat;
-import android.graphics.PixelFormat;
 import android.hardware.HardwareBuffer;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.util.Log;
 import android.view.Surface;
 
 import dalvik.system.VMRuntime;
@@ -74,12 +72,6 @@
     private static final int ACQUIRE_MAX_IMAGES = 2;
 
     /**
-     * Invalid consumer buffer usage flag. This usage flag will be ignored
-     * by the {@code ImageReader} instance is constructed with this value.
-     */
-    private static final long BUFFER_USAGE_UNKNOWN = 0;
-
-    /**
      * <p>
      * Create a new reader for images of the desired size and format.
      * </p>
@@ -129,7 +121,10 @@
      * @see Image
      */
     public static ImageReader newInstance(int width, int height, int format, int maxImages) {
-        return new ImageReader(width, height, format, maxImages, BUFFER_USAGE_UNKNOWN);
+        // If the format is private don't default to USAGE_CPU_READ_OFTEN since it may not
+        // work, and is inscrutable anyway
+        return new ImageReader(width, height, format, maxImages,
+                format == ImageFormat.PRIVATE ? 0 : HardwareBuffer.USAGE_CPU_READ_OFTEN);
     }
 
     /**
@@ -207,18 +202,23 @@
      *            obtained by the user, one of them has to be released before a new Image will
      *            become available for access through {@link #acquireLatestImage()} or
      *            {@link #acquireNextImage()}. Must be greater than 0.
-     * @param usage The intended usage of the images produced by this ImageReader. It needs
-     *            to be one of the Usage defined by {@link HardwareBuffer}, or an
-     *            {@link IllegalArgumentException} will be thrown.
+     * @param usage The intended usage of the images produced by this ImageReader. See the usages
+     *              on {@link HardwareBuffer} for a list of valid usage bits. See also
+     *              {@link HardwareBuffer#isSupported(int, int, int, int, long)} for checking
+     *              if a combination is supported. If it's not supported this will throw
+     *              an {@link IllegalArgumentException}.
      * @see Image
      * @see HardwareBuffer
      */
     public static ImageReader newInstance(int width, int height, int format, int maxImages,
             long usage) {
-        if (!isFormatUsageCombinationAllowed(format, usage)) {
-            throw new IllegalArgumentException("Format usage combination is not supported:"
-                    + " format = " + format + ", usage = " + usage);
-        }
+        // TODO: Check this - can't do it just yet because format support is different
+        // Unify formats! The only reliable way to validate usage is to just try it and see.
+
+//        if (!HardwareBuffer.isSupported(width, height, format, 1, usage)) {
+//            throw new IllegalArgumentException("The given format=" + Integer.toHexString(format)
+//                + " & usage=" + Long.toHexString(usage) + " is not supported");
+//        }
         return new ImageReader(width, height, format, maxImages, usage);
     }
 
@@ -716,19 +716,6 @@
         return si.getReader() == this;
     }
 
-    private static boolean isFormatUsageCombinationAllowed(int format, long usage) {
-        if (!ImageFormat.isPublicFormat(format) && !PixelFormat.isPublicFormat(format)) {
-            return false;
-        }
-
-        // Valid usage needs to be provided.
-        if (usage == BUFFER_USAGE_UNKNOWN) {
-            return false;
-        }
-
-        return true;
-    }
-
     /**
      * Called from Native code when an Event happens.
      *
@@ -833,6 +820,7 @@
                 case ImageFormat.JPEG:
                 case ImageFormat.DEPTH_POINT_CLOUD:
                 case ImageFormat.RAW_PRIVATE:
+                case ImageFormat.DEPTH_JPEG:
                     width = ImageReader.this.getWidth();
                     break;
                 default:
@@ -849,6 +837,7 @@
                 case ImageFormat.JPEG:
                 case ImageFormat.DEPTH_POINT_CLOUD:
                 case ImageFormat.RAW_PRIVATE:
+                case ImageFormat.DEPTH_JPEG:
                     height = ImageReader.this.getHeight();
                     break;
                 default:
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
index 2a0e04e..b77a884 100644
--- a/media/java/android/media/ImageUtils.java
+++ b/media/java/android/media/ImageUtils.java
@@ -63,6 +63,7 @@
             case ImageFormat.DEPTH16:
             case ImageFormat.DEPTH_POINT_CLOUD:
             case ImageFormat.RAW_DEPTH:
+            case ImageFormat.DEPTH_JPEG:
                 return 1;
             case ImageFormat.PRIVATE:
                 return 0;
@@ -192,6 +193,7 @@
             // 10x compression from RGB_888
             case ImageFormat.JPEG:
             case ImageFormat.DEPTH_POINT_CLOUD:
+            case ImageFormat.DEPTH_JPEG:
                 estimatedBytePerPixel = 0.3;
                 break;
             case ImageFormat.Y8:
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 33b8c42..112ce1d 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -920,12 +920,14 @@
     public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32;
 
     /**
-     * @hide
+     * If the media contains EXIF data, this key retrieves the offset value
+     * of the data.
      */
     public static final int METADATA_KEY_EXIF_OFFSET = 33;
 
     /**
-     * @hide
+     * If the media contains EXIF data, this key retrieves the length of the
+     * data.
      */
     public static final int METADATA_KEY_EXIF_LENGTH = 34;
 
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index ffeff4d..3444e92 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -2357,8 +2357,7 @@
                 return;
             }
             if (mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
-                @VolumeProvider.ControlType int volumeControl =
-                        VolumeProvider.VOLUME_CONTROL_FIXED;
+                int volumeControl = VolumeProvider.VOLUME_CONTROL_FIXED;
                 switch (mVolumeHandling) {
                     case RemoteControlClient.PLAYBACK_VOLUME_VARIABLE:
                         volumeControl = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
@@ -2384,8 +2383,7 @@
 
         class SessionVolumeProvider extends VolumeProvider {
 
-            public SessionVolumeProvider(@VolumeProvider.ControlType int volumeControl,
-                    int maxVolume, int currentVolume) {
+            SessionVolumeProvider(int volumeControl, int maxVolume, int currentVolume) {
                 super(volumeControl, maxVolume, currentVolume);
             }
 
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 4eed12f..30719fd 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
@@ -1273,7 +1277,8 @@
                 // we need to query the database in small batches, to avoid problems
                 // with CursorWindow positioning.
                 long lastId = Long.MIN_VALUE;
-                Uri limitUri = mFilesUri.buildUpon().appendQueryParameter("limit", "1000").build();
+                Uri limitUri = mFilesUri.buildUpon()
+                        .appendQueryParameter(MediaStore.PARAM_LIMIT, "1000").build();
 
                 while (true) {
                     selectionArgs[0] = "" + lastId;
@@ -1625,7 +1630,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/apex/java/android/media/Session2Token.java b/media/java/android/media/Session2Token.java
similarity index 70%
rename from media/apex/java/android/media/Session2Token.java
rename to media/java/android/media/Session2Token.java
index 238cc2b..80494ad 100644
--- a/media/apex/java/android/media/Session2Token.java
+++ b/media/java/android/media/Session2Token.java
@@ -19,13 +19,17 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.media.session.MediaSessionManager;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.Process;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -35,7 +39,7 @@
 import java.util.Objects;
 
 /**
- * Represents an ongoing {@link MediaSession2} or a {@link MediaSession2Service}.
+ * Represents an ongoing MediaSession2 or a MediaSession2Service.
  * If it's representing a session service, it may not be ongoing.
  * <p>
  * This API is not generally intended for third party application developers.
@@ -44,7 +48,7 @@
  * for consistent behavior across all devices.
  * <p>
  * This may be passed to apps by the session owner to allow them to create a
- * {@link MediaController2} to communicate with the session.
+ * MediaController2 to communicate with the session.
  * <p>
  * It can be also obtained by {@link android.media.session.MediaSessionManager}.
  */
@@ -64,6 +68,13 @@
     };
 
     /**
+     * The {@link Intent} that must be declared for the session service.
+     * @hide
+     */
+    @SystemApi
+    public static final String SESSION_SERVICE_INTERFACE = "android.media.MediaSession2Service";
+
+    /**
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
@@ -72,22 +83,26 @@
     }
 
     /**
-     * Type for {@link MediaSession2}.
+     * Type for MediaSession2.
      */
     public static final int TYPE_SESSION = 0;
 
     /**
-     * Type for {@link MediaSession2Service}.
+     * Type for MediaSession2Service.
      */
     public static final int TYPE_SESSION_SERVICE = 1;
 
+    private final String mSessionId;
+    private final int mPid;
     private final int mUid;
     @TokenType
     private final int mType;
     private final String mPackageName;
     private final String mServiceName;
-    private final Session2Link mSessionLink;
     private final ComponentName mComponentName;
+    private final Bundle mExtras;
+
+    private boolean mDestroyed = false;
 
     /**
      * Constructor for the token with type {@link #TYPE_SESSION_SERVICE}.
@@ -106,44 +121,67 @@
         final PackageManager manager = context.getPackageManager();
         final int uid = getUid(manager, serviceComponent.getPackageName());
 
-        if (!isInterfaceDeclared(manager, MediaSession2Service.SERVICE_INTERFACE,
-                serviceComponent)) {
+        if (!isInterfaceDeclared(manager, SESSION_SERVICE_INTERFACE, serviceComponent)) {
             Log.w(TAG, serviceComponent + " doesn't implement MediaSession2Service.");
         }
+        mSessionId = null;
         mComponentName = serviceComponent;
         mPackageName = serviceComponent.getPackageName();
         mServiceName = serviceComponent.getClassName();
+        mPid = -1;
         mUid = uid;
         mType = TYPE_SESSION_SERVICE;
-        mSessionLink = null;
+        mExtras = null;
     }
 
-    Session2Token(int uid, int type, String packageName, Session2Link sessionLink) {
-        mUid = uid;
-        mType = type;
-        mPackageName = packageName;
+    /**
+     * Constructor for the token with type {@link #TYPE_SESSION}.
+     *
+     * @param context The context.
+     * @param sessionId The ID of the session. Should be unique.
+     * @param extras The extras.
+     * @hide
+     */
+    @SystemApi
+    public Session2Token(@NonNull Context context, @NonNull String sessionId,
+            @Nullable Bundle extras) {
+        if (sessionId == null) {
+            throw new IllegalArgumentException("sessionId shouldn't be null");
+        }
+        if (context == null) {
+            throw new IllegalArgumentException("context shouldn't be null");
+        }
+        mSessionId = sessionId;
+        mPid = Process.myPid();
+        mUid = Process.myUid();
+        mType = TYPE_SESSION;
+        mPackageName = context.getPackageName();
+        mExtras = extras;
         mServiceName = null;
         mComponentName = null;
-        mSessionLink = sessionLink;
     }
 
     Session2Token(Parcel in) {
+        mSessionId = in.readString();
+        mPid = in.readInt();
         mUid = in.readInt();
         mType = in.readInt();
         mPackageName = in.readString();
         mServiceName = in.readString();
-        mSessionLink = in.readParcelable(null);
         mComponentName = ComponentName.unflattenFromString(in.readString());
+        mExtras = in.readParcelable(null);
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mSessionId);
+        dest.writeInt(mPid);
         dest.writeInt(mUid);
         dest.writeInt(mType);
         dest.writeString(mPackageName);
         dest.writeString(mServiceName);
-        dest.writeParcelable(mSessionLink, flags);
         dest.writeString(mComponentName == null ? "" : mComponentName.flattenToString());
+        dest.writeParcelable(mExtras, flags);
     }
 
     @Override
@@ -153,7 +191,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mType, mUid, mPackageName, mServiceName, mSessionLink);
+        return Objects.hash(mSessionId, mPid, mUid, mType, mPackageName, mServiceName);
     }
 
     @Override
@@ -162,17 +200,27 @@
             return false;
         }
         Session2Token other = (Session2Token) obj;
-        return mUid == other.mUid
-                && TextUtils.equals(mPackageName, other.mPackageName)
-                && TextUtils.equals(mServiceName, other.mServiceName)
+        return TextUtils.equals(mSessionId, other.mSessionId)
+                && mPid == other.mPid
+                && mUid == other.mUid
                 && mType == other.mType
-                && Objects.equals(mSessionLink, other.mSessionLink);
+                && TextUtils.equals(mPackageName, other.mPackageName)
+                && TextUtils.equals(mServiceName, other.mServiceName);
     }
 
     @Override
     public String toString() {
         return "Session2Token {pkg=" + mPackageName + " type=" + mType
-                + " service=" + mServiceName + " Session2Link=" + mSessionLink + "}";
+                + " service=" + mServiceName + "}";
+    }
+
+    /**
+     * @return pid of the session
+     * @hide
+     */
+    @SystemApi
+    public int getPid() {
+        return mPid;
     }
 
     /**
@@ -207,8 +255,36 @@
         return mType;
     }
 
-    Session2Link getSessionLink() {
-        return mSessionLink;
+    /**
+     * @return extras
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras == null ? new Bundle() : new Bundle(mExtras);
+    }
+
+    /**
+     * Destroys this session token. After this method is called,
+     * {@link MediaSessionManager#notifySession2Created(Session2Token)} should not be called
+     * with this token.
+     *
+     * @see MediaSessionManager#notifySession2Created(Session2Token)
+     * @hide
+     */
+    @SystemApi
+    public void destroy() {
+        mDestroyed = true;
+    }
+
+    /**
+     * @return whether this token is destroyed
+     * @hide
+     */
+    @SystemApi
+    public boolean isDestroyed() {
+        return mDestroyed;
     }
 
     private static boolean isInterfaceDeclared(PackageManager manager, String serviceInterface,
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index ed16250..e360808 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -18,12 +18,12 @@
 import android.content.ComponentName;
 import android.media.IRemoteVolumeController;
 import android.media.Session2Token;
-import android.media.session.ControllerLink;
 import android.media.session.IActiveSessionsListener;
 import android.media.session.ICallback;
 import android.media.session.IOnMediaKeyListener;
 import android.media.session.IOnVolumeKeyLongPressListener;
 import android.media.session.ISession2TokensListener;
+import android.media.session.MediaSession;
 import android.media.session.SessionCallbackLink;
 import android.media.session.SessionLink;
 import android.os.Bundle;
@@ -37,7 +37,8 @@
     SessionLink createSession(String packageName, in SessionCallbackLink sessionCb, String tag,
             int userId);
     void notifySession2Created(in Session2Token sessionToken);
-    List<ControllerLink> getSessions(in ComponentName compName, int userId);
+    void notifySession2Destroyed(in Session2Token sessionToken);
+    List<MediaSession.Token> getSessions(in ComponentName compName, int userId);
     List<Session2Token> getSession2Tokens(int userId);
     void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
             boolean needWakeLock);
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 1a185e9..682e79a 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -392,16 +392,6 @@
     }
 
     /**
-     * Notify the system that the remote volume changed.
-     *
-     * @param provider The provider that is handling volume changes.
-     * @hide
-     */
-    public void notifyRemoteVolumeChanged(VolumeProvider provider) {
-        mImpl.notifyRemoteVolumeChanged(provider);
-    }
-
-    /**
      * Returns the name of the package that sent the last media button, transport control, or
      * command from controllers and the system. This is only valid while in a request callback, such
      * as {@link Callback#onPlay}.
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index c64c452..7563867 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -26,7 +26,6 @@
 import android.content.Context;
 import android.media.AudioManager;
 import android.media.IRemoteVolumeController;
-import android.media.MediaSession2;
 import android.media.Session2Token;
 import android.os.Handler;
 import android.os.IBinder;
@@ -115,11 +114,11 @@
     }
 
     /**
-     * Notifies that a new {@link MediaSession2} with type {@link Session2Token#TYPE_SESSION} is
+     * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is
      * created.
      * <p>
      * Do not use this API directly, but create a new instance through the
-     * {@link MediaSession2.Builder} instead.
+     * MediaSession2.Builder instead.
      *
      * @param token newly created session2 token
      */
@@ -130,6 +129,9 @@
         if (token.getType() != Session2Token.TYPE_SESSION) {
             throw new IllegalArgumentException("token's type should be TYPE_SESSION");
         }
+        if (token.isDestroyed()) {
+            throw new IllegalArgumentException("token is already destroyed");
+        }
         try {
             mService.notifySession2Created(token);
         } catch (RemoteException e) {
@@ -138,6 +140,31 @@
     }
 
     /**
+     * Notifies that a new MediaSession2 with type {@link Session2Token#TYPE_SESSION} is
+     * destroyed.
+     * <p>
+     * Do not use this API directly, but close a session with MediaSession2#close() instead.
+     *
+     * @param token destroyed session2 token
+     */
+    public void notifySession2Destroyed(@NonNull Session2Token token) {
+        if (token == null) {
+            throw new IllegalArgumentException("token shouldn't be null");
+        }
+        if (token.getType() != Session2Token.TYPE_SESSION) {
+            throw new IllegalArgumentException("token's type should be TYPE_SESSION");
+        }
+        if (!token.isDestroyed()) {
+            throw new IllegalArgumentException("token should have been destroyed");
+        }
+        try {
+            mService.notifySession2Destroyed(token);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Get a list of controllers for all ongoing sessions. The controllers will
      * be provided in priority order with the most important controller at index
      * 0.
@@ -175,10 +202,10 @@
             @Nullable ComponentName notificationListener, int userId) {
         ArrayList<MediaController> controllers = new ArrayList<MediaController>();
         try {
-            List<ControllerLink> binders = mService.getSessions(notificationListener, userId);
-            int size = binders.size();
+            List<MediaSession.Token> tokens = mService.getSessions(notificationListener, userId);
+            int size = tokens.size();
             for (int i = 0; i < size; i++) {
-                MediaController controller = new MediaController(mContext, binders.get(i));
+                MediaController controller = new MediaController(mContext, tokens.get(i));
                 controllers.add(controller);
             }
         } catch (RemoteException e) {
@@ -192,7 +219,7 @@
      * current user.
      * <p>
      * Although this API can be used without any restriction, each session owners can accept or
-     * reject your uses of {@link MediaSession2}.
+     * reject your uses of MediaSession2.
      *
      * @return A list of {@link Session2Token}.
      */
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 21c58b3..09b7559 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -20,9 +20,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.StringDef;
 import android.annotation.SystemApi;
-import android.annotation.SdkConstant.SdkConstantType;
 import android.app.Activity;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -1680,6 +1680,7 @@
                 TYPE_ISDB_T,
                 TYPE_ISDB_TB,
                 TYPE_ISDB_S,
+                TYPE_ISDB_S3,
                 TYPE_ISDB_C,
                 TYPE_1SEG,
                 TYPE_DTMB,
@@ -1821,6 +1822,13 @@
         public static final String TYPE_ISDB_S = "TYPE_ISDB_S";
 
         /**
+         * The channel type for ISDB-S3 (satellite).
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_ISDB_S3 = "TYPE_ISDB_S3";
+
+        /**
          * The channel type for ISDB-C (cable).
          *
          * @see #COLUMN_TYPE
@@ -2026,6 +2034,7 @@
          * {@link #TYPE_DVB_T2},
          * {@link #TYPE_ISDB_C},
          * {@link #TYPE_ISDB_S},
+         * {@link #TYPE_ISDB_S3},
          * {@link #TYPE_ISDB_T},
          * {@link #TYPE_ISDB_TB},
          * {@link #TYPE_NTSC},
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 48dbf55..0fb4d2a 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -94,26 +94,14 @@
     ],
 
     shared_libs: [
-        // MediaCas
-        "android.hardware.cas@1.0",
-        "android.hardware.cas.native@1.0",
-        "android.hidl.allocator@1.0",
-        "libhidlbase",
-        "libhidlmemory",
-
-        "libmediametrics",  // Used by MediaMetrics. Will be replaced with stable C API.
-        "libbinder",  // Used by JWakeLock and MediaMetrics.
-
-        "libutils",  // Have to use shared lib to make libandroid_runtime behave correctly.
-                     // Otherwise, AndroidRuntime::getJNIEnv() will return NULL.
-
-        // NDK or NDK-compliant
+        // NDK or LLNDK or NDK-compliant
         "libandroid",
         "libbinder_ndk",
         "libmediandk",
+        "libmediametrics",
         "libnativehelper_compat_libc++",
         "liblog",
-        "libz",
+        "libvndksupport",
     ],
 
     header_libs: [
@@ -122,6 +110,16 @@
     ],
 
     static_libs: [
+        // MediaCas
+        "android.hidl.allocator@1.0",
+        "android.hidl.memory@1.0",
+        "libhidlbase",
+        "libhidlmemory",
+        "libhidltransport",
+        "libhwbinder",
+        "libbinderthreadstate",
+
+        // MediaPlayer2 implementation
         "libbase",
         "libcrypto",
         "libcutils",
@@ -131,10 +129,11 @@
         "libmediaplayer2-protos",
         "libmediandk_utils",
         "libmediautils",
+        "libprocessgroup",
         "libprotobuf-cpp-lite",
         "libstagefright",
         "libstagefright_esds",
-        "libstagefright_foundation",
+        "libstagefright_foundation_without_imemory",
         "libstagefright_httplive",
         "libstagefright_id3",
         "libstagefright_mpeg2support",
@@ -142,6 +141,7 @@
         "libstagefright_player2",
         "libstagefright_rtsp_player2",
         "libstagefright_timedtext2",
+        "libutils",
         "libmedia2_jni_core",
     ],
 
@@ -161,6 +161,7 @@
         "-Wno-error=deprecated-declarations",
         "-Wunused",
         "-Wunreachable-code",
+        "-fvisibility=hidden",
     ],
 
     ldflags: ["-Wl,--exclude-libs=ALL,-error-limit=0"],
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index a45aa90..7168b2d 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -379,24 +379,9 @@
     String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d",
             width, height, format, maxImages, getpid(),
             createProcessUniqueId());
-    uint32_t consumerUsage = GRALLOC_USAGE_SW_READ_OFTEN;
-    bool needUsageOverride = ndkUsage != CONSUMER_BUFFER_USAGE_UNKNOWN;
-    uint64_t outProducerUsage = 0;
-    uint64_t outConsumerUsage = 0;
-    android_hardware_HardwareBuffer_convertToGrallocUsageBits(&outProducerUsage, &outConsumerUsage,
-            ndkUsage, 0);
+    uint64_t consumerUsage =
+            android_hardware_HardwareBuffer_convertToGrallocUsageBits(ndkUsage);
 
-    if (isFormatOpaque(nativeFormat)) {
-        // Use the SW_READ_NEVER usage to tell producer that this format is not for preview or video
-        // encoding. The only possibility will be ZSL output.
-        consumerUsage = GRALLOC_USAGE_SW_READ_NEVER;
-        if (needUsageOverride) {
-            consumerUsage = android_convertGralloc1To0Usage(0, outConsumerUsage);
-        }
-    } else if (needUsageOverride) {
-        ALOGW("Consumer usage override for non-opaque format is not implemented yet, "
-                "ignore the provided usage from the application");
-    }
     bufferConsumer = new BufferItemConsumer(gbConsumer, consumerUsage, maxImages,
             /*controlledByApp*/true);
     if (bufferConsumer == nullptr) {
@@ -405,6 +390,11 @@
                 nativeFormat, consumerUsage);
         return;
     }
+
+    if (consumerUsage & GRALLOC_USAGE_PROTECTED) {
+        gbConsumer->setConsumerIsProtected(true);
+    }
+
     ctx->setBufferConsumer(bufferConsumer);
     bufferConsumer->setName(consumerName);
 
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index 3ded8c2..de60b08 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "MediaMetricsJNI"
+
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
 
@@ -21,7 +23,10 @@
 #include <media/MediaAnalyticsItem.h>
 
 
-// Copeid from core/jni/ (libandroid_runtime.so)
+// This source file is compiled and linked into both:
+// core/jni/ (libandroid_runtime.so)
+// media/jni (libmedia2_jni.so)
+
 namespace android {
 
 // place the attributes into a java PersistableBundle object
@@ -29,7 +34,7 @@
 
     jclass clazzBundle = env->FindClass("android/os/PersistableBundle");
     if (clazzBundle==NULL) {
-        ALOGD("can't find android/os/PersistableBundle");
+        ALOGE("can't find android/os/PersistableBundle");
         return NULL;
     }
     // sometimes the caller provides one for us to fill
@@ -86,5 +91,138 @@
     return mybundle;
 }
 
+// convert the specified batch  metrics attributes to a persistent bundle.
+// The encoding of the byte array is specified in
+//     frameworks/av/media/libmediametrics/MediaAnalyticsItem.cpp
+//
+// type encodings; matches frameworks/av/media/libmediametrics/MediaAnalyticsItem.cpp
+enum { kInt32 = 0, kInt64, kDouble, kRate, kCString};
+
+jobject MediaMetricsJNI::writeAttributesToBundle(JNIEnv* env, jobject mybundle, char *buffer, size_t length) {
+    ALOGV("writeAttributes()");
+
+    if (buffer == NULL || length <= 0) {
+        ALOGW("bad parameters to writeAttributesToBundle()");
+        return NULL;
+    }
+
+    jclass clazzBundle = env->FindClass("android/os/PersistableBundle");
+    if (clazzBundle==NULL) {
+        ALOGE("can't find android/os/PersistableBundle");
+        return NULL;
+    }
+    // sometimes the caller provides one for us to fill
+    if (mybundle == NULL) {
+        // create the bundle
+        jmethodID constructID = env->GetMethodID(clazzBundle, "<init>", "()V");
+        mybundle = env->NewObject(clazzBundle, constructID);
+        if (mybundle == NULL) {
+            ALOGD("unable to create mybundle");
+            return NULL;
+        }
+    }
+
+    int left = length;
+    char *buf = buffer;
+
+    // grab methods that we can invoke
+    jmethodID setIntID = env->GetMethodID(clazzBundle, "putInt", "(Ljava/lang/String;I)V");
+    jmethodID setLongID = env->GetMethodID(clazzBundle, "putLong", "(Ljava/lang/String;J)V");
+    jmethodID setDoubleID = env->GetMethodID(clazzBundle, "putDouble", "(Ljava/lang/String;D)V");
+    jmethodID setStringID = env->GetMethodID(clazzBundle, "putString", "(Ljava/lang/String;Ljava/lang/String;)V");
+
+
+#define _EXTRACT(size, val) \
+    { if ((size) > left) goto badness; memcpy(&val, buf, (size)); buf += (size); left -= (size);}
+#define _SKIP(size) \
+    { if ((size) > left) goto badness; buf += (size); left -= (size);}
+
+    int32_t bufsize;
+    _EXTRACT(sizeof(int32_t), bufsize);
+    if (bufsize != length) {
+        goto badness;
+    }
+    int32_t proto;
+    _EXTRACT(sizeof(int32_t), proto);
+    if (proto != 0) {
+        ALOGE("unsupported wire protocol %d", proto);
+        goto badness;
+    }
+
+    int32_t count;
+    _EXTRACT(sizeof(int32_t), count);
+
+    // iterate through my attributes
+    // -- get name, get type, get value, insert into bundle appropriately.
+    for (int i = 0 ; i < count; i++ ) {
+            // prop name len (int16)
+            int16_t keylen;
+            _EXTRACT(sizeof(int16_t), keylen);
+            if (keylen <= 0) goto badness;
+            // prop name itself
+            char *key = buf;
+            jstring keyName = env->NewStringUTF(buf);
+            _SKIP(keylen);
+
+            // prop type (int8_t)
+            int8_t attrType;
+            _EXTRACT(sizeof(int8_t), attrType);
+
+	    int16_t attrSize;
+            _EXTRACT(sizeof(int16_t), attrSize);
+
+            switch (attrType) {
+                case kInt32:
+                    {
+                        int32_t i32;
+                        _EXTRACT(sizeof(int32_t), i32);
+                        env->CallVoidMethod(mybundle, setIntID,
+                                            keyName, (jint) i32);
+                        break;
+                    }
+                case kInt64:
+                    {
+                        int64_t i64;
+                        _EXTRACT(sizeof(int64_t), i64);
+                        env->CallVoidMethod(mybundle, setLongID,
+                                            keyName, (jlong) i64);
+                        break;
+                    }
+                case kDouble:
+                    {
+                        double d64;
+                        _EXTRACT(sizeof(double), d64);
+                        env->CallVoidMethod(mybundle, setDoubleID,
+                                            keyName, (jdouble) d64);
+                        break;
+                    }
+                case kCString:
+                    {
+                        jstring value = env->NewStringUTF(buf);
+                        env->CallVoidMethod(mybundle, setStringID,
+                                            keyName, value);
+                        _SKIP(attrSize);
+                        break;
+                    }
+                default:
+                        ALOGW("ignoring Attribute '%s' unknown type: %d",
+                              key, attrType);
+			_SKIP(attrSize);
+                        break;
+            }
+    }
+
+    // should have consumed it all
+    if (left != 0) {
+        ALOGW("did not consume entire buffer; left(%d) != 0", left);
+	goto badness;
+    }
+
+    return mybundle;
+
+  badness:
+    return NULL;
+}
+
 };  // namespace android
 
diff --git a/media/jni/android_media_MediaMetricsJNI.h b/media/jni/android_media_MediaMetricsJNI.h
index fd621ea..a10780f 100644
--- a/media/jni/android_media_MediaMetricsJNI.h
+++ b/media/jni/android_media_MediaMetricsJNI.h
@@ -27,6 +27,7 @@
 class MediaMetricsJNI {
 public:
     static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle);
+    static jobject writeAttributesToBundle(JNIEnv* env, jobject mybundle, char *buffer, size_t length);
 };
 
 };  // namespace android
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index 9b4e730..3069161 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -786,22 +786,17 @@
         return 0;
     }
 
-    Parcel p;
-    int key = FOURCC('m','t','r','X');
-    status_t status = mp->getParameter(key, &p);
+    char *buffer = NULL;
+    size_t length = 0;
+    status_t status = mp->getMetrics(&buffer, &length);
     if (status != OK) {
         ALOGD("getMetrics() failed: %d", status);
         return (jobject) NULL;
     }
 
-    p.setDataPosition(0);
-    MediaAnalyticsItem *item = new MediaAnalyticsItem;
-    item->readFromParcel(p);
-    jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL);
+    jobject mybundle = MediaMetricsJNI::writeAttributesToBundle(env, NULL, buffer, length);
 
-    // housekeeping
-    delete item;
-    item = NULL;
+    free(buffer);
 
     return mybundle;
 }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
index 5ab5092..0340cec 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
@@ -857,7 +857,7 @@
         // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer.
         // Same goes for DEPTH_POINT_CLOUD
         if (format == ImageFormat.JPEG || format == ImageFormat.DEPTH_POINT_CLOUD ||
-                format == ImageFormat.RAW_PRIVATE) {
+                format == ImageFormat.DEPTH_JPEG || format == ImageFormat.RAW_PRIVATE) {
             buffer = planes[0].getBuffer();
             assertNotNull("Fail to get jpeg or depth ByteBuffer", buffer);
             data = new byte[buffer.remaining()];
@@ -940,6 +940,7 @@
             case ImageFormat.RAW_PRIVATE:
             case ImageFormat.DEPTH16:
             case ImageFormat.DEPTH_POINT_CLOUD:
+            case ImageFormat.DEPTH_JPEG:
                 assertEquals("JPEG/RAW/depth Images should have one plane", 1, planes.length);
                 break;
             default:
@@ -1363,6 +1364,9 @@
             case ImageFormat.RAW_PRIVATE:
                 validateRawPrivateData(data, width, height, image.getTimestamp(), filePath);
                 break;
+            case ImageFormat.DEPTH_JPEG:
+                validateDepthJpegData(data, width, height, format, image.getTimestamp(), filePath);
+                break;
             default:
                 throw new UnsupportedOperationException("Unsupported format for validation: "
                         + format);
@@ -1528,6 +1532,23 @@
 
     }
 
+    private static void validateDepthJpegData(byte[] depthData, int width, int height, int format,
+            long ts, String filePath) {
+
+        if (VERBOSE) Log.v(TAG, "Validating depth jpeg data");
+
+        // Can't validate size since it is variable
+
+        if (DEBUG && filePath != null) {
+            String fileName =
+                    filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".jpg";
+            dumpFile(fileName, depthData);
+        }
+
+        return;
+
+    }
+
     public static <T> T getValueNotNull(CaptureResult result, CaptureResult.Key<T> key) {
         if (result == null) {
             throw new IllegalArgumentException("Result must not be null");
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/native/webview/plat_support/draw_fn.h b/native/webview/plat_support/draw_fn.h
index 0490e65..e31ce19 100644
--- a/native/webview/plat_support/draw_fn.h
+++ b/native/webview/plat_support/draw_fn.h
@@ -20,7 +20,8 @@
 // android to chromium are versioned.
 //
 // 1 is Android Q. This matches kAwDrawGLInfoVersion version 3.
-static const int kAwDrawFnVersion = 1;
+// 2 Adds transfer_function_* and color_space_toXYZD50 to AwDrawFn_DrawGLParams.
+static const int kAwDrawFnVersion = 2;
 
 struct AwDrawFn_OnSyncParams {
   int version;
@@ -64,6 +65,16 @@
   // Input: current transformation matrix in surface pixels.
   // Uses the column-based OpenGL matrix format.
   float transform[16];
+
+  // Input: Color space parameters.
+  float transfer_function_g;
+  float transfer_function_a;
+  float transfer_function_b;
+  float transfer_function_c;
+  float transfer_function_d;
+  float transfer_function_e;
+  float transfer_function_f;
+  float color_space_toXYZD50[9];
 };
 
 struct AwDrawFn_InitVkParams {
diff --git a/native/webview/plat_support/draw_functor.cpp b/native/webview/plat_support/draw_functor.cpp
index b97bbc3..afe103a 100644
--- a/native/webview/plat_support/draw_functor.cpp
+++ b/native/webview/plat_support/draw_functor.cpp
@@ -55,6 +55,8 @@
 
 void draw_gl(int functor, void* data,
              const uirenderer::DrawGlInfo& draw_gl_params) {
+  float gabcdef[7];
+  draw_gl_params.color_space_ptr->transferFn(gabcdef);
   AwDrawFn_DrawGLParams params = {
       .version = kAwDrawFnVersion,
       .clip_left = draw_gl_params.clipLeft,
@@ -64,12 +66,24 @@
       .width = draw_gl_params.width,
       .height = draw_gl_params.height,
       .is_layer = draw_gl_params.isLayer,
+      .transfer_function_g = gabcdef[0],
+      .transfer_function_a = gabcdef[1],
+      .transfer_function_b = gabcdef[2],
+      .transfer_function_c = gabcdef[3],
+      .transfer_function_d = gabcdef[4],
+      .transfer_function_e = gabcdef[5],
+      .transfer_function_f = gabcdef[6],
   };
   COMPILE_ASSERT(NELEM(params.transform) == NELEM(draw_gl_params.transform),
                  mismatched_transform_matrix_sizes);
   for (int i = 0; i < NELEM(params.transform); ++i) {
     params.transform[i] = draw_gl_params.transform[i];
   }
+  COMPILE_ASSERT(sizeof(params.color_space_toXYZD50) == sizeof(skcms_Matrix3x3),
+                 gamut_transform_size_mismatch);
+  draw_gl_params.color_space_ptr->toXYZD50(
+      reinterpret_cast<skcms_Matrix3x3*>(&params.color_space_toXYZD50));
+
   SupportData* support = static_cast<SupportData*>(data);
   support->callbacks.draw_gl(functor, support->data, &params);
 }
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/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index dbddf71..b37c5e6 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -114,9 +114,16 @@
                     new DeviceProvisionedController.DeviceProvisionedListener() {
                         @Override
                         public void onDeviceProvisionedChanged() {
-                            mDeviceIsProvisioned =
-                                    mDeviceProvisionedController.isDeviceProvisioned();
-                            restartNavBars();
+                            mHandler.post(() -> {
+                                // on initial boot we are getting a call even though the value
+                                // is the same so we are confirming the reset is needed
+                                boolean deviceProvisioned =
+                                        mDeviceProvisionedController.isDeviceProvisioned();
+                                if (mDeviceIsProvisioned != deviceProvisioned) {
+                                    mDeviceIsProvisioned = deviceProvisioned;
+                                    restartNavBars();
+                                }
+                            });
                         }
                     });
         }
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/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
index bc6e2fc..5acf4fb 100644
--- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -291,7 +291,7 @@
         Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES);
         if (messages == null || messages.length == 0) {
             return Arrays.asList(new ConversationActions.Message.Builder(
-                    ConversationActions.Message.PERSON_USER_REMOTE)
+                    ConversationActions.Message.PERSON_USER_OTHERS)
                     .setText(notification.extras.getCharSequence(Notification.EXTRA_TEXT))
                     .build());
         }
@@ -310,7 +310,7 @@
                 break;
             }
             Person author = localUser != null && localUser.equals(senderPerson)
-                    ? ConversationActions.Message.PERSON_USER_LOCAL : senderPerson;
+                    ? ConversationActions.Message.PERSON_USER_SELF : senderPerson;
             extractMessages.push(new ConversationActions.Message.Builder(author)
                     .setText(message.getText())
                     .setReferenceTime(
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
index 707349b..7f8127a 100644
--- a/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/SmartActionHelperTest.java
@@ -154,7 +154,7 @@
         ConversationActions.Message secondMessage = messages.get(0);
         MessageSubject.assertThat(secondMessage).hasText("secondMessage");
         MessageSubject.assertThat(secondMessage)
-                .hasPerson(ConversationActions.Message.PERSON_USER_LOCAL);
+                .hasPerson(ConversationActions.Message.PERSON_USER_SELF);
         MessageSubject.assertThat(secondMessage)
                 .hasReferenceTime(createZonedDateTimeFromMsUtc(2000));
 
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/DhcpPacketListener.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java
index dce8b61..eac8d2a 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.net.util.FdEventsReader;
+import android.net.shared.FdEventsReader;
 import android.os.Handler;
 import android.system.Os;
 
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..4315d34 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;
@@ -41,15 +40,12 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpManagerEvent;
 import android.net.shared.InitialConfiguration;
-import android.net.shared.NetdService;
 import android.net.shared.ProvisioningConfiguration;
 import android.net.util.InterfaceParams;
 import android.net.util.SharedLog;
 import android.os.ConditionVariable;
-import android.os.INetworkManagementService;
 import android.os.Message;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.LocalLog;
@@ -65,7 +61,7 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
-import com.android.server.net.NetlinkTracker;
+import com.android.server.NetworkObserverRegistry;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -339,8 +335,9 @@
     private final Dependencies mDependencies;
     private final CountDownLatch mShutdownLatch;
     private final ConnectivityManager mCm;
-    private final INetworkManagementService mNwService;
-    private final NetlinkTracker mNetlinkTracker;
+    private final INetd mNetd;
+    private final NetworkObserverRegistry mObserverRegistry;
+    private final IpClientLinkObserver mLinkObserver;
     private final WakeupMessage mProvisioningTimeoutAlarm;
     private final WakeupMessage mDhcpActionTimeoutAlarm;
     private final SharedLog mLog;
@@ -374,15 +371,6 @@
     private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable();
 
     public static class Dependencies {
-        public INetworkManagementService getNMS() {
-            return INetworkManagementService.Stub.asInterface(
-                    ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
-        }
-
-        public INetd getNetd() {
-            return NetdService.getInstance();
-        }
-
         /**
          * Get interface parameters for the specified interface.
          */
@@ -391,26 +379,14 @@
         }
     }
 
-    public IpClient(Context context, String ifName, IIpClientCallbacks callback) {
-        this(context, ifName, callback, new Dependencies());
-    }
-
-    /**
-     * An expanded constructor, useful for dependency injection.
-     * TODO: migrate all test users to mock IpClient directly and remove this ctor.
-     */
     public IpClient(Context context, String ifName, IIpClientCallbacks callback,
-            INetworkManagementService nwService) {
-        this(context, ifName, callback, new Dependencies() {
-            @Override
-            public INetworkManagementService getNMS() {
-                return nwService;
-            }
-        });
+            NetworkObserverRegistry observerRegistry) {
+        this(context, ifName, callback, observerRegistry, new Dependencies());
     }
 
     @VisibleForTesting
-    IpClient(Context context, String ifName, IIpClientCallbacks callback, Dependencies deps) {
+    IpClient(Context context, String ifName, IIpClientCallbacks callback,
+            NetworkObserverRegistry observerRegistry, Dependencies deps) {
         super(IpClient.class.getSimpleName() + "." + ifName);
         Preconditions.checkNotNull(ifName);
         Preconditions.checkNotNull(callback);
@@ -423,7 +399,7 @@
         mDependencies = deps;
         mShutdownLatch = new CountDownLatch(1);
         mCm = mContext.getSystemService(ConnectivityManager.class);
-        mNwService = deps.getNMS();
+        mObserverRegistry = observerRegistry;
 
         sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag));
         mLog = sSmLogs.get(mInterfaceName);
@@ -434,19 +410,15 @@
 
         // TODO: Consider creating, constructing, and passing in some kind of
         // InterfaceController.Dependencies class.
-        mInterfaceCtrl = new InterfaceController(mInterfaceName, deps.getNetd(), mLog);
+        mNetd = mContext.getSystemService(INetd.class);
+        mInterfaceCtrl = new InterfaceController(mInterfaceName, mNetd, mLog);
 
-        mNetlinkTracker = new NetlinkTracker(
+        mLinkObserver = new IpClientLinkObserver(
                 mInterfaceName,
-                new NetlinkTracker.Callback() {
-                    @Override
-                    public void update() {
-                        sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
-                    }
-                }) {
+                () -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED)) {
             @Override
-            public void interfaceAdded(String iface) {
-                super.interfaceAdded(iface);
+            public void onInterfaceAdded(String iface) {
+                super.onInterfaceAdded(iface);
                 if (mClatInterfaceName.equals(iface)) {
                     mCallback.setNeighborDiscoveryOffload(false);
                 } else if (!mInterfaceName.equals(iface)) {
@@ -458,8 +430,8 @@
             }
 
             @Override
-            public void interfaceRemoved(String iface) {
-                super.interfaceRemoved(iface);
+            public void onInterfaceRemoved(String iface) {
+                super.onInterfaceRemoved(iface);
                 // TODO: Also observe mInterfaceName going down and take some
                 // kind of appropriate action.
                 if (mClatInterfaceName.equals(iface)) {
@@ -571,19 +543,11 @@
     }
 
     private void startStateMachineUpdaters() {
-        try {
-            mNwService.registerObserver(mNetlinkTracker);
-        } catch (RemoteException e) {
-            logError("Couldn't register NetlinkTracker: %s", e);
-        }
+        mObserverRegistry.registerObserverForNonblockingCallback(mLinkObserver);
     }
 
     private void stopStateMachineUpdaters() {
-        try {
-            mNwService.unregisterObserver(mNetlinkTracker);
-        } catch (RemoteException e) {
-            logError("Couldn't unregister NetlinkTracker: %s", e);
-        }
+        mObserverRegistry.unregisterObserver(mLinkObserver);
     }
 
     @Override
@@ -806,7 +770,7 @@
     // we should only call this if we know for sure that there are no IP addresses
     // assigned to the interface, etc.
     private void resetLinkProperties() {
-        mNetlinkTracker.clearLinkProperties();
+        mLinkObserver.clearLinkProperties();
         mConfiguration = null;
         mDhcpResults = null;
         mTcpBufferSizes = "";
@@ -985,10 +949,10 @@
         //         - IPv6 DNS servers
         //
         // N.B.: this is fundamentally race-prone and should be fixed by
-        // changing NetlinkTracker from a hybrid edge/level model to an
+        // changing IpClientLinkObserver from a hybrid edge/level model to an
         // edge-only model, or by giving IpClient its own netlink socket(s)
         // so as to track all required information directly.
-        LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
+        LinkProperties netlinkLinkProperties = mLinkObserver.getLinkProperties();
         newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
         for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
             newLp.addRoute(route);
@@ -1000,7 +964,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);
@@ -1165,8 +1131,7 @@
             // necessary or does reading from settings at startup suffice?).
             final int numSolicits = 5;
             final int interSolicitIntervalMs = 750;
-            setNeighborParameters(mDependencies.getNetd(), mInterfaceName,
-                    numSolicits, interSolicitIntervalMs);
+            setNeighborParameters(mNetd, mInterfaceName, numSolicits, interSolicitIntervalMs);
         } catch (Exception e) {
             mLog.e("Failed to adjust neighbor parameters", e);
             // Carry on using the system defaults (currently: 3, 1000);
diff --git a/packages/NetworkStack/src/android/net/ip/IpClientLinkObserver.java b/packages/NetworkStack/src/android/net/ip/IpClientLinkObserver.java
new file mode 100644
index 0000000..8ad99aa0
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/ip/IpClientLinkObserver.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2014 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.ip;
+
+import android.net.InetAddresses;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
+import android.util.Log;
+
+import com.android.server.NetworkObserver;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Keeps track of link configuration received from Netd.
+ *
+ * An instance of this class is constructed by passing in an interface name and a callback. The
+ * owner is then responsible for registering the tracker with NetworkObserverRegistry. When the
+ * class receives update notifications, it applies the update to its local LinkProperties, and if
+ * something has changed, notifies its owner of the update via the callback.
+ *
+ * The owner can then call {@code getLinkProperties()} in order to find out
+ * what changed. If in the meantime the LinkProperties stored here have changed,
+ * this class will return the current LinkProperties. Because each change
+ * triggers an update callback after the change is made, the owner may get more
+ * callbacks than strictly necessary (some of which may be no-ops), but will not
+ * be out of sync once all callbacks have been processed.
+ *
+ * Threading model:
+ *
+ * - The owner of this class is expected to create it, register it, and call
+ *   getLinkProperties or clearLinkProperties on its thread.
+ * - Most of the methods in the class are implementing NetworkObserver and are called
+ *   on the handler used to register the observer.
+ * - All accesses to mLinkProperties must be synchronized(this). All the other
+ *   member variables are immutable once the object is constructed.
+ *
+ * @hide
+ */
+public class IpClientLinkObserver implements NetworkObserver {
+    private final String mTag;
+
+    /**
+     * Callback used by {@link IpClientLinkObserver} to send update notifications.
+     */
+    public interface Callback {
+        /**
+         * Called when some properties of the link were updated.
+         */
+        void update();
+    }
+
+    private final String mInterfaceName;
+    private final Callback mCallback;
+    private final LinkProperties mLinkProperties;
+    private DnsServerRepository mDnsServerRepository;
+
+    private static final boolean DBG = false;
+
+    public IpClientLinkObserver(String iface, Callback callback) {
+        mTag = "NetlinkTracker/" + iface;
+        mInterfaceName = iface;
+        mCallback = callback;
+        mLinkProperties = new LinkProperties();
+        mLinkProperties.setInterfaceName(mInterfaceName);
+        mDnsServerRepository = new DnsServerRepository();
+    }
+
+    private void maybeLog(String operation, String iface, LinkAddress address) {
+        if (DBG) {
+            Log.d(mTag, operation + ": " + address + " on " + iface
+                    + " flags " + address.getFlags() + " scope " + address.getScope());
+        }
+    }
+
+    private void maybeLog(String operation, Object o) {
+        if (DBG) {
+            Log.d(mTag, operation + ": " + o.toString());
+        }
+    }
+
+    @Override
+    public void onInterfaceRemoved(String iface) {
+        maybeLog("interfaceRemoved", iface);
+        if (mInterfaceName.equals(iface)) {
+            // Our interface was removed. Clear our LinkProperties and tell our owner that they are
+            // now empty. Note that from the moment that the interface is removed, any further
+            // interface-specific messages (e.g., RTM_DELADDR) will not reach us, because the netd
+            // code that parses them will not be able to resolve the ifindex to an interface name.
+            clearLinkProperties();
+            mCallback.update();
+        }
+    }
+
+    @Override
+    public void onInterfaceAddressUpdated(LinkAddress address, String iface) {
+        if (mInterfaceName.equals(iface)) {
+            maybeLog("addressUpdated", iface, address);
+            boolean changed;
+            synchronized (this) {
+                changed = mLinkProperties.addLinkAddress(address);
+            }
+            if (changed) {
+                mCallback.update();
+            }
+        }
+    }
+
+    @Override
+    public void onInterfaceAddressRemoved(LinkAddress address, String iface) {
+        if (mInterfaceName.equals(iface)) {
+            maybeLog("addressRemoved", iface, address);
+            boolean changed;
+            synchronized (this) {
+                changed = mLinkProperties.removeLinkAddress(address);
+            }
+            if (changed) {
+                mCallback.update();
+            }
+        }
+    }
+
+    @Override
+    public void onRouteUpdated(RouteInfo route) {
+        if (mInterfaceName.equals(route.getInterface())) {
+            maybeLog("routeUpdated", route);
+            boolean changed;
+            synchronized (this) {
+                changed = mLinkProperties.addRoute(route);
+            }
+            if (changed) {
+                mCallback.update();
+            }
+        }
+    }
+
+    @Override
+    public void onRouteRemoved(RouteInfo route) {
+        if (mInterfaceName.equals(route.getInterface())) {
+            maybeLog("routeRemoved", route);
+            boolean changed;
+            synchronized (this) {
+                changed = mLinkProperties.removeRoute(route);
+            }
+            if (changed) {
+                mCallback.update();
+            }
+        }
+    }
+
+    @Override
+    public void onInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
+        if (mInterfaceName.equals(iface)) {
+            maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses));
+            boolean changed = mDnsServerRepository.addServers(lifetime, addresses);
+            if (changed) {
+                synchronized (this) {
+                    mDnsServerRepository.setDnsServersOn(mLinkProperties);
+                }
+                mCallback.update();
+            }
+        }
+    }
+
+    /**
+     * Returns a copy of this object's LinkProperties.
+     */
+    public synchronized LinkProperties getLinkProperties() {
+        return new LinkProperties(mLinkProperties);
+    }
+
+    /**
+     * Reset this object's LinkProperties.
+     */
+    public synchronized void clearLinkProperties() {
+        // Clear the repository before clearing mLinkProperties. That way, if a clear() happens
+        // while interfaceDnsServerInfo() is being called, we'll end up with no DNS servers in
+        // mLinkProperties, as desired.
+        mDnsServerRepository = new DnsServerRepository();
+        mLinkProperties.clear();
+        mLinkProperties.setInterfaceName(mInterfaceName);
+    }
+
+    /**
+     * Tracks DNS server updates received from Netlink.
+     *
+     * The network may announce an arbitrary number of DNS servers in Router Advertisements at any
+     * time. Each announcement has a lifetime; when the lifetime expires, the servers should not be
+     * used any more. In this way, the network can gracefully migrate clients from one set of DNS
+     * servers to another. Announcements can both raise and lower the lifetime, and an announcement
+     * can expire servers by announcing them with a lifetime of zero.
+     *
+     * Typically the system will only use a small number (2 or 3; {@code NUM_CURRENT_SERVERS}) of
+     * DNS servers at any given time. These are referred to as the current servers. In case all the
+     * current servers expire, the class also keeps track of a larger (but limited) number of
+     * servers that are promoted to current servers when the current ones expire. In order to
+     * minimize updates to the rest of the system (and potentially expensive cache flushes) this
+     * class attempts to keep the list of current servers constant where possible. More
+     * specifically, the list of current servers is only updated if a new server is learned and
+     * there are not yet {@code NUM_CURRENT_SERVERS} current servers, or if one or more of the
+     * current servers expires or is pushed out of the set. Therefore, the current servers will not
+     * necessarily be the ones with the highest lifetime, but the ones learned first.
+     *
+     * This is by design: if instead the class always preferred the servers with the highest
+     * lifetime, a (misconfigured?) network where two or more routers announce more than
+     * {@code NUM_CURRENT_SERVERS} unique servers would cause persistent oscillations.
+     *
+     * TODO: Currently servers are only expired when a new DNS update is received.
+     * Update them using timers, or possibly on every notification received by NetlinkTracker.
+     *
+     * Threading model: run by NetlinkTracker. Methods are synchronized(this) just in case netlink
+     * notifications are sent by multiple threads. If future threads use alarms to expire, those
+     * alarms must also be synchronized(this).
+     *
+     */
+    private static class DnsServerRepository {
+
+        /** How many DNS servers we will use. 3 is suggested by RFC 6106. */
+        static final int NUM_CURRENT_SERVERS = 3;
+
+        /** How many DNS servers we'll keep track of, in total. */
+        static final int NUM_SERVERS = 12;
+
+        /** Stores up to {@code NUM_CURRENT_SERVERS} DNS servers we're currently using. */
+        private Set<InetAddress> mCurrentServers;
+
+        public static final String TAG = "DnsServerRepository";
+
+        /**
+         * Stores all the DNS servers we know about, for use when the current servers expire.
+         * Always sorted in order of decreasing expiry. The elements in this list are also the
+         * values of mIndex, and may be elements in mCurrentServers.
+         */
+        private ArrayList<DnsServerEntry> mAllServers;
+
+        /**
+         * Indexes the servers so we can update their lifetimes more quickly in the common case
+         * where servers are not being added, but only being refreshed.
+         */
+        private HashMap<InetAddress, DnsServerEntry> mIndex;
+
+        DnsServerRepository() {
+            mCurrentServers = new HashSet<>();
+            mAllServers = new ArrayList<>(NUM_SERVERS);
+            mIndex = new HashMap<>(NUM_SERVERS);
+        }
+
+        /** Sets the DNS servers of the provided LinkProperties object to the current servers. */
+        public synchronized void setDnsServersOn(LinkProperties lp) {
+            lp.setDnsServers(mCurrentServers);
+        }
+
+        /**
+         * Notifies the class of new DNS server information.
+         * @param lifetime the time in seconds that the DNS servers are valid.
+         * @param addresses the string representations of the IP addresses of DNS servers to use.
+         */
+        public synchronized boolean addServers(long lifetime, String[] addresses) {
+            // The lifetime is actually an unsigned 32-bit number, but Java doesn't have unsigned.
+            // Technically 0xffffffff (the maximum) is special and means "forever", but 2^32 seconds
+            // (136 years) is close enough.
+            long now = System.currentTimeMillis();
+            long expiry = now + 1000 * lifetime;
+
+            // Go through the list of servers. For each one, update the entry if one exists, and
+            // create one if it doesn't.
+            for (String addressString : addresses) {
+                InetAddress address;
+                try {
+                    address = InetAddresses.parseNumericAddress(addressString);
+                } catch (IllegalArgumentException ex) {
+                    continue;
+                }
+
+                if (!updateExistingEntry(address, expiry)) {
+                    // There was no entry for this server. Create one, unless it's already expired
+                    // (i.e., if the lifetime is zero; it cannot be < 0 because it's unsigned).
+                    if (expiry > now) {
+                        DnsServerEntry entry = new DnsServerEntry(address, expiry);
+                        mAllServers.add(entry);
+                        mIndex.put(address, entry);
+                    }
+                }
+            }
+
+            // Sort the servers by expiry.
+            Collections.sort(mAllServers);
+
+            // Prune excess entries and update the current server list.
+            return updateCurrentServers();
+        }
+
+        private synchronized boolean updateExistingEntry(InetAddress address, long expiry) {
+            DnsServerEntry existing = mIndex.get(address);
+            if (existing != null) {
+                existing.expiry = expiry;
+                return true;
+            }
+            return false;
+        }
+
+        private synchronized boolean updateCurrentServers() {
+            long now = System.currentTimeMillis();
+            boolean changed = false;
+
+            // Prune excess or expired entries.
+            for (int i = mAllServers.size() - 1; i >= 0; i--) {
+                if (i >= NUM_SERVERS || mAllServers.get(i).expiry < now) {
+                    DnsServerEntry removed = mAllServers.remove(i);
+                    mIndex.remove(removed.address);
+                    changed |= mCurrentServers.remove(removed.address);
+                } else {
+                    break;
+                }
+            }
+
+            // Add servers to the current set, in order of decreasing lifetime, until it has enough.
+            // Prefer existing servers over new servers in order to minimize updates to the rest of
+            // the system and avoid persistent oscillations.
+            for (DnsServerEntry entry : mAllServers) {
+                if (mCurrentServers.size() < NUM_CURRENT_SERVERS) {
+                    changed |= mCurrentServers.add(entry.address);
+                } else {
+                    break;
+                }
+            }
+            return changed;
+        }
+    }
+
+    /**
+     * Represents a DNS server entry with an expiry time.
+     *
+     * Implements Comparable so DNS server entries can be sorted by lifetime, longest-lived first.
+     * The ordering of entries with the same lifetime is unspecified, because given two servers with
+     * identical lifetimes, we don't care which one we use, and only comparing the lifetime is much
+     * faster than comparing the IP address as well.
+     *
+     * Note: this class has a natural ordering that is inconsistent with equals.
+     */
+    private static class DnsServerEntry implements Comparable<DnsServerEntry> {
+        /** The IP address of the DNS server. */
+        public final InetAddress address;
+        /** The time until which the DNS server may be used. A Java millisecond time as might be
+         * returned by currentTimeMillis(). */
+        public long expiry;
+
+        DnsServerEntry(InetAddress address, long expiry) throws IllegalArgumentException {
+            this.address = address;
+            this.expiry = expiry;
+        }
+
+        public int compareTo(DnsServerEntry other) {
+            return Long.compare(other.expiry, this.expiry);
+        }
+    }
+}
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/services/net/java/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
similarity index 62%
copy from services/net/java/android/net/dhcp/DhcpClient.java
copy to packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
index cddb91f..6dcf0c0 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
@@ -14,16 +14,17 @@
  * limitations under the License.
  */
 
-package android.net.dhcp;
+package android.net.util;
 
 /**
- * TODO: remove this class after migrating clients.
+ * Collection of utilities for the network stack.
  */
-public class DhcpClient {
-    public static final int CMD_PRE_DHCP_ACTION = 1003;
-    public static final int CMD_POST_DHCP_ACTION = 1004;
-    public static final int CMD_PRE_DHCP_ACTION_COMPLETE = 1006;
+public class NetworkStackUtils {
 
-    public static final int DHCP_SUCCESS = 1;
-    public static final int DHCP_FAILURE = 2;
+    /**
+     * @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/android/net/util/PacketReader.java b/packages/NetworkStack/src/android/net/util/PacketReader.java
index 4aec6b6..94b1e9f 100644
--- a/packages/NetworkStack/src/android/net/util/PacketReader.java
+++ b/packages/NetworkStack/src/android/net/util/PacketReader.java
@@ -18,6 +18,7 @@
 
 import static java.lang.Math.max;
 
+import android.net.shared.FdEventsReader;
 import android.os.Handler;
 import android.system.Os;
 
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..cccec0b
--- /dev/null
+++ b/packages/NetworkStack/src/com/android/server/NetworkObserver.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+import android.net.RouteInfo;
+
+/**
+ * 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(RouteInfo route) {}
+
+    /**
+     * @see android.net.INetdUnsolicitedEventListener
+     *          #onRouteChanged(boolean, String, String, String)
+     */
+    default void onRouteRemoved(RouteInfo route) {}
+}
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..4f55779
--- /dev/null
+++ b/packages/NetworkStack/src/com/android/server/NetworkObserverRegistry.java
@@ -0,0 +1,182 @@
+/*
+ * 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.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.RouteInfo;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Map;
+import java.util.Optional;
+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 {
+    private static final String TAG = NetworkObserverRegistry.class.getSimpleName();
+
+    /**
+     * 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, Optional<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) {
+        if (handler == null) {
+            throw new IllegalArgumentException("handler must be non-null");
+        }
+        mObservers.put(observer, Optional.of(handler));
+    }
+
+    /**
+     * Registers the specified observer, and start sending callbacks to it.
+     *
+     * <p>This method must only be called with callbacks that are nonblocking, such as callbacks
+     * that only send a message to a StateMachine.
+     */
+    public void registerObserverForNonblockingCallback(@NonNull NetworkObserver observer) {
+        mObservers.put(observer, Optional.empty());
+    }
+
+    /**
+     * 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, Optional<Handler>> entry : mObservers.entrySet()) {
+            final NetworkObserver observer = entry.getKey();
+            final Optional<Handler> handler = entry.getValue();
+            if (handler.isPresent()) {
+                handler.get().post(() -> callback.sendCallback(observer));
+                return;
+            }
+
+            try {
+                callback.sendCallback(observer);
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Error sending callback to observer", e);
+            }
+        }
+    }
+
+    @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) {
+        final RouteInfo processRoute = new RouteInfo(new IpPrefix(route),
+                ("".equals(gateway)) ? null : InetAddresses.parseNumericAddress(gateway),
+                ifName);
+        if (updated) {
+            invokeForAllObservers(o -> o.onRouteUpdated(processRoute));
+        } else {
+            invokeForAllObservers(o -> o.onRouteRemoved(processRoute));
+        }
+    }
+
+    @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..7405c47 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,15 @@
 
         NetworkStackConnector(Context context) {
             mContext = context;
+            mNetd = (INetd) context.getSystemService(Context.NETD_SERVICE);
+            mObserverRegistry = new NetworkObserverRegistry();
             mCm = context.getSystemService(ConnectivityManager.class);
+
+            try {
+                mObserverRegistry.register(mNetd);
+            } catch (RemoteException e) {
+                mLog.e("Error registering observer on Netd", e);
+            }
         }
 
         @NonNull
@@ -147,7 +162,7 @@
 
         @Override
         public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException {
-            final IpClient ipClient = new IpClient(mContext, ifName, cb);
+            final IpClient ipClient = new IpClient(mContext, ifName, cb, mObserverRegistry);
 
             synchronized (mIpClients) {
                 final Iterator<WeakReference<IpClient>> it = mIpClients.iterator();
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/NetworkStack/tests/src/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
index f21809f..7e57d1e 100644
--- a/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
+++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java
@@ -46,7 +46,6 @@
 import android.net.shared.InitialConfiguration;
 import android.net.shared.ProvisioningConfiguration;
 import android.net.util.InterfaceParams;
-import android.os.INetworkManagementService;
 import android.provider.Settings;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -54,7 +53,8 @@
 
 import com.android.internal.R;
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.net.BaseNetworkObserver;
+import com.android.server.NetworkObserver;
+import com.android.server.NetworkObserverRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -87,15 +87,15 @@
 
     @Mock private Context mContext;
     @Mock private ConnectivityManager mCm;
-    @Mock private INetworkManagementService mNMService;
+    @Mock private NetworkObserverRegistry mObserverRegistry;
     @Mock private INetd mNetd;
     @Mock private Resources mResources;
     @Mock private IIpClientCallbacks mCb;
     @Mock private AlarmManager mAlarm;
-    @Mock private IpClient.Dependencies mDependecies;
+    @Mock private IpClient.Dependencies mDependencies;
     private MockContentResolver mContentResolver;
 
-    private BaseNetworkObserver mObserver;
+    private NetworkObserver mObserver;
     private InterfaceParams mIfParams;
 
     @Before
@@ -103,9 +103,8 @@
         MockitoAnnotations.initMocks(this);
 
         when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm);
-        when(mContext.getSystemServiceName(ConnectivityManager.class))
-                .thenReturn(Context.CONNECTIVITY_SERVICE);
-        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
+        when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm);
+        when(mContext.getSystemService(INetd.class)).thenReturn(mNetd);
         when(mContext.getResources()).thenReturn(mResources);
         when(mResources.getInteger(R.integer.config_networkAvoidBadWifi))
                 .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE);
@@ -115,28 +114,24 @@
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
 
         mIfParams = null;
-
-        when(mDependecies.getNMS()).thenReturn(mNMService);
-        when(mDependecies.getNetd()).thenReturn(mNetd);
     }
 
     private void setTestInterfaceParams(String ifname) {
         mIfParams = (ifname != null)
                 ? new InterfaceParams(ifname, TEST_IFINDEX, TEST_MAC)
                 : null;
-        when(mDependecies.getInterfaceParams(anyString())).thenReturn(mIfParams);
+        when(mDependencies.getInterfaceParams(anyString())).thenReturn(mIfParams);
     }
 
     private IpClient makeIpClient(String ifname) throws Exception {
         setTestInterfaceParams(ifname);
-        final IpClient ipc = new IpClient(mContext, ifname, mCb, mDependecies);
+        final IpClient ipc = new IpClient(mContext, ifname, mCb, mObserverRegistry, mDependencies);
         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(ifname, false);
         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(ifname);
-        ArgumentCaptor<BaseNetworkObserver> arg =
-                ArgumentCaptor.forClass(BaseNetworkObserver.class);
-        verify(mNMService, times(1)).registerObserver(arg.capture());
+        ArgumentCaptor<NetworkObserver> arg = ArgumentCaptor.forClass(NetworkObserver.class);
+        verify(mObserverRegistry, times(1)).registerObserverForNonblockingCallback(arg.capture());
         mObserver = arg.getValue();
-        reset(mNMService);
+        reset(mObserverRegistry);
         reset(mNetd);
         // Verify IpClient doesn't call onLinkPropertiesChange() when it starts.
         verify(mCb, never()).onLinkPropertiesChange(any());
@@ -154,7 +149,8 @@
     public void testNullInterfaceNameMostDefinitelyThrows() throws Exception {
         setTestInterfaceParams(null);
         try {
-            final IpClient ipc = new IpClient(mContext, null, mCb, mDependecies);
+            final IpClient ipc = new IpClient(
+                    mContext, null, mCb, mObserverRegistry, mDependencies);
             ipc.shutdown();
             fail();
         } catch (NullPointerException npe) {
@@ -167,7 +163,8 @@
         final String ifname = "lo";
         setTestInterfaceParams(ifname);
         try {
-            final IpClient ipc = new IpClient(mContext, ifname, null, mDependecies);
+            final IpClient ipc = new IpClient(
+                    mContext, ifname, null, mObserverRegistry, mDependencies);
             ipc.shutdown();
             fail();
         } catch (NullPointerException npe) {
@@ -178,14 +175,16 @@
     @Test
     public void testInvalidInterfaceDoesNotThrow() throws Exception {
         setTestInterfaceParams(TEST_IFNAME);
-        final IpClient ipc = new IpClient(mContext, TEST_IFNAME, mCb, mDependecies);
+        final IpClient ipc = new IpClient(
+                mContext, TEST_IFNAME, mCb, mObserverRegistry, mDependencies);
         ipc.shutdown();
     }
 
     @Test
     public void testInterfaceNotFoundFailsImmediately() throws Exception {
         setTestInterfaceParams(null);
-        final IpClient ipc = new IpClient(mContext, TEST_IFNAME, mCb, mDependecies);
+        final IpClient ipc = new IpClient(
+                mContext, TEST_IFNAME, mCb, mObserverRegistry, mDependencies);
         ipc.startProvisioning(new ProvisioningConfiguration());
         verify(mCb, times(1)).onProvisioningFailure(any());
         ipc.shutdown();
@@ -249,13 +248,13 @@
 
         // Add N - 1 addresses
         for (int i = 0; i < lastAddr; i++) {
-            mObserver.addressUpdated(iface, new LinkAddress(addresses[i]));
+            mObserver.onInterfaceAddressUpdated(new LinkAddress(addresses[i]), iface);
             verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(any());
             reset(mCb);
         }
 
         // Add Nth address
-        mObserver.addressUpdated(iface, new LinkAddress(addresses[lastAddr]));
+        mObserver.onInterfaceAddressUpdated(new LinkAddress(addresses[lastAddr]), iface);
         LinkProperties want = linkproperties(links(addresses), routes(prefixes));
         want.setInterfaceName(iface);
         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(argThat(
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index 8b00ed0..00b3736 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -23,6 +23,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.inputmethod.InputMethodManager;
+
 import com.android.printspooler.R;
 
 /**
@@ -366,7 +367,8 @@
             // and is janky. Now it is there but transparent, doing nothing.
             mEmbeddedContentScrim.setOnClickListener(null);
             mEmbeddedContentScrim.setClickable(false);
-            mExpandCollapseIcon.setBackgroundResource(R.drawable.ic_expand_more);
+            mExpandCollapseIcon.setBackgroundResource(
+                    com.android.internal.R.drawable.ic_expand_more);
         } else {
             if (mMoreOptionsButton.getVisibility() != View.GONE) {
                 mMoreOptionsButton.setVisibility(View.VISIBLE);
diff --git a/packages/SettingsLib/res/drawable/ic_info_outline_24dp.xml b/packages/SettingsLib/res/drawable/ic_info_outline_24dp.xml
deleted file mode 100644
index 46b8309..0000000
--- a/packages/SettingsLib/res/drawable/ic_info_outline_24dp.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2016 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="?android:attr/textColorSecondary">
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M11,7h2v2h-2z"/>
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M11,11h2v6h-2z"/>
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10s10,-4.48 10,-10C22,6.48 17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8c0,-4.41 3.59,-8 8,-8s8,3.59 8,8C20,16.41 16.41,20 12,20z"/>
-</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_landscape_from_auto_rotate.xml b/packages/SettingsLib/res/drawable/ic_landscape_from_auto_rotate.xml
deleted file mode 100644
index 44b1866..0000000
--- a/packages/SettingsLib/res/drawable/ic_landscape_from_auto_rotate.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2017 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.
--->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:name="ic_rotate_to_landscape"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportHeight="24.0"
-    android:viewportWidth="24.0"
-    android:tint="?android:attr/colorControlNormal" >
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M12.72,23H11C5.49,23 1,18.51 1,13h2c0,3.78 2.63,6.95 6.15,7.79L8.13,19L9.87,18L12.72,23zM13,1h-1.72l2.85,5L15.87,5l-1.02,-1.79C18.37,4.05 21,7.22 21,11h2C23,5.49 18.51,1 13,1zM10.23,6L18,13.76L13.77,18L6,10.24L10.23,6C10.23,6 10.23,6 10.23,6M10.23,4C9.72,4 9.21,4.2 8.82,4.59L4.59,8.82c-0.78,0.78 -0.78,2.04 0,2.82l7.77,7.77c0.39,0.39 0.9,0.59 1.41,0.59c0.51,0 1.02,-0.2 1.41,-0.59l4.24,-4.24c0.78,-0.78 0.78,-2.04 0,-2.82l-7.77,-7.77C11.26,4.2 10.75,4 10.23,4L10.23,4z"/>
-</vector>
diff --git a/packages/SettingsLib/res/drawable/notification_auto_importance.xml b/packages/SettingsLib/res/drawable/notification_auto_importance.xml
deleted file mode 100644
index c946153..0000000
--- a/packages/SettingsLib/res/drawable/notification_auto_importance.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-    Copyright (C) 2016 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-            android:fillColor="#FFFFFFFF"
-            android:pathData="M10.8,12.7l2.4,0l-1.2,-3.7z"/>
-    <path
-            android:fillColor="#FF000000"
-            android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10s10,-4.5 10,-10S17.5,2 12,2zM14.3,16l-0.7,-2h-3.2l-0.7,2H7.8L11,7h2l3.2,9H14.3z"/>
-</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/layout/zen_mode_condition.xml b/packages/SettingsLib/res/layout/zen_mode_condition.xml
index c85a892..3222174 100644
--- a/packages/SettingsLib/res/layout/zen_mode_condition.xml
+++ b/packages/SettingsLib/res/layout/zen_mode_condition.xml
@@ -67,7 +67,7 @@
         android:layout_toStartOf="@android:id/button2"
         android:contentDescription="@string/accessibility_manual_zen_less_time"
         android:tint="?android:attr/colorAccent"
-        android:src="@drawable/ic_minus" />
+        android:src="@*android:drawable/ic_minus" />
 
     <ImageView
         android:id="@android:id/button2"
@@ -79,6 +79,6 @@
         android:layout_centerVertical="true"
         android:contentDescription="@string/accessibility_manual_zen_more_time"
         android:tint="?android:attr/colorAccent"
-        android:src="@drawable/ic_plus" />
+        android:src="@*android:drawable/ic_plus" />
 
-</RelativeLayout>
\ No newline at end of file
+</RelativeLayout>
diff --git a/packages/SettingsLib/res/values/styles.xml b/packages/SettingsLib/res/values/styles.xml
index bae8387..015f52c 100644
--- a/packages/SettingsLib/res/values/styles.xml
+++ b/packages/SettingsLib/res/values/styles.xml
@@ -24,7 +24,7 @@
 
     <style name="BorderlessButton">
         <item name="android:padding">12dp</item>
-        <item name="android:background">@drawable/btn_borderless_rect</item>
+        <item name="android:background">@*android:drawable/btn_borderless_rect</item>
         <item name="android:gravity">center</item>
     </style>
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index c059156..bed3030 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -296,7 +296,7 @@
     }
 
     public int getDrawableResource(BluetoothClass btClass) {
-        return R.drawable.ic_bt_headphones_a2dp;
+        return com.android.internal.R.drawable.ic_bt_headphones_a2dp;
     }
 
     protected void finalize() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index 873dd1a..4ce9d3e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -205,7 +205,7 @@
     }
 
     public int getDrawableResource(BluetoothClass btClass) {
-        return R.drawable.ic_bt_headphones_a2dp;
+        return com.android.internal.R.drawable.ic_bt_headphones_a2dp;
     }
 
     protected void finalize() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index ee80aa1..a8a0b6d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -61,13 +61,14 @@
         if (btClass != null) {
             switch (btClass.getMajorDeviceClass()) {
                 case BluetoothClass.Device.Major.COMPUTER:
-                    return new Pair<>(getBluetoothDrawable(context, R.drawable.ic_bt_laptop, level,
-                            iconScale),
+                    return new Pair<>(getBluetoothDrawable(context,
+                            com.android.internal.R.drawable.ic_bt_laptop, level, iconScale),
                             context.getString(R.string.bluetooth_talkback_computer));
 
                 case BluetoothClass.Device.Major.PHONE:
                     return new Pair<>(
-                            getBluetoothDrawable(context, R.drawable.ic_bt_cellphone, level,
+                            getBluetoothDrawable(context,
+                                    com.android.internal.R.drawable.ic_bt_cellphone, level,
                                     iconScale),
                             context.getString(R.string.bluetooth_talkback_phone));
 
@@ -79,7 +80,8 @@
 
                 case BluetoothClass.Device.Major.IMAGING:
                     return new Pair<>(
-                            getBluetoothDrawable(context, R.drawable.ic_settings_print, level,
+                            getBluetoothDrawable(context,
+                                    com.android.internal.R.drawable.ic_settings_print, level,
                                     iconScale),
                             context.getString(R.string.bluetooth_talkback_imaging));
 
@@ -98,19 +100,22 @@
         if (btClass != null) {
             if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
                 return new Pair<>(
-                        getBluetoothDrawable(context, R.drawable.ic_bt_headset_hfp, level,
+                        getBluetoothDrawable(context,
+                                com.android.internal.R.drawable.ic_bt_headset_hfp, level,
                                 iconScale),
                         context.getString(R.string.bluetooth_talkback_headset));
             }
             if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
                 return new Pair<>(
-                        getBluetoothDrawable(context, R.drawable.ic_bt_headphones_a2dp, level,
+                        getBluetoothDrawable(context,
+                                com.android.internal.R.drawable.ic_bt_headphones_a2dp, level,
                                 iconScale),
                         context.getString(R.string.bluetooth_talkback_headphone));
             }
         }
         return new Pair<>(
-                getBluetoothDrawable(context, R.drawable.ic_settings_bluetooth, level, iconScale),
+                getBluetoothDrawable(context,
+                        com.android.internal.R.drawable.ic_settings_bluetooth, level, iconScale),
                 context.getString(R.string.bluetooth_talkback_bluetooth));
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 6b6df9b..a1bf936 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -226,7 +226,7 @@
     }
 
     public int getDrawableResource(BluetoothClass btClass) {
-        return R.drawable.ic_bt_headset_hfp;
+        return com.android.internal.R.drawable.ic_bt_headset_hfp;
     }
 
     protected void finalize() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index 77dfbe9..41c1d60 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -228,7 +228,7 @@
     }
 
     public int getDrawableResource(BluetoothClass btClass) {
-        return R.drawable.ic_bt_hearing_aid;
+        return com.android.internal.R.drawable.ic_bt_hearing_aid;
     }
 
     protected void finalize() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
index c6bb2b3..4bdbc31 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -211,7 +211,7 @@
 
     @Override
     public int getDrawableResource(BluetoothClass btClass) {
-        return R.drawable.ic_bt_headset_hfp;
+        return com.android.internal.R.drawable.ic_bt_headset_hfp;
     }
 
     protected void finalize() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
index 4dc050c..35600b5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
@@ -171,7 +171,7 @@
 
     @Override
     public int getDrawableResource(BluetoothClass btClass) {
-        return R.drawable.ic_bt_misc_hid;
+        return com.android.internal.R.drawable.ic_bt_misc_hid;
     }
 
     protected void finalize() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index ca840d9a..7f906f6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -165,7 +165,7 @@
 
     public int getDrawableResource(BluetoothClass btClass) {
         if (btClass == null) {
-            return R.drawable.ic_lockscreen_ime;
+            return com.android.internal.R.drawable.ic_lockscreen_ime;
         }
         return getHidClassDrawable(btClass);
     }
@@ -174,11 +174,11 @@
         switch (btClass.getDeviceClass()) {
             case BluetoothClass.Device.PERIPHERAL_KEYBOARD:
             case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING:
-                return R.drawable.ic_lockscreen_ime;
+                return com.android.internal.R.drawable.ic_lockscreen_ime;
             case BluetoothClass.Device.PERIPHERAL_POINTING:
-                return R.drawable.ic_bt_pointing_hid;
+                return com.android.internal.R.drawable.ic_bt_pointing_hid;
             default:
-                return R.drawable.ic_bt_misc_hid;
+                return com.android.internal.R.drawable.ic_bt_misc_hid;
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index 6acdcac..0afc878 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -200,7 +200,7 @@
     }
 
     public int getDrawableResource(BluetoothClass btClass) {
-        return R.drawable.ic_bt_cellphone;
+        return com.android.internal.R.drawable.ic_bt_cellphone;
     }
 
     protected void finalize() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
index 28975d4..bea944c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -196,7 +196,7 @@
     }
 
     public int getDrawableResource(BluetoothClass btClass) {
-        return R.drawable.ic_bt_cellphone;
+        return com.android.internal.R.drawable.ic_bt_cellphone;
     }
 
     protected void finalize() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
index 2d0a090..6638592 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
@@ -153,7 +153,7 @@
     }
 
     public int getDrawableResource(BluetoothClass btClass) {
-        return R.drawable.ic_bt_network_pan;
+        return com.android.internal.R.drawable.ic_bt_network_pan;
     }
 
     // Tethering direction determines UI strings.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index 4672393..4a27715 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -192,7 +192,7 @@
     }
 
     public int getDrawableResource(BluetoothClass btClass) {
-        return R.drawable.ic_bt_cellphone;
+        return com.android.internal.R.drawable.ic_bt_cellphone;
     }
 
     protected void finalize() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index 1b3c453..165af2c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -135,7 +135,7 @@
     }
 
     public int getDrawableResource(BluetoothClass btClass) {
-        return R.drawable.ic_bt_cellphone;
+        return com.android.internal.R.drawable.ic_bt_cellphone;
     }
 
     protected void finalize() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
index ea2ebde..9e9c5b9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -196,7 +196,7 @@
     }
 
     public int getDrawableResource(BluetoothClass btClass) {
-        return R.drawable.ic_bt_cellphone;
+        return com.android.internal.R.drawable.ic_bt_cellphone;
     }
 
     protected void finalize() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index a5c6f0c..6d0e3ac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.util.Log;
 
-import com.android.settingslib.R;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 
 /**
@@ -45,7 +44,7 @@
     @Override
     public int getIcon() {
         //TODO(b/117129183): This is not final icon for bluetooth device, just for demo.
-        return R.drawable.ic_bt_headphones_a2dp;
+        return com.android.internal.R.drawable.ic_bt_headphones_a2dp;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index 04f70cc..99d9d1c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -20,8 +20,6 @@
 
 import androidx.mediarouter.media.MediaRouter;
 
-import com.android.settingslib.R;
-
 /**
  * InfoMediaDevice extends MediaDevice to represents wifi device.
  */
@@ -45,7 +43,7 @@
     @Override
     public int getIcon() {
         //TODO(b/121083246): This is not final icon for cast device, just for demo.
-        return R.drawable.ic_settings_print;
+        return com.android.internal.R.drawable.ic_settings_print;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index c808214..01003aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -18,7 +18,6 @@
 import android.content.Context;
 import android.util.Log;
 
-import com.android.settingslib.R;
 import com.android.settingslib.bluetooth.A2dpProfile;
 import com.android.settingslib.bluetooth.HearingAidProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -53,7 +52,7 @@
     @Override
     public int getIcon() {
         //TODO(b/117129183): This is not final icon for phone device, just for demo.
-        return R.drawable.ic_bt_cellphone;
+        return com.android.internal.R.drawable.ic_bt_cellphone;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
index f02044d..a106846 100644
--- a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreference.java
@@ -56,7 +56,7 @@
     }
 
     private void init() {
-        setIcon(R.drawable.ic_info_outline_24dp);
+        setIcon(com.android.internal.R.drawable.ic_info_outline_24);
         setKey(KEY_FOOTER);
         setOrder(ORDER_FOOTER);
         setSelectable(false);
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java
index a436cb5..cf7bcb2 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/drawable/UserIconDrawableTest.java
@@ -31,8 +31,6 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.settingslib.R;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -45,7 +43,7 @@
     public void getConstantState_shouldNotBeNull() {
         final Bitmap b = BitmapFactory.decodeResource(
                 InstrumentationRegistry.getTargetContext().getResources(),
-                R.drawable.ic_mode_edit);
+                com.android.internal.R.drawable.ic_mode_edit);
         mDrawable = new UserIconDrawable(100 /* size */).setIcon(b).bake();
         assertThat(mDrawable.getConstantState()).isNotNull();
     }
diff --git a/packages/SettingsLib/tests/robotests/res/drawable/ic_info_outline_24dp.xml b/packages/SettingsLib/tests/robotests/res/drawable/ic_info_outline_24dp.xml
deleted file mode 100644
index 3fe1e9e..0000000
--- a/packages/SettingsLib/tests/robotests/res/drawable/ic_info_outline_24dp.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2016 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="?android:attr/textColorSecondary">
-    <path
-        android:fillColor="#000000"
-        android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
-</vector>
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 0eb6de9..7a71551 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -20,7 +20,6 @@
 import android.bluetooth.BluetoothDevice;
 import android.graphics.drawable.Drawable;
 
-import com.android.settingslib.R;
 import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
 
 import org.junit.Test;
@@ -34,7 +33,7 @@
     @Test
     public void testGetBluetoothDrawable_noBatteryLevel_returnSimpleDrawable() {
         final Drawable drawable = BluetoothUtils.getBluetoothDrawable(
-                RuntimeEnvironment.application, R.drawable.ic_bt_laptop,
+                RuntimeEnvironment.application, com.android.internal.R.drawable.ic_bt_laptop,
                 BluetoothDevice.BATTERY_LEVEL_UNKNOWN, 1 /* iconScale */);
 
         assertThat(drawable).isNotInstanceOf(BluetoothDeviceLayerDrawable.class);
@@ -43,7 +42,7 @@
     @Test
     public void testGetBluetoothDrawable_hasBatteryLevel_returnLayerDrawable() {
         final Drawable drawable = BluetoothUtils.getBluetoothDrawable(
-                RuntimeEnvironment.application, R.drawable.ic_bt_laptop,
+                RuntimeEnvironment.application, com.android.internal.R.drawable.ic_bt_laptop,
                 10 /* batteryLevel */, 1 /* iconScale */);
 
         assertThat(drawable).isInstanceOf(BluetoothDeviceLayerDrawable.class);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java
index b77670b..491f32d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java
@@ -12,8 +12,6 @@
 import android.content.pm.ActivityInfo;
 import android.os.Bundle;
 
-import com.android.settingslib.R;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,7 +29,7 @@
         mActivityInfo = new ActivityInfo();
         mActivityInfo.packageName = RuntimeEnvironment.application.getPackageName();
         mActivityInfo.name = "abc";
-        mActivityInfo.icon = R.drawable.ic_plus;
+        mActivityInfo.icon = com.android.internal.R.drawable.ic_plus;
         mActivityInfo.metaData = new Bundle();
         mTile = new Tile(mActivityInfo, "category");
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index bbb4249..b379b54 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -53,8 +53,6 @@
 import android.util.ArrayMap;
 import android.util.Pair;
 
-import com.android.settingslib.R;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -255,7 +253,7 @@
 
         resolveInfo.activityInfo.metaData = new Bundle(oldMetadata);
         resolveInfo.activityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON,
-                R.drawable.ic_bt_cellphone);
+                com.android.internal.R.drawable.ic_bt_cellphone);
         outTiles.clear();
         TileUtils.getTilesForAction(mContext, UserHandle.CURRENT, IA_SETTINGS_ACTION, addedCache,
                 null /* defaultCategory */, outTiles, false /* usePriority */);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java
index 1b350cc..c6c2a44 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java
@@ -31,7 +31,7 @@
 
 @RunWith(RobolectricTestRunner.class)
 public class BluetoothDeviceLayerDrawableTest {
-    private static final int RES_ID = R.drawable.ic_bt_cellphone;
+    private static final int RES_ID = com.android.internal.R.drawable.ic_bt_cellphone;
     private static final int BATTERY_LEVEL = 15;
     private static final float BATTERY_ICON_SCALE = 0.75f;
     private static final int BATTERY_ICON_PADDING_TOP = 6;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java
index 97de7ef..e24b525 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ActionButtonsPreferenceTest.java
@@ -74,10 +74,10 @@
 
     @Test
     public void onBindViewHolder_setIcon_shouldShowButtonByDefault() {
-        mPref.setButton1Icon(R.drawable.ic_plus);
-        mPref.setButton2Icon(R.drawable.ic_plus);
-        mPref.setButton3Icon(R.drawable.ic_plus);
-        mPref.setButton4Icon(R.drawable.ic_plus);
+        mPref.setButton1Icon(com.android.internal.R.drawable.ic_plus);
+        mPref.setButton2Icon(com.android.internal.R.drawable.ic_plus);
+        mPref.setButton3Icon(com.android.internal.R.drawable.ic_plus);
+        mPref.setButton4Icon(com.android.internal.R.drawable.ic_plus);
 
         mPref.onBindViewHolder(mHolder);
 
@@ -126,10 +126,10 @@
 
     @Test
     public void onBindViewHolder_setVisibleIsGoneAndSetIcon_shouldNotShowButton() {
-        mPref.setButton1Icon(R.drawable.ic_plus).setButton1Visible(false);
-        mPref.setButton2Icon(R.drawable.ic_plus).setButton2Visible(false);
-        mPref.setButton3Icon(R.drawable.ic_plus).setButton3Visible(false);
-        mPref.setButton4Icon(R.drawable.ic_plus).setButton4Visible(false);
+        mPref.setButton1Icon(com.android.internal.R.drawable.ic_plus).setButton1Visible(false);
+        mPref.setButton2Icon(com.android.internal.R.drawable.ic_plus).setButton2Visible(false);
+        mPref.setButton3Icon(com.android.internal.R.drawable.ic_plus).setButton3Visible(false);
+        mPref.setButton4Icon(com.android.internal.R.drawable.ic_plus).setButton4Visible(false);
 
         mPref.onBindViewHolder(mHolder);
 
@@ -215,7 +215,7 @@
     @Test
     public void onBindViewHolder_setButtonIcon_iconMustDisplayAboveText() {
         mPref.setButton1Text(R.string.install_other_apps);
-        mPref.setButton1Icon(R.drawable.ic_plus);
+        mPref.setButton1Icon(com.android.internal.R.drawable.ic_plus);
 
         mPref.onBindViewHolder(mHolder);
         final Drawable[] drawablesAroundText =
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
index 4c68c14..63a958e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppEntitiesHeaderControllerTest.java
@@ -54,7 +54,7 @@
         mController = AppEntitiesHeaderController.newInstance(mContext,
                 mAppEntitiesHeaderView);
         mAppEntityInfo = new AppEntityInfo.Builder()
-                .setIcon(mContext.getDrawable(R.drawable.ic_menu))
+                .setIcon(mContext.getDrawable(com.android.internal.R.drawable.ic_menu))
                 .setTitle(TITLE)
                 .setSummary(SUMMARY)
                 .setOnClickListener(v -> {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java
index cf6137d..c3ea336 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BarChartPreferenceTest.java
@@ -58,7 +58,7 @@
         mHolder = PreferenceViewHolder.createInstanceForTests(mBarChartView);
         mPreference = new BarChartPreference(mContext, null /* attrs */);
 
-        mIcon = mContext.getDrawable(R.drawable.ic_menu);
+        mIcon = mContext.getDrawable(com.android.internal.R.drawable.ic_menu);
         mBarView1 = mBarChartView.findViewById(R.id.bar_view1);
         mBarView2 = mBarChartView.findViewById(R.id.bar_view2);
         mBarView3 = mBarChartView.findViewById(R.id.bar_view3);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index aff6f04..856b167 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -707,17 +707,17 @@
                 Settings.Global.GPU_DEBUG_LAYERS_GLES,
                 GlobalSettingsProto.Gpu.DEBUG_LAYERS_GLES);
         dumpSetting(s, p,
-                Settings.Global.GUP_DEV_ALL_APPS,
-                GlobalSettingsProto.Gpu.GUP_DEV_ALL_APPS);
+                Settings.Global.GAME_DRIVER_ALL_APPS,
+                GlobalSettingsProto.Gpu.GAME_DRIVER_ALL_APPS);
         dumpSetting(s, p,
-                Settings.Global.GUP_DEV_OPT_IN_APPS,
-                GlobalSettingsProto.Gpu.GUP_DEV_OPT_IN_APPS);
+                Settings.Global.GAME_DRIVER_OPT_IN_APPS,
+                GlobalSettingsProto.Gpu.GAME_DRIVER_OPT_IN_APPS);
         dumpSetting(s, p,
-                Settings.Global.GUP_DEV_OPT_OUT_APPS,
-                GlobalSettingsProto.Gpu.GUP_DEV_OPT_OUT_APPS);
+                Settings.Global.GAME_DRIVER_OPT_OUT_APPS,
+                GlobalSettingsProto.Gpu.GAME_DRIVER_OPT_OUT_APPS);
         dumpSetting(s, p,
-                Settings.Global.GUP_BLACKLIST,
-                GlobalSettingsProto.Gpu.GUP_BLACKLIST);
+                Settings.Global.GAME_DRIVER_BLACKLIST,
+                GlobalSettingsProto.Gpu.GAME_DRIVER_BLACKLIST);
         dumpSetting(s, p,
                 Settings.Global.GAME_DRIVER_WHITELIST,
                 GlobalSettingsProto.Gpu.GAME_DRIVER_WHITELIST);
@@ -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/Android.bp b/packages/SystemUI/Android.bp
index 0a62b7c..e0d178f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -57,6 +57,7 @@
         "androidx.slice_slice-builders",
         "androidx.arch.core_core-runtime",
         "androidx.lifecycle_lifecycle-extensions",
+        "androidx.dynamicanimation_dynamicanimation",
         "SystemUI-tags",
         "SystemUI-proto",
         "dagger2-2.19",
@@ -108,6 +109,7 @@
         "androidx.slice_slice-builders",
         "androidx.arch.core_core-runtime",
         "androidx.lifecycle_lifecycle-extensions",
+        "androidx.dynamicanimation_dynamicanimation",
         "SystemUI-tags",
         "SystemUI-proto",
         "metrics-helper-lib",
diff --git a/packages/SystemUI/docs/physics-animation-layout-config-methods.png b/packages/SystemUI/docs/physics-animation-layout-config-methods.png
new file mode 100644
index 0000000..c3a45e2
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-layout-config-methods.png
Binary files differ
diff --git a/packages/SystemUI/docs/physics-animation-layout-control-methods.png b/packages/SystemUI/docs/physics-animation-layout-control-methods.png
new file mode 100644
index 0000000..e77c676
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-layout-control-methods.png
Binary files differ
diff --git a/packages/SystemUI/docs/physics-animation-layout.md b/packages/SystemUI/docs/physics-animation-layout.md
new file mode 100644
index 0000000..a67b5e8
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-layout.md
@@ -0,0 +1,56 @@
+# Physics Animation Layout
+
+## Overview
+**PhysicsAnimationLayout** works with an implementation of **PhysicsAnimationController** to construct and maintain physics animations for each of its child views. During the initial construction of the animations, the layout queries the controller for configuration settings such as which properties to animate, which animations to chain together, and what stiffness or bounciness to use. Once the animations are built to the controller’s specifications, the controller can then ask the layout to start, stop and manipulate them arbitrarily to achieve any desired animation effect. The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively.
+
+An example usage is Bubbles, which uses a PhysicsAnimationLayout for its stack of bubbles. Bubbles has controller subclasses including StackAnimationController and ExpansionAnimationController. StackAnimationController tells the layout to configure the translation animations to be chained (for the ‘following’ drag effect), and has methods such as ```moveStack(x, y)``` to animate the stack to a given point. ExpansionAnimationController asks for no animations to be chained, and exposes methods like ```expandStack()``` and ```collapseStack()```, which animate the bubbles to positions along the bottom of the screen.
+
+## PhysicsAnimationController
+PhysicsAnimationController is a public abstract class in PhysicsAnimationLayout. Controller instances must override configuration methods, which are used by the layout while constructing the animations, and animation control methods, which are called to initiate animations in response to events.
+
+### Configuration Methods
+![Diagram of how animations are configured using the controller's configuration methods.](physics-animation-layout-config-methods.png)
+The controller must override the following methods:
+
+```Set<ViewProperty> getAnimatedProperties()```
+Returns the properties, such as TRANSLATION_X and TRANSLATION_Y, for which the layout should construct physics animations.
+
+```int getNextAnimationInChain(ViewProperty property, int index)```
+If the animation at the given index should update another animation whenever its value changes, return the index of the other animation. Otherwise, return NONE. This is used to chain animations together, so that when one animation moves, the other ‘follows’ closely behind.
+
+```float getOffsetForChainedPropertyAnimation(ViewProperty property)```
+Value to add every time chained animations update the subsequent animation in the chain. For example, returning TRANSLATION_X offset = 20px means that if the first animation in the chain is animated to 10px, the second will update to 30px, the third to 50px, etc.
+
+```SpringForce getSpringForce(ViewProperty property)```
+Returns a SpringForce instance to use for animations of the given property. This allows the controller to configure stiffness and bounciness values. Since the physics animations internally use SpringForce instances to hold inflight animation values, this method needs to return a new SpringForce instance each time - no constants allowed.
+
+### Animation Control Methods
+![Diagram of how calls to animateValueForChildAtIndex dispatch to DynamicAnimations.](physics-animation-layout-control-methods.png)
+Once the layout has used the controller’s configuration properties to build the animations, the controller can use them to actually run animations. This is done for two reasons - reacting to a view being added or removed, or responding to another class (such as a touch handler or broadcast receiver) requesting an animation. ```onChildAdded``` and ```onChildRemoved``` are called automatically by the layout, giving the controller the opportunity to animate the child in/out. Custom methods are called by anyone with access to the controller instance to do things like expand, collapse, or move the child views.
+
+In either case, the controller has access to the layout’s protected ```animateValueForChildAtIndex(ViewProperty property, int index, float value)``` method. This method is used to actually run an animation.
+
+For example, moving the first child view to *(100, 200)*:
+
+```
+animateValueForChildAtIndex(TRANSLATION_X, 0, 100);
+animateValueForChildAtIndex(TRANSLATION_Y, 0, 200);
+```
+
+This would use the physics animations constructed by the layout to spring the view to *(100, 200)*.
+
+If the controller’s ```getNextAnimationInChain``` method set up the first child’s TRANSLATION_X/Y animations to be chained to the second child’s, this would result in the second child also springing towards (100, 200), plus any offset returned by ```getOffsetForChainedPropertyAnimation```.
+
+## PhysicsAnimationLayout
+The layout itself is a FrameLayout descendant with a few extra methods:
+
+```setController(PhysicsAnimationController controller)```
+Attaches the layout to the controller, so that the controller can access the layout’s protected methods. It also constructs or reconfigures the physics animations according to the new controller’s configuration methods.
+
+```setEndListenerForProperty(ViewProperty property, AnimationEndListener endListener)```
+Sets an end listener that is called when all animations on the given property have ended.
+
+```setMaxRenderedChildren(int max)```
+Child views beyond this limit will be set to GONE, and won't be animated, for performance reasons. Defaults to **5**.
+
+It has one protected method, ```animateValueForChildAtIndex(ViewProperty property, int index, float value)```, which is visible to PhysicsAnimationController descendants. This method dispatches the given value to the appropriate animation.
\ No newline at end of file
diff --git a/packages/SystemUI/docs/physics-animation-testing.md b/packages/SystemUI/docs/physics-animation-testing.md
new file mode 100644
index 0000000..47354d4
--- /dev/null
+++ b/packages/SystemUI/docs/physics-animation-testing.md
@@ -0,0 +1,11 @@
+# Physics Animation Testing
+Physics animations are notoriously difficult to test, since they’re essentially small simulations. They have no set duration, and they’re considered ‘finished’ only when the movements imparted by the animation are too small to be user-visible. Mid-states are not deterministic.
+
+For this reason, we only test the end state of animations. Manual testing should be sufficient to reveal flaws in the en-route animation visuals. In a worst-case failure case, as long as the end state is correct, usability will not be affected - animations might just look a bit off until the UI elements settle to their proper positions.
+
+## Waiting for Animations to End
+Testing any kind of animation can be tricky, since animations need to run on the main thread, and they’re asynchronous - the test has to wait for the animation to finish before we can assert anything about its end state. For normal animations, we can invoke skipToEnd to avoid waiting. While this method is available for SpringAnimation, it’s not available for FlingAnimation since its end state is not initially known. A FlingAnimation’s ‘end’ is when the friction simulation reports that motion has slowed to an invisible level. For this reason, we have to actually run the physics animations.
+
+To accommodate this, all tests of the layout itself, as well as any animation controller subclasses, use **PhysicsAnimationLayoutTestCase**. The layout provided to controllers by the test case is a **TestablePhysicsAnimationLayout**, a subclass of PhysicsAnimationLayout whose animation-related methods have been overridden to force them to run on the main thread via a Handler. Animations will simply crash if they’re called directly from the test thread, so this is important.
+
+The test case also provides ```waitForPropertyAnimations```, which uses a **CountDownLatch** to wait for all animations on a given property to complete before continuing the test. This works since the test is not running on the same thread as the animation, so a blocking call to ```latch.await()``` does not affect the animations’ progress. The latch is initialized with a count equal to the number of properties we’re listening to. We then add end listeners to the layout for each property, which call ```latch.countDown()```. Once all of the properties’ animations have completed, the latch count reaches zero and the test’s call to ```await()``` returns, with the animations complete.
\ No newline at end of file
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/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml
index 204408cd..13186fc 100644
--- a/packages/SystemUI/res/layout/bubble_view.xml
+++ b/packages/SystemUI/res/layout/bubble_view.xml
@@ -22,8 +22,8 @@
 
     <com.android.systemui.bubbles.BadgedImageView
         android:id="@+id/bubble_image"
-        android:layout_width="@dimen/bubble_size"
-        android:layout_height="@dimen/bubble_size"
+        android:layout_width="@dimen/individual_bubble_size"
+        android:layout_height="@dimen/individual_bubble_size"
         android:padding="@dimen/bubble_view_padding"
         android:clipToPadding="false"/>
 
diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml
new file mode 100644
index 0000000..e6f2376
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_grid.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.systemui.globalactions.GlobalActionsGridLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@id/global_actions_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:clipToPadding="false"
+    android:theme="@style/qs_theme"
+    android:gravity="bottom|center"
+    android:clipChildren="false"
+>
+
+    <LinearLayout
+        android:layout_height="290dp"
+        android:layout_width="412dp"
+        android:gravity="bottom"
+        android:padding="0dp"
+        android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin"
+    >
+        <!-- For separated items-->
+        <LinearLayout
+            android:id="@+id/separated_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
+            android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+            android:paddingTop="@dimen/global_actions_grid_top_padding"
+            android:paddingLeft="@dimen/global_actions_grid_left_padding"
+            android:paddingBottom="@dimen/global_actions_grid_bottom_padding"
+            android:paddingRight="@dimen/global_actions_grid_right_padding"
+            android:orientation="vertical"
+            android:background="?android:attr/colorBackgroundFloating"
+            android:translationZ="@dimen/global_actions_translate"
+        />
+
+        <Space android:layout_width="match_parent" android:layout_height="2dp"
+               android:layout_weight="1" />
+
+        <!-- Grid of action items -->
+        <com.android.systemui.globalactions.ListGridLayout
+            android:id="@android:id/list"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:layoutDirection="rtl"
+            android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+            android:translationZ="@dimen/global_actions_translate"
+            android:paddingLeft="@dimen/global_actions_grid_left_padding"
+            android:paddingRight="@dimen/global_actions_grid_right_padding"
+            android:paddingTop="@dimen/global_actions_grid_top_padding"
+            android:paddingBottom="@dimen/global_actions_grid_bottom_padding"
+            android:background="?android:attr/colorBackgroundFloating"
+        >
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="bottom|right"
+                android:visibility="gone"
+                android:gravity="bottom"
+                android:orientation="vertical"
+            />
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="bottom|right"
+                android:visibility="gone"
+                android:gravity="bottom"
+                android:orientation="vertical"
+            />
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="bottom|right"
+                android:visibility="gone"
+                android:gravity="bottom"
+                android:orientation="vertical"
+            />
+        </com.android.systemui.globalactions.ListGridLayout>
+    </LinearLayout>
+
+</com.android.systemui.globalactions.GlobalActionsGridLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml
new file mode 100644
index 0000000..0c11cd9
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<!-- RelativeLayouts have an issue enforcing minimum heights, so just
+     work around this for now with LinearLayouts. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="72dp"
+    android:layout_height="72dp"
+    android:gravity="center"
+    android:orientation="vertical"
+    android:layout_marginTop="@dimen/global_actions_grid_item_vertical_margin"
+    android:layout_marginBottom="@dimen/global_actions_grid_item_vertical_margin"
+    android:layout_marginLeft="@dimen/global_actions_grid_item_side_margin"
+    android:layout_marginRight="@dimen/global_actions_grid_item_side_margin"
+    android:paddingEnd="4dip"
+    android:paddingStart="4dip">
+
+    <ImageView
+        android:id="@*android:id/icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_gravity="center"
+        android:scaleType="center"
+        android:alpha="?android:attr/primaryContentAlpha"
+    />
+
+    <TextView
+        android:id="@*android:id/message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top|center_horizontal"
+        android:paddingTop="10dp"
+        android:gravity="center"
+        android:textSize="12sp"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+    />
+
+    <TextView
+        android:id="@*android:id/status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top|center_horizontal"
+        android:gravity="center"
+        android:textColor="?android:attr/textColorTertiary"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+    />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_footer_carrier.xml b/packages/SystemUI/res/layout/qs_footer_carrier.xml
new file mode 100644
index 0000000..bd492b0
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_footer_carrier.xml
@@ -0,0 +1,49 @@
+<!--
+  ~ 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
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/linear_footer_carrier"
+    android:layout_width="0dp"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:layout_weight="1"
+    android:gravity="center_vertical|start"
+    android:background="@android:color/transparent"
+    android:clickable="false"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:paddingStart="16dp" >
+
+    <include
+        layout="@layout/mobile_signal_group"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:visibility="gone" />
+
+    <view class="com.android.systemui.qs.QSFooterImpl$QSCarrierText"
+        android:id="@+id/qs_carrier_text"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:ellipsize="marquee"
+        android:textAppearance="@style/TextAppearance.QS.CarrierInfo"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textDirection="locale"
+        android:singleLine="true" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 890bf5d..67e31ac 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -42,30 +42,31 @@
         android:gravity="end" >
 
         <LinearLayout
+            android:id="@+id/qs_mobile"
             android:layout_width="0dp"
             android:layout_height="match_parent"
             android:layout_weight="1"
             android:gravity="center_vertical|start"
-            android:paddingStart="16dp">
+            android:orientation="horizontal"
+            android:layout_marginEnd="32dp">
 
             <include
-                layout="@layout/mobile_signal_group"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginEnd="8dp"
+                layout="@layout/qs_footer_carrier"
+                android:id="@+id/carrier1" />
+
+            <View
+                android:id="@+id/qs_carrier_divider"
+                android:layout_width="2dp"
+                android:layout_height="match_parent"
+                android:layout_marginTop="15dp"
+                android:layout_marginBottom="15dp"
+                android:background="?android:attr/dividerVertical"
                 android:visibility="gone" />
 
-            <com.android.keyguard.CarrierText
-                android:id="@+id/qs_carrier_text"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:layout_marginEnd="32dp"
-                android:ellipsize="marquee"
-                android:textAppearance="@style/TextAppearance.QS.CarrierInfo"
-                android:textColor="?android:attr/textColorPrimary"
-                android:textDirection="locale"
-                android:singleLine="true" />
+            <include
+                layout="@layout/qs_footer_carrier"
+                android:id="@+id/carrier2"
+                android:visibility="gone"/>
 
         </LinearLayout>
 
@@ -108,7 +109,7 @@
                 android:contentDescription="@string/accessibility_quick_settings_edit"
                 android:focusable="true"
                 android:padding="15dp"
-                android:src="@drawable/ic_mode_edit"
+                android:src="@*android:drawable/ic_mode_edit"
                 android:tint="?android:attr/colorForeground"/>
 
             <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ab0bbe1..d435c67 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -834,6 +834,18 @@
 
     <dimen name="global_actions_panel_width">120dp</dimen>
 
+    <dimen name="global_actions_grid_container_bottom_margin">16dp</dimen>
+
+    <dimen name="global_actions_grid_side_margin">4dp</dimen>
+    <dimen name="global_actions_grid_separated_panel_width">104dp</dimen>
+    <dimen name="global_actions_grid_top_padding">8dp</dimen>
+    <dimen name="global_actions_grid_bottom_padding">8dp</dimen>
+    <dimen name="global_actions_grid_left_padding">4dp</dimen>
+    <dimen name="global_actions_grid_right_padding">4dp</dimen>
+
+    <dimen name="global_actions_grid_item_side_margin">12dp</dimen>
+    <dimen name="global_actions_grid_item_vertical_margin">8dp</dimen>
+
     <dimen name="global_actions_top_padding">120dp</dimen>
 
     <dimen name="global_actions_padding">12dp</dimen>
@@ -876,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
@@ -982,8 +996,8 @@
     <dimen name="bubble_view_padding">0dp</dimen>
     <!-- Padding between bubbles when displayed in expanded state -->
     <dimen name="bubble_padding">8dp</dimen>
-    <!-- Size of the collapsed bubble -->
-    <dimen name="bubble_size">56dp</dimen>
+    <!-- Size of individual bubbles. -->
+    <dimen name="individual_bubble_size">56dp</dimen>
     <!-- How much to inset the icon in the circle -->
     <dimen name="bubble_icon_inset">16dp</dimen>
     <!-- Padding around the view displayed when the bubble is expanded -->
@@ -1000,10 +1014,20 @@
     <dimen name="bubble_expanded_header_height">48dp</dimen>
     <!-- Left and right padding applied to the header. -->
     <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen>
+    <!-- How far, horizontally, to animate the expanded view over when animating in/out. -->
+    <dimen name="bubble_expanded_animate_x_distance">100dp</dimen>
+    <!-- How far, vertically, to animate the expanded view over when animating in/out. -->
+    <dimen name="bubble_expanded_animate_y_distance">500dp</dimen>
     <!-- Max width of the message bubble-->
     <dimen name="bubble_message_max_width">144dp</dimen>
     <!-- Min width of the message bubble -->
     <dimen name="bubble_message_min_width">32dp</dimen>
     <!-- Interior padding of the message bubble -->
     <dimen name="bubble_message_padding">4dp</dimen>
+    <!-- Offset between bubbles in their stacked position. -->
+    <dimen name="bubble_stack_offset">5dp</dimen>
+    <!-- How far offscreen the bubble stack rests. -->
+    <dimen name="bubble_stack_offscreen">5dp</dimen>
+    <!-- How far down the screen the stack starts. -->
+    <dimen name="bubble_stack_starting_offset_y">100dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index bd34bea..6a68457 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -68,6 +68,7 @@
     <item type="id" name="panel_alpha_animator_tag"/>
     <item type="id" name="panel_alpha_animator_start_tag"/>
     <item type="id" name="panel_alpha_animator_end_tag"/>
+    <item type="id" name="cross_fade_layer_type_changed_tag"/>
 
     <!-- Whether the icon is from a notification for which targetSdk < L -->
     <item type="id" name="icon_is_pre_L"/>
@@ -115,6 +116,14 @@
     <item type="id" name="aod_mask_transition_progress_end_tag" />
     <item type="id" name="aod_mask_transition_progress_start_tag" />
 
+    <!-- For saving DynamicAnimation physics animations as view tags. -->
+    <item type="id" name="translation_x_dynamicanimation_tag"/>
+    <item type="id" name="translation_y_dynamicanimation_tag"/>
+    <item type="id" name="translation_z_dynamicanimation_tag"/>
+    <item type="id" name="alpha_dynamicanimation_tag"/>
+    <item type="id" name="scale_x_dynamicanimation_tag"/>
+    <item type="id" name="scale_y_dynamicanimation_tag"/>
+
     <!-- Global Actions Menu -->
     <item type="id" name="global_actions_view" />
 </resources>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index fd7a105..e8fabf5 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -21,4 +21,10 @@
          0) as we can allow the carrier text to stretch as far as needed in the QS footer. -->
     <integer name="qs_footer_actions_width">-2</integer>
     <integer name="qs_footer_actions_weight">0</integer>
+
+    <!-- Maximum number of bubbles to render and animate at one time. While the animations used are
+         lightweight translation animations, this number can be reduced on lower end devices if any
+         performance issues arise. -->
+    <integer name="bubbles_max_rendered">5</integer>
+
 </resources>
\ No newline at end of file
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/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index 8069ce4..adcb7a1 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -42,8 +42,8 @@
     private CarrierTextController.CarrierTextCallback mCarrierTextCallback =
             new CarrierTextController.CarrierTextCallback() {
                 @Override
-                public void updateCarrierText(CharSequence carrierText, boolean simsReady) {
-                    setText(carrierText);
+                public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+                    setText(info.carrierText);
                 }
 
                 @Override
@@ -53,7 +53,7 @@
 
                 @Override
                 public void finishedWakingUp() {
-                    setSelected(mShouldMarquee);
+                    setSelected(true);
                 }
             };
 
@@ -85,7 +85,6 @@
                 mShowMissingSim);
         mShouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
         setSelected(mShouldMarquee); // Allow marquee to work.
-
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 3698a6e..2ce6965 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -46,17 +46,11 @@
     private static final String TAG = "CarrierTextController";
 
     private final boolean mIsEmergencyCallCapable;
-
     private boolean mTelephonyCapable;
-
     private boolean mShowMissingSim;
-
     private boolean mShowAirplaneMode;
-
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
     private WifiManager mWifiManager;
-
     private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()];
     private CarrierTextCallback mCarrierTextCallback;
     private Context mContext;
@@ -132,10 +126,8 @@
     /**
      * Controller that provides updates on text with carriers names or SIM status.
      * Used by {@link CarrierText}.
-     * @param context
+     *
      * @param separator Separator between different parts of the text
-     * @param showAirplaneMode
-     * @param showMissingSim
      */
     public CarrierTextController(Context context, CharSequence separator, boolean showAirplaneMode,
             boolean showMissingSim) {
@@ -186,6 +178,7 @@
     /**
      * Sets the listening status of this controller. If the callback is null, it is set to
      * not listening
+     *
      * @param callback Callback to provide text updates
      */
     public void setListening(CarrierTextCallback callback) {
@@ -199,7 +192,7 @@
             } else {
                 // Don't listen and clear out the text when the device isn't a phone.
                 mKeyguardUpdateMonitor = null;
-                callback.updateCarrierText("", false);
+                callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
             }
         } else {
             mCarrierTextCallback = null;
@@ -217,9 +210,11 @@
 
         List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
         final int numSubs = subs.size();
+        final int[] subsIds = new int[numSubs];
         if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
         for (int i = 0; i < numSubs; i++) {
             int subId = subs.get(i).getSubscriptionId();
+            subsIds[i] = subId;
             IccCardConstants.State simState = mKeyguardUpdateMonitor.getSimState(subId);
             CharSequence carrierName = subs.get(i).getCarrierName();
             CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
@@ -294,9 +289,11 @@
         }
 
         if (mCarrierTextCallback != null) {
-            mCarrierTextCallback.updateCarrierText(displayText, anySimReadyAndInService);
-            mCarrierTextCallback.updateCarrierList(
-                    displayText.toString().split(mSeparator.toString()), anySimReadyAndInService);
+            mCarrierTextCallback.updateCarrierInfo(new CarrierTextCallbackInfo(
+                    displayText,
+                    displayText.toString().split(mSeparator.toString()),
+                    anySimReadyAndInService,
+                    subsIds));
         }
 
     }
@@ -482,22 +479,31 @@
     }
 
     /**
+     * Data structure for passing information to CarrierTextController subscribers
+     */
+    public static final class CarrierTextCallbackInfo {
+        public final CharSequence carrierText;
+        public final CharSequence[] listOfCarriers;
+        public final boolean anySimReady;
+        public final int[] subscriptionIds;
+
+        CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
+                boolean anySimReady, int[] subscriptionIds) {
+            this.carrierText = carrierText;
+            this.listOfCarriers = listOfCarriers;
+            this.anySimReady = anySimReady;
+            this.subscriptionIds = subscriptionIds;
+        }
+    }
+
+    /**
      * Callback to communicate to Views
      */
     public interface CarrierTextCallback {
         /**
-         * Provides an updated list of carrier names
-         * @param listOfCarriers
-         * @param simsReady Whether at least one SIM is ready and with service
+         * Provides updated carrier information.
          */
-        default void updateCarrierList(CharSequence[] listOfCarriers, boolean simsReady) {};
-
-        /**
-         * Provides an updated full carrier text
-         * @param carrierText
-         * @param simsReady Whether at least one SIM is ready and with service
-         */
-        default void updateCarrierText(CharSequence carrierText, boolean simsReady) {};
+        default void updateCarrierInfo(CarrierTextCallbackInfo info) {};
 
         /**
          * Notifies the View that the device is going to sleep
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index d99f234..fece94e 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -44,6 +44,7 @@
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.power.EnhancedEstimates;
 import com.android.systemui.power.PowerUI;
+import com.android.systemui.privacy.PrivacyItemController;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.AmbientPulseManager;
@@ -278,6 +279,7 @@
     @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
     @Inject Lazy<AutoHideController> mAutoHideController;
     @Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener;
+    @Inject Lazy<PrivacyItemController> mPrivacyItemController;
     @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper;
     @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler;
     @Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler;
@@ -452,6 +454,8 @@
         mProviders.put(ForegroundServiceNotificationListener.class,
                 mForegroundServiceNotificationListener::get);
         mProviders.put(ClockManager.class, mClockManager::get);
+        mProviders.put(PrivacyItemController.class, mPrivacyItemController::get);
+
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
index 0c7a9a9..85265f4 100644
--- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
@@ -26,11 +26,11 @@
  * Layout class representing the Global Actions menu which appears when the power button is held.
  */
 public abstract class MultiListLayout extends LinearLayout {
-    boolean mHasOutsideTouch;
-    boolean mHasSeparatedView;
+    protected boolean mHasOutsideTouch;
+    protected boolean mHasSeparatedView;
 
-    int mExpectedSeparatedItemCount;
-    int mExpectedListItemCount;
+    protected int mExpectedSeparatedItemCount;
+    protected int mExpectedListItemCount;
 
     public MultiListLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
index 92d3cc1..36a813b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -57,7 +57,7 @@
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         setScaleType(ScaleType.CENTER_CROP);
-        mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_size);
+        mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
         mDotRenderer = new BadgeRenderer(mIconSize);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index a457dee..da29ab4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -18,9 +18,8 @@
 
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
-import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain;
 
@@ -229,10 +228,6 @@
         }
         mStackView.stackDismissed();
 
-        // Reset the position of the stack (TODO - or should we save / respect last user position?)
-        Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
-        mStackView.setPosition(startPoint.x, startPoint.y);
-
         updateVisibility();
         mNotificationEntryManager.updateNotifications();
     }
@@ -249,16 +244,14 @@
             BubbleView bubble = mBubbles.get(notif.key);
             mStackView.updateBubble(bubble, notif, updatePosition);
         } else {
-            boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE;
             if (mStackView == null) {
-                setPosition = true;
                 mStackView = new BubbleStackView(mContext);
                 ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
                 // XXX: Bug when you expand the shade on top of expanded bubble, there is no scrim
                 // between bubble and the shade
                 int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1;
                 sbv.addView(mStackView, bubblePosition,
-                        new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+                        new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
                 if (mExpandListener != null) {
                     mStackView.setExpandListener(mExpandListener);
                 }
@@ -273,11 +266,6 @@
             }
             mBubbles.put(bubble.getKey(), bubble);
             mStackView.addBubble(bubble);
-            if (setPosition) {
-                // Need to add the bubble to the stack before we can know the width
-                Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
-                mStackView.setPosition(startPoint.x, startPoint.y);
-            }
         }
         updateVisibility();
     }
@@ -423,24 +411,6 @@
         return mStackView;
     }
 
-    // TODO: factor in PIP location / maybe last place user had it
-    /**
-     * Gets an appropriate starting point to position the bubble stack.
-     */
-    private static Point getStartPoint(int size, Point displaySize) {
-        final int x = displaySize.x - size + EDGE_OVERLAP;
-        final int y = displaySize.y / 4;
-        return new Point(x, y);
-    }
-
-    /**
-     * Gets an appropriate position for the bubble when the stack is expanded.
-     */
-    static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) {
-        // Same place for now..
-        return new Point(EDGE_OVERLAP, size);
-    }
-
     /**
      * Whether the notification has been developer configured to bubble and is allowed by the user.
      */
@@ -458,7 +428,7 @@
                 entry.key).canBubble();
         boolean hasOverlayIntent = n.getNotification().getBubbleMetadata() != null
                 && n.getNotification().getBubbleMetadata().getIntent() != null;
-        return hasOverlayIntent && canChannelOverlay && canAppOverlay;
+        return DEBUG_ENABLE_AUTO_BUBBLE && hasOverlayIntent && canChannelOverlay && canAppOverlay;
     }
 
     /**
@@ -468,7 +438,8 @@
      * message-like notification.
      * </p>
      */
-    private boolean shouldAutoBubble(Context context, NotificationEntry entry) {
+    @VisibleForTesting
+    protected boolean shouldAutoBubble(Context context, NotificationEntry entry) {
         if (entry.isBubbleDismissed()) {
             return false;
         }
@@ -495,9 +466,11 @@
         Class<? extends Notification.Style> style = n.getNotification().getNotificationStyle();
         boolean isMessageType = Notification.CATEGORY_MESSAGE.equals(n.getNotification().category);
         boolean isMessageStyle = Notification.MessagingStyle.class.equals(style);
-        return (((isMessageType && hasRemoteInput) || isMessageStyle) && autoBubbleMessages)
+        boolean shouldAutoBubble =
+                (((isMessageType && hasRemoteInput) || isMessageStyle) && autoBubbleMessages)
                 || (isImportantOngoing && autoBubbleOngoing)
                 || autoBubbleAll;
+        return DEBUG_ENABLE_AUTO_BUBBLE && shouldAutoBubble;
     }
 
     private static boolean shouldAutoBubbleMessages(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java
deleted file mode 100644
index c1063fa..0000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleMovementHelper.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bubbles;
-
-import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
-
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.systemui.bubbles.BubbleTouchHandler.FloatingView;
-
-import java.util.Arrays;
-
-/**
- * Math and animators to move bubbles around the screen.
- *
- * TODO: straight up copy paste from old prototype -- consider physics, see if bubble & pip
- * movements can be unified maybe?
- */
-public class BubbleMovementHelper {
-
-    private static final int MAGNET_ANIM_TIME = 150;
-    public static final int EDGE_OVERLAP = 0;
-
-    private Context mContext;
-    private Point mDisplaySize;
-
-    public BubbleMovementHelper(Context context) {
-        mContext = context;
-        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        mDisplaySize = new Point();
-        wm.getDefaultDisplay().getSize(mDisplaySize);
-    }
-
-    /**
-     * @return the distance between the two provided points.
-     */
-    static double distance(Point p1, Point p2) {
-        return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
-    }
-
-    /**
-     * @return the y value of a line defined by y = mx+b
-     */
-    static float findY(float m, float b, float x) {
-        return (m * x) + b;
-    }
-
-    /**
-     * @return the x value of a line defined by y = mx+b
-     */
-    static float findX(float m, float b, float y) {
-        return (y - b) / m;
-    }
-
-    /**
-     * Determines a point on the edge of the screen based on the velocity and position.
-     */
-    public Point getPointOnEdge(View bv, Point p, float velX, float velY) {
-        // Find the slope and the y-intercept
-        velX = velX == 0 ? 1 : velX;
-        final float m = velY / velX;
-        final float b = p.y - m * p.x;
-
-        // There are two lines it can intersect, find the two points
-        Point pointHoriz = new Point();
-        Point pointVert = new Point();
-
-        if (velX > 0) {
-            // right
-            pointHoriz.x = mDisplaySize.x;
-            pointHoriz.y = (int) findY(m, b, mDisplaySize.x);
-        } else {
-            // left
-            pointHoriz.x = EDGE_OVERLAP;
-            pointHoriz.y = (int) findY(m, b, 0);
-        }
-        if (velY > 0) {
-            // bottom
-            pointVert.x = (int) findX(m, b, mDisplaySize.y);
-            pointVert.y = mDisplaySize.y - getNavBarHeight();
-        } else {
-            // top
-            pointVert.x = (int) findX(m, b, 0);
-            pointVert.y = EDGE_OVERLAP;
-        }
-
-        // Use the point that's closest to the start position
-        final double distanceToVertPoint = distance(p, pointVert);
-        final double distanceToHorizPoint = distance(p, pointHoriz);
-        boolean useVert = distanceToVertPoint < distanceToHorizPoint;
-        // Check if we're being flung along the current edge, use opposite point in this case
-        // XXX: on*Edge methods should actually use 'down' position of view and compare 'up' but
-        // this works well enough for now
-        if (onSideEdge(bv, p) && Math.abs(velY) > Math.abs(velX)) {
-            // Flinging along left or right edge, favor vert edge
-            useVert = true;
-
-        } else if (onTopBotEdge(bv, p) && Math.abs(velX) > Math.abs(velY)) {
-            // Flinging along top or bottom edge
-            useVert = false;
-        }
-
-        if (useVert) {
-            pointVert.x = capX(pointVert.x, bv);
-            pointVert.y = capY(pointVert.y, bv);
-            return pointVert;
-
-        }
-        pointHoriz.x = capX(pointHoriz.x, bv);
-        pointHoriz.y = capY(pointHoriz.y, bv);
-        return pointHoriz;
-    }
-
-    /**
-     * @return whether the view is on a side edge of the screen (i.e. left or right).
-     */
-    public boolean onSideEdge(View fv, Point p) {
-        return p.x + fv.getWidth() + EDGE_OVERLAP <= mDisplaySize.x
-                - EDGE_OVERLAP
-                || p.x >= EDGE_OVERLAP;
-    }
-
-    /**
-     * @return whether the view is on a top or bottom edge of the screen.
-     */
-    public boolean onTopBotEdge(View bv, Point p) {
-        return p.y >= getStatusBarHeight() + EDGE_OVERLAP
-                || p.y + bv.getHeight() + EDGE_OVERLAP <= mDisplaySize.y
-                - EDGE_OVERLAP;
-    }
-
-    /**
-     * @return constrained x value based on screen size and how much a view can overlap with a side
-     *         edge.
-     */
-    public int capX(float x, View bv) {
-        // Floating things can't stick to top or bottom edges, so figure out if it's closer to
-        // left or right and just use that side + the overlap.
-        final float centerX = x + bv.getWidth() / 2;
-        if (centerX > mDisplaySize.x / 2) {
-            // Right side
-            return mDisplaySize.x - bv.getWidth() - EDGE_OVERLAP;
-        } else {
-            // Left side
-            return EDGE_OVERLAP;
-        }
-    }
-
-    /**
-     * @return constrained y value based on screen size and how much a view can overlap with a top
-     *         or bottom edge.
-     */
-    public int capY(float y, View bv) {
-        final int height = bv.getHeight();
-        if (y < getStatusBarHeight() + EDGE_OVERLAP) {
-            return getStatusBarHeight() + EDGE_OVERLAP;
-        }
-        if (y + height + EDGE_OVERLAP > mDisplaySize.y - EDGE_OVERLAP) {
-            return mDisplaySize.y - height - EDGE_OVERLAP;
-        }
-        return (int) y;
-    }
-
-    /**
-     * Animation to translate the provided view.
-     */
-    public AnimatorSet animateMagnetTo(final BubbleStackView bv) {
-        Point pos = bv.getPosition();
-
-        // Find the distance to each edge
-        final int leftDistance = pos.x;
-        final int rightDistance = mDisplaySize.x - leftDistance;
-        final int topDistance = pos.y;
-        final int botDistance = mDisplaySize.y - topDistance;
-
-        int smallest;
-        // Find the closest one
-        int[] distances = {
-                leftDistance, rightDistance, topDistance, botDistance
-        };
-        Arrays.sort(distances);
-        smallest = distances[0];
-
-        // Animate to the closest edge
-        Point p = new Point();
-        if (smallest == leftDistance) {
-            p.x = capX(EDGE_OVERLAP, bv);
-            p.y = capY(topDistance, bv);
-        }
-        if (smallest == rightDistance) {
-            p.x = capX(mDisplaySize.x, bv);
-            p.y = capY(topDistance, bv);
-        }
-        if (smallest == topDistance) {
-            p.x = capX(leftDistance, bv);
-            p.y = capY(0, bv);
-        }
-        if (smallest == botDistance) {
-            p.x = capX(leftDistance, bv);
-            p.y = capY(mDisplaySize.y, bv);
-        }
-        return getTranslateAnim(bv, p, MAGNET_ANIM_TIME);
-    }
-
-    /**
-     * Animation to fling the provided view.
-     */
-    public AnimatorSet animateFlingTo(final BubbleStackView bv, float velX, float velY) {
-        Point pos = bv.getPosition();
-        Point endPos = getPointOnEdge(bv, pos, velX, velY);
-        endPos = new Point(capX(endPos.x, bv), capY(endPos.y, bv));
-        final double distance = Math.sqrt(Math.pow(endPos.x - pos.x, 2)
-                + Math.pow(endPos.y - pos.y, 2));
-        final float sumVel = Math.abs(velX) + Math.abs(velY);
-        final int duration = Math.max(Math.min(200, (int) (distance * 1000f / (sumVel / 2))), 50);
-        return getTranslateAnim(bv, endPos, duration);
-    }
-
-    /**
-     * Animation to translate the provided view.
-     */
-    public AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration) {
-        return getTranslateAnim(v, p, duration, 0);
-    }
-
-    /**
-     * Animation to translate the provided view.
-     */
-    public AnimatorSet getTranslateAnim(final FloatingView v, Point p,
-            int duration, int startDelay) {
-        return getTranslateAnim(v, p, duration, startDelay, null);
-    }
-
-    /**
-     * Animation to translate the provided view.
-     *
-     * @param v the view to translate.
-     * @param p the point to translate to.
-     * @param duration the duration of the animation.
-     * @param startDelay the start delay of the animation.
-     * @param listener the listener to add to the animation.
-     *
-     * @return the animation.
-     */
-    public static AnimatorSet getTranslateAnim(final FloatingView v, Point p, int duration,
-            int startDelay, AnimatorListener listener) {
-        Point curPos = v.getPosition();
-        final ValueAnimator animX = ValueAnimator.ofFloat(curPos.x, p.x);
-        animX.setDuration(duration);
-        animX.setStartDelay(startDelay);
-        animX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float value = (float) animation.getAnimatedValue();
-                v.setPositionX((int) value);
-            }
-        });
-
-        final ValueAnimator animY = ValueAnimator.ofFloat(curPos.y, p.y);
-        animY.setDuration(duration);
-        animY.setStartDelay(startDelay);
-        animY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float value = (float) animation.getAnimatedValue();
-                v.setPositionY((int) value);
-            }
-        });
-        if (listener != null) {
-            animY.addListener(listener);
-        }
-
-        AnimatorSet set = new AnimatorSet();
-        set.playTogether(animX, animY);
-        set.setInterpolator(FAST_OUT_SLOW_IN);
-        return set;
-    }
-
-
-    // TODO -- now that this is in system we should be able to get these better, but ultimately
-    // makes more sense to move to movement bounds style a la PIP
-    /**
-     * Returns the status bar height.
-     */
-    public int getStatusBarHeight() {
-        Resources res = mContext.getResources();
-        int resourceId = res.getIdentifier("status_bar_height", "dimen", "android");
-        if (resourceId > 0) {
-            return res.getDimensionPixelSize(resourceId);
-        }
-        return 0;
-    }
-
-    /**
-     * Returns the status bar height.
-     */
-    public int getNavBarHeight() {
-        Resources res = mContext.getResources();
-        int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
-        if (resourceId > 0) {
-            return res.getDimensionPixelSize(resourceId);
-        }
-        return 0;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index dcd121b..b584f67 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -16,59 +16,89 @@
 
 package com.android.systemui.bubbles;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
 import android.app.ActivityView;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
 import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
 import android.view.WindowManager;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.OvershootInterpolator;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
 import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.R;
+import com.android.systemui.bubbles.animation.ExpandedAnimationController;
+import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
+import com.android.systemui.bubbles.animation.StackAnimationController;
 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.ViewState;
 
 /**
  * Renders bubbles in a stack and handles animating expanded and collapsed states.
  */
 public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView {
-
     private static final String TAG = "BubbleStackView";
+
+    /**
+     * Friction applied to fling animations. Since the stack must land on one of the sides of the
+     * screen, we want less friction horizontally so that the stack has a better chance of making it
+     * to the side without needing a spring.
+     */
+    private static final float FLING_FRICTION_X = 1.15f;
+    private static final float FLING_FRICTION_Y = 1.5f;
+
+    /**
+     * Damping ratio to use for the stack spring animation used to spring the stack to its final
+     * position after a fling.
+     */
+    private static final float SPRING_DAMPING_RATIO = 0.85f;
+
+    /**
+     * Minimum fling velocity required to trigger moving the stack from one side of the screen to
+     * the other.
+     */
+    private static final float ESCAPE_VELOCITY = 750f;
+
     private Point mDisplaySize;
 
-    private FrameLayout mBubbleContainer;
+    private final SpringAnimation mExpandedViewXAnim;
+    private final SpringAnimation mExpandedViewYAnim;
+
+    private PhysicsAnimationLayout mBubbleContainer;
+    private StackAnimationController mStackAnimationController;
+    private ExpandedAnimationController mExpandedAnimationController;
+
     private BubbleExpandedViewContainer mExpandedViewContainer;
 
     private int mBubbleSize;
     private int mBubblePadding;
+    private int mExpandedAnimateXDistance;
+    private int mExpandedAnimateYDistance;
 
     private boolean mIsExpanded;
     private int mExpandedBubbleHeight;
     private BubbleTouchHandler mTouchHandler;
     private BubbleView mExpandedBubble;
-    private Point mCollapsedPosition;
     private BubbleController.BubbleExpandListener mExpandListener;
 
     private boolean mViewUpdatedRequested = false;
@@ -110,8 +140,12 @@
         setOnTouchListener(mTouchHandler);
 
         Resources res = getResources();
-        mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
+        mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
         mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mExpandedAnimateXDistance =
+                res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
+        mExpandedAnimateYDistance =
+                res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_y_distance);
 
         mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
         mDisplaySize = new Point();
@@ -120,6 +154,19 @@
 
         int padding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
         int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
+
+        mStackAnimationController = new StackAnimationController();
+        mExpandedAnimationController = new ExpandedAnimationController();
+
+        mBubbleContainer = new PhysicsAnimationLayout(context);
+        mBubbleContainer.setMaxRenderedChildren(
+                getResources().getInteger(R.integer.bubbles_max_rendered));
+        mBubbleContainer.setController(mStackAnimationController);
+        mBubbleContainer.setElevation(elevation);
+        mBubbleContainer.setPadding(padding, 0, padding, 0);
+        mBubbleContainer.setClipChildren(false);
+        addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
         mExpandedViewContainer = (BubbleExpandedViewContainer)
                 LayoutInflater.from(context).inflate(R.layout.bubble_expanded_view,
                         this /* parent */, false /* attachToRoot */);
@@ -128,11 +175,19 @@
         mExpandedViewContainer.setClipChildren(false);
         addView(mExpandedViewContainer);
 
-        mBubbleContainer = new FrameLayout(context);
-        mBubbleContainer.setElevation(elevation);
-        mBubbleContainer.setPadding(padding, 0, padding, 0);
-        mBubbleContainer.setClipChildren(false);
-        addView(mBubbleContainer);
+        mExpandedViewXAnim =
+                new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
+        mExpandedViewXAnim.setSpring(
+                new SpringForce()
+                        .setStiffness(SpringForce.STIFFNESS_LOW)
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
+
+        mExpandedViewYAnim =
+                new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_Y);
+        mExpandedViewYAnim.setSpring(
+                new SpringForce()
+                        .setStiffness(SpringForce.STIFFNESS_LOW)
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
 
         setClipChildren(false);
     }
@@ -144,38 +199,6 @@
     }
 
     @Override
-    public void onMeasure(int widthSpec, int heightSpec) {
-        super.onMeasure(widthSpec, heightSpec);
-
-        int bubbleHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec),
-                MeasureSpec.UNSPECIFIED);
-        if (mIsExpanded) {
-            ViewGroup parent = (ViewGroup) getParent();
-            int parentWidth = MeasureSpec.makeMeasureSpec(
-                    MeasureSpec.getSize(parent.getWidth()), MeasureSpec.EXACTLY);
-            int parentHeight = MeasureSpec.makeMeasureSpec(
-                    MeasureSpec.getSize(parent.getHeight()), MeasureSpec.EXACTLY);
-            measureChild(mBubbleContainer, parentWidth, bubbleHeightSpec);
-
-            int expandedViewHeight = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec),
-                    MeasureSpec.UNSPECIFIED);
-            measureChild(mExpandedViewContainer, parentWidth, expandedViewHeight);
-            setMeasuredDimension(widthSpec, parentHeight);
-        } else {
-            // Not expanded
-            measureChild(mExpandedViewContainer, 0, 0);
-
-            // Bubbles are translated a little to stack on top of each other
-            widthSpec = MeasureSpec.makeMeasureSpec(getStackWidth(), MeasureSpec.EXACTLY);
-            measureChild(mBubbleContainer, widthSpec, bubbleHeightSpec);
-
-            heightSpec = MeasureSpec.makeMeasureSpec(mBubbleContainer.getMeasuredHeight(),
-                    MeasureSpec.EXACTLY);
-            setMeasuredDimension(widthSpec, heightSpec);
-        }
-    }
-
-    @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         float x = ev.getRawX();
         float y = ev.getRawY();
@@ -293,9 +316,11 @@
             boolean updatePosition) {
         bubbleView.update(entry);
         if (updatePosition && !mIsExpanded) {
-            // If alerting it gets promoted to top of the stack
-            mBubbleContainer.removeView(bubbleView);
-            mBubbleContainer.addView(bubbleView, 0);
+            // If alerting it gets promoted to top of the stack.
+            if (mBubbleContainer.indexOfChild(bubbleView) != 0) {
+                mBubbleContainer.removeViewAndThen(bubbleView,
+                        () -> mBubbleContainer.addView(bubbleView, 0));
+            }
             requestUpdate();
         }
         if (mIsExpanded && bubbleView.equals(mExpandedBubble)) {
@@ -359,36 +384,51 @@
         if (mIsExpanded != shouldExpand) {
             mIsExpanded = shouldExpand;
             updateExpandedBubble();
+            applyCurrentState();
+            //requestUpdate();
+
+            mIsAnimating = true;
+
+            Runnable updateAfter = () -> {
+                applyCurrentState();
+                mIsAnimating = false;
+                requestUpdate();
+            };
 
             if (shouldExpand) {
-                // Save current position so that we might return there
-                savePosition();
+                mBubbleContainer.setController(mExpandedAnimationController);
+                mExpandedAnimationController.expandFromStack(
+                                mStackAnimationController.getStackPosition(), updateAfter);
+            } else {
+                mBubbleContainer.cancelAllAnimations();
+                mExpandedAnimationController.collapseBackToStack(
+                        () -> {
+                            mBubbleContainer.setController(mStackAnimationController);
+                            updateAfter.run();
+                        });
             }
 
-            // Determine the translation for the stack
-            Point position = shouldExpand
-                    ? BubbleController.getExpandPoint(this, mBubbleSize, mDisplaySize)
-                    : mCollapsedPosition;
-            int delay = shouldExpand ? 0 : 100;
-            AnimatorSet translationAnim = BubbleMovementHelper.getTranslateAnim(this, position,
-                    200, delay, null);
-            if (!shouldExpand) {
-                // First collapse the stack, then translate, maybe should expand at same time?
-                animateStackExpansion(() -> translationAnim.start());
-            } else {
-                // First translate, then expand
-                translationAnim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        mIsAnimating = true;
-                    }
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        animateStackExpansion(() -> mIsAnimating = false);
-                    }
-                });
-                translationAnim.start();
+            final float xStart =
+                    mStackAnimationController.getStackPosition().x < getWidth() / 2
+                            ? -mExpandedAnimateXDistance
+                            : mExpandedAnimateXDistance;
+
+            final float yStart = Math.min(
+                    mStackAnimationController.getStackPosition().y,
+                    mExpandedAnimateYDistance);
+            final float yDest = getStatusBarHeight() + mExpandedBubble.getHeight() + mBubblePadding;
+
+            if (shouldExpand) {
+                mExpandedViewContainer.setTranslationX(xStart);
+                mExpandedViewContainer.setTranslationY(yStart);
+                mExpandedViewContainer.setAlpha(0f);
             }
+
+            mExpandedViewXAnim.animateToFinalPosition(shouldExpand ? 0f : xStart);
+            mExpandedViewYAnim.animateToFinalPosition(shouldExpand ? yDest : yStart);
+            mExpandedViewContainer.animate()
+                    .setDuration(100)
+                    .alpha(shouldExpand ? 1f : 0f);
         }
     }
 
@@ -401,14 +441,6 @@
                 + mBubbleContainer.getPaddingStart();
     }
 
-    /**
-     * Saves the current position of the stack, used to save user placement of the stack to
-     * return to after an animation.
-     */
-    private void savePosition() {
-        mCollapsedPosition = getPosition();
-    }
-
     private void notifyExpansionChanged(BubbleView bubbleView, boolean expanded) {
         if (mExpandListener != null) {
             NotificationEntry entry = bubbleView != null ? bubbleView.getEntry() : null;
@@ -420,31 +452,154 @@
         return getBubbleAt(0);
     }
 
-    private BubbleView getBubbleAt(int i) {
+    /** Return the BubbleView at the given index from the bubble container. */
+    public BubbleView getBubbleAt(int i) {
         return mBubbleContainer.getChildCount() > i
                 ? (BubbleView) mBubbleContainer.getChildAt(i)
                 : null;
     }
 
     @Override
-    public void setPosition(int x, int y) {
-        setPositionX(x);
-        setPositionY(y);
+    public void setPosition(float x, float y) {
+        mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y);
     }
 
     @Override
-    public void setPositionX(int x) {
-        setTranslationX(x);
+    public void setPositionX(float x) {
+        // Unsupported, use setPosition(x, y).
     }
 
     @Override
-    public void setPositionY(int y) {
-        setTranslationY(y);
+    public void setPositionY(float y) {
+        // Unsupported, use setPosition(x, y).
     }
 
     @Override
-    public Point getPosition() {
-        return new Point((int) getTranslationX(), (int) getTranslationY());
+    public PointF getPosition() {
+        return mStackAnimationController.getStackPosition();
+    }
+
+    /** Called when a drag operation on an individual bubble has started. */
+    public void onBubbleDragStart(BubbleView bubble) {
+        // TODO: Save position and snap back if not dismissed.
+    }
+
+    /** Called with the coordinates to which an individual bubble has been dragged. */
+    public void onBubbleDragged(BubbleView bubble, float x, float y) {
+        bubble.setTranslationX(x);
+        bubble.setTranslationY(y);
+    }
+
+    /** Called when a drag operation on an individual bubble has finished. */
+    public void onBubbleDragFinish(BubbleView bubble, float x, float y, float velX, float velY) {
+        // TODO: Add fling to bottom to dismiss.
+    }
+
+    void onDragStart() {
+        if (mIsExpanded) {
+            return;
+        }
+
+        mStackAnimationController.cancelStackPositionAnimations();
+        mBubbleContainer.setController(mStackAnimationController);
+        mIsAnimating = false;
+    }
+
+    void onDragged(float x, float y) {
+        // TODO: We can drag if animating - just need to reroute inflight anims to drag point.
+        if (mIsExpanded) {
+            return;
+        }
+
+        mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y);
+    }
+
+    void onDragFinish(float x, float y, float velX, float velY) {
+        // TODO: Add fling to bottom to dismiss.
+
+        if (mIsExpanded || mIsAnimating) {
+            return;
+        }
+
+        final boolean stackOnLeftSide = x
+                - mBubbleContainer.getChildAt(0).getWidth() / 2
+                < mDisplaySize.x / 2;
+
+        final boolean stackShouldFlingLeft = stackOnLeftSide
+                ? velX < ESCAPE_VELOCITY
+                : velX < -ESCAPE_VELOCITY;
+
+        final RectF stackBounds = mStackAnimationController.getAllowableStackPositionRegion();
+
+        // Target X translation (either the left or right side of the screen).
+        final float destinationRelativeX = stackShouldFlingLeft
+                ? stackBounds.left : stackBounds.right;
+
+        // Minimum velocity required for the stack to make it to the side of the screen.
+        final float escapeVelocity = getMinXVelocity(
+                x,
+                destinationRelativeX,
+                FLING_FRICTION_X);
+
+        // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity so
+        // that it'll make it all the way to the side of the screen.
+        final float startXVelocity = stackShouldFlingLeft
+                ? Math.min(escapeVelocity, velX)
+                : Math.max(escapeVelocity, velX);
+
+        mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_X,
+                startXVelocity,
+                FLING_FRICTION_X,
+                new SpringForce()
+                        .setStiffness(SpringForce.STIFFNESS_LOW)
+                        .setDampingRatio(SPRING_DAMPING_RATIO),
+                destinationRelativeX);
+
+        mStackAnimationController.flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_Y,
+                velY,
+                FLING_FRICTION_Y,
+                new SpringForce()
+                        .setStiffness(SpringForce.STIFFNESS_LOW)
+                        .setDampingRatio(SPRING_DAMPING_RATIO),
+                /* destination */ null);
+    }
+
+    /**
+     * Minimum velocity, in pixels/second, required to get from x to destX while being slowed by a
+     * given frictional force.
+     *
+     * This is not derived using real math, I just made it up because the math in FlingAnimation
+     * looks hard and this seems to work. It doesn't actually matter because if it doesn't make it
+     * to the edge via Fling, it'll get Spring'd there anyway.
+     *
+     * TODO(tsuji, or someone who likes math): Figure out math.
+     */
+    private float getMinXVelocity(float x, float destX, float friction) {
+        return (destX - x) * (friction * 5) + ESCAPE_VELOCITY;
+    }
+
+    @Override
+    public void getBoundsOnScreen(Rect outRect) {
+        if (!mIsExpanded) {
+            mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
+        } else {
+            mBubbleContainer.getBoundsOnScreen(outRect);
+        }
+    }
+
+    private int getStatusBarHeight() {
+        if (getRootWindowInsets() != null) {
+            WindowInsets insets = getRootWindowInsets();
+            return Math.max(
+                    insets.getSystemWindowInsetTop(),
+                    insets.getDisplayCutout() != null
+                            ? insets.getDisplayCutout().getSafeInsetTop()
+                            : 0);
+        }
+
+        return 0;
     }
 
     private boolean isIntersecting(View view, float x, float y) {
@@ -478,22 +633,6 @@
             final PendingIntent intent = mExpandedBubble.getAppOverlayIntent();
             mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString());
             mExpandedViewContainer.setExpandedView(expandedView);
-            expandedView.setCallback(new ActivityView.StateCallback() {
-                @Override
-                public void onActivityViewReady(ActivityView view) {
-                    Log.d(TAG, "onActivityViewReady("
-                            + mExpandedBubble.getEntry().key + "): " + view);
-                    view.startActivity(intent);
-                }
-
-                @Override
-                public void onActivityViewDestroyed(ActivityView view) {
-                    NotificationEntry entry = mExpandedBubble != null
-                            ? mExpandedBubble.getEntry() : null;
-                    Log.d(TAG, "onActivityViewDestroyed(key="
-                            + ((entry != null) ? entry.key : "(none)") + "): " + view);
-                }
-            });
         } else {
             // Bubble with notification view expanded state
             ExpandableNotificationRow row = mExpandedBubble.getRowView();
@@ -510,9 +649,8 @@
             mExpandedViewContainer.setHeaderText(null);
 
         }
-        int pointerPosition = mExpandedBubble.getPosition().x
-                + (mExpandedBubble.getWidth() / 2);
-        mExpandedViewContainer.setPointerPosition(pointerPosition);
+        float pointerPosition = mExpandedBubble.getPosition().x + (mExpandedBubble.getWidth() / 2);
+        mExpandedViewContainer.setPointerPosition((int) pointerPosition);
     }
 
     private void applyCurrentState() {
@@ -522,7 +660,6 @@
         if (!mIsExpanded) {
             mExpandedViewContainer.setExpandedView(null);
         } else {
-            mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight());
             View expandedView = mExpandedViewContainer.getExpandedView();
             if (expandedView instanceof ActivityView) {
                 if (expandedView.isAttachedToWindow()) {
@@ -537,53 +674,6 @@
             BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
             bv.updateDotVisibility();
             bv.setZ(bubbsCount - i);
-
-            int transX = mIsExpanded ? (bv.getWidth() + mBubblePadding) * i : mBubblePadding * i;
-            ViewState viewState = new ViewState();
-            viewState.initFrom(bv);
-            viewState.xTranslation = transX;
-            viewState.applyToView(bv);
-
-            if (mIsExpanded) {
-                // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler
-                bv.setTag(new Point(transX, 0));
-            }
-        }
-    }
-
-    private void animateStackExpansion(Runnable endRunnable) {
-        int childCount = mBubbleContainer.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            BubbleView child = (BubbleView) mBubbleContainer.getChildAt(i);
-            int transX = mIsExpanded ? (mBubbleSize + mBubblePadding) * i : mBubblePadding * i;
-            int duration = childCount > 1 ? 200 : 0;
-            if (mIsExpanded) {
-                // Save the position so we can magnet back, tag is retrieved in BubbleTouchHandler
-                child.setTag(new Point(transX, 0));
-            }
-            ViewPropertyAnimator anim = child
-                    .animate()
-                    .setStartDelay(15 * i)
-                    .setDuration(duration)
-                    .setInterpolator(mIsExpanded
-                            ? new OvershootInterpolator()
-                            : new AccelerateInterpolator())
-                    .translationY(0)
-                    .translationX(transX);
-            final int fi = i;
-            // Probably want this choreographed with translation somehow / make it snappier
-            anim.withStartAction(() -> mIsAnimating = true);
-            anim.withEndAction(() -> {
-                if (endRunnable != null) {
-                    endRunnable.run();
-                }
-                if (fi == mBubbleContainer.getChildCount() - 1) {
-                    applyCurrentState();
-                    mIsAnimating = false;
-                    requestUpdate();
-                }
-            });
-            anim.start();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 97784b0..22cd2fc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -19,7 +19,7 @@
 import static com.android.systemui.pip.phone.PipDismissViewController.SHOW_TARGET_DELAY;
 
 import android.content.Context;
-import android.graphics.Point;
+import android.graphics.PointF;
 import android.os.Handler;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -37,18 +37,16 @@
 
     private BubbleController mController = Dependency.get(BubbleController.class);
     private PipDismissViewController mDismissViewController;
-    private BubbleMovementHelper mMovementHelper;
 
     // The position of the bubble on down event
-    private int mBubbleDownPosX;
-    private int mBubbleDownPosY;
+    private float mBubbleDownPosX;
+    private float mBubbleDownPosY;
     // The touch position on down event
-    private int mDownX = -1;
-    private int mDownY = -1;
+    private float mDownX = -1;
+    private float mDownY = -1;
 
     private boolean mMovedEnough;
     private int mTouchSlopSquared;
-    private float mMinFlingVelocity;
     private VelocityTracker mVelocityTracker;
 
     private boolean mInDismissTarget;
@@ -71,32 +69,27 @@
         /**
          * Sets the position of the view.
          */
-        void setPosition(int x, int y);
+        void setPosition(float x, float y);
 
         /**
          * Sets the x position of the view.
          */
-        void setPositionX(int x);
+        void setPositionX(float x);
 
         /**
          * Sets the y position of the view.
          */
-        void setPositionY(int y);
+        void setPositionY(float y);
 
         /**
          * @return the position of the view.
          */
-        Point getPosition();
+        PointF getPosition();
     }
 
     public BubbleTouchHandler(Context context) {
         final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         mTouchSlopSquared = touchSlop * touchSlop;
-
-        // Multiply by 3 for better fling
-        mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity() * 3;
-
-        mMovementHelper = new BubbleMovementHelper(context);
         mDismissViewController = new PipDismissViewController(context);
     }
 
@@ -119,9 +112,11 @@
         FloatingView floatingView = (FloatingView) targetView;
         boolean isBubbleStack = floatingView instanceof BubbleStackView;
 
-        Point startPos = floatingView.getPosition();
-        int rawX = (int) event.getRawX();
-        int rawY = (int) event.getRawY();
+        PointF startPos = floatingView.getPosition();
+        float rawX = event.getRawX();
+        float rawY = event.getRawY();
+        float x = mBubbleDownPosX + rawX - mDownX;
+        float y = mBubbleDownPosY + rawY - mDownY;
         switch (action) {
             case MotionEvent.ACTION_DOWN:
                 trackMovement(event);
@@ -134,6 +129,13 @@
                 mDownX = rawX;
                 mDownY = rawY;
                 mMovedEnough = false;
+
+                if (isBubbleStack) {
+                    stack.onDragStart();
+                } else {
+                    stack.onBubbleDragStart((BubbleView) floatingView);
+                }
+
                 break;
 
             case MotionEvent.ACTION_MOVE:
@@ -145,22 +147,23 @@
                     mDownX = rawX;
                     mDownY = rawY;
                 }
-                final int deltaX = rawX - mDownX;
-                final int deltaY = rawY - mDownY;
+                final float deltaX = rawX - mDownX;
+                final float deltaY = rawY - mDownY;
                 if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) {
                     mMovedEnough = true;
                 }
-                int x = mBubbleDownPosX + rawX - mDownX;
-                int y = mBubbleDownPosY + rawY - mDownY;
 
                 if (mMovedEnough) {
-                    if (floatingView instanceof BubbleView && mBubbleDraggingOut == null) {
+                    if (floatingView instanceof BubbleView) {
                         mBubbleDraggingOut = ((BubbleView) floatingView);
+                        stack.onBubbleDragged(mBubbleDraggingOut, x, y);
+                    } else {
+                        stack.onDragged(x, y);
                     }
-                    floatingView.setPosition(x, y);
                 }
                 // TODO - when we're in the target stick to it / animate in some way?
-                mInDismissTarget = mDismissViewController.updateTarget((View) floatingView);
+                mInDismissTarget = mDismissViewController.updateTarget(
+                        isBubbleStack ? stack.getBubbleAt(0) : (View) floatingView);
                 break;
 
             case MotionEvent.ACTION_CANCEL:
@@ -181,19 +184,9 @@
                     final float velX = mVelocityTracker.getXVelocity();
                     final float velY = mVelocityTracker.getYVelocity();
                     if (isBubbleStack) {
-                        if ((Math.abs(velY) > mMinFlingVelocity)
-                                || (Math.abs(velX) > mMinFlingVelocity)) {
-                            // It's being flung somewhere
-                            mMovementHelper.animateFlingTo(stack, velX, velY).start();
-                        } else {
-                            // Magnet back to nearest edge
-                            mMovementHelper.animateMagnetTo(stack).start();
-                        }
+                        stack.onDragFinish(x, y, velX, velY);
                     } else {
-                        // Individual bubble got dragged but not dismissed.. lets animate it back
-                        // into position
-                        Point toGoTo = (Point) ((View) floatingView).getTag();
-                        mMovementHelper.getTranslateAnim(floatingView, toGoTo, 100, 0).start();
+                        stack.onBubbleDragFinish(mBubbleDraggingOut, x, y, velX, velY);
                     }
                 } else if (floatingView.equals(stack.getExpandedBubble())) {
                     stack.collapseStack();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index e8432b9..dc94832 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -22,7 +22,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.graphics.Color;
-import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
@@ -60,6 +60,8 @@
     private NotificationEntry mEntry;
     private PendingIntent mAppOverlayIntent;
     private ActivityView mActivityView;
+    private boolean mActivityViewReady;
+    private boolean mActivityViewStarted;
 
     public BubbleView(Context context) {
         this(context, null);
@@ -192,10 +194,10 @@
                         fraction = showDot ? fraction : 1 - fraction;
                         mBadgedImageView.setDotScale(fraction);
                     }).withEndAction(() -> {
-                        if (!showDot) {
-                            mBadgedImageView.setShowDot(false);
-                        }
-                    }).start();
+                if (!showDot) {
+                    mBadgedImageView.setShowDot(false);
+                }
+            }).start();
         }
     }
 
@@ -232,8 +234,21 @@
      */
     public ActivityView getActivityView() {
         if (mActivityView == null) {
-            mActivityView = new ActivityView(mContext);
+            mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
+                    true /* singleTaskInstance */);
             Log.d(TAG, "[getActivityView] created: " + mActivityView);
+            mActivityView.setCallback(new ActivityView.StateCallback() {
+                @Override
+                public void onActivityViewReady(ActivityView view) {
+                    mActivityViewReady = true;
+                    mActivityView.startActivity(mAppOverlayIntent);
+                }
+
+                @Override
+                public void onActivityViewDestroyed(ActivityView view) {
+                    mActivityViewReady = false;
+                }
+            });
         }
         return mActivityView;
     }
@@ -245,46 +260,44 @@
         if (mActivityView == null) {
             return;
         }
-        // HACK: Only release if initialized. There's no way to know if the ActivityView has
-        // been initialized. Calling release() if it hasn't been initialized will crash.
-
+        if (!mActivityViewReady) {
+            // release not needed, never initialized?
+            mActivityView = null;
+            return;
+        }
+        // HACK: release() will crash if the view is not attached.
         if (!mActivityView.isAttachedToWindow()) {
-            // HACK: release() will crash if the view is not attached.
-
             mActivityView.setVisibility(View.GONE);
             tmpParent.addView(mActivityView, new LinearLayout.LayoutParams(
                     ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.MATCH_PARENT));
         }
-        try {
-            mActivityView.release();
-        } catch (IllegalStateException ex) {
-            Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex);
-        }
+
+        mActivityView.release();
 
         ((ViewGroup) mActivityView.getParent()).removeView(mActivityView);
         mActivityView = null;
     }
 
     @Override
-    public void setPosition(int x, int y) {
+    public void setPosition(float x, float y) {
         setPositionX(x);
         setPositionY(y);
     }
 
     @Override
-    public void setPositionX(int x) {
+    public void setPositionX(float x) {
         setTranslationX(x);
     }
 
     @Override
-    public void setPositionY(int y) {
+    public void setPositionY(float y) {
         setTranslationY(y);
     }
 
     @Override
-    public Point getPosition() {
-        return new Point((int) getTranslationX(), (int) getTranslationY());
+    public PointF getPosition() {
+        return new PointF(getTranslationX(), getTranslationY());
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
new file mode 100644
index 0000000..4f870f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -0,0 +1,159 @@
+/*
+ * 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.systemui.bubbles.animation;
+
+import android.graphics.PointF;
+import android.view.View;
+import android.view.WindowInsets;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.systemui.R;
+
+import com.google.android.collect.Sets;
+
+import java.util.Set;
+
+/**
+ * Animation controller for bubbles when they're in their expanded state, or animating to/from the
+ * expanded state. This controls the expansion animation as well as bubbles 'dragging out' to be
+ * dismissed.
+ */
+public class ExpandedAnimationController
+        extends PhysicsAnimationLayout.PhysicsAnimationController {
+
+    /**
+     * The stack position from which the bubbles were expanded. Saved in {@link #expandFromStack}
+     * and used to return to stack form in {@link #collapseBackToStack}.
+     */
+    private PointF mExpandedFrom;
+
+    /** Horizontal offset between bubbles, which we need to know to re-stack them. */
+    private float mStackOffsetPx;
+    /** Spacing between bubbles in the expanded state. */
+    private float mBubblePaddingPx;
+    /** Size of each bubble. */
+    private float mBubbleSizePx;
+
+    @Override
+    protected void setLayout(PhysicsAnimationLayout layout) {
+        super.setLayout(layout);
+        mStackOffsetPx = layout.getResources().getDimensionPixelSize(
+                R.dimen.bubble_stack_offset);
+        mBubblePaddingPx = layout.getResources().getDimensionPixelSize(
+                R.dimen.bubble_padding);
+        mBubbleSizePx = layout.getResources().getDimensionPixelSize(
+                R.dimen.individual_bubble_size);
+    }
+
+    /**
+     * Animates expanding the bubbles into a row along the top of the screen.
+     *
+     * @return The y-value to which the bubbles were expanded, in case that's useful.
+     */
+    public float expandFromStack(PointF expandedFrom, Runnable after) {
+        mExpandedFrom = expandedFrom;
+
+        // How much to translate the next bubble, so that it is not overlapping the previous one.
+        float translateNextBubbleXBy = mBubblePaddingPx;
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
+            mLayout.animatePositionForChildAtIndex(i, translateNextBubbleXBy, getExpandedY());
+            translateNextBubbleXBy += mBubbleSizePx + mBubblePaddingPx;
+        }
+
+        runAfterTranslationsEnd(after);
+        return getExpandedY();
+    }
+
+    /** Animate collapsing the bubbles back to their stacked position. */
+    public void collapseBackToStack(Runnable after) {
+        // Stack to the left if we're going to the left, or right if not.
+        final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mExpandedFrom.x) ? -1 : 1;
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
+            mLayout.animatePositionForChildAtIndex(
+                    i, mExpandedFrom.x + (sideMultiplier * i * mStackOffsetPx), mExpandedFrom.y);
+        }
+
+        runAfterTranslationsEnd(after);
+    }
+
+    /** The Y value of the row of expanded bubbles. */
+    private float getExpandedY() {
+        final WindowInsets insets = mLayout.getRootWindowInsets();
+        if (insets != null) {
+            return mBubblePaddingPx + Math.max(
+                    insets.getSystemWindowInsetTop(),
+                    insets.getDisplayCutout() != null
+                            ? insets.getDisplayCutout().getSafeInsetTop()
+                            : 0);
+        }
+
+        return mBubblePaddingPx;
+    }
+
+    /** Runs the given Runnable after all translation-related animations have ended. */
+    private void runAfterTranslationsEnd(Runnable after) {
+        DynamicAnimation.OnAnimationEndListener allEndedListener =
+                (animation, canceled, value, velocity) -> {
+                    if (!mLayout.arePropertiesAnimating(
+                            DynamicAnimation.TRANSLATION_X,
+                            DynamicAnimation.TRANSLATION_Y)) {
+                        after.run();
+                    }
+                };
+
+        mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_X);
+        mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_Y);
+    }
+
+    @Override
+    Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
+        return Sets.newHashSet(
+                DynamicAnimation.TRANSLATION_X,
+                DynamicAnimation.TRANSLATION_Y);
+    }
+
+    @Override
+    int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
+        return NONE;
+    }
+
+    @Override
+    float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
+        return 0;
+    }
+
+    @Override
+    SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
+        return new SpringForce()
+                .setStiffness(SpringForce.STIFFNESS_LOW)
+                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+    }
+
+    @Override
+    void onChildAdded(View child, int index) {
+        // TODO: Animate the new bubble into the row, and push the other bubbles out of the way.
+        child.setTranslationY(getExpandedY());
+    }
+
+    @Override
+    void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) {
+        // TODO: Animate the bubble out, and pull the other bubbles into its position.
+        actuallyRemove.run();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java
new file mode 100644
index 0000000..4e0abc8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java
@@ -0,0 +1,34 @@
+/*
+ * 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.systemui.bubbles.animation;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+/**
+ * End listener that removes itself from its animation when called for the first time. Useful since
+ * anonymous OnAnimationEndListener instances can't pass themselves to
+ * {@link DynamicAnimation#removeEndListener}, but can call through to this superclass
+ * implementation.
+ */
+public class OneTimeEndListener implements DynamicAnimation.OnAnimationEndListener {
+
+    @Override
+    public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+            float velocity) {
+        animation.removeEndListener(this);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
new file mode 100644
index 0000000..1ced3a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -0,0 +1,496 @@
+/*
+ * 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.systemui.bubbles.animation;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Layout that constructs physics-based animations for each of its children, which behave according
+ * to settings provided by a {@link PhysicsAnimationController} instance.
+ *
+ * See physics-animation-layout.md.
+ */
+public class PhysicsAnimationLayout extends FrameLayout {
+    private static final String TAG = "Bubbs.PAL";
+
+    /**
+     * Controls the construction, configuration, and use of the physics animations supplied by this
+     * layout.
+     */
+    abstract static class PhysicsAnimationController {
+
+        /**
+         * Constant to return from {@link #getNextAnimationInChain} if the animation should not be
+         * chained at all.
+         */
+        protected static final int NONE = -1;
+
+        /** Set of properties for which the layout should construct physics animations. */
+        abstract Set<DynamicAnimation.ViewProperty> getAnimatedProperties();
+
+        /**
+         * Returns the index of the next animation after the given index in the animation chain, or
+         * {@link #NONE} if it should not be chained, or if the chain should end at the given index.
+         *
+         * If a next index is returned, an update listener will be added to the animation at the
+         * given index that dispatches value updates to the animation at the next index. This
+         * creates a 'following' effect.
+         *
+         * Typical implementations of this method will return either index + 1, or index - 1, to
+         * create forward or backward chains between adjacent child views, but this is not required.
+         */
+        abstract int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index);
+
+        /**
+         * Offsets to be added to the value that chained animations of the given property dispatch
+         * to subsequent child animations.
+         *
+         * This is used for things like maintaining the 'stack' effect in Bubbles, where bubbles
+         * stack off to the left or right side slightly.
+         */
+        abstract float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property);
+
+        /**
+         * Returns the SpringForce to be used for the given child view's property animation. Despite
+         * these usually being similar or identical across properties and views, {@link SpringForce}
+         * also contains the SpringAnimation's final position, so we have to construct a new one for
+         * each animation rather than using a constant.
+         */
+        abstract SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view);
+
+        /**
+         * Called when a new child is added at the specified index. Controllers can use this
+         * opportunity to animate in the new view.
+         */
+        abstract void onChildAdded(View child, int index);
+
+        /**
+         * Called when a child is to be removed from the layout. Controllers can use this
+         * opportunity to animate out the new view before calling the provided callback to actually
+         * remove it.
+         *
+         * Controllers should be careful to ensure that actuallyRemove is called on all code paths
+         * or child views will never be removed.
+         */
+        abstract void onChildToBeRemoved(View child, int index, Runnable actuallyRemove);
+
+        protected PhysicsAnimationLayout mLayout;
+
+        PhysicsAnimationController() { }
+
+        protected void setLayout(PhysicsAnimationLayout layout) {
+            this.mLayout = layout;
+        }
+
+        protected PhysicsAnimationLayout getLayout() {
+            return mLayout;
+        }
+    }
+
+    /**
+     * End listeners that are called when every child's animation of the given property has
+     * finished.
+     */
+    protected final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation.OnAnimationEndListener>
+            mEndListenerForProperty = new HashMap<>();
+
+    /**
+     * List of views that were passed to removeView, but are currently being animated out. These
+     * views will be actually removed by the controller (via super.removeView) once they're done
+     * animating out.
+     */
+    private final List<View> mViewsToBeActuallyRemoved = new ArrayList<>();
+
+    /** The currently active animation controller. */
+    private PhysicsAnimationController mController;
+
+    /**
+     * The maximum number of children to render and animate at a time. See
+     * {@link #setMaxRenderedChildren}.
+     */
+    private int mMaxRenderedChildren = 5;
+
+    public PhysicsAnimationLayout(Context context) {
+        super(context);
+    }
+
+    /**
+     * The maximum number of children to render and animate at a time. Any child views added beyond
+     * this limit will be set to {@link View#GONE}. If any animations attempt to run on the view,
+     * the corresponding property will be set with no animation.
+     */
+    public void setMaxRenderedChildren(int max) {
+        this.mMaxRenderedChildren = max;
+    }
+
+    /**
+     * Sets the animation controller and constructs or reconfigures the layout's physics animations
+     * to meet the controller's specifications.
+     */
+    public void setController(PhysicsAnimationController controller) {
+        cancelAllAnimations();
+        mEndListenerForProperty.clear();
+
+        this.mController = controller;
+        mController.setLayout(this);
+
+        // Set up animations for this controller's animated properties.
+        for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
+            setUpAnimationsForProperty(property);
+        }
+    }
+
+    /**
+     * Sets an end listener that will be called when all child animations for a given property have
+     * stopped running.
+     */
+    public void setEndListenerForProperty(
+            DynamicAnimation.OnAnimationEndListener listener,
+            DynamicAnimation.ViewProperty property) {
+        mEndListenerForProperty.put(property, listener);
+    }
+
+    /**
+     * Removes the end listener that would have been called when all child animations for a given
+     * property stopped running.
+     */
+    public void removeEndListenerForProperty(DynamicAnimation.ViewProperty property) {
+        mEndListenerForProperty.remove(property);
+    }
+
+    /**
+     * Returns the index of the view that precedes the given index, ignoring views that were passed
+     * to removeView, but are currently being animated out before actually being removed.
+     *
+     * @return index of the preceding view, or -1 if there are none.
+     */
+    public int getPrecedingNonRemovedViewIndex(int index) {
+        for (int i = index + 1; i < getChildCount(); i++) {
+            View precedingView = getChildAt(i);
+            if (!mViewsToBeActuallyRemoved.contains(precedingView)) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        super.addView(child, index, params);
+        setChildrenVisibility();
+
+        // Set up animations for the new view, if the controller is set. If it isn't set, we'll be
+        // setting up animations for all children when setController is called.
+        if (mController != null) {
+            for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
+                setUpAnimationForChild(property, child, index);
+            }
+
+            mController.onChildAdded(child, index);
+        }
+    }
+
+    @Override
+    public void removeView(View view) {
+        removeViewAndThen(view, /* callback */ null);
+    }
+
+    /**
+     * Let the controller know that this view should be removed, and then call the callback once the
+     * controller has finished any removal animations and the view has actually been removed.
+     */
+    public void removeViewAndThen(View view, Runnable callback) {
+        if (mController != null) {
+            final int index = indexOfChild(view);
+            // Remove the view only if it exists in this layout, and we're not already working on
+            // animating its removal.
+            if (index > -1 && !mViewsToBeActuallyRemoved.contains(view)) {
+                mViewsToBeActuallyRemoved.add(view);
+                setChildrenVisibility();
+
+                // Tell the controller to animate this view out, and call the callback when it wants
+                // to actually remove the view.
+                mController.onChildToBeRemoved(view, index, () -> {
+                    removeViewImmediateAndThen(view, callback);
+                    mViewsToBeActuallyRemoved.remove(view);
+                });
+            }
+        } else {
+            // Without a controller, nobody will animate this view out, so it gets an unceremonious
+            // departure.
+            removeViewImmediateAndThen(view, callback);
+        }
+    }
+
+    /** Checks whether any animations of the given properties are still running. */
+    public boolean arePropertiesAnimating(DynamicAnimation.ViewProperty... properties) {
+        for (int i = 0; i < getChildCount(); i++) {
+            for (DynamicAnimation.ViewProperty property : properties) {
+                if (getAnimationAtIndex(property, i).isRunning()) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /** Cancels all animations that are running on all child views, for all properties. */
+    public void cancelAllAnimations() {
+        if (mController == null) {
+            return;
+        }
+
+        for (int i = 0; i < getChildCount(); i++) {
+            for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
+                getAnimationAtIndex(property, i).cancel();
+            }
+        }
+    }
+
+    /**
+     * Animates the property of the child at the given index to the given value, then runs the
+     * callback provided when the animation ends.
+     */
+    protected void animateValueForChildAtIndex(
+            DynamicAnimation.ViewProperty property,
+            int index,
+            float value,
+            float startVel,
+            Runnable after) {
+        if (index < getChildCount()) {
+            final SpringAnimation animation = getAnimationAtIndex(property, index);
+            if (after != null) {
+                animation.addEndListener(new OneTimeEndListener() {
+                    @Override
+                    public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
+                            float value, float velocity) {
+                        super.onAnimationEnd(animation, canceled, value, velocity);
+                        after.run();
+                    }
+                });
+            }
+
+            if (startVel != Float.MAX_VALUE) {
+                animation.setStartVelocity(startVel);
+            }
+
+            animation.animateToFinalPosition(value);
+        }
+    }
+
+    /** Shortcut to animate a value with a callback, but no start velocity. */
+    protected void animateValueForChildAtIndex(
+            DynamicAnimation.ViewProperty property,
+            int index,
+            float value,
+            Runnable after) {
+        animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, after);
+    }
+
+    /** Shortcut to animate a value with a start velocity, but no callback. */
+    protected void animateValueForChildAtIndex(
+            DynamicAnimation.ViewProperty property,
+            int index,
+            float value,
+            float startVel) {
+        animateValueForChildAtIndex(property, index, value, startVel, /* callback */ null);
+    }
+
+    /** Shortcut to animate a value without changing the velocity or providing a callback. */
+    protected void animateValueForChildAtIndex(
+            DynamicAnimation.ViewProperty property,
+            int index,
+            float value) {
+        animateValueForChildAtIndex(property, index, value, Float.MAX_VALUE, /* callback */ null);
+    }
+
+    /** Shortcut to animate a child view's TRANSLATION_X and TRANSLATION_Y values. */
+    protected void animatePositionForChildAtIndex(int index, float x, float y) {
+        animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_X, index, x);
+        animateValueForChildAtIndex(DynamicAnimation.TRANSLATION_Y, index, y);
+    }
+
+    /** Whether the first child would be left of center if translated to the given x value. */
+    protected boolean isFirstChildXLeftOfCenter(float x) {
+        if (getChildCount() > 0) {
+            return x + (getChildAt(0).getWidth() / 2) < getWidth() / 2;
+        } else {
+            return false; // If there's no first child, really anything is correct, right?
+        }
+    }
+
+    /** ViewProperty's toString is useless, this returns a readable name for debug logging. */
+    protected static String getReadablePropertyName(DynamicAnimation.ViewProperty property) {
+        if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+            return "TRANSLATION_X";
+        } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
+            return "TRANSLATION_Y";
+        } else if (property.equals(DynamicAnimation.SCALE_X)) {
+            return "SCALE_X";
+        } else if (property.equals(DynamicAnimation.SCALE_Y)) {
+            return "SCALE_Y";
+        } else if (property.equals(DynamicAnimation.ALPHA)) {
+            return "ALPHA";
+        } else {
+            return "Unknown animation property.";
+        }
+    }
+
+
+    /** Immediately removes the view, without notifying the controller, then runs the callback. */
+    private void removeViewImmediateAndThen(View view, Runnable callback) {
+        super.removeView(view);
+
+        if (callback != null) {
+            callback.run();
+        }
+
+        setChildrenVisibility();
+    }
+
+    /**
+     * Retrieves the animation of the given property from the view at the given index via the view
+     * tag system.
+     */
+    private SpringAnimation getAnimationAtIndex(
+            DynamicAnimation.ViewProperty property, int index) {
+        return (SpringAnimation) getChildAt(index).getTag(getTagIdForProperty(property));
+    }
+
+    /** Sets up SpringAnimations of the given property for each child view in the layout. */
+    private void setUpAnimationsForProperty(DynamicAnimation.ViewProperty property) {
+        for (int i = 0; i < getChildCount(); i++) {
+            setUpAnimationForChild(property, getChildAt(i), i);
+        }
+    }
+
+    /** Constructs a SpringAnimation of the given property for a child view. */
+    private void setUpAnimationForChild(
+            DynamicAnimation.ViewProperty property, View child, int index) {
+        SpringAnimation newAnim = new SpringAnimation(child, property);
+        newAnim.addUpdateListener((animation, value, velocity) -> {
+            final int nextAnimInChain =
+                    mController.getNextAnimationInChain(property, indexOfChild(child));
+            if (nextAnimInChain == PhysicsAnimationController.NONE) {
+                return;
+            }
+
+            final int animIndex = indexOfChild(child);
+            final float offset =
+                    mController.getOffsetForChainedPropertyAnimation(property);
+
+            // If this property's animations should be chained, then check to see if there is a
+            // subsequent animation within the rendering limit, and if so, tell it to animate to
+            // this animation's new value (plus the offset).
+            if (nextAnimInChain < Math.min(
+                    getChildCount(),
+                    mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())) {
+                getAnimationAtIndex(property, animIndex + 1)
+                        .animateToFinalPosition(value + offset);
+            } else if (nextAnimInChain < getChildCount()) {
+                // If the next child view is not rendered, update the property directly without
+                // animating it, so that the view is still in the correct state if it later
+                // becomes visible.
+                for (int i = nextAnimInChain; i < getChildCount(); i++) {
+                    // 'value' here is the value of the last child within the rendering limit,
+                    // not the first child's value - so we want to subtract the last child's
+                    // index when calculating the offset.
+                    property.setValue(getChildAt(i), value + offset * (i - animIndex));
+                }
+            }
+        });
+
+        newAnim.setSpring(mController.getSpringForce(property, child));
+        newAnim.addEndListener(new AllAnimationsForPropertyFinishedEndListener(property));
+        child.setTag(getTagIdForProperty(property), newAnim);
+    }
+
+    /** Hides children beyond the max rendering count. */
+    private void setChildrenVisibility() {
+        for (int i = 0; i < getChildCount(); i++) {
+            getChildAt(i).setVisibility(
+                    // Ignore views that are animating out when calculating whether to hide the
+                    // view. That is, if we're supposed to render 5 views, but 4 are animating out
+                    // and will soon be removed, render up to 9 views temporarily.
+                    i < (mMaxRenderedChildren + mViewsToBeActuallyRemoved.size())
+                        ? View.VISIBLE
+                        : View.GONE);
+        }
+    }
+
+    /** Return a stable ID to use as a tag key for the given property's animations. */
+    private int getTagIdForProperty(DynamicAnimation.ViewProperty property) {
+        if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+            return R.id.translation_x_dynamicanimation_tag;
+        } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
+            return R.id.translation_y_dynamicanimation_tag;
+        } else if (property.equals(DynamicAnimation.SCALE_X)) {
+            return R.id.scale_x_dynamicanimation_tag;
+        } else if (property.equals(DynamicAnimation.SCALE_Y)) {
+            return R.id.scale_y_dynamicanimation_tag;
+        } else if (property.equals(DynamicAnimation.ALPHA)) {
+            return R.id.alpha_dynamicanimation_tag;
+        }
+
+        return -1;
+    }
+
+    /**
+     * End listener that is added to each individual DynamicAnimation, which dispatches to a single
+     * listener when every other animation of the given property is no longer running.
+     *
+     * This is required since chained DynamicAnimations can stop and start again due to changes in
+     * upstream animations. This means that adding an end listener to just the last animation is not
+     * sufficient. By firing only when every other animation on the property has stopped running, we
+     * ensure that no animation will be restarted after the single end listener is called.
+     */
+    protected class AllAnimationsForPropertyFinishedEndListener
+            implements DynamicAnimation.OnAnimationEndListener {
+        private DynamicAnimation.ViewProperty mProperty;
+
+        AllAnimationsForPropertyFinishedEndListener(DynamicAnimation.ViewProperty property) {
+            this.mProperty = property;
+        }
+
+        @Override
+        public void onAnimationEnd(
+                DynamicAnimation anim, boolean canceled, float value, float velocity) {
+            if (!arePropertiesAnimating(mProperty)) {
+                if (mEndListenerForProperty.containsKey(mProperty)) {
+                    mEndListenerForProperty.get(mProperty).onAnimationEnd(anim, canceled, value,
+                            velocity);
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
new file mode 100644
index 0000000..0f51376
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -0,0 +1,455 @@
+/*
+ * 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.systemui.bubbles.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FlingAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.systemui.R;
+
+import com.google.android.collect.Sets;
+
+import java.util.HashMap;
+import java.util.Set;
+
+/**
+ * Animation controller for bubbles when they're in their stacked state. Stacked bubbles sit atop
+ * each other with a slight offset to the left or right (depending on which side of the screen they
+ * are on). Bubbles 'follow' each other when dragged, and can be flung to the left or right sides of
+ * the screen.
+ */
+public class StackAnimationController extends
+        PhysicsAnimationLayout.PhysicsAnimationController {
+
+    private static final String TAG = "Bubbs.StackCtrl";
+
+    /** Scale factor to use initially for new bubbles being animated in. */
+    private static final float ANIMATE_IN_STARTING_SCALE = 1.15f;
+
+    /** Translation factor (multiplied by stack offset) to use for new bubbles being animated in. */
+    private static final int ANIMATE_IN_TRANSLATION_FACTOR = 4;
+
+    /**
+     * Values to use for the default {@link SpringForce} provided to the physics animation layout.
+     */
+    private static final float DEFAULT_STIFFNESS = 2500f;
+    private static final float DEFAULT_BOUNCINESS = 0.85f;
+
+    /**
+     * The canonical position of the stack. This is typically the position of the first bubble, but
+     * we need to keep track of it separately from the first bubble's translation in case there are
+     * no bubbles, or the first bubble was just added and being animated to its new position.
+     */
+    private PointF mStackPosition = new PointF();
+
+    /**
+     * Animations on the stack position itself, which would have been started in
+     * {@link #flingThenSpringFirstBubbleWithStackFollowing}. These animations dispatch to
+     * {@link #moveFirstBubbleWithStackFollowing} to move the entire stack (with 'following' effect)
+     * to a legal position on the side of the screen.
+     */
+    private HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mStackPositionAnimations =
+            new HashMap<>();
+
+    /** Horizontal offset of bubbles in the stack. */
+    private float mStackOffset;
+    /** Diameter of the bubbles themselves. */
+    private int mIndividualBubbleSize;
+    /** Size of spacing around the bubbles, separating it from the edge of the screen. */
+    private int mBubblePadding;
+    /** How far offscreen the stack rests. */
+    private int mBubbleOffscreen;
+    /** How far down the screen the stack starts, when there is no pre-existing location. */
+    private int mStackStartingVerticalOffset;
+
+    private Point mDisplaySize;
+    private RectF mAllowableStackPositionRegion;
+
+    @Override
+    protected void setLayout(PhysicsAnimationLayout layout) {
+        super.setLayout(layout);
+
+        Resources res = layout.getResources();
+        mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
+        mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
+        mStackStartingVerticalOffset =
+                res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
+
+        mDisplaySize = new Point();
+        WindowManager wm =
+                (WindowManager) layout.getContext().getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getSize(mDisplaySize);
+    }
+
+    /**
+     * Instantly move the first bubble to the given point, and animate the rest of the stack behind
+     * it with the 'following' effect.
+     */
+    public void moveFirstBubbleWithStackFollowing(float x, float y) {
+        moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X, x);
+        moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y, y);
+    }
+
+    /**
+     * The position of the stack - typically the position of the first bubble; if no bubbles have
+     * been added yet, it will be where the first bubble will go when added.
+     */
+    public PointF getStackPosition() {
+        return mStackPosition;
+    }
+
+    /**
+     * Flings the first bubble along the given property's axis, using the provided configuration
+     * values. When the animation ends - either by hitting the min/max, or by friction sufficiently
+     * reducing momentum - a SpringAnimation takes over to snap the bubble to the given final
+     * position.
+     */
+    public void flingThenSpringFirstBubbleWithStackFollowing(
+            DynamicAnimation.ViewProperty property,
+            float vel,
+            float friction,
+            SpringForce spring,
+            Float finalPosition) {
+        Log.d(TAG, String.format("Flinging %s.",
+                        PhysicsAnimationLayout.getReadablePropertyName(property)));
+
+        StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
+        final float currentValue = firstBubbleProperty.getValue(this);
+        final RectF bounds = getAllowableStackPositionRegion();
+        final float min =
+                property.equals(DynamicAnimation.TRANSLATION_X)
+                        ? bounds.left
+                        : bounds.top;
+        final float max =
+                property.equals(DynamicAnimation.TRANSLATION_X)
+                        ? bounds.right
+                        : bounds.bottom;
+
+        FlingAnimation flingAnimation = new FlingAnimation(this, firstBubbleProperty);
+        flingAnimation.setFriction(friction)
+                .setStartVelocity(vel)
+
+                // If the bubble's property value starts beyond the desired min/max, use that value
+                // instead so that the animation won't immediately end. If, for example, the user
+                // drags the bubbles into the navigation bar, but then flings them upward, we want
+                // the fling to occur despite temporarily having a value outside of the min/max. If
+                // the bubbles are out of bounds and flung even farther out of bounds, the fling
+                // animation will halt immediately and the SpringAnimation will take over, springing
+                // it in reverse to the (legal) final position.
+                .setMinValue(Math.min(currentValue, min))
+                .setMaxValue(Math.max(currentValue, max))
+
+                .addEndListener((animation, canceled, endValue, endVelocity) -> {
+                    if (!canceled) {
+                        springFirstBubbleWithStackFollowing(property, spring, endVelocity,
+                                finalPosition != null
+                                        ? finalPosition
+                                        : Math.max(min, Math.min(max, endValue)));
+                    }
+                });
+
+        cancelStackPositionAnimation(property);
+        mStackPositionAnimations.put(property, flingAnimation);
+        flingAnimation.start();
+    }
+
+    /**
+     * Cancel any stack position animations that were started by calling
+     * @link #flingThenSpringFirstBubbleWithStackFollowing}, and remove any corresponding end
+     * listeners.
+     */
+    public void cancelStackPositionAnimations() {
+        cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_X);
+        cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_Y);
+
+        mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X);
+        mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_Y);
+    }
+
+    /**
+     * Returns the region within which the stack is allowed to rest. This goes slightly off the left
+     * and right sides of the screen, below the status bar/cutout and above the navigation bar.
+     * While the stack is not allowed to rest outside of these bounds, it can temporarily be
+     * animated or dragged beyond them.
+     */
+    public RectF getAllowableStackPositionRegion() {
+        final WindowInsets insets = mLayout.getRootWindowInsets();
+        mAllowableStackPositionRegion = new RectF();
+
+        if (insets != null) {
+            mAllowableStackPositionRegion.left =
+                    -mBubbleOffscreen
+                            - mBubblePadding
+                            + Math.max(
+                            insets.getSystemWindowInsetLeft(),
+                            insets.getDisplayCutout() != null
+                                    ? insets.getDisplayCutout().getSafeInsetLeft()
+                                    : 0);
+            mAllowableStackPositionRegion.right =
+                    mLayout.getWidth()
+                            - mIndividualBubbleSize
+                            + mBubbleOffscreen
+                            - mBubblePadding
+                            - Math.max(
+                            insets.getSystemWindowInsetRight(),
+                            insets.getDisplayCutout() != null
+                                ? insets.getDisplayCutout().getSafeInsetRight()
+                                : 0);
+
+            mAllowableStackPositionRegion.top =
+                    mBubblePadding
+                            + Math.max(
+                            insets.getSystemWindowInsetTop(),
+                            insets.getDisplayCutout() != null
+                                ? insets.getDisplayCutout().getSafeInsetTop()
+                                : 0);
+            mAllowableStackPositionRegion.bottom =
+                    mLayout.getHeight()
+                            - mIndividualBubbleSize
+                            - mBubblePadding
+                            - Math.max(
+                            insets.getSystemWindowInsetBottom(),
+                            insets.getDisplayCutout() != null
+                                    ? insets.getDisplayCutout().getSafeInsetBottom()
+                                    : 0);
+        }
+
+        return mAllowableStackPositionRegion;
+    }
+
+    @Override
+    Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
+        return Sets.newHashSet(
+                DynamicAnimation.TRANSLATION_X, // For positioning.
+                DynamicAnimation.TRANSLATION_Y,
+                DynamicAnimation.ALPHA,         // For fading in new bubbles.
+                DynamicAnimation.SCALE_X,       // For 'popping in' new bubbles.
+                DynamicAnimation.SCALE_Y);
+    }
+
+    @Override
+    int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
+        if (property.equals(DynamicAnimation.TRANSLATION_X)
+                || property.equals(DynamicAnimation.TRANSLATION_Y)) {
+            return index + 1; // Just chain them linearly.
+        } else {
+            return NONE;
+        }
+    }
+
+
+    @Override
+    float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
+        if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+            // Offset to the left if we're on the left, or the right otherwise.
+            return mLayout.isFirstChildXLeftOfCenter(mStackPosition.x)
+                    ? -mStackOffset : mStackOffset;
+        } else {
+            return 0f;
+        }
+    }
+
+    @Override
+    SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
+        return new SpringForce()
+                .setDampingRatio(DEFAULT_BOUNCINESS)
+                .setStiffness(DEFAULT_STIFFNESS);
+    }
+
+    @Override
+    void onChildAdded(View child, int index) {
+        // If this is the first child added, position the stack in its starting position.
+        if (mLayout.getChildCount() == 1) {
+            moveStackToStartPosition();
+        }
+
+        if (mLayout.indexOfChild(child) == 0) {
+            child.setTranslationY(mStackPosition.y);
+
+            // Pop in the new bubble.
+            child.setScaleX(ANIMATE_IN_STARTING_SCALE);
+            child.setScaleY(ANIMATE_IN_STARTING_SCALE);
+            mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_X, 0, 1f);
+            mLayout.animateValueForChildAtIndex(DynamicAnimation.SCALE_Y, 0, 1f);
+
+            // Fade in the new bubble.
+            child.setAlpha(0);
+            mLayout.animateValueForChildAtIndex(DynamicAnimation.ALPHA, 0, 1f);
+
+            // Start the new bubble 4x the normal offset distance in the opposite direction. We'll
+            // animate in from this position. Since the animations are chained, when the new bubble
+            // flies in from the side, it will push the other ones out of the way.
+            float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+            child.setTranslationX(mStackPosition.x - (ANIMATE_IN_TRANSLATION_FACTOR * xOffset));
+            mLayout.animateValueForChildAtIndex(
+                    DynamicAnimation.TRANSLATION_X, 0, mStackPosition.x);
+        }
+    }
+
+    @Override
+    void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) {
+        // Animate the child out, actually removing it once its alpha is zero.
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.ALPHA, index, 0f, () -> {
+                    actuallyRemove.run();
+                });
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.SCALE_X, index, ANIMATE_IN_STARTING_SCALE);
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.SCALE_Y, index, ANIMATE_IN_STARTING_SCALE);
+
+        final boolean hasPrecedingChild = index + 1 < mLayout.getChildCount();
+        if (hasPrecedingChild) {
+            final int precedingViewIndex = mLayout.getPrecedingNonRemovedViewIndex(index);
+            if (precedingViewIndex >= 0) {
+                final float offsetX =
+                        getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+                mLayout.animatePositionForChildAtIndex(
+                        precedingViewIndex,
+                        mStackPosition.x + (index * offsetX),
+                        mStackPosition.y);
+            }
+        }
+    }
+
+    /** Moves the stack, without any animation, to the starting position. */
+    private void moveStackToStartPosition() {
+        mLayout.post(() -> setStackPosition(
+                getAllowableStackPositionRegion().right,
+                getAllowableStackPositionRegion().top + mStackStartingVerticalOffset));
+    }
+
+    /**
+     * Moves the first bubble instantly to the given X or Y translation, and instructs subsequent
+     * bubbles to animate 'following' to the new location.
+     */
+    private void moveFirstBubbleWithStackFollowing(
+            DynamicAnimation.ViewProperty property, float value) {
+
+        // Update the canonical stack position.
+        if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+            mStackPosition.x = value;
+        } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
+            mStackPosition.y = value;
+        }
+
+        if (mLayout.getChildCount() > 0) {
+            property.setValue(mLayout.getChildAt(0), value);
+            mLayout.animateValueForChildAtIndex(
+                    property,
+                    /* index */ 1,
+                    value + getOffsetForChainedPropertyAnimation(property));
+        }
+    }
+
+    /** Moves the stack to a position instantly, with no animation. */
+    private void setStackPosition(float x, float y) {
+        Log.d(TAG, String.format("Setting position to (%f, %f).", x, y));
+        mStackPosition.set(x, y);
+
+        cancelStackPositionAnimations();
+
+        // Since we're not using the chained animations, apply the offsets manually.
+        final float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+        final float yOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_Y);
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
+            mLayout.getChildAt(i).setTranslationX(x + (i * xOffset));
+            mLayout.getChildAt(i).setTranslationY(y + (i * yOffset));
+        }
+    }
+
+    /**
+     * Springs the first bubble to the given final position, with the rest of the stack 'following'.
+     */
+    private void springFirstBubbleWithStackFollowing(
+            DynamicAnimation.ViewProperty property, SpringForce spring,
+            float vel, float finalPosition) {
+
+        Log.d(TAG, String.format("Springing %s to final position %f.",
+                        PhysicsAnimationLayout.getReadablePropertyName(property),
+                        finalPosition));
+
+        StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
+        SpringAnimation springAnimation =
+                new SpringAnimation(this, firstBubbleProperty)
+                        .setSpring(spring)
+                        .setStartVelocity(vel);
+
+        cancelStackPositionAnimation(property);
+        mStackPositionAnimations.put(property, springAnimation);
+        springAnimation.animateToFinalPosition(finalPosition);
+    }
+
+    /**
+     * Cancels any outstanding first bubble property animations that are running. This does not
+     * affect the SpringAnimations controlling the individual bubbles' 'following' effect - it only
+     * cancels animations started from {@link #springFirstBubbleWithStackFollowing} and
+     * {@link #flingThenSpringFirstBubbleWithStackFollowing}.
+     */
+    private void cancelStackPositionAnimation(DynamicAnimation.ViewProperty property) {
+        if (mStackPositionAnimations.containsKey(property)) {
+            mStackPositionAnimations.get(property).cancel();
+        }
+    }
+
+    /**
+     * FloatProperty that uses {@link #moveFirstBubbleWithStackFollowing} to set the first bubble's
+     * translation and animate the rest of the stack with it. A DynamicAnimation can animate this
+     * property directly to move the first bubble and cause the stack to 'follow' to the new
+     * location.
+     *
+     * This could also be achieved by simply animating the first bubble view and adding an update
+     * listener to dispatch movement to the rest of the stack. However, this would require
+     * duplication of logic in that update handler - it's simpler to keep all logic contained in the
+     * {@link #moveFirstBubbleWithStackFollowing} method.
+     */
+    private class StackPositionProperty
+            extends FloatPropertyCompat<StackAnimationController> {
+        private final DynamicAnimation.ViewProperty mProperty;
+
+        private StackPositionProperty(DynamicAnimation.ViewProperty property) {
+            super(property.toString());
+            mProperty = property;
+        }
+
+        @Override
+        public float getValue(StackAnimationController controller) {
+            return mProperty.getValue(mLayout.getChildAt(0));
+        }
+
+        @Override
+        public void setValue(StackAnimationController controller, float value) {
+            moveFirstBubbleWithStackFollowing(mProperty, value);
+        }
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 7b18fad..f5ac0d3 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -1090,9 +1090,16 @@
             }
         }
 
+        protected int getActionLayoutId(Context context) {
+            if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) {
+                return com.android.systemui.R.layout.global_actions_grid_item;
+            }
+            return com.android.systemui.R.layout.global_actions_item;
+        }
+
         public View create(
                 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
-            View v = inflater.inflate(com.android.systemui.R.layout.global_actions_item, parent,
+            View v = inflater.inflate(getActionLayoutId(context), parent,
                     false);
 
             ImageView icon = (ImageView) v.findViewById(R.id.icon);
@@ -1498,7 +1505,8 @@
             window.setBackgroundDrawable(mGradientDrawable);
             window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
 
-            setContentView(com.android.systemui.R.layout.global_actions_wrapped);
+
+            setContentView(getGlobalActionsLayoutId(context));
             mGlobalActionsLayout = (MultiListLayout)
                     findViewById(com.android.systemui.R.id.global_actions_view);
             mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss());
@@ -1515,6 +1523,13 @@
             setTitle(R.string.global_actions);
         }
 
+        private int getGlobalActionsLayoutId(Context context) {
+            if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED)) {
+                return com.android.systemui.R.layout.global_actions_grid;
+            }
+            return com.android.systemui.R.layout.global_actions_wrapped;
+        }
+
         private void updateList() {
             mGlobalActionsLayout.removeAllItems();
             ArrayList<Action> separatedActions =
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
new file mode 100644
index 0000000..0e49b5f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
@@ -0,0 +1,101 @@
+/*
+ * 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.systemui.globalactions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.HardwareBgDrawable;
+import com.android.systemui.MultiListLayout;
+
+/**
+ * Grid-based implementation of the button layout created by the global actions dialog.
+ */
+public class GlobalActionsGridLayout extends MultiListLayout {
+
+    boolean mBackgroundsSet;
+
+    public GlobalActionsGridLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    private void setBackgrounds() {
+        HardwareBgDrawable listBackground  = new HardwareBgDrawable(true, true, getContext());
+        HardwareBgDrawable separatedViewBackground = new HardwareBgDrawable(true, true,
+                getContext());
+        getListView().setBackground(listBackground);
+        getSeparatedView().setBackground(separatedViewBackground);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        // backgrounds set only once, the first time onMeasure is called after inflation
+        if (getListView() != null && !mBackgroundsSet) {
+            setBackgrounds();
+            mBackgroundsSet = true;
+        }
+    }
+
+    @Override
+    public void setExpectedListItemCount(int count) {
+        mExpectedListItemCount = count;
+        getListView().setExpectedCount(count);
+    }
+
+    @Override
+    protected ViewGroup getSeparatedView() {
+        return findViewById(com.android.systemui.R.id.separated_button);
+    }
+
+    @Override
+    protected ListGridLayout getListView() {
+        return findViewById(android.R.id.list);
+    }
+
+    @Override
+    public void removeAllItems() {
+        ViewGroup separatedList = getSeparatedView();
+        ListGridLayout list = getListView();
+        if (separatedList != null) {
+            separatedList.removeAllViews();
+        }
+        if (list != null) {
+            list.removeAllItems();
+        }
+    }
+
+    @Override
+    public ViewGroup getParentView(boolean separated, int index) {
+        if (separated) {
+            return getSeparatedView();
+        } else {
+            return getListView().getParentView(index);
+        }
+    }
+
+    /**
+     * Not used in this implementation of the Global Actions Menu, but necessary for some others.
+     */
+    @Override
+    public void setDivisionView(View v) {
+
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
new file mode 100644
index 0000000..3775515
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
@@ -0,0 +1,111 @@
+/*
+ * 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.systemui.globalactions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * Layout which uses nested LinearLayouts to create a grid with the following behavior:
+ *
+ * * Try to maintain a 'square' grid (equal number of columns and rows) based on the expected item
+ *   count.
+ * * Display and hide sub-lists as needed, depending on the expected item count.
+ * * Favor bias toward having more rows or columns depending on the orientation of the device
+ *   (TODO(123344999): Implement this, currently always favors adding more rows.)
+ * * Change the orientation (horizontal vs. vertical) of the container and sub-lists to act as rows
+ *   or columns depending on the orientation of the device.
+ *   (TODO(123344999): Implement this, currently always columns.)
+ *
+ * While we could implement this behavior with a GridLayout, it would take significantly more
+ * time and effort, and would require more substantial refactoring of the existing code in
+ * GlobalActionsDialog, since it would require manipulation of the child items themselves.
+ *
+ */
+
+public class ListGridLayout extends LinearLayout {
+    private int mExpectedCount;
+    private int mRows;
+    private int mColumns;
+
+    public ListGridLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * Remove all items from this grid.
+     */
+    public void removeAllItems() {
+        for (int i = 0; i < getChildCount(); i++) {
+            ViewGroup subList = (ViewGroup) getChildAt(i);
+            if (subList != null) {
+                subList.removeAllViews();
+            }
+        }
+    }
+
+    /**
+     * Get the parent view associated with the item which should be placed at the given position.
+     */
+    public ViewGroup getParentView(int index) {
+        ViewGroup firstParent = (ViewGroup) getChildAt(0);
+        if (mRows == 0) {
+            return firstParent;
+        }
+        int column = (int) Math.floor(index / mRows);
+        ViewGroup parent = (ViewGroup) getChildAt(column);
+        return parent != null ? parent : firstParent;
+    }
+
+    /**
+     * Sets the expected number of items that this grid will be responsible for rendering.
+     */
+    public void setExpectedCount(int count) {
+        mExpectedCount = count;
+        mRows = getRowCount();
+        mColumns = getColumnCount();
+
+        for (int i = 0; i < getChildCount(); i++) {
+            if (i <= mColumns) {
+                setSublistVisibility(i, true);
+            } else {
+                setSublistVisibility(i, false);
+            }
+        }
+
+    }
+
+    private void setSublistVisibility(int index, boolean visible) {
+        View subList = getChildAt(index);
+        Log.d("ListGrid", "index: " + index  + ", visibility: "  + visible);
+        if (subList != null) {
+            subList.setVisibility(visible ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    private int getRowCount() {
+        return (int) Math.ceil(Math.sqrt(mExpectedCount));
+    }
+
+    private int getColumnCount() {
+        return (int) Math.round(Math.sqrt(mExpectedCount));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index f14495b..4527f73 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -40,6 +40,7 @@
 import android.content.IntentFilter;
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricSourceType;
+import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.SoundPool;
 import android.os.Bundle;
@@ -750,7 +751,14 @@
 
         mDeviceInteractive = mPM.isInteractive();
 
-        mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0);
+        mLockSounds = new SoundPool.Builder()
+                .setMaxStreams(1)
+                .setAudioAttributes(
+                        new AudioAttributes.Builder()
+                                .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+                                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                                .build())
+                .build();
         String soundPath = Settings.Global.getString(cr, Settings.Global.LOCK_SOUND);
         if (soundPath != null) {
             mLockSoundId = mLockSounds.load(soundPath, 1);
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 b218e80..f1c3bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -30,8 +30,12 @@
 import com.android.systemui.appops.AppOpItem
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.R
+import java.lang.ref.WeakReference
+import javax.inject.Inject
+import javax.inject.Singleton
 
-class PrivacyItemController(val context: Context, val callback: Callback) {
+@Singleton
+class PrivacyItemController @Inject constructor(val context: Context) {
 
     companion object {
         val OPS = intArrayOf(AppOpsManager.OP_CAMERA,
@@ -55,10 +59,12 @@
     @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 {
-        callback.privacyChanged(privacyList)
+        callbacks.forEach { it.get()?.privacyChanged(privacyList) }
     }
 
     private val updateListAndNotifyChanges = Runnable {
@@ -88,8 +94,8 @@
             registerReceiver()
         }
 
-    init {
-        registerReceiver()
+    private fun unregisterReceiver() {
+        context.unregisterReceiver(userSwitcherReceiver)
     }
 
     private fun registerReceiver() {
@@ -108,17 +114,41 @@
         bgHandler.post(updateListAndNotifyChanges)
     }
 
-    fun setListening(listen: Boolean) {
+    @VisibleForTesting
+    internal fun setListening(listen: Boolean) {
         if (listening == listen) return
         listening = listen
         if (listening) {
             appOpsController.addCallback(OPS, cb)
+            registerReceiver()
             update(true)
         } else {
             appOpsController.removeCallback(OPS, cb)
+            unregisterReceiver()
         }
     }
 
+    private fun addCallback(callback: WeakReference<Callback>) {
+        callbacks.add(callback)
+        if (callbacks.isNotEmpty() && !listening) setListening(true)
+        // Notify this callback if we didn't set to listening
+        else uiHandler.post(NotifyChangesToCallback(callback.get(), privacyList))
+    }
+
+    private fun removeCallback(callback: WeakReference<Callback>) {
+        // Removes also if the callback is null
+        callbacks.removeIf { it.get()?.equals(callback.get()) ?: true }
+        if (callbacks.isEmpty()) setListening(false)
+    }
+
+    fun addCallback(callback: Callback) {
+        addCallback(WeakReference(callback))
+    }
+
+    fun removeCallback(callback: Callback) {
+        removeCallback(WeakReference(callback))
+    }
+
     private fun updatePrivacyList() {
         privacyList = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
                 .mapNotNull { toPrivacyItem(it) }.distinct()
@@ -133,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)
     }
 
@@ -149,4 +179,13 @@
             }
         }
     }
+
+    private class NotifyChangesToCallback(
+        private val callback: Callback?,
+        private val list: List<PrivacyItem>
+    ) : Runnable {
+        override fun run() {
+            callback?.privacyChanged(list)
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index e63f88a..c0ed4b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -30,14 +30,18 @@
 import android.graphics.drawable.RippleDrawable;
 import android.os.Bundle;
 import android.os.UserManager;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
@@ -45,7 +49,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.keyguard.CarrierText;
+import com.android.keyguard.CarrierTextController;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.settingslib.Utils;
 import com.android.settingslib.drawable.UserIconDrawable;
@@ -68,7 +72,11 @@
 import javax.inject.Named;
 
 public class QSFooterImpl extends FrameLayout implements QSFooter,
-        OnClickListener, OnUserInfoChangedListener, EmergencyListener, SignalCallback {
+        OnClickListener, OnUserInfoChangedListener, EmergencyListener, SignalCallback,
+        CarrierTextController.CarrierTextCallback {
+
+    private static final int SIM_SLOTS = 2;
+    private static final String TAG = "QSFooterImpl";
 
     private final ActivityStarter mActivityStarter;
     private final UserInfoController mUserInfoController;
@@ -77,7 +85,6 @@
     private SettingsButton mSettingsButton;
     protected View mSettingsContainer;
     private PageIndicator mPageIndicator;
-    private CarrierText mCarrierText;
 
     private boolean mQsDisabled;
     private QSPanel mQsPanel;
@@ -99,12 +106,20 @@
 
     private View mActionsContainer;
     private View mDragHandle;
-    private View mMobileGroup;
-    private ImageView mMobileSignal;
-    private ImageView mMobileRoaming;
+
+    private View mCarrierDivider;
+    private ViewGroup mMobileFooter;
+    private View[] mMobileGroups = new View[SIM_SLOTS];
+    private ViewGroup[] mCarrierGroups = new ViewGroup[SIM_SLOTS];
+    private TextView[] mCarrierTexts = new TextView[SIM_SLOTS];
+    private ImageView[] mMobileSignals = new ImageView[SIM_SLOTS];
+    private ImageView[] mMobileRoamings = new ImageView[SIM_SLOTS];
+    private final CellSignalState[] mInfos =
+            new CellSignalState[]{new CellSignalState(), new CellSignalState()};
+
     private final int mColorForeground;
-    private final CellSignalState mInfo = new CellSignalState();
     private OnClickListener mExpandClickListener;
+    private CarrierTextController mCarrierTextController;
 
     @Inject
     public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
@@ -134,10 +149,20 @@
         mSettingsContainer = findViewById(R.id.settings_button_container);
         mSettingsButton.setOnClickListener(this);
 
-        mMobileGroup = findViewById(R.id.mobile_combo);
-        mMobileSignal = findViewById(R.id.mobile_signal);
-        mMobileRoaming = findViewById(R.id.mobile_roaming);
-        mCarrierText = findViewById(R.id.qs_carrier_text);
+        mMobileFooter = findViewById(R.id.qs_mobile);
+        mCarrierGroups[0] = findViewById(R.id.carrier1);
+        mCarrierGroups[1] = findViewById(R.id.carrier2);
+
+        for (int i = 0; i < SIM_SLOTS; i++) {
+            mMobileGroups[i] = mCarrierGroups[i].findViewById(R.id.mobile_combo);
+            mMobileSignals[i] = mCarrierGroups[i].findViewById(R.id.mobile_signal);
+            mMobileRoamings[i] = mCarrierGroups[i].findViewById(R.id.mobile_roaming);
+            mCarrierTexts[i] = mCarrierGroups[i].findViewById(R.id.qs_carrier_text);
+        }
+        mCarrierDivider = findViewById(R.id.qs_carrier_divider);
+        CharSequence separator = mContext.getString(
+                com.android.internal.R.string.kg_text_message_separator);
+        mCarrierTextController = new CarrierTextController(mContext, separator, false, false);
 
         mMultiUserSwitch = findViewById(R.id.multi_user_switch);
         mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
@@ -204,8 +229,8 @@
     private TouchAnimator createFooterAnimator() {
         return new TouchAnimator.Builder()
                 .addFloat(mDivider, "alpha", 0, 1)
-                .addFloat(mCarrierText, "alpha", 0, 0, 1)
-                .addFloat(mMobileGroup, "alpha", 0, 1)
+                .addFloat(mMobileFooter, "alpha", 0, 0, 1)
+                .addFloat(mCarrierDivider, "alpha", 0, 1)
                 .addFloat(mActionsContainer, "alpha", 0, 1)
                 .addFloat(mDragHandle, "alpha", 1, 0, 0)
                 .addFloat(mPageIndicator, "alpha", 0, 1)
@@ -332,10 +357,12 @@
                 mNetworkController.addEmergencyListener(this);
                 mNetworkController.addCallback(this);
             }
+            mCarrierTextController.setListening(this);
         } else {
             mUserInfoController.removeCallback(this);
             mNetworkController.removeEmergencyListener(this);
             mNetworkController.removeCallback(this);
+            mCarrierTextController.setListening(null);
         }
     }
 
@@ -358,7 +385,8 @@
         if (v == mSettingsButton) {
             if (!mDeviceProvisionedController.isCurrentUserSetup()) {
                 // If user isn't setup just unlock the device and dump them back at SUW.
-                mActivityStarter.postQSRunnableDismissingKeyguard(() -> { });
+                mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
+                });
                 return;
             }
             MetricsLogger.action(mContext,
@@ -415,32 +443,64 @@
     }
 
     private void handleUpdateState() {
-        mMobileGroup.setVisibility(mInfo.visible ? View.VISIBLE : View.GONE);
-        if (mInfo.visible) {
-            mMobileRoaming.setVisibility(mInfo.roaming ? View.VISIBLE : View.GONE);
-            mMobileRoaming.setImageTintList(ColorStateList.valueOf(mColorForeground));
-            SignalDrawable d = new SignalDrawable(mContext);
-            d.setDarkIntensity(QuickStatusBarHeader.getColorIntensity(mColorForeground));
-            mMobileSignal.setImageDrawable(d);
-            mMobileSignal.setImageLevel(mInfo.mobileSignalIconId);
+        for (int i = 0; i < SIM_SLOTS; i++) {
+            mMobileGroups[i].setVisibility(mInfos[i].visible ? View.VISIBLE : View.GONE);
+            if (mInfos[i].visible) {
+                mMobileRoamings[i].setVisibility(mInfos[i].roaming ? View.VISIBLE : View.GONE);
+                mMobileRoamings[i].setImageTintList(ColorStateList.valueOf(mColorForeground));
+                SignalDrawable d = new SignalDrawable(mContext);
+                d.setDarkIntensity(QuickStatusBarHeader.getColorIntensity(mColorForeground));
+                mMobileSignals[i].setImageDrawable(d);
+                mMobileSignals[i].setImageLevel(mInfos[i].mobileSignalIconId);
 
-            StringBuilder contentDescription = new StringBuilder();
-            if (mInfo.contentDescription != null) {
-                contentDescription.append(mInfo.contentDescription).append(", ");
+                StringBuilder contentDescription = new StringBuilder();
+                if (mInfos[i].contentDescription != null) {
+                    contentDescription.append(mInfos[i].contentDescription).append(", ");
+                }
+                if (mInfos[i].roaming) {
+                    contentDescription
+                            .append(mContext.getString(R.string.data_connection_roaming))
+                            .append(", ");
+                }
+                // TODO: show mobile data off/no internet text for 5 seconds before carrier text
+                if (TextUtils.equals(mInfos[i].typeContentDescription,
+                        mContext.getString(R.string.data_connection_no_internet))
+                        || TextUtils.equals(mInfos[i].typeContentDescription,
+                        mContext.getString(R.string.cell_data_off_content_description))) {
+                    contentDescription.append(mInfos[i].typeContentDescription);
+                }
+                mMobileSignals[i].setContentDescription(contentDescription);
             }
-            if (mInfo.roaming) {
-                contentDescription
-                        .append(mContext.getString(R.string.data_connection_roaming))
-                        .append(", ");
+        }
+        mCarrierDivider.setVisibility(
+                mInfos[0].visible && mInfos[1].visible ? View.VISIBLE : View.GONE);
+    }
+
+    @Override
+    public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+        if (info.anySimReady) {
+            boolean[] slotSeen = new boolean[SIM_SLOTS];
+            for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
+                int slot = SubscriptionManager.getSlotIndex(info.subscriptionIds[i]);
+                mInfos[slot].visible = true;
+                slotSeen[slot] = true;
+                mCarrierTexts[slot].setText(info.listOfCarriers[i].toString().trim());
+                mCarrierGroups[slot].setVisibility(View.VISIBLE);
             }
-            // TODO: show mobile data off/no internet text for 5 seconds before carrier text
-            if (TextUtils.equals(mInfo.typeContentDescription,
-                    mContext.getString(R.string.data_connection_no_internet))
-                || TextUtils.equals(mInfo.typeContentDescription,
-                    mContext.getString(R.string.cell_data_off_content_description))) {
-                contentDescription.append(mInfo.typeContentDescription);
+            for (int i = 0; i < SIM_SLOTS; i++) {
+                if (!slotSeen[i]) {
+                    mInfos[i].visible = false;
+                    mCarrierGroups[i].setVisibility(View.GONE);
+                }
             }
-            mMobileSignal.setContentDescription(contentDescription);
+            handleUpdateState();
+        } else {
+            mInfos[0].visible = false;
+            mInfos[1].visible = false;
+            mCarrierTexts[0].setText(info.carrierText);
+            mCarrierGroups[0].setVisibility(View.VISIBLE);
+            mCarrierGroups[1].setVisibility(View.GONE);
+            handleUpdateState();
         }
     }
 
@@ -450,18 +510,23 @@
             int qsType, boolean activityIn, boolean activityOut,
             String typeContentDescription,
             String description, boolean isWide, int subId, boolean roaming) {
-        mInfo.visible = statusIcon.visible;
-        mInfo.mobileSignalIconId = statusIcon.icon;
-        mInfo.contentDescription = statusIcon.contentDescription;
-        mInfo.typeContentDescription = typeContentDescription;
-        mInfo.roaming = roaming;
+        int slotIndex = SubscriptionManager.getSlotIndex(subId);
+        if (slotIndex >= SIM_SLOTS) {
+            Log.e(TAG, "setMobileDataIndicators - slot: " + slotIndex);
+        }
+        mInfos[slotIndex].visible = statusIcon.visible;
+        mInfos[slotIndex].mobileSignalIconId = statusIcon.icon;
+        mInfos[slotIndex].contentDescription = statusIcon.contentDescription;
+        mInfos[slotIndex].typeContentDescription = typeContentDescription;
+        mInfos[slotIndex].roaming = roaming;
         handleUpdateState();
     }
 
     @Override
     public void setNoSims(boolean hasNoSims, boolean simDetected) {
         if (hasNoSims) {
-            mInfo.visible = false;
+            mInfos[0].visible = false;
+            mInfos[1].visible = false;
         }
         handleUpdateState();
     }
@@ -473,4 +538,38 @@
         String typeContentDescription;
         boolean roaming;
     }
+
+
+    /**
+     * TextView that changes its ellipsize value with its visibility.
+     */
+    public static class QSCarrierText extends TextView {
+        public QSCarrierText(Context context) {
+            super(context);
+        }
+
+        public QSCarrierText(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+        }
+
+        public QSCarrierText(Context context, AttributeSet attrs, int defStyleAttr,
+                int defStyleRes) {
+            super(context, attrs, defStyleAttr, defStyleRes);
+        }
+
+        @Override
+        protected void onVisibilityChanged(View changedView, int visibility) {
+            super.onVisibilityChanged(changedView, visibility);
+            // Only show marquee when visible
+            if (visibility == VISIBLE) {
+                setEllipsize(TextUtils.TruncateAt.MARQUEE);
+            } else {
+                setEllipsize(TextUtils.TruncateAt.END);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 75ab5df..2d64ecd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -180,14 +180,14 @@
     public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
             NextAlarmController nextAlarmController, ZenModeController zenModeController,
             BatteryController batteryController, StatusBarIconController statusBarIconController,
-            ActivityStarter activityStarter) {
+            ActivityStarter activityStarter, PrivacyItemController privacyItemController) {
         super(context, attrs);
         mAlarmController = nextAlarmController;
         mZenController = zenModeController;
         mBatteryController = batteryController;
         mStatusBarIconController = statusBarIconController;
         mActivityStarter = activityStarter;
-        mPrivacyItemController = new PrivacyItemController(context, mPICCallback);
+        mPrivacyItemController = privacyItemController;
         mShownCount = getStoredShownCount();
     }
 
@@ -512,7 +512,6 @@
             return;
         }
         mHeaderQsPanel.setListening(listening);
-        mPrivacyItemController.setListening(listening);
         mListening = listening;
 
         if (listening) {
@@ -520,9 +519,11 @@
             mAlarmController.addCallback(this);
             mContext.registerReceiver(mRingerReceiver,
                     new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
+            mPrivacyItemController.addCallback(mPICCallback);
         } else {
             mZenController.removeCallback(this);
             mAlarmController.removeCallback(this);
+            mPrivacyItemController.removeCallback(mPICCallback);
             mContext.unregisterReceiver(mRingerReceiver);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index d740033..a0f4e24 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -24,7 +24,6 @@
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
-import com.android.systemui.R.drawable;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.qs.QSHost;
@@ -38,7 +37,7 @@
 /** Quick settings tile: Location **/
 public class LocationTile extends QSTileImpl<BooleanState> {
 
-    private final Icon mIcon = ResourceIcon.get(drawable.ic_signal_location);
+    private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_signal_location);
 
     private final LocationController mController;
     private final KeyguardMonitor mKeyguard;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 43ce0b5..de78d33 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -111,7 +111,7 @@
     protected void handleUpdateState(BooleanState state, Object arg) {
         state.value = mController.isActivated();
         state.label = mContext.getString(R.string.quick_settings_night_display_label);
-        state.icon = ResourceIcon.get(R.drawable.ic_qs_night_display_on);
+        state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_night_display_on);
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.secondaryLabel = getSecondaryLabel(state.value);
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/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 6c3e504..04534ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -19,6 +19,7 @@
 import android.view.View;
 
 import com.android.systemui.Interpolators;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 /**
@@ -92,9 +93,15 @@
 
     private static void updateLayerType(View view, float alpha) {
         if (view.hasOverlappingRendering() && alpha > 0.0f && alpha < 1.0f) {
-            view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-        } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) {
-            view.setLayerType(View.LAYER_TYPE_NONE, null);
+            if (view.getLayerType() != View.LAYER_TYPE_HARDWARE) {
+                view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                view.setTag(R.id.cross_fade_layer_type_changed_tag, true);
+            }
+        } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE
+                && view.getTag(R.id.cross_fade_layer_type_changed_tag) != null) {
+            if (view.getTag(R.id.cross_fade_layer_type_changed_tag) != null) {
+                view.setLayerType(View.LAYER_TYPE_NONE, null);
+            }
         }
     }
 
@@ -114,7 +121,7 @@
                 .setStartDelay(delay)
                 .setInterpolator(Interpolators.ALPHA_IN)
                 .withEndAction(null);
-        if (view.hasOverlappingRendering()) {
+        if (view.hasOverlappingRendering() && view.getLayerType() != View.LAYER_TYPE_HARDWARE) {
             view.animate().withLayer();
         }
     }
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/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
index 6df72fe..580e702 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
@@ -165,8 +165,7 @@
     private LogMaker getLogMaker() {
         return mBlockingHelperRow.getStatusBarNotification()
             .getLogMaker()
-            .setCategory(MetricsEvent.NOTIFICATION_ITEM)
-            .setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER);
+            .setCategory(MetricsEvent.NOTIFICATION_BLOCKING_HELPER);
     }
 
     // Format must stay in sync with frameworks/base/core/res/res/values/config.xml
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 2a9a815..359bc6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -126,14 +126,17 @@
     private OnClickListener mOnKeepShowing = v -> {
         mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
         closeControls(v);
-        mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+        mMetricsLogger.write(getLogMaker().setCategory(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+                .setType(MetricsEvent.TYPE_ACTION)
                 .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT));
     };
 
     private OnClickListener mOnToggleSilent = v -> {
         Runnable saveImportance = () -> {
             swapContent(ACTION_TOGGLE_SILENT, true /* animate */);
-            mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+            mMetricsLogger.write(getLogMaker()
+                    .setCategory(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+                    .setType(MetricsEvent.TYPE_ACTION)
                     .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_ALERT_ME));
         };
         if (mCheckSaveListener != null) {
@@ -146,7 +149,9 @@
     private OnClickListener mOnStopOrMinimizeNotifications = v -> {
         Runnable saveImportance = () -> {
             swapContent(ACTION_BLOCK, true /* animate */);
-            mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+            mMetricsLogger.write(getLogMaker()
+                    .setCategory(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+                    .setType(MetricsEvent.TYPE_ACTION)
                     .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_BLOCKED));
         };
         if (mCheckSaveListener != null) {
@@ -162,7 +167,8 @@
         logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
         mMetricsLogger.write(importanceChangeLogMaker().setType(MetricsEvent.TYPE_DISMISS));
         swapContent(ACTION_UNDO, true /* animate */);
-        mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+        mMetricsLogger.write(getLogMaker().setCategory(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+                .setType(MetricsEvent.TYPE_DISMISS)
                 .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_UNDO));
     };
 
@@ -263,7 +269,8 @@
         bindPrompt();
         bindButtons();
 
-        mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+        mMetricsLogger.write(getLogMaker().setCategory(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+                .setType(MetricsEvent.TYPE_OPEN)
                 .setSubtype(MetricsEvent.BLOCKING_HELPER_DISPLAY));
     }
 
@@ -609,8 +616,8 @@
         confirmation.setAlpha(1f);
         header.setVisibility(VISIBLE);
         header.setAlpha(1f);
-        mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
-                .setSubtype(MetricsEvent.BLOCKING_HELPER_DISMISS));
+        mMetricsLogger.write(getLogMaker().setCategory(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+                .setType(MetricsEvent.TYPE_CLOSE));
     }
 
     @Override
@@ -758,6 +765,6 @@
     }
 
     private LogMaker getLogMaker() {
-        return mSbn.getLogMaker().setCategory(MetricsEvent.NOTIFICATION_ITEM);
+        return mSbn.getLogMaker();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
index db7b4fc..4bdc170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java
@@ -17,8 +17,15 @@
 package com.android.systemui.statusbar.notification.row.wrapper;
 
 import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.os.Build;
 import android.view.View;
 
+import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
@@ -42,6 +49,47 @@
     }
 
     @Override
+    public void onReinflated() {
+        super.onReinflated();
+
+        Configuration configuration = mView.getResources().getConfiguration();
+        boolean nightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                == Configuration.UI_MODE_NIGHT_YES;
+
+        float[] hsl = new float[] {0f, 0f, 0f};
+        ColorUtils.colorToHSL(mBackgroundColor, hsl);
+        boolean backgroundIsDark = Color.alpha(mBackgroundColor) == 0
+                || hsl[1] == 0 && hsl[2] < 0.5;
+        boolean backgroundHasColor = hsl[1] > 0;
+
+        // Let's invert the notification colors when we're in night mode and
+        // the notification background isn't colorized.
+        if (!backgroundIsDark && !backgroundHasColor && nightMode
+                && mRow.getEntry().targetSdk < Build.VERSION_CODES.Q) {
+            Paint paint = new Paint();
+            ColorMatrix matrix = new ColorMatrix();
+            ColorMatrix tmp = new ColorMatrix();
+            // Inversion should happen on Y'UV space to conseve the colors and
+            // only affect the luminosity.
+            matrix.setRGB2YUV();
+            tmp.set(new float[]{
+                    -1f, 0f, 0f, 0f, 255f,
+                    0f, 1f, 0f, 0f, 0f,
+                    0f, 0f, 1f, 0f, 0f,
+                    0f, 0f, 0f, 1f, 0f
+            });
+            matrix.postConcat(tmp);
+            tmp.setYUV2RGB();
+            matrix.postConcat(tmp);
+            paint.setColorFilter(new ColorMatrixColorFilter(matrix));
+            mView.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
+
+            hsl[2] = 1f - hsl[2];
+            mBackgroundColor = ColorUtils.HSLToColor(hsl);
+        }
+    }
+
+    @Override
     protected boolean shouldClearBackgroundOnReapply() {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 1efdc56..9258c99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -37,7 +37,7 @@
     protected final View mView;
     protected final ExpandableNotificationRow mRow;
 
-    private int mBackgroundColor = 0;
+    protected int mBackgroundColor = 0;
 
     public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
         if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 03375d20..e953ad5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -113,6 +113,7 @@
      * Ratio representing being in ambient mode or not.
      */
     private float mDarkAmount;
+    private boolean mDozing;
 
     public KeyguardStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -210,7 +211,7 @@
                 mMultiUserSwitch.setVisibility(View.GONE);
             }
         }
-        mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable);
+        mBatteryView.setForceShowPercent(mBatteryCharging && mShowPercentAvailable || mDozing);
     }
 
     private void updateSystemIconsLayoutParams() {
@@ -347,7 +348,7 @@
         mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
         Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
         onThemeChanged();
-        updateDozeState();
+        updateDarkState();
     }
 
     @Override
@@ -506,21 +507,29 @@
         }
     }
 
+    public void setDozing(boolean dozing) {
+        if (mDozing == dozing) {
+            return;
+        }
+        mDozing = dozing;
+        updateVisibilities();
+    }
+
     public void setDarkAmount(float darkAmount) {
         mDarkAmount = darkAmount;
         if (darkAmount == 0) {
             dozeTimeTick();
         }
-        updateDozeState();
+        updateDarkState();
     }
 
     public void dozeTimeTick() {
         mCurrentBurnInOffsetX = getBurnInOffset(mBurnInOffset, true /* xAxis */);
         mCurrentBurnInOffsetY = getBurnInOffset(mBurnInOffset, false /* xAxis */);
-        updateDozeState();
+        updateDarkState();
     }
 
-    private void updateDozeState() {
+    private void updateDarkState() {
         float alpha = 1f - mDarkAmount;
         int visibility = alpha != 0f ? VISIBLE : INVISIBLE;
         mCarrierLabel.setAlpha(alpha * alpha);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 0d5ebb9..711b08e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -140,7 +140,8 @@
 
     private KeyguardAffordanceHelper mAffordanceHelper;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
-    private KeyguardStatusBarView mKeyguardStatusBar;
+    @VisibleForTesting
+    protected KeyguardStatusBarView mKeyguardStatusBar;
     private ViewGroup mBigClockContainer;
     private QS mQs;
     private FrameLayout mQsFrame;
@@ -447,6 +448,11 @@
                 false);
         addView(mKeyguardStatusView, index);
 
+        // Re-associate the clock container with the keyguard clock switch.
+        mBigClockContainer.removeAllViews();
+        KeyguardClockSwitch keyguardClockSwitch = findViewById(R.id.keyguard_clock_container);
+        keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
+
         // Update keyguard bottom area
         index = indexOfChild(mKeyguardBottomArea);
         removeView(mKeyguardBottomArea);
@@ -2792,6 +2798,7 @@
         if (mDozing) {
             mNotificationStackScroller.setShowDarkShelf(!hasCustomClock());
         }
+        mKeyguardStatusBar.setDozing(mDozing);
 
         if (mBarState == StatusBarState.KEYGUARD
                 || mBarState == StatusBarState.SHADE_LOCKED) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 43c35f1..18711c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -173,7 +173,7 @@
         mProvisionedController = Dependency.get(DeviceProvisionedController.class);
         mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
         mLocationController = Dependency.get(LocationController.class);
-        mPrivacyItemController = new PrivacyItemController(mContext, this);
+        mPrivacyItemController = Dependency.get(PrivacyItemController.class);
 
         mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
         mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -266,7 +266,7 @@
         mNextAlarmController.addCallback(mNextAlarmCallback);
         mDataSaver.addCallback(this);
         mKeyguardMonitor.addCallback(this);
-        mPrivacyItemController.setListening(true);
+        mPrivacyItemController.addCallback(this);
 
         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallback(this);
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
@@ -294,7 +294,7 @@
         mNextAlarmController.removeCallback(mNextAlarmCallback);
         mDataSaver.removeCallback(this);
         mKeyguardMonitor.removeCallback(this);
-        mPrivacyItemController.setListening(false);
+        mPrivacyItemController.removeCallback(this);
         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallback(this);
         mContext.unregisterReceiver(mIntentReceiver);
 
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/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 60a20cf..44ff4a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -36,6 +36,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
@@ -61,7 +62,6 @@
     private IActivityManager mActivityManager;
     @Mock
     private DozeParameters mDozeParameters;
-    @Mock
     private FrameLayout mStatusBarView;
     @Captor
     private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
@@ -80,6 +80,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mStatusBarView = new FrameLayout(mContext);
         mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
 
         // Bubbles get added to status bar window view
@@ -189,5 +190,10 @@
                 StatusBarWindowController statusBarWindowController) {
             super(context, statusBarWindowController);
         }
+
+        @Override
+        public boolean shouldAutoBubble(Context c, NotificationEntry entry) {
+            return entry.notification.getNotification().getBubbleMetadata() != null;
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
new file mode 100644
index 0000000..1bb7ef4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.systemui.bubbles.animation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+import com.android.systemui.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
+
+    @Spy
+    private ExpandedAnimationController mExpandedController = new ExpandedAnimationController();
+
+    private int mStackOffset;
+    private float mBubblePadding;
+    private float mBubbleSize;
+
+    private PointF mExpansionPoint;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        addOneMoreThanRenderLimitBubbles();
+        mLayout.setController(mExpandedController);
+        Resources res = mLayout.getResources();
+        mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
+        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+    }
+
+    @Test
+    public void testExpansionAndCollapse() throws InterruptedException {
+        mExpansionPoint = new PointF(100, 100);
+        Runnable afterExpand = Mockito.mock(Runnable.class);
+        mExpandedController.expandFromStack(mExpansionPoint, afterExpand);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+        testExpanded();
+        Mockito.verify(afterExpand).run();
+
+        Runnable afterCollapse = Mockito.mock(Runnable.class);
+        mExpandedController.collapseBackToStack(afterCollapse);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+        testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1);
+        Mockito.verify(afterExpand).run();
+    }
+
+    /** Check that children are in the correct positions for being stacked. */
+    private void testStackedAtPosition(float x, float y, int offsetMultiplier) {
+        // Make sure the rest of the stack moved again, including the first bubble not moving, and
+        // is stacked to the right now that we're on the right side of the screen.
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
+            assertEquals(x + i * offsetMultiplier * mStackOffset,
+                    mViews.get(i).getTranslationX(), 2f);
+            assertEquals(y, mViews.get(i).getTranslationY(), 2f);
+        }
+    }
+
+    /** Check that children are in the correct positions for being expanded. */
+    private void testExpanded() {
+        // Make sure the rest of the stack moved again, including the first bubble not moving, and
+        // is stacked to the right now that we're on the right side of the screen.
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
+            assertEquals(mBubblePadding + (i * (mBubbleSize + mBubblePadding)),
+                    mViews.get(i).getTranslationX(),
+                    2f);
+            assertEquals(mBubblePadding + mCutoutInsetSize,
+                    mViews.get(i).getTranslationY(), 2f);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
new file mode 100644
index 0000000..bfc02d9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
@@ -0,0 +1,471 @@
+/*
+ * 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.systemui.bubbles.animation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+
+import android.os.SystemClock;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.google.android.collect.Sets;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+/** Tests the PhysicsAnimationLayout itself, with a basic test animation controller. */
+public class PhysicsAnimationLayoutTest extends PhysicsAnimationLayoutTestCase {
+    static final float TEST_TRANSLATION_X_OFFSET = 15f;
+
+    @Spy
+    private TestableAnimationController mTestableController = new TestableAnimationController();
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // By default, use translation animations, chain the X animations with the default
+        // offset, and don't actually remove views immediately (since most implementations will wait
+        // to animate child views out before actually removing them).
+        mTestableController.setAnimatedProperties(Sets.newHashSet(
+                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y));
+        mTestableController.setChainedProperties(Sets.newHashSet(DynamicAnimation.TRANSLATION_X));
+        mTestableController.setOffsetForProperty(
+                DynamicAnimation.TRANSLATION_X, TEST_TRANSLATION_X_OFFSET);
+        mTestableController.setRemoveImmediately(false);
+    }
+
+    @Test
+    public void testRenderVisibility() {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+
+        // The last child should be GONE, the rest VISIBLE.
+        for (int i = 0; i < mMaxRenderedBubbles + 1; i++) {
+            assertEquals(i == mMaxRenderedBubbles ? View.GONE : View.VISIBLE,
+                    mLayout.getChildAt(i).getVisibility());
+        }
+    }
+
+    @Test
+    public void testHierarchyChanges() {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+
+        // Make sure the controller was notified of all the views we added.
+        for (View mView : mViews) {
+            Mockito.verify(mTestableController).onChildAdded(mView, 0);
+        }
+
+        // Remove some views and ensure the controller was notified, with the proper indices.
+        mTestableController.setRemoveImmediately(true);
+        mLayout.removeView(mViews.get(1));
+        mLayout.removeView(mViews.get(2));
+        Mockito.verify(mTestableController).onChildToBeRemoved(
+                eq(mViews.get(1)), eq(1), any());
+        Mockito.verify(mTestableController).onChildToBeRemoved(
+                eq(mViews.get(2)), eq(1), any());
+
+        // Make sure we still get view added notifications after doing some removals.
+        final View newBubble = new FrameLayout(mContext);
+        mLayout.addView(newBubble, 0);
+        Mockito.verify(mTestableController).onChildAdded(newBubble, 0);
+    }
+
+    @Test
+    public void testUpdateValueNotChained() throws InterruptedException {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+
+        // Don't chain any values.
+        mTestableController.setChainedProperties(Sets.newHashSet());
+
+        // Child views should not be translated.
+        assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f);
+        assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f);
+
+        // Animate the first child's translation X.
+        final CountDownLatch animLatch = new CountDownLatch(1);
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                100,
+                animLatch::countDown);
+        animLatch.await(1, TimeUnit.SECONDS);
+
+        // Ensure that the first view has been translated, but not the second one.
+        assertEquals(100, mLayout.getChildAt(0).getTranslationX(), .1f);
+        assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f);
+    }
+
+    @Test
+    public void testUpdateValueXChained() throws InterruptedException {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+        testChainedTranslationAnimations();
+    }
+
+    @Test
+    public void testSetEndListeners() throws InterruptedException {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+        mTestableController.setChainedProperties(Sets.newHashSet());
+
+        final CountDownLatch xLatch = new CountDownLatch(1);
+        OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() {
+            @Override
+            public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+                    float velocity) {
+                super.onAnimationEnd(animation, canceled, value, velocity);
+                xLatch.countDown();
+            }
+        });
+
+        final CountDownLatch yLatch = new CountDownLatch(1);
+        final OneTimeEndListener yEndListener = Mockito.spy(new OneTimeEndListener() {
+            @Override
+            public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+                    float velocity) {
+                super.onAnimationEnd(animation, canceled, value, velocity);
+                yLatch.countDown();
+            }
+        });
+
+        // Set end listeners for both x and y.
+        mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
+        mLayout.setEndListenerForProperty(yEndListener, DynamicAnimation.TRANSLATION_Y);
+
+        // Animate x, and wait for it to finish.
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                100);
+        xLatch.await();
+        yLatch.await(1, TimeUnit.SECONDS);
+
+        // Make sure the x end listener was called only one time, and the y listener was never
+        // called since we didn't animate y. Wait 1 second after the original animation end trigger
+        // to make sure it doesn't get called again.
+        Mockito.verify(xEndListener, Mockito.after(1000).times(1))
+                .onAnimationEnd(
+                        any(),
+                        eq(false),
+                        eq(100f),
+                        anyFloat());
+        Mockito.verify(yEndListener, Mockito.after(1000).never())
+                .onAnimationEnd(any(), anyBoolean(), anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testRemoveEndListeners() throws InterruptedException {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+        mTestableController.setChainedProperties(Sets.newHashSet());
+
+        final CountDownLatch xLatch = new CountDownLatch(1);
+        OneTimeEndListener xEndListener = Mockito.spy(new OneTimeEndListener() {
+            @Override
+            public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+                    float velocity) {
+                super.onAnimationEnd(animation, canceled, value, velocity);
+                xLatch.countDown();
+            }
+        });
+
+        // Set the end listener.
+        mLayout.setEndListenerForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
+
+        // Animate x, and wait for it to finish.
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                100);
+        xLatch.await();
+
+        InOrder endListenerCalls = inOrder(xEndListener);
+        endListenerCalls.verify(xEndListener, Mockito.times(1))
+                .onAnimationEnd(
+                        any(),
+                        eq(false),
+                        eq(100f),
+                        anyFloat());
+
+        // Animate X again, remove the end listener.
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                1000);
+        mLayout.removeEndListenerForProperty(DynamicAnimation.TRANSLATION_X);
+        xLatch.await(1, TimeUnit.SECONDS);
+
+        // Make sure the end listener was not called.
+        endListenerCalls.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testPrecedingNonRemovedIndex() {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+
+        // Call removeView at index 4, but don't actually remove it yet (as if we're animating it
+        // out). The preceding, non-removed view index to 3 should initially be 4, but then 5 since
+        // 4 is on its way out.
+        assertEquals(4, mLayout.getPrecedingNonRemovedViewIndex(3));
+        mLayout.removeView(mViews.get(4));
+        assertEquals(5, mLayout.getPrecedingNonRemovedViewIndex(3));
+
+        // Call removeView at index 1, and actually remove it immediately. With the old view at 1
+        // instantly gone, the preceding view to 0 should be 1 in both cases.
+        assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0));
+        mTestableController.setRemoveImmediately(true);
+        mLayout.removeView(mViews.get(1));
+        assertEquals(1, mLayout.getPrecedingNonRemovedViewIndex(0));
+    }
+
+    @Test
+    public void testSetController() throws InterruptedException {
+        // Add the bubbles, then set the controller, to make sure that a controller added to an
+        // already-initialized view works correctly.
+        addOneMoreThanRenderLimitBubbles();
+        mLayout.setController(mTestableController);
+        testChainedTranslationAnimations();
+
+        TestableAnimationController secondController =
+                Mockito.spy(new TestableAnimationController());
+        secondController.setAnimatedProperties(Sets.newHashSet(
+                DynamicAnimation.SCALE_X, DynamicAnimation.SCALE_Y));
+        secondController.setChainedProperties(Sets.newHashSet(
+                DynamicAnimation.SCALE_X));
+        secondController.setOffsetForProperty(
+                DynamicAnimation.SCALE_X, 10f);
+        secondController.setRemoveImmediately(true);
+
+        mLayout.setController(secondController);
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.SCALE_X,
+                0,
+                1.5f);
+
+        waitForPropertyAnimations(DynamicAnimation.SCALE_X);
+
+        // Make sure we never asked the original controller about any SCALE animations, that would
+        // mean the controller wasn't switched over properly.
+        Mockito.verify(mTestableController, Mockito.never())
+                .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt());
+        Mockito.verify(mTestableController, Mockito.never())
+                .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X));
+
+        // Make sure we asked the new controller about its animated properties, and configuration
+        // options.
+        Mockito.verify(secondController, Mockito.atLeastOnce())
+                .getAnimatedProperties();
+        Mockito.verify(secondController, Mockito.atLeastOnce())
+                .getNextAnimationInChain(eq(DynamicAnimation.SCALE_X), anyInt());
+        Mockito.verify(secondController, Mockito.atLeastOnce())
+                .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.SCALE_X));
+
+        mLayout.setController(mTestableController);
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                100f);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X);
+
+        // Make sure we never asked the second controller about the TRANSLATION_X animation.
+        Mockito.verify(secondController, Mockito.never())
+                .getNextAnimationInChain(eq(DynamicAnimation.TRANSLATION_X), anyInt());
+        Mockito.verify(secondController, Mockito.never())
+                .getOffsetForChainedPropertyAnimation(eq(DynamicAnimation.TRANSLATION_X));
+
+    }
+
+    @Test
+    public void testArePropertiesAnimating() throws InterruptedException {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+
+        assertFalse(mLayout.arePropertiesAnimating(
+                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y));
+
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                100);
+
+        // Wait for the animations to get underway.
+        SystemClock.sleep(50);
+
+        assertTrue(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_X));
+        assertFalse(mLayout.arePropertiesAnimating(DynamicAnimation.TRANSLATION_Y));
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X);
+
+        assertFalse(mLayout.arePropertiesAnimating(
+                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y));
+    }
+
+    @Test
+    public void testCancelAllAnimations() throws InterruptedException {
+        mLayout.setController(mTestableController);
+        addOneMoreThanRenderLimitBubbles();
+
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                1000);
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_Y,
+                0,
+                1000);
+
+        mLayout.cancelAllAnimations();
+
+        waitForLayoutMessageQueue();
+
+        // Animations should be somewhere before their end point.
+        assertTrue(mViews.get(0).getTranslationX() < 1000);
+        assertTrue(mViews.get(0).getTranslationY() < 1000);
+    }
+
+
+    /** Standard test of chained translation animations. */
+    private void testChainedTranslationAnimations() throws InterruptedException {
+        assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f);
+        assertEquals(0, mLayout.getChildAt(1).getTranslationX(), .1f);
+
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_X,
+                0,
+                100);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X);
+
+        // Since we enabled chaining, animating the first view to 100 should animate the second to
+        // 115 (since we set the offset to 15) and the third to 130, etc. Despite the sixth bubble
+        // not being visible, or animated, make sure that it has the appropriate chained
+        // translation.
+        for (int i = 0; i < mMaxRenderedBubbles + 1; i++) {
+            assertEquals(
+                    100 + i * TEST_TRANSLATION_X_OFFSET,
+                    mLayout.getChildAt(i).getTranslationX(), .1f);
+        }
+
+        // Ensure that the Y translations were unaffected.
+        assertEquals(0, mLayout.getChildAt(0).getTranslationY(), .1f);
+        assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f);
+
+        // Animate the first child's Y translation.
+        mLayout.animateValueForChildAtIndex(
+                DynamicAnimation.TRANSLATION_Y,
+                0,
+                100);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_Y);
+
+        // Ensure that only the first view's Y translation chained, since we only chained X
+        // translations.
+        assertEquals(100, mLayout.getChildAt(0).getTranslationY(), .1f);
+        assertEquals(0, mLayout.getChildAt(1).getTranslationY(), .1f);
+    }
+
+    /**
+     * Animation controller with configuration methods whose return values can be set by individual
+     * tests.
+     */
+    private class TestableAnimationController
+            extends PhysicsAnimationLayout.PhysicsAnimationController {
+        private Set<DynamicAnimation.ViewProperty> mAnimatedProperties = new HashSet<>();
+        private Set<DynamicAnimation.ViewProperty> mChainedProperties = new HashSet<>();
+        private HashMap<DynamicAnimation.ViewProperty, Float> mOffsetForProperty = new HashMap<>();
+        private boolean mRemoveImmediately = false;
+
+        void setAnimatedProperties(
+                Set<DynamicAnimation.ViewProperty> animatedProperties) {
+            mAnimatedProperties = animatedProperties;
+        }
+
+        void setChainedProperties(
+                Set<DynamicAnimation.ViewProperty> chainedProperties) {
+            mChainedProperties = chainedProperties;
+        }
+
+        void setOffsetForProperty(
+                DynamicAnimation.ViewProperty property, float offset) {
+            mOffsetForProperty.put(property, offset);
+        }
+
+        public void setRemoveImmediately(boolean removeImmediately) {
+            mRemoveImmediately = removeImmediately;
+        }
+
+        @Override
+        Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
+            return mAnimatedProperties;
+        }
+
+        @Override
+        int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
+            return mChainedProperties.contains(property) ? index + 1 : NONE;
+        }
+
+        @Override
+        float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
+            return mOffsetForProperty.getOrDefault(property, 0f);
+        }
+
+        @Override
+        SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
+            return new SpringForce();
+        }
+
+        @Override
+        void onChildAdded(View child, int index) {}
+
+        @Override
+        void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) {
+            if (mRemoveImmediately) {
+                actuallyRemove.run();
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
new file mode 100644
index 0000000..186a762
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -0,0 +1,190 @@
+/*
+ * 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.systemui.bubbles.animation;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.DisplayCutout;
+import android.view.View;
+import android.view.WindowInsets;
+import android.widget.FrameLayout;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test case for tests that involve the {@link PhysicsAnimationLayout}. This test case constructs a
+ * testable version of the layout, and provides some helpful methods to add views to the layout and
+ * wait for physics animations to finish running.
+ *
+ * See physics-animation-testing.md.
+ */
+public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
+    TestablePhysicsAnimationLayout mLayout;
+    List<View> mViews = new ArrayList<>();
+
+    Handler mMainThreadHandler;
+
+    int mMaxRenderedBubbles;
+    int mSystemWindowInsetSize = 50;
+    int mCutoutInsetSize = 100;
+
+    int mWidth = 1000;
+    int mHeight = 1000;
+
+    @Mock
+    private WindowInsets mWindowInsets;
+
+    @Mock
+    private DisplayCutout mCutout;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mLayout = new TestablePhysicsAnimationLayout(mContext);
+        mLayout.setLeft(0);
+        mLayout.setRight(mWidth);
+        mLayout.setTop(0);
+        mLayout.setBottom(mHeight);
+
+        mMaxRenderedBubbles =
+                getContext().getResources().getInteger(R.integer.bubbles_max_rendered);
+        mMainThreadHandler = new Handler(Looper.getMainLooper());
+
+        when(mWindowInsets.getSystemWindowInsetTop()).thenReturn(mSystemWindowInsetSize);
+        when(mWindowInsets.getSystemWindowInsetBottom()).thenReturn(mSystemWindowInsetSize);
+        when(mWindowInsets.getSystemWindowInsetLeft()).thenReturn(mSystemWindowInsetSize);
+        when(mWindowInsets.getSystemWindowInsetRight()).thenReturn(mSystemWindowInsetSize);
+
+        when(mWindowInsets.getDisplayCutout()).thenReturn(mCutout);
+        when(mCutout.getSafeInsetTop()).thenReturn(mCutoutInsetSize);
+        when(mCutout.getSafeInsetBottom()).thenReturn(mCutoutInsetSize);
+        when(mCutout.getSafeInsetLeft()).thenReturn(mCutoutInsetSize);
+        when(mCutout.getSafeInsetRight()).thenReturn(mCutoutInsetSize);
+    }
+
+    /** Add one extra bubble over the limit, so we can make sure it's gone/chains appropriately. */
+    void addOneMoreThanRenderLimitBubbles() {
+        for (int i = 0; i < mMaxRenderedBubbles + 1; i++) {
+            final View newView = new FrameLayout(mContext);
+            mLayout.addView(newView, 0);
+            mViews.add(0, newView);
+
+            newView.setTranslationX(0);
+            newView.setTranslationY(0);
+        }
+    }
+
+    /**
+     * Uses a {@link java.util.concurrent.CountDownLatch} to wait for the given properties'
+     * animations to finish before allowing the test to proceed.
+     */
+    void waitForPropertyAnimations(DynamicAnimation.ViewProperty... properties)
+            throws InterruptedException {
+        final CountDownLatch animLatch = new CountDownLatch(properties.length);
+        for (DynamicAnimation.ViewProperty property : properties) {
+            mLayout.setTestEndListenerForProperty(new OneTimeEndListener() {
+                @Override
+                public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
+                        float value,
+                        float velocity) {
+                    super.onAnimationEnd(animation, canceled, value, velocity);
+                    animLatch.countDown();
+                }
+            }, property);
+        }
+        animLatch.await(1, TimeUnit.SECONDS);
+    }
+
+    /** Uses a latch to wait for the message queue to finish. */
+    void waitForLayoutMessageQueue() throws InterruptedException {
+        // Wait for layout, then the view should be actually removed.
+        CountDownLatch layoutLatch = new CountDownLatch(1);
+        mLayout.post(layoutLatch::countDown);
+        layoutLatch.await(1, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Testable subclass of the PhysicsAnimationLayout that ensures methods that trigger animations
+     * are run on the main thread, which is a requirement of DynamicAnimation.
+     */
+    protected class TestablePhysicsAnimationLayout extends PhysicsAnimationLayout {
+        public TestablePhysicsAnimationLayout(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void setController(PhysicsAnimationController controller) {
+            mMainThreadHandler.post(() -> super.setController(controller));
+            try {
+                waitForLayoutMessageQueue();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        @Override
+        public void cancelAllAnimations() {
+            mMainThreadHandler.post(super::cancelAllAnimations);
+        }
+
+        @Override
+        protected void animateValueForChildAtIndex(DynamicAnimation.ViewProperty property,
+                int index, float value, float startVel, Runnable after) {
+            mMainThreadHandler.post(() ->
+                    super.animateValueForChildAtIndex(property, index, value, startVel, after));
+        }
+
+        @Override
+        public WindowInsets getRootWindowInsets() {
+            return mWindowInsets;
+        }
+
+        /**
+         * Sets an end listener that will be called after the 'real' end listener that was already
+         * set.
+         */
+        private void setTestEndListenerForProperty(DynamicAnimation.OnAnimationEndListener listener,
+                DynamicAnimation.ViewProperty property) {
+            final DynamicAnimation.OnAnimationEndListener realEndListener =
+                    mEndListenerForProperty.get(property);
+
+            setEndListenerForProperty((animation, canceled, value, velocity) -> {
+                if (realEndListener != null) {
+                    realEndListener.onAnimationEnd(animation, canceled, value, velocity);
+                }
+
+                listener.onAnimationEnd(animation, canceled, value, velocity);
+            }, property);
+        }
+    }
+}
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
new file mode 100644
index 0000000..db819d5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.systemui.bubbles.animation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.PointF;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+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;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase {
+
+    @Spy
+    private TestableStackController mStackController = new TestableStackController();
+
+    private int mStackOffset;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        addOneMoreThanRenderLimitBubbles();
+        mLayout.setController(mStackController);
+        mStackOffset = mLayout.getResources().getDimensionPixelSize(R.dimen.bubble_stack_offset);
+    }
+
+    /**
+     * Test moving around the stack, and make sure the position is updated correctly, and the stack
+     * direction is correct.
+     */
+    @Test
+    public void testMoveFirstBubbleWithStackFollowing() throws InterruptedException {
+        mStackController.moveFirstBubbleWithStackFollowing(200, 100);
+
+        // The first bubble should have moved instantly, the rest should be waiting for animation.
+        assertEquals(200, mViews.get(0).getTranslationX(), .1f);
+        assertEquals(100, mViews.get(0).getTranslationY(), .1f);
+        assertEquals(0, mViews.get(1).getTranslationX(), .1f);
+        assertEquals(0, mViews.get(1).getTranslationY(), .1f);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+        // Make sure the rest of the stack got moved to the right place and is stacked to the left.
+        testStackedAtPosition(200, 100, -1);
+        assertEquals(new PointF(200, 100), mStackController.getStackPosition());
+
+        mStackController.moveFirstBubbleWithStackFollowing(1000, 500);
+
+        // The first bubble again should have moved instantly while the rest remained where they
+        // were until the animation takes over.
+        assertEquals(1000, mViews.get(0).getTranslationX(), .1f);
+        assertEquals(500, mViews.get(0).getTranslationY(), .1f);
+        assertEquals(200 + -mStackOffset, mViews.get(1).getTranslationX(), .1f);
+        assertEquals(100, mViews.get(1).getTranslationY(), .1f);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+        // Make sure the rest of the stack moved again, including the first bubble not moving, and
+        // is stacked to the right now that we're on the right side of the screen.
+        testStackedAtPosition(1000, 500, 1);
+        assertEquals(new PointF(1000, 500), mStackController.getStackPosition());
+    }
+
+    @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
+        // but should bounce back down.
+        mStackController.flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_X,
+                5000f, 1.15f, new SpringForce(), mWidth * 1f);
+        mStackController.flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_Y,
+                0f, 1.15f, new SpringForce(), 0f);
+
+        // Nothing should move initially since the animations haven't begun, including the first
+        // view.
+        assertEquals(0f, mViews.get(0).getTranslationX(), 1f);
+        assertEquals(0f, mViews.get(0).getTranslationY(), 1f);
+
+        // Wait for the flinging.
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
+                DynamicAnimation.TRANSLATION_Y);
+
+        // Wait for the springing.
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
+                DynamicAnimation.TRANSLATION_Y);
+
+        // Once the dust has settled, we should have flung all the way to the right side, with the
+        // stack stacked off to the right now.
+        testStackedAtPosition(mWidth * 1f, 0f, 1);
+    }
+
+    @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);
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+        // 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
+        // but should bounce back down.
+        mStackController.flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_X,
+                0, 1.15f, new SpringForce(), 27f);
+        mStackController.flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_Y,
+                5000f, 1.15f, new SpringForce(), 27f);
+
+        // Nothing should move initially since the animations haven't begun.
+        assertEquals(mWidth / 2f, mViews.get(0).getTranslationX(), .1f);
+        assertEquals(mHeight + 100, mViews.get(0).getTranslationY(), .1f);
+
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
+                DynamicAnimation.TRANSLATION_Y);
+
+        // Once the dust has settled, we should have flung a bit but then sprung to the final
+        // destination which is (27, 27).
+        testStackedAtPosition(27, 27, -1);
+    }
+
+    @Test
+    public void testChildAdded() throws InterruptedException {
+        // Move the stack to y = 500.
+        mStackController.moveFirstBubbleWithStackFollowing(0f, 500f);
+        waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X,
+                DynamicAnimation.TRANSLATION_Y);
+
+        final View newView = new FrameLayout(mContext);
+        mLayout.addView(
+                newView,
+                0,
+                new FrameLayout.LayoutParams(50, 50));
+
+        waitForPropertyAnimations(
+                DynamicAnimation.TRANSLATION_X,
+                DynamicAnimation.TRANSLATION_Y,
+                DynamicAnimation.SCALE_X,
+                DynamicAnimation.SCALE_Y);
+
+        // The new view should be at the top of the stack, in the correct position.
+        assertEquals(0f, newView.getTranslationX(), .1f);
+        assertEquals(500f, newView.getTranslationY(), .1f);
+        assertEquals(1f, newView.getScaleX(), .1f);
+        assertEquals(1f, newView.getScaleY(), .1f);
+        assertEquals(1f, newView.getAlpha(), .1f);
+    }
+
+    @Test
+    public void testChildRemoved() throws InterruptedException {
+        final View firstView = mLayout.getChildAt(0);
+        mLayout.removeView(firstView);
+
+        // The view should still be there, since the controller is animating it out and hasn't yet
+        // actually removed it from the parent view.
+        assertEquals(0, mLayout.indexOfChild(firstView));
+
+        waitForPropertyAnimations(DynamicAnimation.ALPHA);
+        waitForLayoutMessageQueue();
+
+        assertEquals(-1, mLayout.indexOfChild(firstView));
+
+        // The subsequent view should have been translated over to 0, not stacked off to the left.
+        assertEquals(0, mLayout.getChildAt(0).getTranslationX(), .1f);
+    }
+
+    /**
+     * Checks every child view to make sure it's stacked at the given coordinates, off to the left
+     * or right side depending on offset multiplier.
+     */
+    private void testStackedAtPosition(float x, float y, int offsetMultiplier) {
+        // Make sure the rest of the stack moved again, including the first bubble not moving, and
+        // is stacked to the right now that we're on the right side of the screen.
+        for (int i = 0; i < mLayout.getChildCount(); i++) {
+            assertEquals(x + i * offsetMultiplier * mStackOffset,
+                    mViews.get(i).getTranslationX(), 2f);
+            assertEquals(y, mViews.get(i).getTranslationY(), 2f);
+        }
+    }
+
+    /**
+     * Testable version of the stack controller that dispatches its animations on the main thread.
+     */
+    private class TestableStackController extends StackAnimationController {
+        @Override
+        public void flingThenSpringFirstBubbleWithStackFollowing(
+                DynamicAnimation.ViewProperty property, float vel, float friction,
+                SpringForce spring, Float finalPosition) {
+            mMainThreadHandler.post(() ->
+                    super.flingThenSpringFirstBubbleWithStackFollowing(
+                            property, vel, friction, spring, finalPosition));
+        }
+    }
+}
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/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index e6d7ee7..98bf3c27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -45,9 +45,12 @@
 import org.mockito.Mock
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
 
 @RunWith(AndroidTestingRunner::class)
@@ -73,6 +76,8 @@
     private lateinit var userManager: UserManager
     @Captor
     private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>>
+    @Captor
+    private lateinit var argCaptorCallback: ArgumentCaptor<AppOpsController.Callback>
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var privacyItemController: PrivacyItemController
@@ -95,7 +100,16 @@
             }
         })).`when`(userManager).getProfiles(anyInt())
 
-        privacyItemController = PrivacyItemController(mContext, callback)
+        privacyItemController = PrivacyItemController(mContext)
+    }
+
+    @Test
+    fun testSetListeningTrueByAddingCallback() {
+        privacyItemController.addCallback(callback)
+        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS),
+                any(AppOpsController.Callback::class.java))
+        testableLooper.processAllMessages()
+        verify(callback).privacyChanged(anyList())
     }
 
     @Test
@@ -103,8 +117,6 @@
         privacyItemController.setListening(true)
         verify(appOpsController).addCallback(eq(PrivacyItemController.OPS),
                 any(AppOpsController.Callback::class.java))
-        testableLooper.processAllMessages()
-        verify(callback).privacyChanged(anyList())
     }
 
     @Test
@@ -121,7 +133,7 @@
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1)))
                 .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
 
-        privacyItemController.setListening(true)
+        privacyItemController.addCallback(callback)
         testableLooper.processAllMessages()
         verify(callback).privacyChanged(capture(argCaptor))
         assertEquals(1, argCaptor.value.size)
@@ -131,7 +143,7 @@
     fun testSystemApps() {
         doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, SYSTEM_UID, TEST_PACKAGE_NAME,
                 0))).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
-        privacyItemController.setListening(true)
+        privacyItemController.addCallback(callback)
         testableLooper.processAllMessages()
         verify(callback).privacyChanged(capture(argCaptor))
         assertEquals(1, argCaptor.value.size)
@@ -142,8 +154,8 @@
     @Test
     fun testRegisterReceiver_allUsers() {
         val spiedContext = spy(mContext)
-        val itemController = PrivacyItemController(spiedContext, callback)
-
+        val itemController = PrivacyItemController(spiedContext)
+        itemController.setListening(true)
         verify(spiedContext, atLeastOnce()).registerReceiverAsUser(
                 eq(itemController.userSwitcherReceiver), eq(UserHandle.ALL), any(), eq(null),
                 eq(null))
@@ -170,4 +182,54 @@
                 Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED))
         verify(userManager).getProfiles(anyInt())
     }
+
+    @Test
+    fun testAddMultipleCallbacks() {
+        val otherCallback = mock(PrivacyItemController.Callback::class.java)
+        privacyItemController.addCallback(callback)
+        testableLooper.processAllMessages()
+        verify(callback).privacyChanged(anyList())
+
+        privacyItemController.addCallback(otherCallback)
+        testableLooper.processAllMessages()
+        verify(otherCallback).privacyChanged(anyList())
+        // Adding a callback should not unnecessarily call previous ones
+        verifyNoMoreInteractions(callback)
+    }
+
+    @Test
+    fun testMultipleCallbacksAreUpdated() {
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+
+        val otherCallback = mock(PrivacyItemController.Callback::class.java)
+        privacyItemController.addCallback(callback)
+        privacyItemController.addCallback(otherCallback)
+        testableLooper.processAllMessages()
+        reset(callback)
+        reset(otherCallback)
+
+        verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback))
+        argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
+        testableLooper.processAllMessages()
+        verify(callback).privacyChanged(anyList())
+        verify(otherCallback).privacyChanged(anyList())
+    }
+
+    @Test
+    fun testRemoveCallback() {
+        doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+        val otherCallback = mock(PrivacyItemController.Callback::class.java)
+        privacyItemController.addCallback(callback)
+        privacyItemController.addCallback(otherCallback)
+        testableLooper.processAllMessages()
+        reset(callback)
+        reset(otherCallback)
+
+        verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback))
+        privacyItemController.removeCallback(callback)
+        argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
+        testableLooper.processAllMessages()
+        verify(callback, never()).privacyChanged(anyList())
+        verify(otherCallback).privacyChanged(anyList())
+    }
 }
\ No newline at end of file
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/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index 08955e3..554baaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -504,7 +504,8 @@
                 IMPORTANCE_DEFAULT, true);
         mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
         verify(mMetricsLogger).write(argThat(logMaker ->
-                logMaker.getType() == MetricsEvent.NOTIFICATION_BLOCKING_HELPER
+                logMaker.getCategory() == MetricsEvent.NOTIFICATION_BLOCKING_HELPER
+                        && logMaker.getType() == MetricsEvent.TYPE_OPEN
                         && logMaker.getSubtype() == MetricsEvent.BLOCKING_HELPER_DISPLAY
         ));
     }
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/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index b7b95ef..3b98f0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -51,6 +51,8 @@
     private NotificationStackScrollLayout mNotificationStackScrollLayout;
     @Mock
     private KeyguardStatusView mKeyguardStatusView;
+    @Mock
+    private KeyguardStatusBarView mKeyguardStatusBar;
     private NotificationPanelView mNotificationPanelView;
 
     @Before
@@ -93,6 +95,7 @@
             super(NotificationPanelViewTest.this.mContext, null);
             mNotificationStackScroller = mNotificationStackScrollLayout;
             mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView;
+            mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar;
         }
     }
 }
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 73fcb01..385931d 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -6766,10 +6766,10 @@
     // OS: Q
     ZEN_CUSTOM_SETTINGS_DIALOG = 1612;
 
-    // OPEN: Settings > Developer Options > Game Update Packages
+    // OPEN: Settings > Developer Options > Game Driver Preferences
     // CATEGORY: SETTINGS
     // OS: Q
-    SETTINGS_GUP_DASHBOARD = 1613;
+    SETTINGS_GAME_DRIVER_DASHBOARD = 1613;
 
     // CATEGORY: The category for all actions relating to language detection logging.
     // OS: Q
@@ -6904,6 +6904,12 @@
     // OS: Q
     FIELD_TEXT_CLASSIFIER_WIDGET_VERSION = 1640;
 
+    // Tagged data for NOTIFICATION_ITEM. One of the CATEGORY String constants from
+    // https://developer.android.com/reference/android/app/Notification .
+    // OS: Q
+    // CATEGORY: NOTIFICATION
+    FIELD_NOTIFICATION_CATEGORY = 1641;
+
     // ---- End Q Constants, all Q constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 2c075dc..f4ac130 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -275,6 +275,9 @@
                 & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) == 0) {
             return false;
         }
+        if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+            return false;
+        }
         try {
             mServiceInterface.onKeyEvent(keyEvent, sequenceNumber);
         } catch (RemoteException e) {
@@ -388,6 +391,9 @@
             if (mSecurityPolicy.mWindows == null) {
                 return null;
             }
+            if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+                return null;
+            }
             List<AccessibilityWindowInfo> windows = new ArrayList<>();
             final int windowCount = mSecurityPolicy.mWindows.size();
             for (int i = 0; i < windowCount; i++) {
@@ -413,6 +419,9 @@
             if (!permissionGranted) {
                 return null;
             }
+            if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+                return null;
+            }
             AccessibilityWindowInfo window = mSecurityPolicy.findA11yWindowInfoById(windowId);
             if (window != null) {
                 AccessibilityWindowInfo windowClone = AccessibilityWindowInfo.obtain(window);
@@ -455,6 +464,9 @@
             }
             spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
         }
+        if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+            return null;
+        }
         final int interrogatingPid = Binder.getCallingPid();
         callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
@@ -511,6 +523,9 @@
             }
             spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
         }
+        if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+            return null;
+        }
         final int interrogatingPid = Binder.getCallingPid();
         callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
@@ -567,6 +582,9 @@
             }
             spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
         }
+        if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+            return null;
+        }
         final int interrogatingPid = Binder.getCallingPid();
         callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
@@ -623,6 +641,9 @@
             }
             spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
         }
+        if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+            return null;
+        }
         final int interrogatingPid = Binder.getCallingPid();
         callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
@@ -678,6 +699,9 @@
             }
             spec = mSystemSupport.getCompatibleMagnificationSpecLocked(resolvedWindowId);
         }
+        if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+            return null;
+        }
         final int interrogatingPid = Binder.getCallingPid();
         callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
                 interrogatingPid, interrogatingTid);
@@ -722,6 +746,9 @@
                 return false;
             }
         }
+        if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+            return false;
+        }
         boolean returnValue =
                 mSystemSupport.performAccessibilityAction(resolvedWindowId, accessibilityNodeId,
                 action, arguments, interactionId, callback, mFetchFlags, interrogatingTid);
@@ -974,6 +1001,9 @@
                 return;
             }
 
+            if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+                return;
+            }
             // Make a copy since during dispatch it is possible the event to
             // be modified to remove its source if the receiving service does
             // not have permission to access the window content.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index bcff4e0..303230b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -402,7 +402,7 @@
                 MagnificationGestureHandler magnificationGestureHandler =
                         new MagnificationGestureHandler(mContext,
                                 mAms.getMagnificationController(),
-                                detectControlGestures, triggerable);
+                                detectControlGestures, triggerable, displayId);
                 addFirstEventHandler(displayId, magnificationGestureHandler);
                 mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
             }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 305c53e..f88d521 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3787,6 +3787,31 @@
                 return findWindowIdLocked(token);
             }
         }
+
+        public boolean checkAccessibilityAccess(AbstractAccessibilityServiceConnection service) {
+            final String packageName = service.getComponentName().getPackageName();
+            final ResolveInfo resolveInfo = service.getServiceInfo().getResolveInfo();
+
+            if (resolveInfo == null) {
+                // For InteractionBridge and UiAutomation
+                return true;
+            }
+
+            final int uid = resolveInfo.serviceInfo.applicationInfo.uid;
+            final long identityToken = Binder.clearCallingIdentity();
+            try {
+                // For the caller is system, just block the data to a11y services.
+                if (OWN_PROCESS_ID == Binder.getCallingPid()) {
+                    return mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY,
+                            uid, packageName) == AppOpsManager.MODE_ALLOWED;
+                }
+
+                return mAppOpsManager.noteOp(AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY,
+                        uid, packageName) == AppOpsManager.MODE_ALLOWED;
+            } finally {
+                Binder.restoreCallingIdentity(identityToken);
+            }
+        }
     }
 
     /**
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 49db488..2fbaee6 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -43,7 +43,6 @@
 import android.util.MathUtils;
 import android.util.Slog;
 import android.util.TypedValue;
-import android.view.Display;
 import android.view.GestureDetector;
 import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.MotionEvent;
@@ -149,6 +148,8 @@
     private PointerCoords[] mTempPointerCoords;
     private PointerProperties[] mTempPointerProperties;
 
+    private final int mDisplayId;
+
     private final Queue<MotionEvent> mDebugInputEventHistory;
     private final Queue<MotionEvent> mDebugOutputEventHistory;
 
@@ -162,11 +163,13 @@
      * @param detectShortcutTrigger {@code true} if this detector should be "triggerable" by some
      *                           external shortcut invoking {@link #notifyShortcutTriggered},
      *                           {@code false} if it should ignore such triggers.
+     * @param displayId The logical display id.
      */
     public MagnificationGestureHandler(Context context,
             MagnificationController magnificationController,
             boolean detectTripleTap,
-            boolean detectShortcutTrigger) {
+            boolean detectShortcutTrigger,
+            int displayId) {
         if (DEBUG_ALL) {
             Log.i(LOG_TAG,
                     "MagnificationGestureHandler(detectTripleTap = " + detectTripleTap
@@ -174,6 +177,7 @@
         }
 
         mMagnificationController = magnificationController;
+        mDisplayId = displayId;
 
         mDelegatingState = new DelegatingState();
         mDetectingState = new DetectingState(context);
@@ -259,8 +263,7 @@
 
     void notifyShortcutTriggered() {
         if (mDetectShortcutTrigger) {
-            // TODO: multi-display support for magnification gesture handler
-            boolean wasMagnifying = mMagnificationController.resetIfNeeded(Display.DEFAULT_DISPLAY,
+            boolean wasMagnifying = mMagnificationController.resetIfNeeded(mDisplayId,
                     /* animate */ true);
             if (wasMagnifying) {
                 clearAndTransitionToStateDetecting();
@@ -422,8 +425,7 @@
                 Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
                         + " scrollY: " + distanceY);
             }
-            // TODO: multi-display support for magnification gesture handler
-            mMagnificationController.offsetMagnifiedRegion(Display.DEFAULT_DISPLAY, distanceX,
+            mMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
                     distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
             return /* event consumed: */ true;
         }
@@ -440,8 +442,7 @@
                 return mScaling;
             }
 
-            // TODO: multi-display support for magnification gesture handler
-            final float initialScale = mMagnificationController.getScale(Display.DEFAULT_DISPLAY);
+            final float initialScale = mMagnificationController.getScale(mDisplayId);
             final float targetScale = initialScale * detector.getScaleFactor();
 
             // Don't allow a gesture to move the user further outside the
@@ -463,8 +464,7 @@
             final float pivotX = detector.getFocusX();
             final float pivotY = detector.getFocusY();
             if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x");
-            // TODO: multi-display support for magnification gesture handler
-            mMagnificationController.setScale(Display.DEFAULT_DISPLAY, scale, pivotX, pivotY, false,
+            mMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false,
                     AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
             return /* handled: */ true;
         }
@@ -524,10 +524,9 @@
                     }
                     final float eventX = event.getX();
                     final float eventY = event.getY();
-                    // TODO: multi-display support for magnification gesture handler
                     if (mMagnificationController.magnificationRegionContains(
-                            Display.DEFAULT_DISPLAY, eventX, eventY)) {
-                        mMagnificationController.setCenter(Display.DEFAULT_DISPLAY, eventX, eventY,
+                            mDisplayId, eventX, eventY)) {
+                        mMagnificationController.setCenter(mDisplayId, eventX, eventY,
                                 /* animate */ mLastMoveOutsideMagnifiedRegion,
                                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
                         mLastMoveOutsideMagnifiedRegion = false;
@@ -665,9 +664,8 @@
 
                     mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
 
-                    // TODO: multi-display support for magnification gesture handler
                     if (!mMagnificationController.magnificationRegionContains(
-                            Display.DEFAULT_DISPLAY, event.getX(), event.getY())) {
+                            mDisplayId, event.getX(), event.getY())) {
 
                         transitionToDelegatingStateAndClear();
 
@@ -684,8 +682,7 @@
                             // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
                             // to ensure reachability of
                             // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
-                            // TODO: multi-display support for magnification gesture handler
-                            || mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
+                            || mMagnificationController.isMagnifying(mDisplayId)) {
 
                         afterMultiTapTimeoutTransitionToDelegatingState();
 
@@ -697,8 +694,7 @@
                 }
                 break;
                 case ACTION_POINTER_DOWN: {
-                    // TODO: multi-display support for magnification gesture handler
-                    if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
+                    if (mMagnificationController.isMagnifying(mDisplayId)) {
                         transitionTo(mPanningScalingState);
                         clear();
                     } else {
@@ -727,9 +723,8 @@
 
                     mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
 
-                    // TODO: multi-display support for magnification gesture handler
                     if (!mMagnificationController.magnificationRegionContains(
-                            Display.DEFAULT_DISPLAY, event.getX(), event.getY())) {
+                            mDisplayId, event.getX(), event.getY())) {
 
                         transitionToDelegatingStateAndClear();
 
@@ -880,8 +875,7 @@
             clear();
 
             // Toggle zoom
-            // TODO: multi-display support for magnification gesture handler
-            if (mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY)) {
+            if (mMagnificationController.isMagnifying(mDisplayId)) {
                 zoomOff();
             } else {
                 zoomOn(up.getX(), up.getY());
@@ -893,9 +887,8 @@
             if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
             clear();
 
-            // TODO: multi-display support for magnification gesture handler
             mViewportDraggingState.mZoomedInBeforeDrag =
-                    mMagnificationController.isMagnifying(Display.DEFAULT_DISPLAY);
+                    mMagnificationController.isMagnifying(mDisplayId);
 
             zoomOn(down.getX(), down.getY());
 
@@ -922,8 +915,7 @@
             if (DEBUG_DETECTING) Slog.i(LOG_TAG, "setShortcutTriggered(" + state + ")");
 
             mShortcutTriggered = state;
-            // TODO: multi-display support for magnification gesture handler
-            mMagnificationController.setForceShowMagnifiableBounds(Display.DEFAULT_DISPLAY, state);
+            mMagnificationController.setForceShowMagnifiableBounds(mDisplayId, state);
         }
 
         /**
@@ -958,8 +950,7 @@
         final float scale = MathUtils.constrain(
                 mMagnificationController.getPersistedScale(),
                 MIN_SCALE, MAX_SCALE);
-        // TODO: multi-display support for magnification gesture handler
-        mMagnificationController.setScaleAndCenter(Display.DEFAULT_DISPLAY,
+        mMagnificationController.setScaleAndCenter(mDisplayId,
                 scale, centerX, centerY,
                 /* animate */ true,
                 AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -967,8 +958,7 @@
 
     private void zoomOff() {
         if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()");
-        // TODO: multi-display support for magnification gesture handler
-        mMagnificationController.reset(Display.DEFAULT_DISPLAY, /* animate */ true);
+        mMagnificationController.reset(mDisplayId, /* animate */ true);
     }
 
     private static MotionEvent recycleAndNullify(@Nullable MotionEvent event) {
@@ -990,6 +980,7 @@
                 ", mCurrentState=" + State.nameOf(mCurrentState) +
                 ", mPreviousState=" + State.nameOf(mPreviousState) +
                 ", mMagnificationController=" + mMagnificationController +
+                ", mDisplayId=" + mDisplayId +
                 '}';
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index 8ffadde..65e31f3 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -435,7 +435,7 @@
         MotionEvent click_event = MotionEvent.obtain(event.getDownTime(),
                 event.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
                 coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0,
-                event.getSource(), event.getFlags());
+                event.getSource(), event.getDisplayId(), event.getFlags());
         final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS);
         sendActionDownAndUp(click_event, policyFlags, targetAccessibilityFocus);
         click_event.recycle();
@@ -1029,7 +1029,7 @@
                 event.getEventTime(), event.getAction(), event.getPointerCount(),
                 props, coords, event.getMetaState(), event.getButtonState(),
                 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
-                event.getSource(), event.getFlags());
+                event.getSource(), event.getDisplayId(), event.getFlags());
     }
 
     /**
diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java
index 33d21cb0..f62a875 100644
--- a/services/backup/java/com/android/server/backup/FullBackupJob.java
+++ b/services/backup/java/com/android/server/backup/FullBackupJob.java
@@ -32,9 +32,9 @@
     private static final String USER_ID_EXTRA_KEY = "userId";
 
     @VisibleForTesting
-    static final int MIN_JOB_ID = 52418896;
+    public static final int MIN_JOB_ID = 52418896;
     @VisibleForTesting
-    static final int MAX_JOB_ID = 52419896;
+    public static final int MAX_JOB_ID = 52419896;
 
     private static ComponentName sIdleService =
             new ComponentName("android", FullBackupJob.class.getName());
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
index d43859e..72d81d3 100644
--- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -58,8 +58,10 @@
     @GuardedBy("KeyValueBackupJob.class")
     private static final SparseLongArray sNextScheduledForUserId = new SparseLongArray();
 
-    private static final int MIN_JOB_ID = 52417896;
-    private static final int MAX_JOB_ID = 52418896;
+    @VisibleForTesting
+    public static final int MIN_JOB_ID = 52417896;
+    @VisibleForTesting
+    public static final int MAX_JOB_ID = 52418896;
 
     public static void schedule(int userId, Context ctx, BackupManagerConstants constants) {
         schedule(userId, ctx, 0, constants);
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 115e924..7d228f1 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -1906,13 +1906,7 @@
                 final long interval = mConstants.getFullBackupIntervalMilliseconds();
                 final long appLatency = (timeSinceLast < interval) ? (interval - timeSinceLast) : 0;
                 final long latency = Math.max(transportMinLatency, appLatency);
-                Runnable r = new Runnable() {
-                    @Override
-                    public void run() {
-                        FullBackupJob.schedule(mUserId, mContext, latency, mConstants);
-                    }
-                };
-                mBackupHandler.postDelayed(r, 2500);
+                FullBackupJob.schedule(mUserId, mContext, latency, mConstants);
             } else {
                 if (DEBUG_SCHEDULING) {
                     Slog.i(TAG, "Full backup queue empty; not scheduling");
@@ -2144,12 +2138,7 @@
                     Slog.i(TAG, "Nothing pending full backup; rescheduling +" + latency);
                 }
                 final long deferTime = latency;     // pin for the closure
-                mBackupHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        FullBackupJob.schedule(mUserId, mContext, deferTime, mConstants);
-                    }
-                });
+                FullBackupJob.schedule(mUserId, mContext, deferTime, mConstants);
                 return false;
             }
 
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 844096d..6108aaa 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -177,12 +177,13 @@
     final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
 
         @Override
-        public void startSession(@UserIdInt int userId, @NonNull IBinder activityToken,
+        public void startSession(@NonNull IBinder activityToken,
                 @NonNull ComponentName componentName, @NonNull String sessionId, int flags,
                 @NonNull IResultReceiver result) {
             Preconditions.checkNotNull(activityToken);
             Preconditions.checkNotNull(componentName);
             Preconditions.checkNotNull(sessionId);
+            final int userId = UserHandle.getCallingUserId();
 
             // TODO(b/111276913): refactor getTaskIdForActivity() to also return ComponentName,
             // so we don't pass it on startSession (same for Autofill)
@@ -199,8 +200,9 @@
         }
 
         @Override
-        public void finishSession(@UserIdInt int userId, @NonNull String sessionId) {
+        public void finishSession(@NonNull String sessionId) {
             Preconditions.checkNotNull(sessionId);
+            final int userId = UserHandle.getCallingUserId();
 
             synchronized (mLock) {
                 final ContentCapturePerUserService service = getServiceForUserLocked(userId);
@@ -209,15 +211,16 @@
         }
 
         @Override
-        public void getReceiverServiceComponentName(@UserIdInt int userId,
-                IResultReceiver receiver) {
+        public void getServiceComponentName(@NonNull IResultReceiver result) {
+            final int userId = UserHandle.getCallingUserId();
             ComponentName connectedServiceComponentName;
             synchronized (mLock) {
                 final ContentCapturePerUserService service = getServiceForUserLocked(userId);
                 connectedServiceComponentName = service.getServiceComponentName();
             }
             try {
-                receiver.send(0, SyncResultReceiver.bundleFor(connectedServiceComponentName));
+                result.send(/* resultCode= */ 0,
+                        SyncResultReceiver.bundleFor(connectedServiceComponentName));
             } catch (RemoteException e) {
                 // Ignore exception as we need to be resilient against app behavior.
                 Slog.w(TAG, "Unable to send service component name: " + e);
@@ -225,8 +228,9 @@
         }
 
         @Override
-        public void removeUserData(@UserIdInt int userId, @NonNull UserDataRemovalRequest request) {
+        public void removeUserData(@NonNull UserDataRemovalRequest request) {
             Preconditions.checkNotNull(request);
+            final int userId = UserHandle.getCallingUserId();
             synchronized (mLock) {
                 final ContentCapturePerUserService service = getServiceForUserLocked(userId);
                 service.removeUserDataLocked(request);
diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java
index dbff957..2756610 100644
--- a/services/core/java/com/android/server/AlarmManagerInternal.java
+++ b/services/core/java/com/android/server/AlarmManagerInternal.java
@@ -17,5 +17,15 @@
 package com.android.server;
 
 public interface AlarmManagerInternal {
-    void removeAlarmsForUid(int uid);
+    // Some other components in the system server need to know about
+    // broadcast alarms currently in flight
+    public interface InFlightListener {
+        /** There is now an alarm pending delivery to the given app */
+        void broadcastAlarmPending(int recipientUid);
+        /** A broadcast alarm targeted to the given app has completed delivery */
+        void broadcastAlarmComplete(int recipientUid);
+    }
+
+    public void removeAlarmsForUid(int uid);
+    public void registerInFlightListener(InFlightListener callback);
 }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index e3dcb7d..10b5327 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -197,6 +197,8 @@
     PowerManager.WakeLock mWakeLock;
     ArrayList<Alarm> mPendingNonWakeupAlarms = new ArrayList<>();
     ArrayList<InFlight> mInFlight = new ArrayList<>();
+    private final ArrayList<AlarmManagerInternal.InFlightListener> mInFlightListeners =
+            new ArrayList<>();
     AlarmHandler mHandler;
     AppWakeupHistory mAppWakeupHistory;
     ClockReceiver mClockReceiver;
@@ -1315,6 +1317,10 @@
             mAlarmType = alarm.type;
         }
 
+        boolean isBroadcast() {
+            return mPendingIntent != null && mPendingIntent.isBroadcast();
+        }
+
         @Override
         public String toString() {
             return "InFlight{"
@@ -1354,6 +1360,20 @@
         }
     }
 
+    private void notifyBroadcastAlarmPendingLocked(int uid) {
+        final int numListeners = mInFlightListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            mInFlightListeners.get(i).broadcastAlarmPending(uid);
+        }
+    }
+
+    private void notifyBroadcastAlarmCompleteLocked(int uid) {
+        final int numListeners = mInFlightListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            mInFlightListeners.get(i).broadcastAlarmComplete(uid);
+        }
+    }
+
     static final class FilterStats {
         final BroadcastStats mBroadcastStats;
         final String mTag;
@@ -1976,6 +1996,13 @@
                 removeLocked(uid);
             }
         }
+
+        @Override
+        public void registerInFlightListener(InFlightListener callback) {
+            synchronized (mLock) {
+                mInFlightListeners.add(callback);
+            }
+        }
     }
 
     /**
@@ -4426,7 +4453,11 @@
 
         private InFlight removeLocked(PendingIntent pi, Intent intent) {
             for (int i = 0; i < mInFlight.size(); i++) {
-                if (mInFlight.get(i).mPendingIntent == pi) {
+                final InFlight inflight = mInFlight.get(i);
+                if (inflight.mPendingIntent == pi) {
+                    if (pi.isBroadcast()) {
+                        notifyBroadcastAlarmCompleteLocked(inflight.mUid);
+                    }
                     return mInFlight.remove(i);
                 }
             }
@@ -4649,6 +4680,9 @@
             final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED);
             mInFlight.add(inflight);
             mBroadcastRefCount++;
+            if (inflight.isBroadcast()) {
+                notifyBroadcastAlarmPendingLocked(alarm.uid);
+            }
             if (allowWhileIdle) {
                 // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
                 mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 14e2354..1519c17 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1884,6 +1884,12 @@
                 "ConnectivityService");
     }
 
+    private void enforceControlAlwaysOnVpnPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CONTROL_ALWAYS_ON_VPN,
+                "ConnectivityService");
+    }
+
     private void enforceNetworkStackSettingsOrSetup() {
         enforceAnyPermissionOf(
             android.Manifest.permission.NETWORK_SETTINGS,
@@ -1891,6 +1897,12 @@
             android.Manifest.permission.NETWORK_STACK);
     }
 
+    private void enforceNetworkStackPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.NETWORK_STACK,
+                "ConnectivityService");
+    }
+
     private boolean checkNetworkStackPermission() {
         return PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.NETWORK_STACK);
@@ -4147,8 +4159,9 @@
     }
 
     @Override
-    public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown) {
-        enforceConnectivityInternalPermission();
+    public boolean setAlwaysOnVpnPackage(
+            int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) {
+        enforceControlAlwaysOnVpnPermission();
         enforceCrossUserPermission(userId);
 
         synchronized (mVpns) {
@@ -4162,11 +4175,11 @@
                 Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                 return false;
             }
-            if (!vpn.setAlwaysOnPackage(packageName, lockdown)) {
+            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist)) {
                 return false;
             }
             if (!startAlwaysOnVpn(userId)) {
-                vpn.setAlwaysOnPackage(null, false);
+                vpn.setAlwaysOnPackage(null, false, null);
                 return false;
             }
         }
@@ -4175,7 +4188,7 @@
 
     @Override
     public String getAlwaysOnVpnPackage(int userId) {
-        enforceConnectivityInternalPermission();
+        enforceControlAlwaysOnVpnPermission();
         enforceCrossUserPermission(userId);
 
         synchronized (mVpns) {
@@ -4189,6 +4202,36 @@
     }
 
     @Override
+    public boolean isVpnLockdownEnabled(int userId) {
+        enforceControlAlwaysOnVpnPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                Slog.w(TAG, "User " + userId + " has no Vpn configuration");
+                return false;
+            }
+            return vpn.getLockdown();
+        }
+    }
+
+    @Override
+    public List<String> getVpnLockdownWhitelist(int userId) {
+        enforceControlAlwaysOnVpnPermission();
+        enforceCrossUserPermission(userId);
+
+        synchronized (mVpns) {
+            Vpn vpn = mVpns.get(userId);
+            if (vpn == null) {
+                Slog.w(TAG, "User " + userId + " has no Vpn configuration");
+                return null;
+            }
+            return vpn.getLockdownWhitelist();
+        }
+    }
+
+    @Override
     public int checkMobileProvisioning(int suggestedTimeOutMs) {
         // TODO: Remove?  Any reason to trigger a provisioning check?
         return -1;
@@ -4417,7 +4460,7 @@
             if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
                 Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user "
                         + userId);
-                vpn.setAlwaysOnPackage(null, false);
+                vpn.setAlwaysOnPackage(null, false, null);
             }
         }
     }
@@ -6297,7 +6340,7 @@
             synchronized (mVpns) {
                 final String alwaysOnPackage = getAlwaysOnVpnPackage(userId);
                 if (alwaysOnPackage != null) {
-                    setAlwaysOnVpnPackage(userId, null, false);
+                    setAlwaysOnVpnPackage(userId, null, false, null);
                     setVpnPackageAuthorization(alwaysOnPackage, userId, false);
                 }
 
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/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 89ff338..b3997ef 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -46,11 +46,11 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
-import android.net.InetAddresses;
 import android.net.INetd;
 import android.net.INetdUnsolicitedEventListener;
 import android.net.INetworkManagementEventObserver;
 import android.net.ITetheringStatsProvider;
+import android.net.InetAddresses;
 import android.net.InterfaceConfiguration;
 import android.net.InterfaceConfigurationParcel;
 import android.net.IpPrefix;
@@ -466,9 +466,12 @@
     /**
      * Notify our observers of a change in the data activity state of the interface
      */
-    private void notifyInterfaceClassActivity(int type, int powerState, long tsNanos,
+    private void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos,
             int uid, boolean fromRadio) {
         final boolean isMobile = ConnectivityManager.isNetworkTypeMobile(type);
+        int powerState = isActive
+                ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
+                : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
         if (isMobile) {
             if (!fromRadio) {
                 if (mMobileActivityFromRadio) {
@@ -503,9 +506,6 @@
             }
         }
 
-        boolean isActive = powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
-                || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
-
         if (!isMobile || fromRadio || !mMobileActivityFromRadio) {
             // Report the change in data activity.  We don't do this if this is a change
             // on the mobile network, that is not coming from the radio itself, and we
@@ -739,10 +739,8 @@
             } else {
                 timestampNanos = timestamp;
             }
-            mDaemonHandler.post(() -> notifyInterfaceClassActivity(label,
-                    isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
-                    : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
-                    timestampNanos, uid, false));
+            mDaemonHandler.post(() ->
+                    notifyInterfaceClassActivity(label, isActive, timestampNanos, uid, false));
         }
 
         @Override
@@ -909,9 +907,7 @@
                     }
                     boolean isActive = cooked[2].equals("active");
                     notifyInterfaceClassActivity(Integer.parseInt(cooked[3]),
-                            isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH
-                            : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
-                            timestampNanos, processUid, false);
+                            isActive, timestampNanos, processUid, false);
                     return true;
                     // break;
             case NetdResponseCode.InterfaceAddressChange:
@@ -1461,8 +1457,7 @@
             if (ConnectivityManager.isNetworkTypeMobile(type)) {
                 mNetworkActive = false;
             }
-            mDaemonHandler.post(() -> notifyInterfaceClassActivity(type,
-                    DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+            mDaemonHandler.post(() -> notifyInterfaceClassActivity(type, true,
                     SystemClock.elapsedRealtimeNanos(), -1, false));
         }
     }
@@ -1486,8 +1481,7 @@
                 throw new IllegalStateException(e);
             }
             mActiveIdleTimers.remove(iface);
-            mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type,
-                    DataConnectionRealTimeInfo.DC_POWER_STATE_LOW,
+            mDaemonHandler.post(() -> notifyInterfaceClassActivity(params.type, false,
                     SystemClock.elapsedRealtimeNanos(), -1, false));
         }
     }
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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 9d810cd..cecd55a3 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -112,6 +112,7 @@
 import android.os.storage.StorageVolume;
 import android.os.storage.VolumeInfo;
 import android.os.storage.VolumeRecord;
+import android.provider.DeviceConfig;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.sysprop.VoldProperties;
@@ -463,6 +464,7 @@
         = { "password", "default", "pattern", "pin" };
 
     private final Context mContext;
+    private final ContentResolver mResolver;
 
     private volatile IVold mVold;
     private volatile IStoraged mStoraged;
@@ -797,6 +799,11 @@
                     refreshIsolatedStorageSettings();
                 }
             });
+        // For now, simply clone property when it changes
+        DeviceConfig.addOnPropertyChangedListener(DeviceConfig.Storage.NAMESPACE,
+                mContext.getMainExecutor(), (namespace, name, value) -> {
+                    refreshIsolatedStorageSettings();
+                });
         refreshIsolatedStorageSettings();
     }
 
@@ -830,6 +837,12 @@
     }
 
     private void refreshIsolatedStorageSettings() {
+        // Always copy value from newer DeviceConfig location
+        Settings.Global.putString(mResolver,
+                Settings.Global.ISOLATED_STORAGE_REMOTE,
+                DeviceConfig.getProperty(DeviceConfig.Storage.NAMESPACE,
+                        DeviceConfig.Storage.ISOLATED_STORAGE_ENABLED));
+
         final int local = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.ISOLATED_STORAGE_LOCAL, 0);
         final int remote = Settings.Global.getInt(mContext.getContentResolver(),
@@ -1523,6 +1536,8 @@
                 SystemProperties.getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false)));
 
         mContext = context;
+        mResolver = mContext.getContentResolver();
+
         mCallbacks = new Callbacks(FgThread.get().getLooper());
         mLockPatternUtils = new LockPatternUtils(mContext);
 
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index e543617..10b126f 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -206,9 +206,10 @@
 
     private Map<Integer, List<EmergencyNumber>> mEmergencyNumberList;
 
-    private CallQuality mCallQuality;
+    private CallQuality mCallQuality = new CallQuality();
 
-    private CallAttributes mCallAttributes;
+    private CallAttributes mCallAttributes = new CallAttributes(new PreciseCallState(),
+            TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality());
 
     private int[] mSrvccState;
 
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/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a96676e..5d6c2f0 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -677,6 +677,7 @@
 
             final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, r.packageName);
             intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
@@ -1649,6 +1650,7 @@
 
             final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, s.packageName);
             intent.putExtra(Intent.EXTRA_REMOTE_CALLBACK, callback);
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 0aaea2f..e42666c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -48,6 +48,7 @@
     static final boolean DEBUG_BROADCAST = DEBUG_ALL || false;
     static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false;
     static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
+    static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false;
     static final boolean DEBUG_LRU = DEBUG_ALL || false;
     static final boolean DEBUG_MU = DEBUG_ALL || false;
     static final boolean DEBUG_NETWORK = DEBUG_ALL || false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index eb643b6..d9f3c02 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -224,7 +224,6 @@
 import android.location.LocationManager;
 import android.media.audiofx.AudioEffect;
 import android.net.Proxy;
-import android.net.ProxyInfo;
 import android.net.Uri;
 import android.os.AppZygote;
 import android.os.BatteryStats;
@@ -2090,6 +2089,8 @@
             if (phase == PHASE_SYSTEM_SERVICES_READY) {
                 mService.mBatteryStatsService.systemServicesReady();
                 mService.mServices.systemServicesReady();
+            } else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
+                mService.startBroadcastObservers();
             }
         }
 
@@ -2266,12 +2267,27 @@
         mProcessList.init(this, activeUids);
         mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
 
+        // Broadcast policy parameters
+        final BroadcastConstants foreConstants = new BroadcastConstants(
+                Settings.Global.BROADCAST_FG_CONSTANTS);
+        foreConstants.TIMEOUT = BROADCAST_FG_TIMEOUT;
+
+        final BroadcastConstants backConstants = new BroadcastConstants(
+                Settings.Global.BROADCAST_BG_CONSTANTS);
+        backConstants.TIMEOUT = BROADCAST_BG_TIMEOUT;
+
+        final BroadcastConstants offloadConstants = new BroadcastConstants(
+                Settings.Global.BROADCAST_OFFLOAD_CONSTANTS);
+        offloadConstants.TIMEOUT = BROADCAST_BG_TIMEOUT;
+        // by default, no "slow" policy in this queue
+        offloadConstants.SLOW_TIME = Integer.MAX_VALUE;
+
         mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
-                "foreground", BROADCAST_FG_TIMEOUT, false);
+                "foreground", foreConstants, false);
         mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
-                "background", BROADCAST_BG_TIMEOUT, true);
+                "background", backConstants, true);
         mOffloadBroadcastQueue = new BroadcastQueue(this, mHandler,
-                "offload", BROADCAST_BG_TIMEOUT, true);
+                "offload", offloadConstants, true);
         mBroadcastQueues[0] = mFgBroadcastQueue;
         mBroadcastQueues[1] = mBgBroadcastQueue;
         mBroadcastQueues[2] = mOffloadBroadcastQueue;
@@ -5226,6 +5242,15 @@
     }
 
     @Override
+    public boolean isIntentSenderABroadcast(IIntentSender pendingResult) {
+        if (pendingResult instanceof PendingIntentRecord) {
+            final PendingIntentRecord res = (PendingIntentRecord) pendingResult;
+            return res.key.type == ActivityManager.INTENT_SENDER_BROADCAST;
+        }
+        return false;
+    }
+
+    @Override
     public Intent getIntentForIntentSender(IIntentSender pendingResult) {
         enforceCallingPermission(Manifest.permission.GET_INTENT_SENDER_INTENT,
                 "getIntentForIntentSender()");
@@ -7211,7 +7236,6 @@
         synchronized (this) {
             mSystemProvidersInstalled = true;
         }
-
         mConstants.start(mContext.getContentResolver());
         mCoreSettingsObserver = new CoreSettingsObserver(this);
         mActivityTaskManager.installSystemProviders();
@@ -8711,6 +8735,12 @@
         }
     }
 
+    private void startBroadcastObservers() {
+        for (BroadcastQueue queue : mBroadcastQueues) {
+            queue.start(mContext.getContentResolver());
+        }
+    }
+
     private void updateForceBackgroundCheck(boolean enabled) {
         synchronized (this) {
             if (mForceBackgroundCheck != enabled) {
@@ -14909,10 +14939,7 @@
                     resultData, resultExtras, ordered, sticky, false, userId,
                     allowBackgroundActivityStarts);
 
-            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r
-                    + ": prev had " + queue.mOrderedBroadcasts.size());
-            if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,
-                    "Enqueueing broadcast " + r.intent.getAction());
+            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
 
             final BroadcastRecord oldRecord =
                     replacePending ? queue.replaceOrderedBroadcastLocked(r) : null;
@@ -15788,13 +15815,12 @@
      * Returns true if things are idle enough to perform GCs.
      */
     private final boolean canGcNowLocked() {
-        boolean processingBroadcasts = false;
         for (BroadcastQueue q : mBroadcastQueues) {
-            if (q.mParallelBroadcasts.size() != 0 || q.mOrderedBroadcasts.size() != 0) {
-                processingBroadcasts = true;
+            if (!q.mParallelBroadcasts.isEmpty() || !q.mDispatcher.isEmpty()) {
+                return false;
             }
         }
-        return !processingBroadcasts && mAtmInternal.canGcNow();
+        return mAtmInternal.canGcNow();
     }
 
     /**
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/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
new file mode 100644
index 0000000..820caf1
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -0,0 +1,151 @@
+/*
+ * 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 com.android.server.am;
+
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Tunable parameters for broadcast dispatch policy
+ */
+public class BroadcastConstants {
+    private static final String TAG = "BroadcastConstants";
+
+    // Value element names within the Settings record
+    static final String KEY_TIMEOUT = "bcast_timeout";
+    static final String KEY_SLOW_TIME = "bcast_slow_time";
+    static final String KEY_DEFERRAL = "bcast_deferral";
+    static final String KEY_DEFERRAL_DECAY_FACTOR = "bcast_deferral_decay_factor";
+    static final String KEY_DEFERRAL_FLOOR = "bcast_deferral_floor";
+
+    // All time intervals are in milliseconds
+    private static final long DEFAULT_TIMEOUT = 10_000;
+    private static final long DEFAULT_SLOW_TIME = 5_000;
+    private static final long DEFAULT_DEFERRAL = 5_000;
+    private static final float DEFAULT_DEFERRAL_DECAY_FACTOR = 0.75f;
+    private static final long DEFAULT_DEFERRAL_FLOOR = 0;
+
+    // All time constants are in milliseconds
+
+    // Timeout period for this broadcast queue
+    public long TIMEOUT = DEFAULT_TIMEOUT;
+    // Handling time above which we declare that a broadcast recipient was "slow".  Any
+    // value <= zero is interpreted as disabling broadcast deferral policy entirely.
+    public long SLOW_TIME = DEFAULT_SLOW_TIME;
+    // How long to initially defer broadcasts, if an app is slow to handle one
+    public long DEFERRAL = DEFAULT_DEFERRAL;
+    // Decay factor for successive broadcasts' deferral time
+    public float DEFERRAL_DECAY_FACTOR = DEFAULT_DEFERRAL_DECAY_FACTOR;
+    // Minimum that the deferral time can decay to until the backlog fully clears
+    public long DEFERRAL_FLOOR = DEFAULT_DEFERRAL_FLOOR;
+
+    // Settings override tracking for this instance
+    private String mSettingsKey;
+    private SettingsObserver mSettingsObserver;
+    private ContentResolver mResolver;
+    private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+    class SettingsObserver extends ContentObserver {
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            updateConstants();
+        }
+    }
+
+    // A given constants instance is configured to observe specific keys from which
+    // that instance's values are drawn.
+    public BroadcastConstants(String settingsKey) {
+        mSettingsKey = settingsKey;
+    }
+
+    /**
+     * Spin up the observer lazily, since it can only happen once the settings provider
+     * has been brought into service
+     */
+    public void startObserving(Handler handler, ContentResolver resolver) {
+        mResolver = resolver;
+
+        mSettingsObserver = new SettingsObserver(handler);
+        mResolver.registerContentObserver(Settings.Global.getUriFor(mSettingsKey),
+                false, mSettingsObserver);
+
+        updateConstants();
+    }
+
+    private void updateConstants() {
+        synchronized (mParser) {
+            try {
+                mParser.setString(Settings.Global.getString(mResolver, mSettingsKey));
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Bad broadcast settings in key '" + mSettingsKey + "'", e);
+                return;
+            }
+
+            // Unspecified fields retain their current value rather than revert to default
+            TIMEOUT = mParser.getLong(KEY_TIMEOUT, TIMEOUT);
+            SLOW_TIME = mParser.getLong(KEY_SLOW_TIME, SLOW_TIME);
+            DEFERRAL = mParser.getLong(KEY_DEFERRAL, DEFERRAL);
+            DEFERRAL_DECAY_FACTOR = mParser.getFloat(KEY_DEFERRAL_DECAY_FACTOR,
+                    DEFERRAL_DECAY_FACTOR);
+            DEFERRAL_FLOOR = mParser.getLong(KEY_DEFERRAL_FLOOR, DEFERRAL_FLOOR);
+        }
+    }
+
+    /**
+     * Standard dumpsys support; invoked from BroadcastQueue dump
+     */
+    public void dump(PrintWriter pw) {
+        synchronized (mParser) {
+            pw.println();
+            pw.print("  Broadcast parameters (key=");
+            pw.print(mSettingsKey);
+            pw.print(", observing=");
+            pw.print(mSettingsObserver != null);
+            pw.println("):");
+
+            pw.print("    "); pw.print(KEY_TIMEOUT); pw.print(" = ");
+            TimeUtils.formatDuration(TIMEOUT, pw);
+            pw.println();
+
+            pw.print("    "); pw.print(KEY_SLOW_TIME); pw.print(" = ");
+            TimeUtils.formatDuration(SLOW_TIME, pw);
+            pw.println();
+
+            pw.print("    "); pw.print(KEY_DEFERRAL); pw.print(" = ");
+            TimeUtils.formatDuration(DEFERRAL, pw);
+            pw.println();
+
+            pw.print("    "); pw.print(KEY_DEFERRAL_DECAY_FACTOR); pw.print(" = ");
+            pw.println(DEFERRAL_DECAY_FACTOR);
+
+            pw.print("    "); pw.print(KEY_DEFERRAL_FLOOR); pw.print(" = ");
+            TimeUtils.formatDuration(DEFERRAL_FLOOR, pw);
+            pw.println();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java
new file mode 100644
index 0000000..0d46379
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java
@@ -0,0 +1,687 @@
+/*
+ * 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 com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
+
+import android.content.Intent;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.AlarmManagerInternal;
+import com.android.server.LocalServices;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * Manages ordered broadcast delivery, applying policy to mitigate the effects of
+ * slow receivers.
+ */
+public class BroadcastDispatcher {
+    private static final String TAG = "BroadcastDispatcher";
+
+    // Deferred broadcasts to one app; times are all uptime time base like
+    // other broadcast-related timekeeping
+    static class Deferrals {
+        final int uid;
+        long deferredAt;    // when we started deferring
+        long deferredBy;    // how long did we defer by last time?
+        long deferUntil;    // when does the next element become deliverable?
+        int alarmCount;
+
+        final ArrayList<BroadcastRecord> broadcasts;
+
+        Deferrals(int uid, long now, long backoff, int count) {
+            this.uid = uid;
+            this.deferredAt = now;
+            this.deferredBy = backoff;
+            this.deferUntil = now + backoff;
+            this.alarmCount = count;
+            broadcasts = new ArrayList<>();
+        }
+
+        void add(BroadcastRecord br) {
+            broadcasts.add(br);
+        }
+
+        void writeToProto(ProtoOutputStream proto, long fieldId) {
+            for (BroadcastRecord br : broadcasts) {
+                br.writeToProto(proto, fieldId);
+            }
+        }
+
+        void dumpLocked(Dumper d) {
+            for (BroadcastRecord br : broadcasts) {
+                d.dump(br);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("Deferrals{uid=");
+            sb.append(uid);
+            sb.append(", deferUntil=");
+            sb.append(deferUntil);
+            sb.append(", #broadcasts=");
+            sb.append(broadcasts.size());
+            sb.append("}");
+            return sb.toString();
+        }
+    }
+
+    // Carrying dump formatting state across multiple concatenated datasets
+    class Dumper {
+        final PrintWriter mPw;
+        final String mQueueName;
+        final String mDumpPackage;
+        final SimpleDateFormat mSdf;
+        boolean mPrinted;
+        boolean mNeedSep;
+        String mHeading;
+        String mLabel;
+        int mOrdinal;
+
+        Dumper(PrintWriter pw, String queueName, String dumpPackage, SimpleDateFormat sdf) {
+            mPw = pw;
+            mQueueName = queueName;
+            mDumpPackage = dumpPackage;
+            mSdf = sdf;
+
+            mPrinted = false;
+            mNeedSep = true;
+        }
+
+        void setHeading(String heading) {
+            mHeading = heading;
+            mPrinted = false;
+        }
+
+        void setLabel(String label) {
+            //"  Active Ordered Broadcast " + mQueueName + " #" + i + ":"
+            mLabel = "  " + label + " " + mQueueName + " #";
+            mOrdinal = 0;
+        }
+
+        boolean didPrint() {
+            return mPrinted;
+        }
+
+        void dump(BroadcastRecord br) {
+            if (mDumpPackage == null || mDumpPackage.equals(br.callerPackage)) {
+                if (!mPrinted) {
+                    if (mNeedSep) {
+                        mPw.println();
+                    }
+                    mPrinted = true;
+                    mNeedSep = true;
+                    mPw.println("  " + mHeading + " [" + mQueueName + "]:");
+                }
+                mPw.println(mLabel + mOrdinal + ":");
+                mOrdinal++;
+
+                br.dump(mPw, "    ", mSdf);
+            }
+        }
+    }
+
+    private final Object mLock;
+    private final BroadcastQueue mQueue;
+    private final BroadcastConstants mConstants;
+    private final Handler mHandler;
+    private AlarmManagerInternal mAlarm;
+
+    // Current alarm targets; mapping uid -> in-flight alarm count
+    final SparseIntArray mAlarmUids = new SparseIntArray();
+    final AlarmManagerInternal.InFlightListener mAlarmListener =
+            new AlarmManagerInternal.InFlightListener() {
+        @Override
+        public void broadcastAlarmPending(final int recipientUid) {
+            synchronized (mLock) {
+                final int newCount = mAlarmUids.get(recipientUid, 0) + 1;
+                mAlarmUids.put(recipientUid, newCount);
+                // any deferred broadcasts to this app now get fast-tracked
+                final int numEntries = mDeferredBroadcasts.size();
+                for (int i = 0; i < numEntries; i++) {
+                    if (recipientUid == mDeferredBroadcasts.get(i).uid) {
+                        Deferrals d = mDeferredBroadcasts.remove(i);
+                        mAlarmBroadcasts.add(d);
+                        break;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void broadcastAlarmComplete(final int recipientUid) {
+            synchronized (mLock) {
+                final int newCount = mAlarmUids.get(recipientUid, 0) - 1;
+                if (newCount >= 0) {
+                    mAlarmUids.put(recipientUid, newCount);
+                } else {
+                    Slog.wtf(TAG, "Undercount of broadcast alarms in flight for " + recipientUid);
+                    mAlarmUids.put(recipientUid, 0);
+                }
+
+                // No longer an alarm target, so resume ordinary deferral policy
+                if (newCount <= 0) {
+                    final int numEntries = mAlarmBroadcasts.size();
+                    for (int i = 0; i < numEntries; i++) {
+                        if (recipientUid == mAlarmBroadcasts.get(i).uid) {
+                            Deferrals d = mAlarmBroadcasts.remove(i);
+                            insertLocked(mDeferredBroadcasts, d);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    };
+
+    // Queue recheck operation used to tickle broadcast delivery when appropriate
+    final Runnable mScheduleRunnable = new Runnable() {
+        @Override
+        public void run() {
+            synchronized (mLock) {
+                if (DEBUG_BROADCAST_DEFERRAL) {
+                    Slog.v(TAG, "Deferral recheck of pending broadcasts");
+                }
+                mQueue.scheduleBroadcastsLocked();
+                mRecheckScheduled = false;
+            }
+        }
+    };
+    private boolean mRecheckScheduled = false;
+
+    // Usual issuance-order outbound queue
+    private final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();
+    // General deferrals not holding up alarms
+    private final ArrayList<Deferrals> mDeferredBroadcasts = new ArrayList<>();
+    // Deferrals that *are* holding up alarms; ordered by alarm dispatch time
+    private final ArrayList<Deferrals> mAlarmBroadcasts = new ArrayList<>();
+
+    // Next outbound broadcast, established by getNextBroadcastLocked()
+    private BroadcastRecord mCurrentBroadcast;
+
+    /**
+     * Constructed & sharing a lock with its associated BroadcastQueue instance
+     */
+    public BroadcastDispatcher(BroadcastQueue queue, BroadcastConstants constants,
+            Handler handler, Object lock) {
+        mQueue = queue;
+        mConstants = constants;
+        mHandler = handler;
+        mLock = lock;
+    }
+
+    /**
+     * Spin up the integration with the alarm manager service; done lazily to manage
+     * service availability ordering during boot.
+     */
+    public void start() {
+        // Set up broadcast alarm tracking
+        mAlarm = LocalServices.getService(AlarmManagerInternal.class);
+        mAlarm.registerInFlightListener(mAlarmListener);
+    }
+
+    /**
+     * Standard contents-are-empty check
+     */
+    public boolean isEmpty() {
+        synchronized (mLock) {
+            return mCurrentBroadcast == null
+                    && mOrderedBroadcasts.isEmpty()
+                    && mDeferredBroadcasts.isEmpty()
+                    && mAlarmBroadcasts.isEmpty();
+        }
+    }
+
+    /**
+     * Not quite the traditional size() measurement; includes any in-process but
+     * not yet retired active outbound broadcast.
+     */
+    public int totalUndelivered() {
+        synchronized (mLock) {
+            return mAlarmBroadcasts.size()
+                    + mDeferredBroadcasts.size()
+                    + mOrderedBroadcasts.size()
+                    + (mCurrentBroadcast == null ? 0 : 1);
+        }
+    }
+
+    // ----------------------------------
+    // BroadcastQueue operation support
+
+    void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
+        mOrderedBroadcasts.add(r);
+    }
+
+    // Returns the now-replaced broadcast record, or null if none
+    BroadcastRecord replaceBroadcastLocked(BroadcastRecord r, String typeForLogging) {
+        // Simple case, in the ordinary queue.
+        BroadcastRecord old = replaceBroadcastLocked(mOrderedBroadcasts, r, typeForLogging);
+
+        // If we didn't find it, less-simple:  in a deferral queue?
+        if (old == null) {
+            old = replaceDeferredBroadcastLocked(mAlarmBroadcasts, r, typeForLogging);
+        }
+        if (old == null) {
+            old = replaceDeferredBroadcastLocked(mDeferredBroadcasts, r, typeForLogging);
+        }
+        return old;
+    }
+
+    private BroadcastRecord replaceDeferredBroadcastLocked(ArrayList<Deferrals> list,
+            BroadcastRecord r, String typeForLogging) {
+        BroadcastRecord old;
+        final int numEntries = list.size();
+        for (int i = 0; i < numEntries; i++) {
+            final Deferrals d = list.get(i);
+            old = replaceBroadcastLocked(d.broadcasts, r, typeForLogging);
+            if (old != null) {
+                return old;
+            }
+        }
+        return null;
+    }
+
+    private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> list,
+            BroadcastRecord r, String typeForLogging) {
+        BroadcastRecord old;
+        final Intent intent = r.intent;
+        // Any in-flight broadcast has already been popped, and cannot be replaced.
+        // (This preserves existing behavior of the replacement API)
+        for (int i = list.size() - 1; i >= 0; i++) {
+            old = list.get(i);
+            if (old.userId == r.userId && intent.filterEquals(old.intent)) {
+                if (DEBUG_BROADCAST) {
+                    Slog.v(TAG, "***** Replacing " + typeForLogging
+                            + " [" + mQueue.mQueueName + "]: " + intent);
+                }
+                // Clone deferral state too if any
+                r.deferred = old.deferred;
+                list.set(i, r);
+                return old;
+            }
+        }
+        return null;
+    }
+
+    boolean cleanupDisabledPackageReceiversLocked(final String packageName,
+            Set<String> filterByClasses, final int userId, final boolean doit) {
+        // Note: fast short circuits when 'doit' is false, as soon as we hit any
+        // "yes we would do something" circumstance
+        boolean didSomething = cleanupBroadcastListDisabledReceiversLocked(mOrderedBroadcasts,
+                packageName, filterByClasses, userId, doit);
+        if (doit || !didSomething) {
+            didSomething |= cleanupDeferralsListDisabledReceiversLocked(mAlarmBroadcasts,
+                    packageName, filterByClasses, userId, doit);
+        }
+        if (doit || !didSomething) {
+            didSomething |= cleanupDeferralsListDisabledReceiversLocked(mDeferredBroadcasts,
+                    packageName, filterByClasses, userId, doit);
+        }
+        if ((doit || !didSomething) && mCurrentBroadcast != null) {
+            didSomething |= mCurrentBroadcast.cleanupDisabledPackageReceiversLocked(
+                    packageName, filterByClasses, userId, doit);
+        }
+
+        return didSomething;
+    }
+
+    private boolean cleanupDeferralsListDisabledReceiversLocked(ArrayList<Deferrals> list,
+            final String packageName, Set<String> filterByClasses, final int userId,
+            final boolean doit) {
+        boolean didSomething = false;
+        for (Deferrals d : list) {
+            didSomething = cleanupBroadcastListDisabledReceiversLocked(d.broadcasts,
+                    packageName, filterByClasses, userId, doit);
+            if (!doit && didSomething) {
+                return true;
+            }
+        }
+        return didSomething;
+    }
+
+    private boolean cleanupBroadcastListDisabledReceiversLocked(ArrayList<BroadcastRecord> list,
+            final String packageName, Set<String> filterByClasses, final int userId,
+            final boolean doit) {
+        boolean didSomething = false;
+        for (BroadcastRecord br : list) {
+            didSomething |= br.cleanupDisabledPackageReceiversLocked(packageName,
+                    filterByClasses, userId, doit);
+            if (!doit && didSomething) {
+                return true;
+            }
+        }
+        return didSomething;
+    }
+
+    /**
+     * Standard proto dump entry point
+     */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        if (mCurrentBroadcast != null) {
+            mCurrentBroadcast.writeToProto(proto, fieldId);
+        }
+        for (Deferrals d : mAlarmBroadcasts) {
+            d.writeToProto(proto, fieldId);
+        }
+        for (BroadcastRecord br : mOrderedBroadcasts) {
+            br.writeToProto(proto, fieldId);
+        }
+        for (Deferrals d : mDeferredBroadcasts) {
+            d.writeToProto(proto, fieldId);
+        }
+    }
+
+    // ----------------------------------
+    // Dispatch & deferral management
+
+    public BroadcastRecord getActiveBroadcastLocked() {
+        return mCurrentBroadcast;
+    }
+
+    /**
+     * If there is a deferred broadcast that is being sent to an alarm target, return
+     * that one.  If there's no deferred alarm target broadcast but there is one
+     * that has reached the end of its deferral, return that.
+     *
+     * This stages the broadcast internally until it is retired, and returns that
+     * staged record if this is called repeatedly, until retireBroadcast(r) is called.
+     */
+    public BroadcastRecord getNextBroadcastLocked(final long now) {
+        if (mCurrentBroadcast != null) {
+            return mCurrentBroadcast;
+        }
+
+        BroadcastRecord next = null;
+        if (!mAlarmBroadcasts.isEmpty()) {
+            next = popLocked(mAlarmBroadcasts);
+            if (DEBUG_BROADCAST_DEFERRAL && next != null) {
+                Slog.i(TAG, "Next broadcast from alarm targets: " + next);
+            }
+        }
+
+        if (next == null && !mDeferredBroadcasts.isEmpty()) {
+            for (int i = 0; i < mDeferredBroadcasts.size(); i++) {
+                Deferrals d = mDeferredBroadcasts.get(i);
+                if (now < d.deferUntil) {
+                    // No more deferrals due
+                    break;
+                }
+
+                if (d.broadcasts.size() > 0) {
+                    next = d.broadcasts.remove(0);
+                    // apply deferral-interval decay policy and move this uid's
+                    // deferred broadcasts down in the delivery queue accordingly
+                    mDeferredBroadcasts.remove(i); // already 'd'
+                    d.deferredBy = calculateDeferral(d.deferredBy);
+                    d.deferUntil += d.deferredBy;
+                    insertLocked(mDeferredBroadcasts, d);
+                    if (DEBUG_BROADCAST_DEFERRAL) {
+                        Slog.i(TAG, "Next broadcast from deferrals " + next
+                                + ", deferUntil now " + d.deferUntil);
+                    }
+                    break;
+                }
+            }
+        }
+
+        if (next == null && !mOrderedBroadcasts.isEmpty()) {
+            next = mOrderedBroadcasts.remove(0);
+            if (DEBUG_BROADCAST_DEFERRAL) {
+                Slog.i(TAG, "Next broadcast from main queue: " + next);
+            }
+        }
+
+        mCurrentBroadcast = next;
+        return next;
+    }
+
+    /**
+     * Called after the broadcast queue finishes processing the currently
+     * active broadcast (obtained by calling getNextBroadcastLocked()).
+     */
+    public void retireBroadcastLocked(final BroadcastRecord r) {
+        // ERROR if 'r' is not the active broadcast
+        if (r != mCurrentBroadcast) {
+            Slog.wtf(TAG, "Retiring broadcast " + r
+                    + " doesn't match current outgoing " + mCurrentBroadcast);
+        }
+        mCurrentBroadcast = null;
+    }
+
+    /**
+     * Called prior to broadcast dispatch to check whether the intended
+     * recipient is currently subject to deferral policy.
+     */
+    public boolean isDeferringLocked(final int uid) {
+        Deferrals d = findUidLocked(uid);
+        if (d != null && d.broadcasts.isEmpty()) {
+            // once we've caught up with deferred broadcasts to this uid
+            // and time has advanced sufficiently that we wouldn't be
+            // deferring newly-enqueued ones, we're back to normal policy.
+            if (SystemClock.uptimeMillis() >= d.deferUntil) {
+                if (DEBUG_BROADCAST_DEFERRAL) {
+                    Slog.i(TAG, "No longer deferring broadcasts to uid " + d.uid);
+                }
+                removeDeferral(d);
+                return false;
+            }
+        }
+        return (d != null);
+    }
+
+    /**
+     * Defer broadcasts for the given app.  If 'br' is non-null, this also makes
+     * sure that broadcast record is enqueued as the next upcoming broadcast for
+     * the app.
+     */
+    public void startDeferring(final int uid) {
+        synchronized (mLock) {
+            Deferrals d = findUidLocked(uid);
+
+            // If we're not yet tracking this app, set up that bookkeeping
+            if (d == null) {
+                // Start a new deferral
+                final long now = SystemClock.uptimeMillis();
+                d = new Deferrals(uid,
+                        now,
+                        mConstants.DEFERRAL,
+                        mAlarmUids.get(uid, 0));
+                if (DEBUG_BROADCAST_DEFERRAL) {
+                    Slog.i(TAG, "Now deferring broadcasts to " + uid
+                            + " until " + d.deferUntil);
+                }
+                // where it goes depends on whether it is coming into an alarm-related situation
+                if (d.alarmCount == 0) {
+                    // common case, put it in the ordinary priority queue
+                    insertLocked(mDeferredBroadcasts, d);
+                    scheduleDeferralCheckLocked(true);
+                } else {
+                    // alarm-related: strict order-encountered
+                    mAlarmBroadcasts.add(d);
+                }
+            } else {
+                // We're already deferring, but something was slow again.  Reset the
+                // deferral decay progression.
+                d.deferredBy = mConstants.DEFERRAL;
+                if (DEBUG_BROADCAST_DEFERRAL) {
+                    Slog.i(TAG, "Uid " + uid + " slow again, deferral interval reset to "
+                            + d.deferredBy);
+                }
+            }
+        }
+    }
+
+    /**
+     * Key entry point when a broadcast about to be delivered is instead
+     * set aside for deferred delivery
+     */
+    public void addDeferredBroadcast(final int uid, BroadcastRecord br) {
+        if (DEBUG_BROADCAST_DEFERRAL) {
+            Slog.i(TAG, "Enqueuing deferred broadcast " + br);
+        }
+        synchronized (mLock) {
+            Deferrals d = findUidLocked(uid);
+            if (d == null) {
+                Slog.wtf(TAG, "Adding deferred broadcast but not tracking " + uid);
+            } else {
+                if (br == null) {
+                    Slog.wtf(TAG, "Deferring null broadcast to " + uid);
+                } else {
+                    br.deferred = true;
+                    d.add(br);
+                }
+            }
+        }
+    }
+
+    /**
+     * When there are deferred broadcasts, we need to make sure to recheck the
+     * dispatch queue when they come due.  Alarm-sensitive deferrals get dispatched
+     * aggressively, so we only need to use the ordinary deferrals timing to figure
+     * out when to recheck.
+     */
+    public void scheduleDeferralCheckLocked(boolean force) {
+        if ((force || !mRecheckScheduled) && !mDeferredBroadcasts.isEmpty()) {
+            final Deferrals d = mDeferredBroadcasts.get(0);
+            if (!d.broadcasts.isEmpty()) {
+                mHandler.removeCallbacks(mScheduleRunnable);
+                mHandler.postAtTime(mScheduleRunnable, d.deferUntil);
+                mRecheckScheduled = true;
+                if (DEBUG_BROADCAST_DEFERRAL) {
+                    Slog.i(TAG, "Scheduling deferred broadcast recheck at " + d.deferUntil);
+                }
+            }
+        }
+    }
+
+    // ----------------------------------
+
+    /**
+     * If broadcasts to this uid are being deferred, find the deferrals record about it.
+     * @return null if this uid's broadcasts are not being deferred
+     */
+    private Deferrals findUidLocked(final int uid) {
+        // The common case is that they it isn't also an alarm target...
+        Deferrals d = findUidLocked(uid, mDeferredBroadcasts);
+        // ...but if not there, also check alarm-prioritized deferrals
+        if (d == null) {
+            d = findUidLocked(uid, mAlarmBroadcasts);
+        }
+        return d;
+    }
+
+    /**
+     * Remove the given deferral record from whichever queue it might be in at present
+     * @return true if the deferral was in fact found, false if this made no changes
+     */
+    private boolean removeDeferral(Deferrals d) {
+        boolean didRemove = mDeferredBroadcasts.remove(d);
+        if (!didRemove) {
+            didRemove = mAlarmBroadcasts.remove(d);
+        }
+        return didRemove;
+    }
+
+    /**
+     * Find the deferrals record for the given uid in the given list
+     */
+    private static Deferrals findUidLocked(final int uid, ArrayList<Deferrals> list) {
+        final int numElements = list.size();
+        for (int i = 0; i < numElements; i++) {
+            Deferrals d = list.get(i);
+            if (uid == d.uid) {
+                return d;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Pop the next broadcast record from the head of the given deferrals list,
+     * if one exists.
+     */
+    private static BroadcastRecord popLocked(ArrayList<Deferrals> list) {
+        final Deferrals d = list.get(0);
+        return d.broadcasts.isEmpty() ? null : d.broadcasts.remove(0);
+    }
+
+    /**
+     * Insert the given Deferrals into the priority queue, sorted by defer-until milestone
+     */
+    private static void insertLocked(ArrayList<Deferrals> list, Deferrals d) {
+        // Simple linear search is appropriate here because we expect to
+        // have very few entries in the deferral lists (i.e. very few badly-
+        // behaving apps currently facing deferral)
+        int i;
+        final int numElements = list.size();
+        for (i = 0; i < numElements; i++) {
+            if (d.deferUntil < list.get(i).deferUntil) {
+                break;
+            }
+        }
+        list.add(i, d);
+    }
+
+    /**
+     * Calculate a new deferral time based on the previous time.  This should decay
+     * toward zero, though a small nonzero floor is an option.
+     */
+    private long calculateDeferral(long previous) {
+        return Math.max(mConstants.DEFERRAL_FLOOR,
+                (long) (previous * mConstants.DEFERRAL_DECAY_FACTOR));
+    }
+
+    // ----------------------------------
+
+    boolean dumpLocked(PrintWriter pw, String dumpPackage, String queueName,
+            SimpleDateFormat sdf) {
+        final Dumper dumper = new Dumper(pw, queueName, dumpPackage, sdf);
+        boolean printed = false;
+
+        dumper.setHeading("Active ordered broadcasts");
+        dumper.setLabel("Active Ordered Broadcast");
+        for (Deferrals d : mAlarmBroadcasts) {
+            d.dumpLocked(dumper);
+        }
+        printed |= dumper.didPrint();
+
+        for (BroadcastRecord br : mOrderedBroadcasts) {
+            dumper.dump(br);
+        }
+        printed |= dumper.didPrint();
+
+        dumper.setHeading("Deferred ordered broadcasts");
+        dumper.setLabel("Deferred Ordered Broadcast");
+        for (Deferrals d : mDeferredBroadcasts) {
+            d.dumpLocked(dumper);
+        }
+        printed |= dumper.didPrint();
+
+        return printed;
+    }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 353749f..64a36ef 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -24,6 +24,7 @@
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
 import android.content.Intent;
@@ -45,6 +46,7 @@
 import android.os.UserHandle;
 import android.util.EventLog;
 import android.util.Slog;
+import android.util.SparseIntArray;
 import android.util.StatsLog;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
@@ -75,16 +77,17 @@
     final ActivityManagerService mService;
 
     /**
+     * Behavioral parameters such as timeouts and deferral policy, tracking Settings
+     * for runtime configurability
+     */
+    final BroadcastConstants mConstants;
+
+    /**
      * Recognizable moniker for this queue
      */
     final String mQueueName;
 
     /**
-     * Timeout period for this queue's broadcasts
-     */
-    final long mTimeoutPeriod;
-
-    /**
      * If true, we can delay broadcasts while waiting services to finish in the previous
      * receiver's process.
      */
@@ -100,13 +103,18 @@
     final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
 
     /**
-     * List of all active broadcasts that are to be executed one at a time.
-     * The object at the top of the list is the currently activity broadcasts;
-     * those after it are waiting for the top to finish.  As with parallel
-     * broadcasts, separate background- and foreground-priority queues are
-     * maintained.
+     * Tracking of the ordered broadcast queue, including deferral policy and alarm
+     * prioritization.
      */
-    final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();
+    final BroadcastDispatcher mDispatcher;
+
+    /**
+     * Refcounting for completion callbacks of split/deferred broadcasts.  The key
+     * is an opaque integer token assigned lazily when a broadcast is first split
+     * into multiple BroadcastRecord objects.
+     */
+    final SparseIntArray mSplitRefcounts = new SparseIntArray();
+    private int mNextToken = 0;
 
     /**
      * Historical data of past broadcasts, for debugging.  This is a ring buffer
@@ -173,7 +181,8 @@
             switch (msg.what) {
                 case BROADCAST_INTENT_MSG: {
                     if (DEBUG_BROADCAST) Slog.v(
-                            TAG_BROADCAST, "Received BROADCAST_INTENT_MSG");
+                            TAG_BROADCAST, "Received BROADCAST_INTENT_MSG ["
+                            + mQueueName + "]");
                     processNextBroadcast(true);
                 } break;
                 case BROADCAST_TIMEOUT_MSG: {
@@ -201,12 +210,19 @@
     }
 
     BroadcastQueue(ActivityManagerService service, Handler handler,
-            String name, long timeoutPeriod, boolean allowDelayBehindServices) {
+            String name, BroadcastConstants constants, boolean allowDelayBehindServices) {
         mService = service;
         mHandler = new BroadcastHandler(handler.getLooper());
         mQueueName = name;
-        mTimeoutPeriod = timeoutPeriod;
         mDelayBehindServices = allowDelayBehindServices;
+
+        mConstants = constants;
+        mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService);
+    }
+
+    void start(ContentResolver resolver) {
+        mDispatcher.start();
+        mConstants.startObserving(mHandler, resolver);
     }
 
     @Override
@@ -224,7 +240,7 @@
     }
 
     public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
-        mOrderedBroadcasts.add(r);
+        mDispatcher.enqueueOrderedBroadcastLocked(r);
         enqueueBroadcastHelper(r);
     }
 
@@ -255,7 +271,7 @@
      * the old one.
      */
     public final BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r) {
-        return replaceBroadcastLocked(mOrderedBroadcasts, r, "ORDERED");
+        return mDispatcher.replaceBroadcastLocked(r, "ORDERED");
     }
 
     private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
@@ -365,14 +381,17 @@
         }
     }
 
+    // Skip the current receiver, if any, that is in flight to the given process
     public void skipCurrentReceiverLocked(ProcessRecord app) {
         BroadcastRecord r = null;
-        if (mOrderedBroadcasts.size() > 0) {
-            BroadcastRecord br = mOrderedBroadcasts.get(0);
-            if (br.curApp == app) {
-                r = br;
-            }
+        final BroadcastRecord curActive = mDispatcher.getActiveBroadcastLocked();
+        if (curActive != null && curActive.curApp == app) {
+            // confirmed: the current active broadcast is to the given app
+            r = curActive;
         }
+
+        // If the current active broadcast isn't this BUT we're waiting for
+        // mPendingBroadcast to spin up the target app, that's what we use.
         if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
             if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
                     "[" + mQueueName + "] skip & discard pending app " + r);
@@ -404,20 +423,29 @@
     }
 
     public BroadcastRecord getMatchingOrderedReceiver(IBinder receiver) {
-        if (mOrderedBroadcasts.size() > 0) {
-            final BroadcastRecord r = mOrderedBroadcasts.get(0);
-            if (r != null && r.receiver == receiver) {
-                return r;
-            }
+        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
+        if (br != null && br.receiver == receiver) {
+            return br;
         }
         return null;
     }
 
+    // > 0 only, no worry about "eventual" recycling
+    private int nextSplitTokenLocked() {
+        int next = mNextToken + 1;
+        if (next <= 0) {
+            next = 1;
+        }
+        mNextToken = next;
+        return next;
+    }
+
     public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
             String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
         final int state = r.state;
         final ActivityInfo receiver = r.curReceiver;
         final long finishTime = SystemClock.uptimeMillis();
+        final long elapsed = finishTime - r.receiverTime;
         r.state = BroadcastRecord.IDLE;
         if (state == BroadcastRecord.IDLE) {
             Slog.w(TAG, "finishReceiver [" + mQueueName + "] called but state is IDLE");
@@ -428,8 +456,22 @@
         // If we're abandoning this broadcast before any receivers were actually spun up,
         // nextReceiver is zero; in which case time-to-process bookkeeping doesn't apply.
         if (r.nextReceiver > 0) {
-            r.duration[r.nextReceiver - 1] = finishTime - r.receiverTime;
+            r.duration[r.nextReceiver - 1] = elapsed;
         }
+
+        // if this receiver was slow, impose deferral policy on the app.  This will kick in
+        // when processNextBroadcastLocked() next finds this uid as a receiver identity.
+        if (mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) {
+            if (DEBUG_BROADCAST_DEFERRAL) {
+                Slog.i(TAG, "Broadcast receiver was slow: " + receiver + " br=" + r);
+            }
+            if (r.curApp != null) {
+                mDispatcher.startDeferring(r.curApp.uid);
+            } else {
+                Slog.d(TAG, "finish receiver curApp is null? " + r);
+            }
+        }
+
         r.receiver = null;
         r.intent.setComponent(null);
         if (r.curApp != null && r.curApp.curReceivers.contains(r)) {
@@ -452,9 +494,10 @@
             r.resultAbort = false;
         }
 
+        // If we want to wait behind services *AND* we're finishing the head/
+        // active broadcast on its queue
         if (waitForServices && r.curComponent != null && r.queue.mDelayBehindServices
-                && r.queue.mOrderedBroadcasts.size() > 0
-                && r.queue.mOrderedBroadcasts.get(0) == r) {
+                && r.queue.mDispatcher.getActiveBroadcastLocked() == r) {
             ActivityInfo nextReceiver;
             if (r.nextReceiver < r.receivers.size()) {
                 Object obj = r.receivers.get(r.nextReceiver);
@@ -488,8 +531,8 @@
     }
 
     public void backgroundServicesFinishedLocked(int userId) {
-        if (mOrderedBroadcasts.size() > 0) {
-            BroadcastRecord br = mOrderedBroadcasts.get(0);
+        BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
+        if (br != null) {
             if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) {
                 Slog.i(TAG, "Resuming delayed broadcast");
                 br.curComponent = null;
@@ -776,6 +819,7 @@
 
             final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, receivingPackageName);
             intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
@@ -860,7 +904,7 @@
         if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast ["
                 + mQueueName + "]: "
                 + mParallelBroadcasts.size() + " parallel broadcasts, "
-                + mOrderedBroadcasts.size() + " ordered broadcasts");
+                + mDispatcher.totalUndelivered() + " ordered broadcasts");
 
         mService.updateCpuStats();
 
@@ -936,8 +980,12 @@
         boolean looped = false;
 
         do {
-            if (mOrderedBroadcasts.size() == 0) {
-                // No more broadcasts pending, so all done!
+            final long now = SystemClock.uptimeMillis();
+            r = mDispatcher.getNextBroadcastLocked(now);
+
+            if (r == null) {
+                // No more broadcasts are deliverable right now, so all done!
+                mDispatcher.scheduleDeferralCheckLocked(false);
                 mService.scheduleAppGcsLocked();
                 if (looped) {
                     // If we had finished the last ordered broadcast, then
@@ -953,7 +1001,7 @@
 
                 return;
             }
-            r = mOrderedBroadcasts.get(0);
+
             boolean forceReceive = false;
 
             // Ensure that even if something goes awry with the timeout
@@ -966,9 +1014,8 @@
             // significant amounts of time.
             int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
             if (mService.mProcessesReady && r.dispatchTime > 0) {
-                long now = SystemClock.uptimeMillis();
                 if ((numReceivers > 0) &&
-                        (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
+                        (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
                     Slog.w(TAG, "Hung broadcast ["
                             + mQueueName + "] discarded after timeout failure:"
                             + " now=" + now
@@ -992,27 +1039,53 @@
                 return;
             }
 
+            // Is the current broadcast is done for any reason?
             if (r.receivers == null || r.nextReceiver >= numReceivers
                     || r.resultAbort || forceReceive) {
-                // No more receivers for this broadcast!  Send the final
-                // result if requested...
+                // Send the final result if requested
                 if (r.resultTo != null) {
-                    try {
-                        if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,
-                                "Finishing broadcast [" + mQueueName + "] "
-                                + r.intent.getAction() + " app=" + r.callerApp);
-                        performReceiveLocked(r.callerApp, r.resultTo,
-                            new Intent(r.intent), r.resultCode,
-                            r.resultData, r.resultExtras, false, false, r.userId);
-                        // Set this to null so that the reference
-                        // (local and remote) isn't kept in the mBroadcastHistory.
-                        r.resultTo = null;
-                    } catch (RemoteException e) {
-                        r.resultTo = null;
-                        Slog.w(TAG, "Failure ["
-                                + mQueueName + "] sending broadcast result of "
-                                + r.intent, e);
+                    boolean sendResult = true;
 
+                    // if this was part of a split/deferral complex, update the refcount and only
+                    // send the completion when we clear all of them
+                    if (r.splitToken != 0) {
+                        int newCount = mSplitRefcounts.get(r.splitToken) - 1;
+                        if (newCount == 0) {
+                            // done!  clear out this record's bookkeeping and deliver
+                            if (DEBUG_BROADCAST_DEFERRAL) {
+                                Slog.i(TAG, "Sending broadcast completion for split token "
+                                        + r.splitToken);
+                            }
+                            mSplitRefcounts.delete(r.splitToken);
+                        } else {
+                            // still have some split broadcast records in flight; update refcount
+                            // and hold off on the callback
+                            if (DEBUG_BROADCAST_DEFERRAL) {
+                                Slog.i(TAG, "Result refcount " + newCount + " for split token "
+                                        + r.splitToken + " - not sending completion yet");
+                            }
+                            sendResult = false;
+                            mSplitRefcounts.put(r.splitToken, newCount);
+                        }
+                    }
+                    if (sendResult) {
+                        try {
+                            if (DEBUG_BROADCAST) {
+                                Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] "
+                                        + r.intent.getAction() + " app=" + r.callerApp);
+                            }
+                            performReceiveLocked(r.callerApp, r.resultTo,
+                                    new Intent(r.intent), r.resultCode,
+                                    r.resultData, r.resultExtras, false, false, r.userId);
+                            // Set this to null so that the reference
+                            // (local and remote) isn't kept in the mBroadcastHistory.
+                            r.resultTo = null;
+                        } catch (RemoteException e) {
+                            r.resultTo = null;
+                            Slog.w(TAG, "Failure ["
+                                    + mQueueName + "] sending broadcast result of "
+                                    + r.intent, e);
+                        }
                     }
                 }
 
@@ -1030,11 +1103,76 @@
                     mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
                             r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime);
                 }
-                mOrderedBroadcasts.remove(0);
+                mDispatcher.retireBroadcastLocked(r);
                 r = null;
                 looped = true;
                 continue;
             }
+
+            // Check whether the next receiver is under deferral policy, and handle that
+            // accordingly.  If the current broadcast was already part of deferred-delivery
+            // tracking, we know that it must now be deliverable as-is without re-deferral.
+            if (!r.deferred) {
+                final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver));
+                if (mDispatcher.isDeferringLocked(receiverUid)) {
+                    if (DEBUG_BROADCAST_DEFERRAL) {
+                        Slog.i(TAG_BROADCAST, "Next receiver in " + r + " uid " + receiverUid
+                                + " at " + r.nextReceiver + " is under deferral");
+                    }
+                    // If this is the only (remaining) receiver in the broadcast, "splitting"
+                    // doesn't make sense -- just defer it as-is and retire it as the
+                    // currently active outgoing broadcast.
+                    BroadcastRecord defer;
+                    if (r.nextReceiver + 1 == numReceivers) {
+                        if (DEBUG_BROADCAST_DEFERRAL) {
+                            Slog.i(TAG, "Sole receiver of " + r
+                                    + " is under deferral; setting aside and proceeding");
+                        }
+                        defer = r;
+                        mDispatcher.retireBroadcastLocked(r);
+                    } else {
+                        // Nontrivial case; split out 'uid's receivers to a new broadcast record
+                        // and defer that, then loop and pick up continuing delivery of the current
+                        // record (now absent those receivers).
+
+                        // The split operation is guaranteed to match at least at 'nextReceiver'
+                        defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver);
+                        if (DEBUG_BROADCAST_DEFERRAL) {
+                            Slog.i(TAG_BROADCAST, "Post split:");
+                            Slog.i(TAG_BROADCAST, "Original broadcast receivers:");
+                            for (int i = 0; i < r.receivers.size(); i++) {
+                                Slog.i(TAG_BROADCAST, "  " + r.receivers.get(i));
+                            }
+                            Slog.i(TAG_BROADCAST, "Split receivers:");
+                            for (int i = 0; i < defer.receivers.size(); i++) {
+                                Slog.i(TAG_BROADCAST, "  " + defer.receivers.get(i));
+                            }
+                        }
+                        // Track completion refcount as well if relevant
+                        if (r.resultTo != null) {
+                            int token = r.splitToken;
+                            if (token == 0) {
+                                // first split of this record; refcount for 'r' and 'deferred'
+                                r.splitToken = defer.splitToken = nextSplitTokenLocked();
+                                mSplitRefcounts.put(r.splitToken, 2);
+                            } else {
+                                // new split from an already-refcounted situation; increment count
+                                final int curCount = mSplitRefcounts.get(token);
+                                if (DEBUG_BROADCAST_DEFERRAL) {
+                                    if (curCount == 0) {
+                                        Slog.wtf(TAG, "Split refcount is zero with token for " + r);
+                                    }
+                                }
+                                mSplitRefcounts.put(token, curCount + 1);
+                            }
+                        }
+                    }
+                    mDispatcher.addDeferredBroadcast(receiverUid, defer);
+                    r = null;
+                    looped = true;
+                    continue;
+                }
+            }
         } while (r == null);
 
         // Get the next receiver...
@@ -1065,7 +1203,7 @@
                     + mQueueName + "] " + r);
         }
         if (! mPendingBroadcastTimeoutMessage) {
-            long timeoutTime = r.receiverTime + mTimeoutPeriod;
+            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
             if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
                     "Submitting BROADCAST_TIMEOUT_MSG ["
                     + mQueueName + "] for " + r + " at " + timeoutTime);
@@ -1473,12 +1611,12 @@
             mPendingBroadcastTimeoutMessage = false;
         }
 
-        if (mOrderedBroadcasts.size() == 0) {
+        if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) {
             return;
         }
 
         long now = SystemClock.uptimeMillis();
-        BroadcastRecord r = mOrderedBroadcasts.get(0);
+        BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
         if (fromMsg) {
             if (!mService.mProcessesReady) {
                 // Only process broadcast timeouts if the system is ready. That way
@@ -1487,7 +1625,7 @@
                 return;
             }
 
-            long timeoutTime = r.receiverTime + mTimeoutPeriod;
+            long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
             if (timeoutTime > now) {
                 // We can observe premature timeouts because we do not cancel and reset the
                 // broadcast timeout message after each receiver finishes.  Instead, we set up
@@ -1502,16 +1640,15 @@
             }
         }
 
-        BroadcastRecord br = mOrderedBroadcasts.get(0);
-        if (br.state == BroadcastRecord.WAITING_SERVICES) {
+        if (r.state == BroadcastRecord.WAITING_SERVICES) {
             // In this case the broadcast had already finished, but we had decided to wait
             // for started services to finish as well before going on.  So if we have actually
             // waited long enough time timeout the broadcast, let's give up on the whole thing
             // and just move on to the next.
-            Slog.i(TAG, "Waited long enough for: " + (br.curComponent != null
-                    ? br.curComponent.flattenToShortString() : "(null)"));
-            br.curComponent = null;
-            br.state = BroadcastRecord.IDLE;
+            Slog.i(TAG, "Waited long enough for: " + (r.curComponent != null
+                    ? r.curComponent.flattenToShortString() : "(null)"));
+            r.curComponent = null;
+            r.state = BroadcastRecord.IDLE;
             processNextBroadcast(false);
             return;
         }
@@ -1618,13 +1755,8 @@
             }
         }
 
-        for (int i = mOrderedBroadcasts.size() - 1; i >= 0; i--) {
-            didSomething |= mOrderedBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
-                    packageName, filterByClasses, userId, doit);
-            if (!doit && didSomething) {
-                return true;
-            }
-        }
+        didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName,
+                filterByClasses, userId, doit);
 
         return didSomething;
     }
@@ -1664,7 +1796,7 @@
     }
 
     final boolean isIdle() {
-        return mParallelBroadcasts.isEmpty() && mOrderedBroadcasts.isEmpty()
+        return mParallelBroadcasts.isEmpty() && mDispatcher.isEmpty()
                 && (mPendingBroadcast == null);
     }
 
@@ -1676,10 +1808,7 @@
         for (int i = N - 1; i >= 0; i--) {
             mParallelBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.PARALLEL_BROADCASTS);
         }
-        N = mOrderedBroadcasts.size();
-        for (int i = N - 1; i >= 0; i--) {
-            mOrderedBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
-        }
+        mDispatcher.writeToProto(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
         if (mPendingBroadcast != null) {
             mPendingBroadcast.writeToProto(proto, BroadcastQueueProto.PENDING_BROADCAST);
         }
@@ -1720,7 +1849,7 @@
     final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-        if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0
+        if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty()
                 || mPendingBroadcast != null) {
             boolean printed = false;
             for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
@@ -1739,29 +1868,12 @@
                 pw.println("  Active Broadcast " + mQueueName + " #" + i + ":");
                 br.dump(pw, "    ", sdf);
             }
-            printed = false;
-            needSep = true;
-            for (int i = mOrderedBroadcasts.size() - 1; i >= 0; i--) {
-                BroadcastRecord br = mOrderedBroadcasts.get(i);
-                if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
-                    continue;
-                }
-                if (!printed) {
-                    if (needSep) {
-                        pw.println();
-                    }
-                    needSep = true;
-                    printed = true;
-                    pw.println("  Active ordered broadcasts [" + mQueueName + "]:");
-                }
-                pw.println("  Active Ordered Broadcast " + mQueueName + " #" + i + ":");
-                mOrderedBroadcasts.get(i).dump(pw, "    ", sdf);
-            }
+
+            mDispatcher.dumpLocked(pw, dumpPackage, mQueueName, sdf);
+
             if (dumpPackage == null || (mPendingBroadcast != null
                     && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
-                if (needSep) {
-                    pw.println();
-                }
+                pw.println();
                 pw.println("  Pending broadcast [" + mQueueName + "]:");
                 if (mPendingBroadcast != null) {
                     mPendingBroadcast.dump(pw, "    ", sdf);
@@ -1772,6 +1884,8 @@
             }
         }
 
+        mConstants.dump(pw);
+
         int i;
         boolean printed = false;
 
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 9e799f6..d9e03f8 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -36,10 +36,12 @@
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * An active intent broadcast.
@@ -64,6 +66,9 @@
     final int[] delivery;   // delivery state of each receiver
     final long[] duration;   // duration a receiver took to process broadcast
     IIntentReceiver resultTo; // who receives final result if non-null
+    boolean deferred;
+    int splitCount;         // refcount for result callback, when split
+    int splitToken;         // identifier for cross-BroadcastRecord refcount
     long enqueueClockTime;  // the clock time the broadcast was enqueued
     long dispatchTime;      // when dispatch started on this set of receivers
     long dispatchClockTime; // the clock time the dispatch started
@@ -106,6 +111,9 @@
     ComponentName curComponent; // the receiver class that is currently running.
     ActivityInfo curReceiver;   // info about the receiver that is currently running.
 
+    // Private refcount-management bookkeeping; start > 0
+    static AtomicInteger sNextToken = new AtomicInteger(1);
+
     void dump(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
         final long now = SystemClock.uptimeMillis();
 
@@ -304,6 +312,52 @@
         allowBackgroundActivityStarts = from.allowBackgroundActivityStarts;
     }
 
+    /**
+     * Split off a new BroadcastRecord that clones this one, but contains only the
+     * recipient records for the current (just-finished) receiver's app, starting
+     * after the just-finished receiver [i.e. at r.nextReceiver].  Returns null
+     * if there are no matching subsequent receivers in this BroadcastRecord.
+     */
+    BroadcastRecord splitRecipientsLocked(int slowAppUid, int startingAt) {
+        // Do we actually have any matching receivers down the line...?
+        ArrayList splitReceivers = null;
+        for (int i = startingAt; i < receivers.size(); ) {
+            Object o = receivers.get(i);
+            if (getReceiverUid(o) == slowAppUid) {
+                if (splitReceivers == null) {
+                    splitReceivers = new ArrayList<>();
+                }
+                splitReceivers.add(o);
+                receivers.remove(i);
+                break;
+            } else {
+                i++;
+            }
+        }
+
+        // No later receivers in the same app, so we have no more to do
+        if (splitReceivers == null) {
+            return null;
+        }
+
+        // build a new BroadcastRecord around that single-target list
+        BroadcastRecord split = new BroadcastRecord(queue, intent, callerApp,
+                callerPackage, callingPid, callingUid, callerInstantApp, resolvedType,
+                requiredPermissions, appOp, options, splitReceivers, resultTo, resultCode,
+                resultData, resultExtras, ordered, sticky, initialSticky, userId,
+                allowBackgroundActivityStarts);
+
+        return split;
+    }
+
+    int getReceiverUid(Object receiver) {
+        if (receiver instanceof BroadcastFilter) {
+            return ((BroadcastFilter) receiver).owningUid;
+        } else /* if (receiver instanceof ResolveInfo) */ {
+            return ((ResolveInfo) receiver).activityInfo.applicationInfo.uid;
+        }
+    }
+
     public BroadcastRecord maybeStripForHistory() {
         if (!intent.canStripForHistory()) {
             return this;
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 3d69aa8..360d296 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -69,10 +69,11 @@
         sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYERS_GLES, String.class);
         sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_LAYER_APP, String.class);
         sGlobalSettingToTypeMap.put(Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, int.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GUP_DEV_ALL_APPS, int.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GUP_DEV_OPT_IN_APPS, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GUP_DEV_OPT_OUT_APPS, String.class);
-        sGlobalSettingToTypeMap.put(Settings.Global.GUP_BLACKLIST, String.class);
+        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_ALL_APPS, int.class);
+        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_OPT_IN_APPS, String.class);
+        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_OPT_OUT_APPS, String.class);
+        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_BLACKLIST, String.class);
+        sGlobalSettingToTypeMap.put(Settings.Global.GAME_DRIVER_WHITELIST, String.class);
         // add other global settings here...
     }
 
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..5b469fe 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -16,6 +16,10 @@
 
 package com.android.server.attention;
 
+import static android.provider.DeviceConfig.AttentionManagerService.NAMESPACE;
+import static android.provider.DeviceConfig.AttentionManagerService.PROPERTY_COMPONENT_NAME;
+import static android.provider.DeviceConfig.AttentionManagerService.PROPERTY_SERVICE_ENABLED;
+
 import android.Manifest;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -40,6 +44,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.service.attention.AttentionService;
 import android.service.attention.AttentionService.AttentionFailureCodes;
 import android.service.attention.IAttentionCallback;
@@ -66,6 +71,9 @@
 public class AttentionManagerService extends SystemService {
     private static final String LOG_TAG = "AttentionManagerService";
 
+    /** Default value in absence of {@link DeviceConfig} override. */
+    private static final boolean DEFAULT_SERVICE_ENABLED = true;
+
     /** Service will unbind if connection is not used for that amount of time. */
     private static final long CONNECTION_TTL_MILLIS = 60_000;
 
@@ -105,7 +113,7 @@
         super.onBootPhase(phase);
         if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             mComponentName = resolveAttentionService(mContext);
-            if (mComponentName != null) {
+            if (isAttentionServiceSupported()) {
                 // If the service is supported we want to keep receiving the screen off events.
                 mContext.registerReceiver(new ScreenStateReceiver(),
                         new IntentFilter(Intent.ACTION_SCREEN_OFF));
@@ -117,7 +125,12 @@
      * Returns {@code true} if attention service is supported on this device.
      */
     public boolean isAttentionServiceSupported() {
-        return mComponentName != null;
+        return mComponentName != null && isServiceEnabled();
+    }
+
+    private boolean isServiceEnabled() {
+        final String enabled = DeviceConfig.getProperty(NAMESPACE, PROPERTY_SERVICE_ENABLED);
+        return enabled == null ? DEFAULT_SERVICE_ENABLED : "true".equals(enabled);
     }
 
     /**
@@ -174,10 +187,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);
                         }
@@ -265,8 +279,9 @@
      * system.
      */
     private static ComponentName resolveAttentionService(Context context) {
-        // TODO(b/111939367): add a flag to turn on/off.
-        final String componentNameString = context.getString(
+        final String flag = DeviceConfig.getProperty(NAMESPACE, PROPERTY_COMPONENT_NAME);
+
+        final String componentNameString = flag != null ? flag : context.getString(
                 R.string.config_defaultAttentionService);
 
         if (TextUtils.isEmpty(componentNameString)) {
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/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 174ecfa..2791165 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -279,7 +279,7 @@
         }
     }
 
-    protected class EnrollClientImpl extends EnrollClient {
+    protected abstract class EnrollClientImpl extends EnrollClient {
 
         public EnrollClientImpl(Context context, DaemonWrapper daemon, long halDeviceId,
                 IBinder token, ServiceListener listener, int userId, int groupId,
diff --git a/services/core/java/com/android/server/biometrics/EnrollClient.java b/services/core/java/com/android/server/biometrics/EnrollClient.java
index 8a0f085..3ff94bc 100644
--- a/services/core/java/com/android/server/biometrics/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/EnrollClient.java
@@ -36,6 +36,8 @@
     private final BiometricUtils mBiometricUtils;
     private final int[] mDisabledFeatures;
 
+    public abstract boolean shouldVibrate();
+
     public EnrollClient(Context context, Metrics metrics,
             BiometricServiceBase.DaemonWrapper daemon, long halDeviceId, IBinder token,
             BiometricServiceBase.ServiceListener listener, int userId, int groupId,
@@ -62,7 +64,9 @@
      */
     private boolean sendEnrollResult(BiometricAuthenticator.Identifier identifier,
             int remaining) {
-        vibrateSuccess();
+        if (shouldVibrate()) {
+            vibrateSuccess();
+        }
         mMetricsLogger.action(mMetrics.actionBiometricEnroll());
         try {
             getListener().onEnrollResult(identifier, remaining);
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index a2aacdd..d4be539 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -121,7 +121,12 @@
             final boolean restricted = isRestricted();
             final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
                     mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId,
-                    0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures);
+                    0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures) {
+                @Override
+                public boolean shouldVibrate() {
+                    return false;
+                }
+            };
 
             enrollInternal(client, UserHandle.getCallingUserId());
         }
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..f84cda03 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;
@@ -144,7 +142,12 @@
             final int groupId = userId; // default group for fingerprint enrollment
             final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
                     mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId, groupId,
-                    cryptoToken, restricted, opPackageName, new int[0] /* disabledFeatures */);
+                    cryptoToken, restricted, opPackageName, new int[0] /* disabledFeatures */) {
+                @Override
+                public boolean shouldVibrate() {
+                    return true;
+                }
+            };
 
             enrollInternal(client, userId);
         }
@@ -588,11 +591,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 +600,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/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 62a1b03..2508844 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -151,7 +151,7 @@
                 .divide(BigInteger.valueOf(100));
     }
     // How many routes to evaluate before bailing and declaring this Vpn should provide
-    // the INTERNET capability. This is necessary because computing the adress space is
+    // the INTERNET capability. This is necessary because computing the address space is
     // O(n²) and this is running in the system service, so a limit is needed to alleviate
     // the risk of attack.
     // This is taken as a total of IPv4 + IPV6 routes for simplicity, but the algorithm
@@ -194,6 +194,12 @@
     private boolean mLockdown = false;
 
     /**
+     * Set of packages in addition to the VPN app itself that can access the network directly when
+     * VPN is not connected even if {@code mLockdown} is set.
+     */
+    private @NonNull List<String> mLockdownWhitelist = Collections.emptyList();
+
+    /**
      * List of UIDs for which networking should be blocked until VPN is ready, during brief periods
      * when VPN is not running. For example, during system startup or after a crash.
      * @see mLockdown
@@ -320,9 +326,9 @@
      *
      * Used to enable/disable legacy VPN lockdown.
      *
-     * This uses the same ip rule mechanism as {@link #setAlwaysOnPackage(String, boolean)};
-     * previous settings from calling that function will be replaced and saved with the
-     * always-on state.
+     * This uses the same ip rule mechanism as
+     * {@link #setAlwaysOnPackage(String, boolean, List<String>)}; previous settings from calling
+     * that function will be replaced and saved with the always-on state.
      *
      * @param lockdown whether to prevent all traffic outside of a VPN.
      */
@@ -419,12 +425,14 @@
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
+     * @param lockdownWhitelist packages to be whitelisted from lockdown.
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
-    public synchronized boolean setAlwaysOnPackage(String packageName, boolean lockdown) {
+    public synchronized boolean setAlwaysOnPackage(
+            String packageName, boolean lockdown, List<String> lockdownWhitelist) {
         enforceControlPermissionOrInternalCaller();
 
-        if (setAlwaysOnPackageInternal(packageName, lockdown)) {
+        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist)) {
             saveAlwaysOnPackage();
             return true;
         }
@@ -439,15 +447,27 @@
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
+     * @param lockdownWhitelist packages to be whitelisted from lockdown. This is only used if
+     *        {@code lockdown} is {@code true}. Packages must not contain commas.
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
     @GuardedBy("this")
-    private boolean setAlwaysOnPackageInternal(String packageName, boolean lockdown) {
+    private boolean setAlwaysOnPackageInternal(
+            String packageName, boolean lockdown, List<String> lockdownWhitelist) {
         if (VpnConfig.LEGACY_VPN.equals(packageName)) {
             Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
             return false;
         }
 
+        if (lockdownWhitelist != null) {
+            for (String pkg : lockdownWhitelist) {
+                if (pkg.contains(",")) {
+                    Log.w(TAG, "Not setting always-on vpn, invalid whitelisted package: " + pkg);
+                    return false;
+                }
+            }
+        }
+
         if (packageName != null) {
             // Pre-authorize new always-on VPN package.
             if (!setPackageAuthorization(packageName, true)) {
@@ -460,13 +480,18 @@
         }
 
         mLockdown = (mAlwaysOn && lockdown);
+        mLockdownWhitelist = (mLockdown && lockdownWhitelist != null)
+                ? Collections.unmodifiableList(new ArrayList<>(lockdownWhitelist))
+                : Collections.emptyList();
+
         if (isCurrentPreparedPackage(packageName)) {
             updateAlwaysOnNotification(mNetworkInfo.getDetailedState());
+            setVpnForcedLocked(mLockdown);
         } else {
             // Prepare this app. The notification will update as a side-effect of updateState().
+            // It also calls setVpnForcedLocked().
             prepareInternal(packageName);
         }
-        setVpnForcedLocked(mLockdown);
         return true;
     }
 
@@ -478,7 +503,6 @@
      * @return the package name of the VPN controller responsible for always-on VPN,
      *         or {@code null} if none is set or always-on VPN is controlled through
      *         lockdown instead.
-     * @hide
      */
     public synchronized String getAlwaysOnPackage() {
         enforceControlPermissionOrInternalCaller();
@@ -486,6 +510,13 @@
     }
 
     /**
+     * @return an immutable list of packages whitelisted from always-on VPN lockdown.
+     */
+    public synchronized List<String> getLockdownWhitelist() {
+        return mLockdown ? mLockdownWhitelist : null;
+    }
+
+    /**
      * Save the always-on package and lockdown config into Settings.Secure
      */
     @GuardedBy("this")
@@ -496,6 +527,9 @@
                     getAlwaysOnPackage(), mUserHandle);
             mSystemServices.settingsSecurePutIntForUser(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
                     (mAlwaysOn && mLockdown ? 1 : 0), mUserHandle);
+            mSystemServices.settingsSecurePutStringForUser(
+                    Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST,
+                    String.join(",", mLockdownWhitelist), mUserHandle);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -512,7 +546,11 @@
                     Settings.Secure.ALWAYS_ON_VPN_APP, mUserHandle);
             final boolean alwaysOnLockdown = mSystemServices.settingsSecureGetIntForUser(
                     Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, 0 /*default*/, mUserHandle) != 0;
-            setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown);
+            final String whitelistString = mSystemServices.settingsSecureGetStringForUser(
+                    Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, mUserHandle);
+            final List<String> whitelistedPackages = TextUtils.isEmpty(whitelistString)
+                    ? Collections.emptyList() : Arrays.asList(whitelistString.split(","));
+            setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown, whitelistedPackages);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -532,7 +570,7 @@
             }
             // Remove always-on VPN if it's not supported.
             if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
-                setAlwaysOnPackage(null, false);
+                setAlwaysOnPackage(null, false, null);
                 return false;
             }
             // Skip if the service is already established. This isn't bulletproof: it's not bound
@@ -1249,9 +1287,10 @@
     }
 
     /**
-     * Restrict network access from all UIDs affected by this {@link Vpn}, apart from the VPN
-     * service app itself, to only sockets that have had {@code protect()} called on them. All
-     * non-VPN traffic is blocked via a {@code PROHIBIT} response from the kernel.
+     * Restricts network access from all UIDs affected by this {@link Vpn}, apart from the VPN
+     * service app itself and whitelisted packages, to only sockets that have had {@code protect()}
+     * called on them. All non-VPN traffic is blocked via a {@code PROHIBIT} response from the
+     * kernel.
      *
      * The exception for the VPN UID isn't technically necessary -- setup should use protected
      * sockets -- but in practice it saves apps that don't protect their sockets from breaking.
@@ -1267,8 +1306,13 @@
      */
     @GuardedBy("this")
     private void setVpnForcedLocked(boolean enforce) {
-        final List<String> exemptedPackages =
-                isNullOrLegacyVpn(mPackage) ? null : Collections.singletonList(mPackage);
+        final List<String> exemptedPackages;
+        if (isNullOrLegacyVpn(mPackage)) {
+            exemptedPackages = null;
+        } else {
+            exemptedPackages = new ArrayList<>(mLockdownWhitelist);
+            exemptedPackages.add(mPackage);
+        }
         final Set<UidRange> removedRanges = new ArraySet<>(mBlockedUsers);
 
         Set<UidRange> addedRanges = Collections.emptySet();
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/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 9566598..7414e55 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -418,10 +418,15 @@
         // Now add back the offset for the masked area.
         mTempDisplayRect.offset(maskingInsets.left, maskingInsets.top);
 
-        mTempDisplayRect.left += mDisplayOffsetX;
-        mTempDisplayRect.right += mDisplayOffsetX;
-        mTempDisplayRect.top += mDisplayOffsetY;
-        mTempDisplayRect.bottom += mDisplayOffsetY;
+        if (orientation == Surface.ROTATION_0) {
+            mTempDisplayRect.offset(mDisplayOffsetX, mDisplayOffsetY);
+        } else if (orientation == Surface.ROTATION_90) {
+            mTempDisplayRect.offset(mDisplayOffsetY, -mDisplayOffsetX);
+        } else if (orientation == Surface.ROTATION_180) {
+            mTempDisplayRect.offset(-mDisplayOffsetX, -mDisplayOffsetY);
+        } else {  // Surface.ROTATION_270
+            mTempDisplayRect.offset(-mDisplayOffsetY, mDisplayOffsetX);
+        }
         device.setProjectionLocked(t, orientation, mTempLayerStackRect, mTempDisplayRect);
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3fd3945..d20508a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -17,9 +17,6 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.inputmethod.InputMethodSystemProperty.PER_PROFILE_IME_ENABLED;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -110,7 +107,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
-import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputBinding;
@@ -207,6 +204,8 @@
     static final int MSG_SET_ACTIVE = 3020;
     static final int MSG_SET_INTERACTIVE = 3030;
     static final int MSG_REPORT_FULLSCREEN_MODE = 3045;
+    static final int MSG_REPORT_PRE_RENDERED = 3060;
+    static final int MSG_APPLY_IME_VISIBILITY = 3070;
 
     static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
 
@@ -288,6 +287,8 @@
     private static final class DebugFlags {
         static final DebugFlag FLAG_OPTIMIZE_START_INPUT =
                 new DebugFlag("debug.optimize_startinput", false);
+        static final DebugFlag FLAG_PRE_RENDER_IME_VIEWS =
+                new DebugFlag("persist.pre_render_ime_views", false);
     }
 
     @UserIdInt
@@ -304,6 +305,7 @@
     final boolean mHasFeature;
     private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
             new ArrayMap<>();
+    private final boolean mIsLowRam;
     private final HardKeyboardListener mHardKeyboardListener;
     private final AppOpsManager mAppOpsManager;
     private final UserManager mUserManager;
@@ -403,6 +405,10 @@
         final ClientDeathRecipient clientDeathRecipient;
 
         boolean sessionRequested;
+        // Determines if IMEs should be pre-rendered.
+        // DebugFlag can be flipped anytime. This flag is kept per-client to maintain behavior
+        // through the life of the current client.
+        boolean shouldPreRenderIme;
         SessionState curSession;
 
         @Override
@@ -474,7 +480,7 @@
     IBinder mLastImeTargetWindow;
 
     /**
-     * {@link WindowManager.LayoutParams#softInputMode} of {@link #mCurFocusedWindow}.
+     * {@link LayoutParams#softInputMode} of {@link #mCurFocusedWindow}.
      *
      * @see #mCurFocusedWindow
      */
@@ -615,6 +621,10 @@
      * <dd>
      *   If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
      * </dd>
+     * dt>{@link InputMethodService#IME_INVISIBLE}</dt>
+     * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is
+     *    currently invisible.
+     * </dd>
      * </dl>
      * <em>Do not update this value outside of setImeWindowStatus.</em>
      */
@@ -1361,6 +1371,7 @@
         mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
         mHardKeyboardBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_externalHardKeyboardBehavior);
+        mIsLowRam = ActivityManager.isLowRamDeviceStatic();
 
         Bundle extras = new Bundle();
         extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true);
@@ -1393,7 +1404,7 @@
 
         // mSettings should be created before buildInputMethodListLocked
         mSettings = new InputMethodSettings(
-                mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady);
+                mRes, context.getContentResolver(), mMethodMap, userId, !mSystemReady);
 
         updateCurrentProfileIds();
         AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
@@ -1682,7 +1693,7 @@
         queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
                 methodList);
         final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
-                mContext.getContentResolver(), methodMap, methodList, userId, true);
+                mContext.getContentResolver(), methodMap, userId, true);
         return settings.getEnabledInputMethodListLocked();
     }
 
@@ -1738,7 +1749,7 @@
             return Collections.emptyList();
         }
         final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
-                mContext.getContentResolver(), methodMap, methodList, userId, true);
+                mContext.getContentResolver(), methodMap, userId, true);
         return settings.getEnabledInputMethodSubtypeListLocked(
                 mContext, imi, allowsImplicitlySelectedSubtypes);
     }
@@ -2028,7 +2039,8 @@
                     Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
                             + mCurTokenDisplayId);
                 }
-                mIWindowManager.addWindowToken(mCurToken, TYPE_INPUT_METHOD, mCurTokenDisplayId);
+                mIWindowManager.addWindowToken(mCurToken, LayoutParams.TYPE_INPUT_METHOD,
+                        mCurTokenDisplayId);
             } catch (RemoteException e) {
             }
             return new InputBindResult(
@@ -2264,7 +2276,10 @@
         if (mSwitchingDialog != null) return false;
         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
                 && mKeyguardManager != null && mKeyguardManager.isKeyguardSecure()) return false;
-        if ((visibility & InputMethodService.IME_ACTIVE) == 0) return false;
+        if ((visibility & InputMethodService.IME_ACTIVE) == 0
+                || (visibility & InputMethodService.IME_INVISIBLE) != 0) {
+            return false;
+        }
         if (mWindowManagerInternal.isHardKeyboardAvailable()) {
             if (mHardKeyboardBehavior == HardKeyboardBehavior.WIRELESS_AFFORDANCE) {
                 // When physical keyboard is attached, we show the ime switcher (or notification if
@@ -2372,6 +2387,12 @@
         if (mCurToken == null) {
             return;
         }
+        if (DEBUG) {
+            Slog.d(TAG, "IME window vis: " + vis
+                    + " active: " + (vis & InputMethodService.IME_ACTIVE)
+                    + " inv: " + (vis & InputMethodService.IME_INVISIBLE));
+        }
+
         // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
         // all updateSystemUi happens on system previlege.
         final long ident = Binder.clearCallingIdentity();
@@ -2734,9 +2755,34 @@
             Slog.e(TAG, "windowToken cannot be null.");
             return InputBindResult.NULL;
         }
-        final InputBindResult result = startInputOrWindowGainedFocusInternal(startInputReason,
-                client, windowToken, startInputFlags, softInputMode, windowFlags, attribute,
-                inputContext, missingMethods, unverifiedTargetSdkVersion);
+        final int callingUserId = UserHandle.getCallingUserId();
+        final int userId;
+        if (attribute != null && attribute.targetInputMethodUser != null
+                && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
+            mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "Using EditorInfo.targetInputMethodUser requires INTERACT_ACROSS_USERS_FULL.");
+            userId = attribute.targetInputMethodUser.getIdentifier();
+            if (!mUserManagerInternal.isUserRunning(userId)) {
+                // There is a chance that we hit here because of race condition.  Let's just return
+                // an error code instead of crashing the caller process, which at least has
+                // INTERACT_ACROSS_USERS_FULL permission thus is likely to be an important process.
+                Slog.e(TAG, "User #" + userId + " is not running.");
+                return InputBindResult.INVALID_USER;
+            }
+        } else {
+            userId = callingUserId;
+        }
+        final InputBindResult result;
+        synchronized (mMethodMap) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client,
+                        windowToken, startInputFlags, softInputMode, windowFlags, attribute,
+                        inputContext, missingMethods, unverifiedTargetSdkVersion, userId);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
         if (result == null) {
             // This must never happen, but just in case.
             Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
@@ -2749,234 +2795,212 @@
     }
 
     @NonNull
-    private InputBindResult startInputOrWindowGainedFocusInternal(
+    private InputBindResult startInputOrWindowGainedFocusInternalLocked(
             @StartInputReason int startInputReason, IInputMethodClient client,
             @NonNull IBinder windowToken, @StartInputFlags int startInputFlags,
             @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute,
             IInputContext inputContext, @MissingMethodFlags int missingMethods,
-            int unverifiedTargetSdkVersion) {
-        final int callingUserId = UserHandle.getCallingUserId();
-        final int userId;
-        if (attribute != null && attribute.targetInputMethodUser != null
-                && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
-            mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                    "Using EditorInfo.user requires INTERACT_ACROSS_USERS_FULL.");
-            userId = attribute.targetInputMethodUser.getIdentifier();
-            if (!mUserManagerInternal.isUserRunning(userId)) {
-                // There is a chance that we hit here because of race condition.  Let's just return
-                // an error code instead of crashing the caller process, which at least has
-                // INTERACT_ACROSS_USERS_FULL permission thus is likely to be an important process.
-                Slog.e(TAG, "User #" + userId + " is not running.");
-                return InputBindResult.INVALID_USER;
-            }
-        } else {
-            userId = callingUserId;
+            int unverifiedTargetSdkVersion, @UserIdInt int userId) {
+        if (DEBUG) {
+            Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
+                    + InputMethodDebug.startInputReasonToString(startInputReason)
+                    + " client=" + client.asBinder()
+                    + " inputContext=" + inputContext
+                    + " missingMethods="
+                    + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
+                    + " attribute=" + attribute
+                    + " startInputFlags="
+                    + InputMethodDebug.startInputFlagsToString(startInputFlags)
+                    + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
+                    + " windowFlags=#" + Integer.toHexString(windowFlags)
+                    + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion);
         }
+
+        final int windowDisplayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
+
+        final ClientState cs = mClients.get(client.asBinder());
+        if (cs == null) {
+            throw new IllegalArgumentException("unknown client " + client.asBinder());
+        }
+        if (cs.selfReportedDisplayId != windowDisplayId) {
+            Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch."
+                    + " from client:" + cs.selfReportedDisplayId
+                    + " from window:" + windowDisplayId);
+            return InputBindResult.DISPLAY_ID_MISMATCH;
+        }
+
+        if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid,
+                cs.selfReportedDisplayId)) {
+            // Check with the window manager to make sure this client actually
+            // has a window with focus.  If not, reject.  This is thread safe
+            // because if the focus changes some time before or after, the
+            // next client receiving focus that has any interest in input will
+            // be calling through here after that change happens.
+            if (DEBUG) {
+                Slog.w(TAG, "Focus gain on non-focused client " + cs.client
+                        + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
+            }
+            return InputBindResult.NOT_IME_TARGET_WINDOW;
+        }
+
+        // cross-profile access is always allowed here to allow profile-switching.
+        if (!mSettings.isCurrentProfile(userId)) {
+            Slog.w(TAG, "A background user is requesting window. Hiding IME.");
+            Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
+                    + " a background user, use EditorInfo.targetInputMethodUser with"
+                    + " INTERACT_ACROSS_USERS_FULL permission.");
+            hideCurrentInputLocked(0, null);
+            return InputBindResult.INVALID_USER;
+        }
+
+        if (PER_PROFILE_IME_ENABLED && userId != mSettings.getCurrentUserId()) {
+            switchUserLocked(userId);
+        }
+        // Master feature flag that overrides other conditions and forces IME preRendering.
+        if (DEBUG) {
+            Slog.v(TAG, "IME PreRendering MASTER flag: "
+                    + DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() + ", LowRam: " + mIsLowRam);
+        }
+        // pre-rendering not supported on low-ram devices.
+        cs.shouldPreRenderIme = DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() && !mIsLowRam;
+
+        if (mCurFocusedWindow == windowToken) {
+            if (DEBUG) {
+                Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
+                        + " attribute=" + attribute + ", token = " + windowToken);
+            }
+            if (attribute != null) {
+                return startInputUncheckedLocked(cs, inputContext, missingMethods,
+                        attribute, startInputFlags, startInputReason);
+            }
+            return new InputBindResult(
+                    InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
+                    null, null, null, -1);
+        }
+        mCurFocusedWindow = windowToken;
+        mCurFocusedWindowSoftInputMode = softInputMode;
+        mCurFocusedWindowClient = cs;
+
+        // Should we auto-show the IME even if the caller has not
+        // specified what should be done with it?
+        // We only do this automatically if the window can resize
+        // to accommodate the IME (so what the user sees will give
+        // them good context without input information being obscured
+        // by the IME) or if running on a large screen where there
+        // is more room for the target window + IME.
+        final boolean doAutoShow =
+                (softInputMode & LayoutParams.SOFT_INPUT_MASK_ADJUST)
+                        == LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+                || mRes.getConfiguration().isLayoutSizeAtLeast(
+                        Configuration.SCREENLAYOUT_SIZE_LARGE);
+        final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
+
+        // We want to start input before showing the IME, but after closing
+        // it.  We want to do this after closing it to help the IME disappear
+        // more quickly (not get stuck behind it initializing itself for the
+        // new focused input, even if its window wants to hide the IME).
+        boolean didStart = false;
+
         InputBindResult res = null;
-        synchronized (mMethodMap) {
-            final int windowDisplayId =
-                    mWindowManagerInternal.getDisplayIdForWindow(windowToken);
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                if (DEBUG) Slog.v(TAG, "startInputOrWindowGainedFocusInternal: reason="
-                        + InputMethodDebug.startInputReasonToString(startInputReason)
-                        + " client=" + client.asBinder()
-                        + " inputContext=" + inputContext
-                        + " missingMethods="
-                        + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
-                        + " attribute=" + attribute
-                        + " startInputFlags="
-                        + InputMethodDebug.startInputFlagsToString(startInputFlags)
-                        + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
-                        + " windowFlags=#" + Integer.toHexString(windowFlags)
-                        + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion);
+        switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) {
+            case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
+                if (!isTextEditor || !doAutoShow) {
+                    if (LayoutParams.mayUseInputMethod(windowFlags)) {
+                        // There is no focus view, and this window will
+                        // be behind any soft input window, so hide the
+                        // soft input window if it is shown.
+                        if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
+                        hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
 
-                ClientState cs = mClients.get(client.asBinder());
-                if (cs == null) {
-                    throw new IllegalArgumentException("unknown client "
-                            + client.asBinder());
-                }
-                if (cs.selfReportedDisplayId != windowDisplayId) {
-                    Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch."
-                            + " from client:" + cs.selfReportedDisplayId
-                            + " from window:" + windowDisplayId);
-                    return InputBindResult.DISPLAY_ID_MISMATCH;
-                }
-
-                if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid,
-                        cs.selfReportedDisplayId)) {
-                    // Check with the window manager to make sure this client actually
-                    // has a window with focus.  If not, reject.  This is thread safe
-                    // because if the focus changes some time before or after, the
-                    // next client receiving focus that has any interest in input will
-                    // be calling through here after that change happens.
-                    if (DEBUG) {
-                        Slog.w(TAG, "Focus gain on non-focused client " + cs.client
-                                + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
+                        // If focused display changed, we should unbind current method
+                        // to make app window in previous display relayout after Ime
+                        // window token removed.
+                        // Note that we can trust client's display ID as long as it matches
+                        // to the display ID obtained from the window.
+                        if (cs.selfReportedDisplayId != mCurTokenDisplayId) {
+                            unbindCurrentMethodLocked();
+                        }
                     }
-                    return InputBindResult.NOT_IME_TARGET_WINDOW;
-                }
-
-                // cross-profile access is always allowed here to allow profile-switching.
-                if (!mSettings.isCurrentProfile(userId)) {
-                    Slog.w(TAG, "A background user is requesting window. Hiding IME.");
-                    Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
-                            + " a background user, use EditorInfo.targetInputMethodUser with"
-                            + " INTERACT_ACROSS_USERS_FULL permission.");
-                    hideCurrentInputLocked(0, null);
-                    return InputBindResult.INVALID_USER;
-                }
-
-                if (PER_PROFILE_IME_ENABLED && userId != mSettings.getCurrentUserId()) {
-                    switchUserLocked(userId);
-                }
-
-                if (mCurFocusedWindow == windowToken) {
-                    if (DEBUG) {
-                        Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
-                                + " attribute=" + attribute + ", token = " + windowToken);
-                    }
+                } else if (isTextEditor && doAutoShow
+                        && (softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
+                    // There is a focus view, and we are navigating forward
+                    // into the window, so show the input window for the user.
+                    // We only do this automatically if the window can resize
+                    // to accommodate the IME (so what the user sees will give
+                    // them good context without input information being obscured
+                    // by the IME) or if running on a large screen where there
+                    // is more room for the target window + IME.
+                    if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
                     if (attribute != null) {
-                        return startInputUncheckedLocked(cs, inputContext, missingMethods,
+                        res = startInputUncheckedLocked(cs, inputContext, missingMethods,
                                 attribute, startInputFlags, startInputReason);
+                        didStart = true;
                     }
-                    return new InputBindResult(
-                            InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
-                            null, null, null, -1);
+                    showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
                 }
-                mCurFocusedWindow = windowToken;
-                mCurFocusedWindowSoftInputMode = softInputMode;
-                mCurFocusedWindowClient = cs;
-
-                // Should we auto-show the IME even if the caller has not
-                // specified what should be done with it?
-                // We only do this automatically if the window can resize
-                // to accommodate the IME (so what the user sees will give
-                // them good context without input information being obscured
-                // by the IME) or if running on a large screen where there
-                // is more room for the target window + IME.
-                final boolean doAutoShow =
-                        (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
-                                == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
-                        || mRes.getConfiguration().isLayoutSizeAtLeast(
-                                Configuration.SCREENLAYOUT_SIZE_LARGE);
-                final boolean isTextEditor =
-                        (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
-
-                // We want to start input before showing the IME, but after closing
-                // it.  We want to do this after closing it to help the IME disappear
-                // more quickly (not get stuck behind it initializing itself for the
-                // new focused input, even if its window wants to hide the IME).
-                boolean didStart = false;
-
-                switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
-                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
-                        if (!isTextEditor || !doAutoShow) {
-                            if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
-                                // There is no focus view, and this window will
-                                // be behind any soft input window, so hide the
-                                // soft input window if it is shown.
-                                if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
-                                hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
-
-                                // If focused display changed, we should unbind current method
-                                // to make app window in previous display relayout after Ime
-                                // window token removed.
-                                // Note that we can trust client's display ID as long as it matches
-                                // to the display ID obtained from the window.
-                                if (cs.selfReportedDisplayId != mCurTokenDisplayId) {
-                                    unbindCurrentMethodLocked();
-                                }
-                            }
-                        } else if (isTextEditor && doAutoShow && (softInputMode &
-                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
-                            // There is a focus view, and we are navigating forward
-                            // into the window, so show the input window for the user.
-                            // We only do this automatically if the window can resize
-                            // to accommodate the IME (so what the user sees will give
-                            // them good context without input information being obscured
-                            // by the IME) or if running on a large screen where there
-                            // is more room for the target window + IME.
-                            if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
-                            if (attribute != null) {
-                                res = startInputUncheckedLocked(cs, inputContext, missingMethods,
-                                        attribute, startInputFlags, startInputReason);
-                                didStart = true;
-                            }
-                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
-                        }
-                        break;
-                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
-                        // Do nothing.
-                        break;
-                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
-                        if ((softInputMode &
-                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
-                            if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
-                            hideCurrentInputLocked(0, null);
-                        }
-                        break;
-                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
-                        if (DEBUG) Slog.v(TAG, "Window asks to hide input");
-                        hideCurrentInputLocked(0, null);
-                        break;
-                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
-                        if ((softInputMode &
-                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
-                            if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
-                            if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
-                                    unverifiedTargetSdkVersion, startInputFlags)) {
-                                if (attribute != null) {
-                                    res = startInputUncheckedLocked(cs, inputContext,
-                                            missingMethods, attribute, startInputFlags,
-                                            startInputReason);
-                                    didStart = true;
-                                }
-                                showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
-                            } else {
-                                Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
-                                        + " there is no focused view that also returns true from"
-                                        + " View#onCheckIsTextEditor()");
-                            }
-                        }
-                        break;
-                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
-                        if (DEBUG) Slog.v(TAG, "Window asks to always show input");
-                        if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
-                                unverifiedTargetSdkVersion, startInputFlags)) {
-                            if (attribute != null) {
-                                res = startInputUncheckedLocked(cs, inputContext, missingMethods,
-                                        attribute, startInputFlags, startInputReason);
-                                didStart = true;
-                            }
-                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
-                        } else {
-                            Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
-                                    + " there is no focused view that also returns true from"
-                                    + " View#onCheckIsTextEditor()");
-                        }
-                        break;
+                break;
+            case LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
+                // Do nothing.
+                break;
+            case LayoutParams.SOFT_INPUT_STATE_HIDDEN:
+                if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
+                    if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
+                    hideCurrentInputLocked(0, null);
                 }
-
-                if (!didStart) {
-                    if (attribute != null) {
-                        if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
-                                || (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) {
+                break;
+            case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
+                if (DEBUG) Slog.v(TAG, "Window asks to hide input");
+                hideCurrentInputLocked(0, null);
+                break;
+            case LayoutParams.SOFT_INPUT_STATE_VISIBLE:
+                if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
+                    if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
+                    if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
+                            unverifiedTargetSdkVersion, startInputFlags)) {
+                        if (attribute != null) {
                             res = startInputUncheckedLocked(cs, inputContext, missingMethods,
-                                    attribute,
-                                    startInputFlags, startInputReason);
-                        } else {
-                            res = InputBindResult.NO_EDITOR;
+                                    attribute, startInputFlags, startInputReason);
+                            didStart = true;
                         }
+                        showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
                     } else {
-                        res = InputBindResult.NULL_EDITOR_INFO;
+                        Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
+                                + " there is no focused view that also returns true from"
+                                + " View#onCheckIsTextEditor()");
                     }
                 }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
+                break;
+            case LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
+                if (DEBUG) Slog.v(TAG, "Window asks to always show input");
+                if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
+                        unverifiedTargetSdkVersion, startInputFlags)) {
+                    if (attribute != null) {
+                        res = startInputUncheckedLocked(cs, inputContext, missingMethods,
+                                attribute, startInputFlags, startInputReason);
+                        didStart = true;
+                    }
+                    showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
+                } else {
+                    Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
+                            + " there is no focused view that also returns true from"
+                            + " View#onCheckIsTextEditor()");
+                }
+                break;
         }
 
+        if (!didStart) {
+            if (attribute != null) {
+                if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
+                        || (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) {
+                    res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
+                            startInputFlags, startInputReason);
+                } else {
+                    res = InputBindResult.NO_EDITOR;
+                }
+            } else {
+                res = InputBindResult.NULL_EDITOR_INFO;
+            }
+        }
         return res;
     }
 
@@ -3298,6 +3322,32 @@
         }
     }
 
+    @BinderThread
+    private void reportPreRendered(IBinder token, EditorInfo info) {
+        synchronized (mMethodMap) {
+            if (!calledWithValidTokenLocked(token)) {
+                return;
+            }
+            if (mCurClient != null && mCurClient.client != null) {
+                executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
+                        MSG_REPORT_PRE_RENDERED, info, mCurClient));
+            }
+        }
+    }
+
+    @BinderThread
+    private void applyImeVisibility(IBinder token, boolean setVisible) {
+        synchronized (mMethodMap) {
+            if (!calledWithValidTokenLocked(token)) {
+                return;
+            }
+            if (mCurClient != null && mCurClient.client != null) {
+                executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
+                        MSG_APPLY_IME_VISIBILITY, setVisible ? 1 : 0, mCurClient));
+            }
+        }
+    }
+
     private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) {
         if (token == null) {
             if (mContext.checkCallingOrSelfPermission(
@@ -3493,7 +3543,7 @@
                 try {
                     setEnabledSessionInMainThread(session);
                     session.method.startInput(startInputToken, inputContext, missingMethods,
-                            editorInfo, restarting);
+                            editorInfo, restarting, session.client.shouldPreRenderIme);
                 } catch (RemoteException e) {
                 }
                 args.recycle();
@@ -3551,6 +3601,32 @@
                 }
                 return true;
             }
+            case MSG_REPORT_PRE_RENDERED: {
+                args = (SomeArgs) msg.obj;
+                final EditorInfo info = (EditorInfo) args.arg1;
+                final ClientState clientState = (ClientState) args.arg2;
+                try {
+                    clientState.client.reportPreRendered(info);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Got RemoteException sending "
+                            + "reportPreRendered(" + info + ") notification to pid="
+                            + clientState.pid + " uid=" + clientState.uid);
+                }
+                args.recycle();
+                return true;
+            }
+            case MSG_APPLY_IME_VISIBILITY: {
+                final boolean setVisible = msg.arg1 != 0;
+                final ClientState clientState = (ClientState) msg.obj;
+                try {
+                    clientState.client.applyImeVisibility(setVisible);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Got RemoteException sending "
+                            + "applyImeVisibility(" + setVisible + ") notification to pid="
+                            + clientState.pid + " uid=" + clientState.uid);
+                }
+                return true;
+            }
 
             // --------------------------------------------------------------
             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
@@ -3892,13 +3968,13 @@
             mSwitchingDialog = mDialogBuilder.create();
             mSwitchingDialog.setCanceledOnTouchOutside(true);
             final Window w = mSwitchingDialog.getWindow();
-            final WindowManager.LayoutParams attrs = w.getAttributes();
-            w.setType(TYPE_INPUT_METHOD_DIALOG);
+            final LayoutParams attrs = w.getAttributes();
+            w.setType(LayoutParams.TYPE_INPUT_METHOD_DIALOG);
             // Use an alternate token for the dialog for that window manager can group the token
             // with other IME windows based on type vs. grouping based on whichever token happens
             // to get selected by the system later on.
             attrs.token = mSwitchingDialogToken;
-            attrs.privateFlags |= PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+            attrs.privateFlags |= LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
             attrs.setTitle("Select input method");
             w.setAttributes(attrs);
             updateSystemUiLocked(mImeWindowVis, mBackDisposition);
@@ -4421,6 +4497,7 @@
         @ShellCommandResult
         private int refreshDebugProperties() {
             DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh();
+            DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.refresh();
             return ShellCommandResult.SUCCESS;
         }
 
@@ -4726,5 +4803,17 @@
         public void notifyUserAction() {
             mImms.notifyUserAction(mToken);
         }
+
+        @BinderThread
+        @Override
+        public void reportPreRendered(EditorInfo info) {
+            mImms.reportPreRendered(mToken, info);
+        }
+
+        @BinderThread
+        @Override
+        public void applyImeVisibility(boolean setVisible) {
+            mImms.applyImeVisibility(mToken, setVisible);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 88d1a9c..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;
@@ -66,9 +67,9 @@
  */
 final class InputMethodUtils {
     public static final boolean DEBUG = false;
-    public static final int NOT_A_SUBTYPE_ID = -1;
-    public static final String SUBTYPE_MODE_ANY = null;
-    public static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+    static final int NOT_A_SUBTYPE_ID = -1;
+    private static final String SUBTYPE_MODE_ANY = null;
+    static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
     private static final String TAG = "InputMethodUtils";
     private static final Locale ENGLISH_LOCALE = new Locale("en");
     private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
@@ -108,7 +109,7 @@
 
     // ----------------------------------------------------------------------
     // Utilities for debug
-    public static String getApiCallStack() {
+    static String getApiCallStack() {
         String apiCallStack = "";
         try {
             throw new RuntimeException();
@@ -131,10 +132,9 @@
     }
     // ----------------------------------------------------------------------
 
-    public static boolean isSystemImeThatHasSubtypeOf(final InputMethodInfo imi,
-            final Context context, final boolean checkDefaultAttribute,
-            @Nullable final Locale requiredLocale, final boolean checkCountry,
-            final String requiredSubtypeMode) {
+    private static boolean isSystemImeThatHasSubtypeOf(InputMethodInfo imi, Context context,
+            boolean checkDefaultAttribute, @Nullable Locale requiredLocale, boolean checkCountry,
+            String requiredSubtypeMode) {
         if (!imi.isSystem()) {
             return false;
         }
@@ -148,8 +148,8 @@
     }
 
     @Nullable
-    public static Locale getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis,
-            final Context context) {
+    private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis,
+            Context context) {
         // At first, find the fallback locale from the IMEs that are declared as "default" in the
         // current locale.  Note that IME developers can declare an IME as "default" only for
         // some particular locales but "not default" for other locales.
@@ -177,8 +177,8 @@
         return null;
     }
 
-    private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(final InputMethodInfo imi,
-            final Context context, final boolean checkDefaultAttribute) {
+    private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi,
+            Context context, boolean checkDefaultAttribute) {
         if (!imi.isSystem()) {
             return false;
         }
@@ -198,7 +198,7 @@
         return false;
     }
 
-    public static Locale getSystemLocaleFromContext(final Context context) {
+    private static Locale getSystemLocaleFromContext(Context context) {
         try {
             return context.getResources().getConfiguration().locale;
         } catch (Resources.NotFoundException ex) {
@@ -212,10 +212,9 @@
         @NonNull
         private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>();
 
-        public InputMethodListBuilder fillImes(final ArrayList<InputMethodInfo> imis,
-                final Context context, final boolean checkDefaultAttribute,
-                @Nullable final Locale locale, final boolean checkCountry,
-                final String requiredSubtypeMode) {
+        InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context,
+                boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry,
+                String requiredSubtypeMode) {
             for (int i = 0; i < imis.size(); ++i) {
                 final InputMethodInfo imi = imis.get(i);
                 if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale,
@@ -228,8 +227,7 @@
 
         // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
         // documented more clearly.
-        public InputMethodListBuilder fillAuxiliaryImes(final ArrayList<InputMethodInfo> imis,
-                final Context context) {
+        InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) {
             // If one or more auxiliary input methods are available, OK to stop populating the list.
             for (final InputMethodInfo imi : mInputMethodSet) {
                 if (imi.isAuxiliaryIme()) {
@@ -269,8 +267,8 @@
     }
 
     private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
-            final ArrayList<InputMethodInfo> imis, final Context context,
-            @Nullable final Locale systemLocale, @Nullable final Locale fallbackLocale) {
+            ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale,
+            @Nullable Locale fallbackLocale) {
         // Once the system becomes ready, we pick up at least one keyboard in the following order.
         // Secondary users fall into this category in general.
         // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true
@@ -317,7 +315,7 @@
         return builder;
     }
 
-    public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
+    static ArrayList<InputMethodInfo> getDefaultEnabledImes(
             Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) {
         final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
         // We will primarily rely on the system locale, but also keep relying on the fallback locale
@@ -336,13 +334,13 @@
         return builder.build();
     }
 
-    public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
+    static ArrayList<InputMethodInfo> getDefaultEnabledImes(
             Context context, ArrayList<InputMethodInfo> imis) {
         return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
     }
 
-    public static boolean containsSubtypeOf(final InputMethodInfo imi,
-            @Nullable final Locale locale, final boolean checkCountry, final String mode) {
+    static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale,
+            boolean checkCountry, String mode) {
         if (locale == null) {
             return false;
         }
@@ -371,7 +369,7 @@
         return false;
     }
 
-    public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
+    static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
         ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
         final int subtypeCount = imi.getSubtypeCount();
         for (int i = 0; i < subtypeCount; ++i) {
@@ -380,7 +378,7 @@
         return subtypes;
     }
 
-    public static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
+    static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
         if (enabledImes == null || enabledImes.isEmpty()) {
             return null;
         }
@@ -404,11 +402,11 @@
         return enabledImes.get(Math.max(firstFoundSystemIme, 0));
     }
 
-    public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
+    static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
         return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
     }
 
-    public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
+    static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
         if (imi != null) {
             final int subtypeCount = imi.getSubtypeCount();
             for (int i = 0; i < subtypeCount; ++i) {
@@ -430,7 +428,7 @@
             };
 
     @VisibleForTesting
-    public static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
+    static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
             Resources res, InputMethodInfo imi) {
         final LocaleList systemLocales = res.getConfiguration().getLocales();
 
@@ -564,7 +562,7 @@
      * it will return the first subtype matched with mode
      * @return the most applicable subtypeId
      */
-    public static InputMethodSubtype findLastResortApplicableSubtypeLocked(
+    static InputMethodSubtype findLastResortApplicableSubtypeLocked(
             Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
             boolean canIgnoreLocaleAsLastResort) {
         if (subtypes == null || subtypes.size() == 0) {
@@ -615,14 +613,13 @@
         return applicableSubtype;
     }
 
-    public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
+    static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
         if (subtype == null) return true;
         return !subtype.isAuxiliary();
     }
 
-    public static void setNonSelectedSystemImesDisabledUntilUsed(
-            IPackageManager packageManager, List<InputMethodInfo> enabledImis,
-            int userId, String callingPackage) {
+    static void setNonSelectedSystemImesDisabledUntilUsed(IPackageManager packageManager,
+            List<InputMethodInfo> enabledImis, @UserIdInt int userId, String callingPackage) {
         if (DEBUG) {
             Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
         }
@@ -710,7 +707,7 @@
         }
     }
 
-    public static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
+    static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
             InputMethodSubtype subtype) {
         final CharSequence imiLabel = imi.loadLabel(context.getPackageManager());
         return subtype != null
@@ -730,8 +727,8 @@
      * @param packageName the package name.
      * @return {@code true} if the package name belongs to the UID.
      */
-    public static boolean checkIfPackageBelongsToUid(final AppOpsManager appOpsManager,
-            final int uid, final String packageName) {
+    static boolean checkIfPackageBelongsToUid(AppOpsManager appOpsManager,
+            @UserIdInt int uid, String packageName) {
         try {
             appOpsManager.checkPackage(uid, packageName);
             return true;
@@ -760,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 = "";
@@ -802,10 +807,9 @@
             return imsList;
         }
 
-        public InputMethodSettings(
-                Resources res, ContentResolver resolver,
-                ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
-                @UserIdInt int userId, boolean copyOnWrite) {
+        InputMethodSettings(Resources res, ContentResolver resolver,
+                ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId,
+                boolean copyOnWrite) {
             mRes = res;
             mResolver = resolver;
             mMethodMap = methodMap;
@@ -820,7 +824,7 @@
          * (e.g. {@link Settings.Secure#ACTION_INPUT_METHOD_SUBTYPE_SETTINGS}) we use the actual
          * settings on the {@link Settings.Secure} until we do the first write operation.
          */
-        public void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
+        void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
             if (DEBUG) {
                 Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId);
             }
@@ -834,16 +838,18 @@
             // TODO: mCurrentProfileIds should be updated here.
         }
 
-        private void putString(@NonNull final String key, @Nullable final String str) {
+        private void putString(@NonNull String key, @Nullable String str) {
             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);
             }
         }
 
         @Nullable
-        private String getString(@NonNull final String key, @Nullable final String defaultValue) {
+        private String getString(@NonNull String key, @Nullable String defaultValue) {
             final String result;
             if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
                 result = mCopyOnWriteDataStore.get(key);
@@ -853,27 +859,29 @@
             return result != null ? result : defaultValue;
         }
 
-        private void putInt(final String key, final int value) {
+        private void putInt(String key, int value) {
             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(final String key, final int defaultValue) {
+        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);
         }
 
-        private void putBoolean(final String key, final boolean value) {
+        private void putBoolean(String key, boolean value) {
             putInt(key, value ? 1 : 0);
         }
 
-        private boolean getBoolean(final String key, final boolean defaultValue) {
+        private boolean getBoolean(String key, boolean defaultValue) {
             return getInt(key, defaultValue ? 1 : 0) == 1;
         }
 
@@ -893,12 +901,12 @@
             }
         }
 
-        public ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
+        ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
             return createEnabledInputMethodListLocked(
                     getEnabledInputMethodsAndSubtypeListLocked());
         }
 
-        public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
+        List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
                 Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
             List<InputMethodSubtype> enabledSubtypes =
                     getEnabledInputMethodSubtypeListLocked(imi);
@@ -909,8 +917,7 @@
             return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
         }
 
-        public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
-                InputMethodInfo imi) {
+        List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
             List<Pair<String, ArrayList<String>>> imsList =
                     getEnabledInputMethodsAndSubtypeListLocked();
             ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
@@ -934,13 +941,13 @@
             return enabledSubtypes;
         }
 
-        public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
+        List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
             return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
                     mInputMethodSplitter,
                     mSubtypeSplitter);
         }
 
-        public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
+        void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
             if (reloadInputMethodStr) {
                 getEnabledInputMethodsStr();
             }
@@ -957,7 +964,7 @@
          * Build and put a string of EnabledInputMethods with removing specified Id.
          * @return the specified id was removed or not.
          */
-        public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+        boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
                 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
             boolean isRemoved = false;
             boolean needsAppendSeparator = false;
@@ -1012,7 +1019,7 @@
         }
 
         @NonNull
-        public String getEnabledInputMethodsStr() {
+        String getEnabledInputMethodsStr() {
             mEnabledInputMethodsStrCache = getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
             if (DEBUG) {
                 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
@@ -1080,12 +1087,12 @@
             }
         }
 
-        public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
+        Pair<String, String> getLastInputMethodAndSubtypeLocked() {
             // Gets the first one from the history
             return getLastSubtypeForInputMethodLockedInternal(null);
         }
 
-        public String getLastSubtypeForInputMethodLocked(String imeId) {
+        String getLastSubtypeForInputMethodLocked(String imeId) {
             Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
             if (ime != null) {
                 return ime.second;
@@ -1203,7 +1210,7 @@
             return history;
         }
 
-        public void putSelectedInputMethod(String imeId) {
+        void putSelectedInputMethod(String imeId) {
             if (DEBUG) {
                 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
                         + mCurrentUserId);
@@ -1211,7 +1218,7 @@
             putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
         }
 
-        public void putSelectedSubtype(int subtypeId) {
+        void putSelectedSubtype(int subtypeId) {
             if (DEBUG) {
                 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
                         + mCurrentUserId);
@@ -1220,7 +1227,7 @@
         }
 
         @Nullable
-        public String getSelectedInputMethod() {
+        String getSelectedInputMethod() {
             final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
             if (DEBUG) {
                 Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
@@ -1228,7 +1235,7 @@
             return imi;
         }
 
-        public boolean isSubtypeSelected() {
+        boolean isSubtypeSelected() {
             return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
         }
 
@@ -1236,11 +1243,11 @@
             return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID);
         }
 
-        public boolean isShowImeWithHardKeyboardEnabled() {
+        boolean isShowImeWithHardKeyboardEnabled() {
             return getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, false);
         }
 
-        public void setShowImeWithHardKeyboard(boolean show) {
+        void setShowImeWithHardKeyboard(boolean show) {
             putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show);
         }
 
@@ -1249,7 +1256,7 @@
             return mCurrentUserId;
         }
 
-        public int getSelectedInputMethodSubtypeId(String selectedImiId) {
+        int getSelectedInputMethodSubtypeId(String selectedImiId) {
             final InputMethodInfo imi = mMethodMap.get(selectedImiId);
             if (imi == null) {
                 return NOT_A_SUBTYPE_ID;
@@ -1258,8 +1265,8 @@
             return getSubtypeIdFromHashCode(imi, subtypeHashCode);
         }
 
-        public void saveCurrentInputMethodAndSubtypeToHistory(
-                String curMethodId, InputMethodSubtype currentSubtype) {
+        void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
+                InputMethodSubtype currentSubtype) {
             String subtypeId = NOT_A_SUBTYPE_ID_STR;
             if (currentSubtype != null) {
                 subtypeId = String.valueOf(currentSubtype.hashCode());
@@ -1277,8 +1284,8 @@
         }
     }
 
-    public static boolean isSoftInputModeStateVisibleAllowed(
-            int targetSdkVersion, @StartInputFlags int startInputFlags) {
+    static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion,
+            @StartInputFlags int startInputFlags) {
         if (targetSdkVersion < Build.VERSION_CODES.P) {
             // for compatibility.
             return true;
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 7625aaf..65a078b 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -508,7 +508,7 @@
         private static final int DEFAULT_STANDBY_RARE_BEATS = 130; // ~ 24 hours
         private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
         private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
-        private static final boolean DEFAULT_USE_HEARTBEATS = true;
+        private static final boolean DEFAULT_USE_HEARTBEATS = false;
         private static final boolean DEFAULT_TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
         private static final long DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS =
                 10 * 60 * 1000L; // 10 minutes
@@ -2040,6 +2040,7 @@
                         if (DEBUG) {
                             Slog.d(TAG, "MSG_CHECK_JOB");
                         }
+                        removeMessages(MSG_CHECK_JOB);
                         if (mReportedActive) {
                             // if jobs are currently being run, queue all ready jobs for execution.
                             queueReadyJobsForExecutionLocked();
@@ -2099,7 +2100,6 @@
                 }
                 maybeRunPendingJobsLocked();
                 // Don't remove JOB_EXPIRED in case one came along while processing the queue.
-                removeMessages(MSG_CHECK_JOB);
             }
         }
     }
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 82bfa51..ed1a6d0 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -33,6 +33,7 @@
 import android.util.ArraySet;
 import android.util.Pair;
 import android.util.Slog;
+import android.util.StatsLog;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
@@ -40,6 +41,7 @@
 import com.android.server.job.GrantedUriPermissions;
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobServerProtoEnums;
 import com.android.server.job.JobStatusDumpProto;
 import com.android.server.job.JobStatusShortInfoProto;
 
@@ -80,6 +82,28 @@
     static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24;
     static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1<<22;
 
+    /**
+     * The constraints that we want to log to statsd.
+     *
+     * Constraints that can be inferred from other atoms have been excluded to avoid logging too
+     * much information and to reduce redundancy:
+     *
+     * * CONSTRAINT_CHARGING can be inferred with PluggedStateChanged (Atom #32)
+     * * CONSTRAINT_BATTERY_NOT_LOW can be inferred with BatteryLevelChanged (Atom #30)
+     * * CONSTRAINT_CONNECTIVITY can be partially inferred with ConnectivityStateChanged
+     * (Atom #98) and BatterySaverModeStateChanged (Atom #20).
+     * * CONSTRAINT_DEVICE_NOT_DOZING can be mostly inferred with DeviceIdleModeStateChanged
+     * (Atom #21)
+     * * CONSTRAINT_BACKGROUND_NOT_RESTRICTED can be inferred with BatterySaverModeStateChanged
+     * (Atom #20)
+     */
+    private static final int STATSD_CONSTRAINTS_TO_LOG = CONSTRAINT_CONTENT_TRIGGER
+            | CONSTRAINT_DEADLINE
+            | CONSTRAINT_IDLE
+            | CONSTRAINT_STORAGE_NOT_LOW
+            | CONSTRAINT_TIMING_DELAY
+            | CONSTRAINT_WITHIN_QUOTA;
+
     // Soft override: ignore constraints like time that don't affect API availability
     public static final int OVERRIDE_SOFT = 1;
     // Full override: ignore all constraints including API-affecting like connectivity
@@ -976,6 +1000,12 @@
         }
         satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0);
         mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
+        if ((STATSD_CONSTRAINTS_TO_LOG & constraint) != 0) {
+            StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED,
+                    sourceUid, null, getBatteryName(), getProtoConstraint(constraint),
+                    state ? StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED__STATE__SATISFIED
+                            : StatsLog.SCHEDULED_JOB_CONSTRAINT_CHANGED__STATE__UNSATISFIED);
+        }
         return true;
     }
 
@@ -1228,37 +1258,70 @@
         }
     }
 
+    /** Returns a {@link JobServerProtoEnums.Constraint} enum value for the given constraint. */
+    private int getProtoConstraint(int constraint) {
+        switch (constraint) {
+            case CONSTRAINT_BACKGROUND_NOT_RESTRICTED:
+                return JobServerProtoEnums.CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
+            case CONSTRAINT_BATTERY_NOT_LOW:
+                return JobServerProtoEnums.CONSTRAINT_BATTERY_NOT_LOW;
+            case CONSTRAINT_CHARGING:
+                return JobServerProtoEnums.CONSTRAINT_CHARGING;
+            case CONSTRAINT_CONNECTIVITY:
+                return JobServerProtoEnums.CONSTRAINT_CONNECTIVITY;
+            case CONSTRAINT_CONTENT_TRIGGER:
+                return JobServerProtoEnums.CONSTRAINT_CONTENT_TRIGGER;
+            case CONSTRAINT_DEADLINE:
+                return JobServerProtoEnums.CONSTRAINT_DEADLINE;
+            case CONSTRAINT_DEVICE_NOT_DOZING:
+                return JobServerProtoEnums.CONSTRAINT_DEVICE_NOT_DOZING;
+            case CONSTRAINT_IDLE:
+                return JobServerProtoEnums.CONSTRAINT_IDLE;
+            case CONSTRAINT_STORAGE_NOT_LOW:
+                return JobServerProtoEnums.CONSTRAINT_STORAGE_NOT_LOW;
+            case CONSTRAINT_TIMING_DELAY:
+                return JobServerProtoEnums.CONSTRAINT_TIMING_DELAY;
+            case CONSTRAINT_WITHIN_QUOTA:
+                return JobServerProtoEnums.CONSTRAINT_WITHIN_QUOTA;
+            default:
+                return JobServerProtoEnums.CONSTRAINT_UNKNOWN;
+        }
+    }
+
     /** Writes constraints to the given repeating proto field. */
     void dumpConstraints(ProtoOutputStream proto, long fieldId, int constraints) {
         if ((constraints & CONSTRAINT_CHARGING) != 0) {
-            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_CHARGING);
+            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CHARGING);
         }
         if ((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0) {
-            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_BATTERY_NOT_LOW);
+            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_BATTERY_NOT_LOW);
         }
         if ((constraints & CONSTRAINT_STORAGE_NOT_LOW) != 0) {
-            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_STORAGE_NOT_LOW);
+            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_STORAGE_NOT_LOW);
         }
         if ((constraints & CONSTRAINT_TIMING_DELAY) != 0) {
-            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_TIMING_DELAY);
+            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_TIMING_DELAY);
         }
         if ((constraints & CONSTRAINT_DEADLINE) != 0) {
-            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_DEADLINE);
+            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_DEADLINE);
         }
         if ((constraints & CONSTRAINT_IDLE) != 0) {
-            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_IDLE);
+            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_IDLE);
         }
         if ((constraints & CONSTRAINT_CONNECTIVITY) != 0) {
-            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_CONNECTIVITY);
+            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CONNECTIVITY);
         }
         if ((constraints & CONSTRAINT_CONTENT_TRIGGER) != 0) {
-            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_CONTENT_TRIGGER);
+            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_CONTENT_TRIGGER);
         }
         if ((constraints & CONSTRAINT_DEVICE_NOT_DOZING) != 0) {
-            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_DEVICE_NOT_DOZING);
+            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_DEVICE_NOT_DOZING);
         }
         if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) {
-            proto.write(fieldId, JobStatusDumpProto.CONSTRAINT_WITHIN_QUOTA);
+            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_WITHIN_QUOTA);
+        }
+        if ((constraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
+            proto.write(fieldId, JobServerProtoEnums.CONSTRAINT_BACKGROUND_NOT_RESTRICTED);
         }
     }
 
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/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java
index 591889d..ca9c0e0 100644
--- a/services/core/java/com/android/server/location/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java
@@ -19,9 +19,15 @@
 import android.annotation.SuppressLint;
 import android.app.AppOpsManager;
 import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
@@ -55,6 +61,8 @@
     private static final String LOCATION_PERMISSION_NAME =
             "android.permission.ACCESS_FINE_LOCATION";
 
+    private static final String[] NO_LOCATION_ENABLED_PROXY_APPS = new String[0];
+
     // Wakelocks
     private static final String WAKELOCK_KEY = TAG;
     private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
@@ -66,13 +74,16 @@
     private final Handler mHandler;
     private final Context mContext;
 
+    private boolean mIsMasterLocationSettingsEnabled = true;
+    private boolean mIsOnRoamingNetwork = false;
+
     // Number of non-framework location access proxy apps is expected to be small (< 5).
     private static final int HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS = 7;
     private HashMap<String, Boolean> mProxyAppToLocationPermissions = new HashMap<>(
             HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS);
 
     private PackageManager.OnPermissionsChangedListener mOnPermissionsChangedListener =
-            uid -> postEvent(() -> handlePermissionsChanged(uid));
+            uid -> runOnHandler(() -> handlePermissionsChanged(uid));
 
     GnssVisibilityControl(Context context, Looper looper) {
         mContext = context;
@@ -81,8 +92,15 @@
         mHandler = new Handler(looper);
         mAppOps = mContext.getSystemService(AppOpsManager.class);
         mPackageManager = mContext.getPackageManager();
+
+        // Set to empty proxy app list initially until the configuration properties are loaded.
+        updateNfwLocationAccessProxyAppsInGnssHal();
+
+        // Listen for proxy app package installation, removal events.
+        listenForProxyAppsPackageUpdates();
+        listenForRoamingNetworkUpdate();
+
         // TODO(b/122855984): Handle global location settings on/off.
-        // TODO(b/122856189): Handle roaming case.
     }
 
     void updateProxyApps(List<String> nfwLocationAccessProxyApps) {
@@ -90,18 +108,68 @@
         //       but rather piggy backs on the GnssLocationProvider SIM_STATE_CHANGED handling
         //       so that the order of processing is preserved. GnssLocationProvider should
         //       first load the new config parameters for the new SIM and then call this method.
-        postEvent(() -> handleSubscriptionOrSimChanged(nfwLocationAccessProxyApps));
+        runOnHandler(() -> handleUpdateProxyApps(nfwLocationAccessProxyApps));
+    }
+
+    void masterLocationSettingsUpdated(boolean enabled) {
+        runOnHandler(() -> handleMasterLocationSettingsUpdated(enabled));
     }
 
     void reportNfwNotification(String proxyAppPackageName, byte protocolStack,
             String otherProtocolStackName, byte requestor, String requestorId, byte responseType,
             boolean inEmergencyMode, boolean isCachedLocation) {
-        postEvent(() -> handleNfwNotification(
+        runOnHandler(() -> handleNfwNotification(
                 new NfwNotification(proxyAppPackageName, protocolStack, otherProtocolStackName,
                         requestor, requestorId, responseType, inEmergencyMode, isCachedLocation)));
     }
 
-    private void handleSubscriptionOrSimChanged(List<String> nfwLocationAccessProxyApps) {
+    private void listenForProxyAppsPackageUpdates() {
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        intentFilter.addDataScheme("package");
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action == null) {
+                    return;
+                }
+
+                switch (action) {
+                    case Intent.ACTION_PACKAGE_ADDED:
+                    case Intent.ACTION_PACKAGE_REMOVED:
+                    case Intent.ACTION_PACKAGE_REPLACED:
+                        String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+                        handleProxyAppPackageUpdate(pkgName, action);
+                        break;
+                }
+            }
+        }, UserHandle.ALL, intentFilter, null, mHandler);
+    }
+
+    private void handleProxyAppPackageUpdate(String pkgName, String action) {
+        final Boolean locationPermission = mProxyAppToLocationPermissions.get(pkgName);
+        // pkgName is not one of the proxy apps in our list.
+        if (locationPermission == null) {
+            return;
+        }
+
+        Log.i(TAG, "Proxy app " + pkgName + " package changed: " + action);
+        final boolean updatedLocationPermission = hasLocationPermission(pkgName);
+        if (locationPermission != updatedLocationPermission) {
+            // Permission changed. So, update the GNSS HAL with the updated list.
+            mProxyAppToLocationPermissions.put(pkgName, updatedLocationPermission);
+            updateNfwLocationAccessProxyAppsInGnssHal();
+        }
+    }
+
+    private void handleUpdateProxyApps(List<String> nfwLocationAccessProxyApps) {
+        if (!isProxyAppListUpdated(nfwLocationAccessProxyApps)) {
+            return;
+        }
+
         if (nfwLocationAccessProxyApps.isEmpty()) {
             // Stop listening for app permission changes. Clear the app list in GNSS HAL.
             if (!mProxyAppToLocationPermissions.isEmpty()) {
@@ -125,6 +193,27 @@
         updateNfwLocationAccessProxyAppsInGnssHal();
     }
 
+    private boolean isProxyAppListUpdated(List<String> nfwLocationAccessProxyApps) {
+        if (nfwLocationAccessProxyApps.size() != mProxyAppToLocationPermissions.size()) {
+            return true;
+        }
+
+        for (String nfwLocationAccessProxyApp : nfwLocationAccessProxyApps) {
+            if (!mProxyAppToLocationPermissions.containsKey(nfwLocationAccessProxyApp)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private void handleMasterLocationSettingsUpdated(boolean enabled) {
+        mIsMasterLocationSettingsEnabled = enabled;
+        Log.i(TAG, "Master location settings switch changed to "
+                + (enabled ? "enabled" : "disabled"));
+        updateNfwLocationAccessProxyAppsInGnssHal();
+    }
+
     // Represents NfwNotification structure in IGnssVisibilityControlCallback.hal
     private static class NfwNotification {
         private static final String KEY_PROTOCOL_STACK = "ProtocolStack";
@@ -149,7 +238,7 @@
         private final boolean mInEmergencyMode;
         private final boolean mIsCachedLocation;
 
-        NfwNotification(String proxyAppPackageName, byte protocolStack,
+        private NfwNotification(String proxyAppPackageName, byte protocolStack,
                 String otherProtocolStackName, byte requestor, String requestorId,
                 byte responseType, boolean inEmergencyMode, boolean isCachedLocation) {
             mProxyAppPackageName = proxyAppPackageName;
@@ -162,7 +251,7 @@
             mIsCachedLocation = isCachedLocation;
         }
 
-        void copyFieldsToIntent(Intent intent) {
+        private void copyFieldsToIntent(Intent intent) {
             intent.putExtra(KEY_PROTOCOL_STACK, mProtocolStack);
             if (!TextUtils.isEmpty(mOtherProtocolStackName)) {
                 intent.putExtra(KEY_OTHER_PROTOCOL_STACK_NAME, mOtherProtocolStackName);
@@ -188,7 +277,7 @@
                     mRequestor, mRequestorId, mResponseType, mInEmergencyMode, mIsCachedLocation);
         }
 
-        String getResponseTypeAsString() {
+        private String getResponseTypeAsString() {
             switch (mResponseType) {
                 case NFW_RESPONSE_TYPE_REJECTED:
                     return "REJECTED";
@@ -246,6 +335,24 @@
     }
 
     private void updateNfwLocationAccessProxyAppsInGnssHal() {
+        final String[] locationPermissionEnabledProxyApps = shouldDisableNfwLocationAccess()
+                ? NO_LOCATION_ENABLED_PROXY_APPS : getLocationPermissionEnabledProxyApps();
+        final String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps);
+        Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: "
+                + proxyAppsStr);
+        boolean result = native_enable_nfw_location_access(locationPermissionEnabledProxyApps);
+        if (!result) {
+            Log.e(TAG, "Failed to update non-framework location access proxy apps in the"
+                    + " GNSS HAL to: " + proxyAppsStr);
+        }
+    }
+
+    private boolean shouldDisableNfwLocationAccess() {
+        // TODO(b/122856189): Add disableWhenRoaming configuration per proxy app.
+        return mIsOnRoamingNetwork || !mIsMasterLocationSettingsEnabled;
+    }
+
+    private String[] getLocationPermissionEnabledProxyApps() {
         // Get a count of proxy apps with location permission enabled to array creation size.
         int countLocationPermissionEnabledProxyApps = 0;
         for (Boolean hasLocationPermissionEnabled : mProxyAppToLocationPermissions.values()) {
@@ -264,15 +371,7 @@
                 locationPermissionEnabledProxyApps[i++] = proxyApp;
             }
         }
-
-        String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps);
-        Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: "
-                + proxyAppsStr);
-        boolean result = native_enable_nfw_location_access(locationPermissionEnabledProxyApps);
-        if (!result) {
-            Log.e(TAG, "Failed to update non-framework location access proxy apps in the"
-                    + " GNSS HAL to: " + proxyAppsStr);
-        }
+        return locationPermissionEnabledProxyApps;
     }
 
     private void handleNfwNotification(NfwNotification nfwNotification) {
@@ -360,7 +459,31 @@
                 isPermissionMismatched);
     }
 
-    private void postEvent(Runnable event) {
+    private void listenForRoamingNetworkUpdate() {
+        // Register for network capabilities changes to monitor roaming changes.
+        ConnectivityManager mConnMgr = (ConnectivityManager) mContext.getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+        NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+        networkRequestBuilder.addCapability(NetworkCapabilities.TRANSPORT_CELLULAR);
+        NetworkRequest networkRequest = networkRequestBuilder.build();
+        mConnMgr.registerNetworkCallback(networkRequest,
+                new ConnectivityManager.NetworkCallback() {
+                    @Override
+                    public void onCapabilitiesChanged(Network network,
+                            NetworkCapabilities capabilities) {
+                        boolean isRoaming = !capabilities.hasTransport(
+                                NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
+                        // No locking required for this test and set because the callback
+                        // runs in mHandler thread.
+                        if (mIsOnRoamingNetwork != isRoaming) {
+                            mIsOnRoamingNetwork = isRoaming;
+                            updateNfwLocationAccessProxyAppsInGnssHal();
+                        }
+                    }
+                }, mHandler);
+    }
+
+    private void runOnHandler(Runnable event) {
         // Hold a wake lock until this message is delivered.
         // Note that this assumes the message will not be removed from the queue before
         // it is handled (otherwise the wake lock would be leaked).
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/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
index 1541b1d..94de49e 100644
--- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
@@ -42,10 +42,7 @@
 import android.media.AudioSystem;
 import android.media.IAudioService;
 import android.media.IRemoteVolumeController;
-import android.media.MediaController2;
-import android.media.Session2CommandGroup;
 import android.media.Session2Token;
-import android.media.session.ControllerLink;
 import android.media.session.IActiveSessionsListener;
 import android.media.session.ICallback;
 import android.media.session.IOnMediaKeyListener;
@@ -60,7 +57,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.PowerManager;
@@ -1007,38 +1003,71 @@
                 if (DEBUG) {
                     Log.d(TAG, "Session2 is created " + sessionToken);
                 }
+                if (pid != sessionToken.getPid()) {
+                    throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid
+                            + " but actually=" + sessionToken.getPid());
+                }
                 if (uid != sessionToken.getUid()) {
                     throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
                             + " but actually=" + sessionToken.getUid());
                 }
-                Controller2Callback callback = new Controller2Callback(sessionToken);
-                // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
-                //       it's closed.
-                // TODO: Keep controller as well for better readability
-                //       because the GC behavior isn't straightforward.
-                MediaController2 controller = new MediaController2(mContext, sessionToken,
-                        new HandlerExecutor(mHandler), callback);
+                int userId = UserHandle.getUserId(uid);
+                List<Session2Token> session2Tokens = mSession2TokensPerUser.get(userId);
+                if (session2Tokens.contains(sessionToken)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "notifySession2Created(): Ignoring already existing token "
+                                + sessionToken);
+                    }
+                    return;
+                }
+                session2Tokens.add(sessionToken);
+                pushSession2TokensChangedLocked(userId);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
         }
 
         @Override
-        public List<ControllerLink> getSessions(ComponentName componentName, int userId) {
+        public void notifySession2Destroyed(Session2Token sessionToken) throws RemoteException {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (DEBUG) {
+                    Log.d(TAG, "Session2 is destroyed " + sessionToken);
+                }
+                if (pid != sessionToken.getPid()) {
+                    throw new SecurityException("Unexpected Session2Token's PID, expected=" + pid
+                            + " but actually=" + sessionToken.getPid());
+                }
+                if (uid != sessionToken.getUid()) {
+                    throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
+                            + " but actually=" + sessionToken.getUid());
+                }
+                int userId = UserHandle.getUserId(uid);
+                mSession2TokensPerUser.get(userId).remove(sessionToken);
+                pushSession2TokensChangedLocked(userId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public List<MediaSession.Token> getSessions(ComponentName componentName, int userId) {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
 
             try {
                 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
-                ArrayList<ControllerLink> binders = new ArrayList<>();
+                ArrayList<MediaSession.Token> tokens = new ArrayList<>();
                 synchronized (mLock) {
                     List<MediaSessionRecord> records = getActiveSessionsLocked(resolvedUserId);
                     for (MediaSessionRecord record : records) {
-                        binders.add(record.getControllerLink());
+                        tokens.add(record.getSessionToken());
                     }
                 }
-                return binders;
+                return tokens;
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -2114,30 +2143,4 @@
             obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
         }
     }
-
-    private class Controller2Callback extends MediaController2.ControllerCallback {
-        private final Session2Token mToken;
-
-        Controller2Callback(Session2Token token) {
-            mToken = token;
-        }
-
-        @Override
-        public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
-            synchronized (mLock) {
-                int userId = UserHandle.getUserId(mToken.getUid());
-                mSession2TokensPerUser.get(userId).add(mToken);
-                pushSession2TokensChangedLocked(userId);
-            }
-        }
-
-        @Override
-        public void onDisconnected(MediaController2 controller) {
-            synchronized (mLock) {
-                int userId = UserHandle.getUserId(mToken.getUid());
-                mSession2TokensPerUser.get(userId).remove(mToken);
-                pushSession2TokensChangedLocked(userId);
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a164686..238eed4 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -121,6 +121,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
@@ -769,7 +770,10 @@
                                 action.isContextual() ? 1 : 0)
                         .addTaggedData(
                                 MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED,
-                                generatedByAssistant ? 1 : 0));
+                                generatedByAssistant ? 1 : 0)
+                        .addTaggedData(MetricsEvent.NOTIFICATION_LOCATION,
+                                nv.location.toMetricsEventEnum()));
+
                 EventLogTags.writeNotificationActionClicked(key, actionIndex,
                         r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now),
                         nv.rank, nv.count);
@@ -1087,7 +1091,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 +1113,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 +1673,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);
 
@@ -2320,18 +2343,24 @@
         public boolean areBubblesAllowedForPackage(String pkg, int uid) {
             enforceSystemOrSystemUIOrSamePackage(pkg,
                     "Caller not system or systemui or same package");
-            return mPreferencesHelper.areBubblessAllowed(pkg, uid);
+            return mPreferencesHelper.areBubblesAllowed(pkg, uid);
         }
 
         @Override
         public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
-            checkCallerIsSystem();
-
+            enforceSystemOrSystemUI("Caller not system or systemui");
             mPreferencesHelper.setBubblesAllowed(pkg, uid, allowed);
             handleSavePolicyFile();
         }
 
         @Override
+        public boolean hasUserApprovedBubblesForPackage(String pkg, int uid) {
+            enforceSystemOrSystemUI("Caller not system or systemui");
+            int lockedFields = mPreferencesHelper.getAppLockedFields(pkg, uid);
+            return (lockedFields & PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE) != 0;
+        }
+
+        @Override
         public int getPackageImportance(String pkg) {
             checkCallerIsSystemOrSameApp(pkg);
             return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid());
@@ -3840,6 +3869,24 @@
             return mLockScreenAllowSecureNotifications;
         }
 
+        @Override
+        public boolean isPackagePaused(String pkg) {
+            Preconditions.checkNotNull(pkg);
+            checkCallerIsSameApp(pkg);
+
+            boolean isPaused;
+
+            final PackageManagerInternal pmi = LocalServices.getService(
+                    PackageManagerInternal.class);
+            int flags = pmi.getDistractingPackageRestrictions(
+                    pkg, Binder.getCallingUserHandle().getIdentifier());
+            isPaused = ((flags & PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) != 0);
+
+            isPaused |= isPackageSuspendedForUser(pkg, Binder.getCallingUid());
+
+            return isPaused;
+        }
+
         private void verifyPrivilegedListener(INotificationListener token, UserHandle user,
                 boolean assistantAllowed) {
             ManagedServiceInfo info;
@@ -7743,6 +7790,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 02fc51f..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()));
@@ -690,6 +690,8 @@
                                     importance));
                 }
             }
+            // We have now gotten all the information out of the adjustments and can forget them.
+            mAdjustments.clear();
         }
     }
 
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/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 7a21aa2..3f0043c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -473,7 +473,7 @@
      * @param uid the uid to check if bubbles are allowed for.
      * @return whether bubbles are allowed.
      */
-    public boolean areBubblessAllowed(String pkg, int uid) {
+    public boolean areBubblesAllowed(String pkg, int uid) {
         return getOrCreatePackagePreferences(pkg, uid).allowBubble;
     }
 
@@ -489,7 +489,6 @@
         return getOrCreatePackagePreferences(packageName, uid).importance;
     }
 
-
     /**
      * Returns whether the importance of the corresponding notification is user-locked and shouldn't
      * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index d0ef4f1..d5089cb 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -696,7 +696,7 @@
                 return null;
             }
             return new LauncherApps.AppUsageLimit(
-                    data.isGroupLimit(), data.getTotalUsageLimit(), data.getUsageRemaining());
+                    data.getTotalUsageLimit(), data.getUsageRemaining());
         }
 
         private void ensureShortcutPermission(@NonNull String callingPackage) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0ab2a73..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();
 
@@ -537,7 +552,8 @@
         session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                 mInstallThread.getLooper(), mStagingManager, sessionId, userId,
                 installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
-                false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR);
+                false, null, SessionInfo.INVALID_ID, false, false, false, SessionInfo.NO_ERROR,
+                "");
 
         synchronized (mSessions) {
             mSessions.put(sessionId, session);
@@ -1125,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/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index b8825bb..494ec3f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -155,6 +155,7 @@
     private static final String ATTR_IS_FAILED = "isFailed";
     private static final String ATTR_IS_APPLIED = "isApplied";
     private static final String ATTR_STAGED_SESSION_ERROR_CODE = "errorCode";
+    private static final String ATTR_STAGED_SESSION_ERROR_MESSAGE = "errorMessage";
     private static final String ATTR_MODE = "mode";
     private static final String ATTR_INSTALL_FLAGS = "installFlags";
     private static final String ATTR_INSTALL_LOCATION = "installLocation";
@@ -267,6 +268,8 @@
     private boolean mStagedSessionFailed;
     @GuardedBy("mLock")
     private int mStagedSessionErrorCode = SessionInfo.NO_ERROR;
+    @GuardedBy("mLock")
+    private String mStagedSessionErrorMessage;
 
     /**
      * Path to the validated base APK for this session, which may point at an
@@ -413,7 +416,8 @@
             String installerPackageName, int installerUid, SessionParams params, long createdMillis,
             File stageDir, String stageCid, boolean prepared, boolean sealed,
             @Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
-            boolean isFailed, boolean isApplied, int stagedSessionErrorCode) {
+            boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
+            String stagedSessionErrorMessage) {
         mCallback = callback;
         mContext = context;
         mPm = pm;
@@ -447,6 +451,8 @@
         mStagedSessionFailed = isFailed;
         mStagedSessionApplied = isApplied;
         mStagedSessionErrorCode = stagedSessionErrorCode;
+        mStagedSessionErrorMessage =
+                stagedSessionErrorMessage != null ? stagedSessionErrorMessage : "";
         if (sealed) {
             synchronized (mLock) {
                 try {
@@ -499,7 +505,7 @@
             info.isSessionApplied = mStagedSessionApplied;
             info.isSessionReady = mStagedSessionReady;
             info.isSessionFailed = mStagedSessionFailed;
-            info.setStagedSessionErrorCode(mStagedSessionErrorCode);
+            info.setStagedSessionErrorCode(mStagedSessionErrorCode, mStagedSessionErrorMessage);
         }
         return info;
     }
@@ -1971,17 +1977,21 @@
             mStagedSessionApplied = false;
             mStagedSessionFailed = false;
             mStagedSessionErrorCode = SessionInfo.NO_ERROR;
+            mStagedSessionErrorMessage = "";
         }
         mCallback.onStagedSessionChanged(this);
     }
 
     /** {@hide} */
-    void setStagedSessionFailed(@StagedSessionErrorCode int errorCode) {
+    void setStagedSessionFailed(@StagedSessionErrorCode int errorCode,
+                                String errorMessage) {
         synchronized (mLock) {
             mStagedSessionReady = false;
             mStagedSessionApplied = false;
             mStagedSessionFailed = true;
             mStagedSessionErrorCode = errorCode;
+            mStagedSessionErrorMessage = errorMessage;
+            Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage);
         }
         mCallback.onStagedSessionChanged(this);
     }
@@ -1993,6 +2003,7 @@
             mStagedSessionApplied = true;
             mStagedSessionFailed = false;
             mStagedSessionErrorCode = SessionInfo.NO_ERROR;
+            mStagedSessionErrorMessage = "";
         }
         mCallback.onStagedSessionChanged(this);
     }
@@ -2017,6 +2028,11 @@
         return mStagedSessionErrorCode;
     }
 
+    /** {@hide} */
+    String getStagedSessionErrorMessage() {
+        return mStagedSessionErrorMessage;
+    }
+
     private void destroyInternal() {
         synchronized (mLock) {
             mSealed = true;
@@ -2133,6 +2149,8 @@
             writeBooleanAttribute(out, ATTR_IS_FAILED, mStagedSessionFailed);
             writeBooleanAttribute(out, ATTR_IS_APPLIED, mStagedSessionApplied);
             writeIntAttribute(out, ATTR_STAGED_SESSION_ERROR_CODE, mStagedSessionErrorCode);
+            writeStringAttribute(out, ATTR_STAGED_SESSION_ERROR_MESSAGE,
+                    mStagedSessionErrorMessage);
             // TODO(patb,109941548): avoid writing to xml and instead infer / validate this after
             //                       we've read all sessions.
             writeIntAttribute(out, ATTR_PARENT_SESSION_ID, mParentSessionId);
@@ -2253,6 +2271,8 @@
         final boolean isApplied = readBooleanAttribute(in, ATTR_IS_APPLIED);
         final int stagedSessionErrorCode = readIntAttribute(in, ATTR_STAGED_SESSION_ERROR_CODE,
                 SessionInfo.NO_ERROR);
+        final String stagedSessionErrorMessage = readStringAttribute(in,
+                ATTR_STAGED_SESSION_ERROR_MESSAGE);
 
         if (!isStagedSessionStateValid(isReady, isApplied, isFailed)) {
             throw new IllegalArgumentException("Can't restore staged session with invalid state.");
@@ -2296,7 +2316,7 @@
                 installerThread, stagingManager, sessionId, userId, installerPackageName,
                 installerUid, params, createdMillis, stageDir, stageCid, prepared, sealed,
                 childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
-                stagedSessionErrorCode);
+                stagedSessionErrorCode, stagedSessionErrorMessage);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9100f6a..265d07a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -42,7 +42,6 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
-import static android.content.pm.PackageManager.INSTALL_ALLOW_DOWNGRADE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
@@ -202,8 +201,8 @@
 import android.content.pm.dex.ArtManager;
 import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.dex.IArtManager;
-import android.content.rollback.IRollbackManager;
 import android.content.res.Resources;
+import android.content.rollback.IRollbackManager;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.hardware.display.DisplayManager;
@@ -449,8 +448,7 @@
 
     private static final long BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60);
 
-    private static final boolean PRECOMPILED_LAYOUT_ENABLED =
-            SystemProperties.getBoolean("view.precompiled_layout_enabled", false);
+    private static final String PRECOMPILE_LAYOUTS = "pm.precompile_layouts";
 
     private static final int RADIO_UID = Process.PHONE_UID;
     private static final int LOG_UID = Process.LOG_UID;
@@ -9119,7 +9117,7 @@
                 pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
             }
 
-            if (PRECOMPILED_LAYOUT_ENABLED) {
+            if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
                 mArtManagerService.compileLayouts(pkg);
             }
 
@@ -16211,7 +16209,7 @@
 
             if (performDexopt) {
                 // Compile the layout resources.
-                if (PRECOMPILED_LAYOUT_ENABLED) {
+                if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts");
                     mViewCompiler.compileLayouts(pkg);
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -20097,6 +20095,24 @@
     }
 
     @Override
+    public String getContentCaptureServicePackageName() {
+        String contentCaptureServiceName =
+                mContext.getString(R.string.config_defaultContentCaptureService);
+
+        if (TextUtils.isEmpty(contentCaptureServiceName)) {
+            return null;
+        }
+
+        int separatorIndex = contentCaptureServiceName.indexOf("/");
+
+        if (separatorIndex < 0) {
+            return null;
+        }
+
+        return contentCaptureServiceName.substring(0, separatorIndex);
+    }
+
+    @Override
     public void setApplicationEnabledSetting(String appPackageName,
             int newState, int flags, int userId, String callingPackage) {
         if (!sUserManager.exists(userId)) return;
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/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 563fd7f..84c8b60 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -690,6 +690,10 @@
         return result;
     }
 
+    public boolean hasShareTargets() {
+        return !mShareTargets.isEmpty();
+    }
+
     /**
      * Return the filenames (excluding path names) of icon bitmap files from this package.
      */
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index fdbaba2..ff6d7a8 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2154,6 +2154,8 @@
     public ParceledListSlice<ShortcutManager.ShareShortcutInfo> getShareTargets(String packageName,
             IntentFilter filter, @UserIdInt int userId) {
         verifyCaller(packageName, userId);
+        enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
+                "getShareTargets");
 
         synchronized (mLock) {
             throwIfUserLockedL(userId);
@@ -2167,6 +2169,19 @@
         }
     }
 
+    @Override
+    public boolean hasShareTargets(String packageName, String packageToCheck,
+            @UserIdInt int userId) {
+        verifyCaller(packageName, userId);
+        enforceSystem();
+
+        synchronized (mLock) {
+            throwIfUserLockedL(userId);
+
+            return getPackageShortcutsLocked(packageToCheck, userId).hasShareTargets();
+        }
+    }
+
     @GuardedBy("mLock")
     private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
             @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 5311c2a..c4d27e5 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -153,6 +153,19 @@
         return success;
     }
 
+    private static boolean sendMarkStagedSessionReadyRequest(int sessionId) {
+        final IApexService apex = IApexService.Stub.asInterface(
+                ServiceManager.getService("apexservice"));
+        boolean success;
+        try {
+            success = apex.markStagedSessionReady(sessionId);
+        } catch (RemoteException re) {
+            Slog.e(TAG, "Unable to contact apexservice", re);
+            return false;
+        }
+        return success;
+    }
+
     private static boolean isApexSession(@NonNull PackageInstallerSession session) {
         return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
     }
@@ -166,6 +179,7 @@
         if (!session.isMultiPackage()
                 && isApexSession(session)) {
             success = submitSessionToApexService(session, null, apexInfoList);
+
         } else if (session.isMultiPackage()) {
             List<PackageInstallerSession> childSessions =
                     Arrays.stream(session.getChildSessionIds())
@@ -179,7 +193,13 @@
             } // else this is a staged multi-package session with no APEX files.
         }
 
-        if (success && (apexInfoList.apexInfos.length > 0)) {
+        if (!success) {
+            session.setStagedSessionFailed(
+                    SessionInfo.VERIFICATION_FAILED,
+                    "APEX staging failed, check logcat messages from apexd for more details.");
+        }
+
+        if (apexInfoList.apexInfos.length > 0) {
             // For APEXes, we validate the signature here before we mark the session as ready,
             // so we fail the session early if there is a signature mismatch. For APKs, the
             // signature verification will be done by the package manager at the point at which
@@ -190,16 +210,22 @@
             for (ApexInfo apexPackage : apexInfoList.apexInfos) {
                 if (!validateApexSignatureLocked(apexPackage.packagePath,
                         apexPackage.packageName)) {
-                    success = false;
-                    break;
+                    session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED,
+                            "APK-container signature verification failed for package "
+                                    + apexPackage.packageName + ". Signature of file "
+                                    + apexPackage.packagePath + " does not match the signature of "
+                                    + " the package already installed.");
+                    // TODO(b/118865310): abort the session on apexd.
+                    return;
                 }
             }
         }
 
-        if (success) {
-            session.setStagedSessionReady();
-        } else {
-            session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED);
+        session.setStagedSessionReady();
+        if (!sendMarkStagedSessionReadyRequest(session.sessionId)) {
+            session.setStagedSessionFailed(SessionInfo.VERIFICATION_FAILED,
+                            "APEX staging failed, check logcat messages from apexd for more "
+                            + "details.");
         }
     }
 
@@ -217,13 +243,20 @@
             return;
         }
         if (apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown) {
-            session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED);
+            session.setStagedSessionFailed(SessionInfo.ACTIVATION_FAILED,
+                    "APEX activation failed. Check logcat messages from apexd for "
+                                  + "more information.");
+        }
+        if (apexSessionInfo.isVerified) {
+            // Session has been previously submitted to apexd, but didn't complete all the
+            // pre-reboot verification, perhaps because the device rebooted in the meantime.
+            // Greedily re-trigger the pre-reboot verification.
+            mBgHandler.post(() -> preRebootVerification(session));
         }
         if (apexSessionInfo.isActivated) {
             session.setStagedSessionApplied();
             // TODO(b/118865310) if multi-package proceed with the installation of APKs.
         }
-        // TODO(b/118865310) if (apexSessionInfo.isVerified) { /* mark this as staged in apexd */ }
         // In every other case apexd will retry to apply the session at next boot.
     }
 
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
new file mode 100644
index 0000000..a2e98cc
--- /dev/null
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "system/apex/tests"
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 2455113..8d64b81 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -16,10 +16,6 @@
 
 package com.android.server.pm;
 
-import com.google.android.collect.Sets;
-
-import com.android.internal.util.Preconditions;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -42,6 +38,10 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.util.Preconditions;
+
+import com.google.android.collect.Sets;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
 
@@ -666,6 +666,7 @@
 
             case android.provider.Settings.Secure.ALWAYS_ON_VPN_APP:
             case android.provider.Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN:
+            case android.provider.Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST:
                 // Whitelist system uid (ConnectivityService) and root uid to change always-on vpn
                 final int appId = UserHandle.getAppId(callingUid);
                 if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) {
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 863bfd5..a8be07d 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -480,6 +480,10 @@
             final String apkPath = pkg.baseCodePath;
             final ApplicationInfo appInfo = pkg.applicationInfo;
             final String outDexFile = appInfo.dataDir + "/code_cache/compiled_view.dex";
+            if (appInfo.isPrivilegedApp()) {
+                // Privileged apps prefer to load trusted code so they don't use compiled views.
+                return false;
+            }
             Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
                     ") to " + outDexFile);
             long callingId = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 20d6d4e..bfa539c 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -737,6 +737,14 @@
         grantSystemFixedPermissionsToSystemPackage("com.android.sharedstoragebackup", userId,
                 STORAGE_PERMISSIONS);
 
+        // Content Capture Service
+        String contentCaptureServicePackageName =
+                mContext.getPackageManager().getContentCaptureServicePackageName();
+        if (!TextUtils.isEmpty(contentCaptureServicePackageName)) {
+            grantPermissionsToSystemPackage(contentCaptureServicePackageName, userId,
+                    MICROPHONE_PERMISSIONS);
+        }
+
         if (mPermissionGrantedCallback != null) {
             mPermissionGrantedCallback.onDefaultRuntimePermissionsGranted(userId);
         }
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/policy/DisplayFoldController.java b/services/core/java/com/android/server/policy/DisplayFoldController.java
new file mode 100644
index 0000000..fdcafa7
--- /dev/null
+++ b/services/core/java/com/android/server/policy/DisplayFoldController.java
@@ -0,0 +1,155 @@
+/*
+ * 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.policy;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.view.DisplayInfo;
+import android.view.IDisplayFoldListener;
+
+import com.android.server.DisplayThread;
+import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
+
+/**
+ * Controls the behavior of foldable devices whose screen can literally bend and fold.
+ */
+class DisplayFoldController {
+
+    private static final String TAG = "DisplayFoldController";
+    private final WindowManagerInternal mWindowManagerInternal;
+    private final DisplayManagerInternal mDisplayManagerInternal;
+    private final int mDisplayId;
+
+    /** The display area while device is folded. */
+    private final Rect mFoldedArea;
+    private final Handler mHandler;
+
+    private final DisplayInfo mNonOverrideDisplayInfo = new DisplayInfo();
+    private final RemoteCallbackList<IDisplayFoldListener> mListeners = new RemoteCallbackList<>();
+    private Boolean mFolded;
+
+    DisplayFoldController(WindowManagerInternal windowManagerInternal,
+            DisplayManagerInternal displayManagerInternal, int displayId, Rect foldedArea,
+            Handler handler) {
+        mWindowManagerInternal = windowManagerInternal;
+        mDisplayManagerInternal = displayManagerInternal;
+        mDisplayId = displayId;
+        mFoldedArea = new Rect(foldedArea);
+        mHandler = handler;
+    }
+
+    void requestDeviceFolded(boolean folded) {
+        mHandler.post(() -> setDeviceFolded(folded));
+    }
+
+    void setDeviceFolded(boolean folded) {
+        if (mFolded != null && mFolded == folded) {
+            return;
+        }
+        if (folded) {
+            mDisplayManagerInternal.getNonOverrideDisplayInfo(mDisplayId, mNonOverrideDisplayInfo);
+            final int dx = (mNonOverrideDisplayInfo.logicalWidth - mFoldedArea.width()) / 2
+                    - mFoldedArea.left;
+            final int dy = (mNonOverrideDisplayInfo.logicalHeight - mFoldedArea.height()) / 2
+                    - mFoldedArea.top;
+
+            mWindowManagerInternal.setForcedDisplaySize(mDisplayId, mFoldedArea.width(),
+                    mFoldedArea.height());
+            mDisplayManagerInternal.setDisplayOffsets(mDisplayId, -dx, -dy);
+        } else {
+            mWindowManagerInternal.clearForcedDisplaySize(mDisplayId);
+            mDisplayManagerInternal.setDisplayOffsets(mDisplayId, 0, 0);
+        }
+        mFolded = folded;
+
+        final int n = mListeners.beginBroadcast();
+        for (int i = 0; i < n; i++) {
+            try {
+                mListeners.getBroadcastItem(i).onDisplayFoldChanged(mDisplayId, folded);
+            } catch (RemoteException e) {
+                // Listener died.
+            }
+        }
+        mListeners.finishBroadcast();
+    }
+
+    void registerDisplayFoldListener(IDisplayFoldListener listener) {
+        mListeners.register(listener);
+        if (mFolded == null) {
+            return;
+        }
+        mHandler.post(() -> {
+            try {
+                listener.onDisplayFoldChanged(mDisplayId, mFolded);
+            } catch (RemoteException e) {
+                // Listener died.
+            }
+        });
+    }
+
+    void unregisterDisplayFoldListener(IDisplayFoldListener listener) {
+        mListeners.unregister(listener);
+    }
+
+    /**
+     * Only used for the case that persist.debug.force_foldable is set.
+     * This is using proximity sensor to simulate the fold state switch.
+     */
+    static DisplayFoldController createWithProxSensor(Context context, int displayId) {
+        final SensorManager sensorManager = context.getSystemService(SensorManager.class);
+        final Sensor proxSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+        if (proxSensor == null) {
+            return null;
+        }
+
+        final DisplayFoldController result = create(displayId);
+        sensorManager.registerListener(new SensorEventListener() {
+            @Override
+            public void onSensorChanged(SensorEvent event) {
+                result.requestDeviceFolded(event.values[0] < 1f);
+            }
+
+            @Override
+            public void onAccuracyChanged(Sensor sensor, int accuracy) {
+                // Ignore.
+            }
+        }, proxSensor, SensorManager.SENSOR_DELAY_NORMAL);
+
+        return result;
+    }
+
+    static DisplayFoldController create(int displayId) {
+        final DisplayManagerInternal displayService =
+                LocalServices.getService(DisplayManagerInternal.class);
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayService.getNonOverrideDisplayInfo(displayId, displayInfo);
+        final Rect foldedArea = new Rect(0, displayInfo.logicalHeight / 2,
+                displayInfo.logicalWidth, displayInfo.logicalHeight);
+
+        return new DisplayFoldController(LocalServices.getService(WindowManagerInternal.class),
+                displayService, displayId, foldedArea, DisplayThread.getHandler());
+    }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 13c4d88..0796a9c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -177,6 +177,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
+import android.view.IDisplayFoldListener;
 import android.view.IWindowManager;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
@@ -374,6 +375,7 @@
     SearchManager mSearchManager;
     AccessibilityManager mAccessibilityManager;
     BurnInProtectionHelper mBurnInProtectionHelper;
+    private DisplayFoldController mDisplayFoldController;
     AppOpsManager mAppOpsManager;
     private ScreenshotHelper mScreenshotHelper;
     private boolean mHasFeatureWatch;
@@ -471,6 +473,7 @@
     int mLidNavigationAccessibility;
     boolean mLidControlsScreenLock;
     boolean mLidControlsSleep;
+    private boolean mLidControlsDisplayFold;
     int mShortPressOnPowerBehavior;
     int mLongPressOnPowerBehavior;
     int mVeryLongPressOnPowerBehavior;
@@ -1794,6 +1797,8 @@
                 com.android.internal.R.bool.config_lidControlsScreenLock);
         mLidControlsSleep = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_lidControlsSleep);
+        mLidControlsDisplayFold = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_lidControlsDisplayFold);
 
         mAllowTheaterModeWakeFromKey = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromKey);
@@ -1850,6 +1855,13 @@
 
         readConfigurationDependentBehaviors();
 
+        if (mLidControlsDisplayFold) {
+            mDisplayFoldController = DisplayFoldController.create(DEFAULT_DISPLAY);
+        } else if (SystemProperties.getBoolean("persist.debug.force_foldable", false)) {
+            mDisplayFoldController = DisplayFoldController.createWithProxSensor(context,
+                    DEFAULT_DISPLAY);
+        }
+
         mAccessibilityManager = (AccessibilityManager) context.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
 
@@ -3194,6 +3206,20 @@
     }
 
     @Override
+    public void registerDisplayFoldListener(IDisplayFoldListener listener) {
+        if (mDisplayFoldController != null) {
+            mDisplayFoldController.registerDisplayFoldListener(listener);
+        }
+    }
+
+    @Override
+    public void unregisterDisplayFoldListener(IDisplayFoldListener listener) {
+        if (mDisplayFoldController != null) {
+            mDisplayFoldController.unregisterDisplayFoldListener(listener);
+        }
+    }
+
+    @Override
     public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
             throws RemoteException {
         synchronized (mLock) {
@@ -4972,7 +4998,9 @@
 
     private void applyLidSwitchState() {
         final int lidState = mDefaultDisplayPolicy.getLidState();
-        if (lidState == LID_CLOSED && mLidControlsSleep) {
+        if (mLidControlsDisplayFold && mDisplayFoldController != null) {
+            mDisplayFoldController.requestDeviceFolded(lidState == LID_CLOSED);
+        } else if (lidState == LID_CLOSED && mLidControlsSleep) {
             goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_LID_SWITCH,
                     PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
         } else if (lidState == LID_CLOSED && mLidControlsScreenLock) {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 1d82970..e18cd17 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -78,6 +78,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.IApplicationToken;
+import android.view.IDisplayFoldListener;
 import android.view.IWindowManager;
 import android.view.InputEventReceiver;
 import android.view.KeyEvent;
@@ -1457,6 +1458,16 @@
     public void requestUserActivityNotification();
 
     /**
+     * Registers an IDisplayFoldListener.
+     */
+    default void registerDisplayFoldListener(IDisplayFoldListener listener) {}
+
+    /**
+     * Unregisters an IDisplayFoldListener.
+     */
+    default void unregisterDisplayFoldListener(IDisplayFoldListener listener) {}
+
+    /**
      * Updates the flag about whether AOD is showing.
      *
      * @return whether the value was changed.
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..289618e 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -28,11 +28,11 @@
 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;
 import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
@@ -55,13 +55,12 @@
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
+import java.util.Collections;
 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 +199,21 @@
     }
 
     @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,
+                            Collections.emptyList()));
             }
+            return new ParceledListSlice<>(rollbacks);
         }
-        return new StringParceledListSlice(new ArrayList<>(packageNames));
     }
 
     @Override
@@ -258,8 +230,8 @@
     }
 
     @Override
-    public void executeRollback(RollbackInfo rollback, String callerPackageName,
-            IntentSender statusReceiver) {
+    public void commitRollback(int rollbackId, ParceledListSlice causePackages,
+            String callerPackageName, IntentSender statusReceiver) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.MANAGE_ROLLBACKS,
                 "executeRollback");
@@ -269,33 +241,29 @@
         appOps.checkPackage(callingUid, callerPackageName);
 
         getHandler().post(() ->
-                executeRollbackInternal(rollback, callerPackageName, statusReceiver));
+                commitRollbackInternal(rollbackId, causePackages.getList(),
+                    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, List<VersionedPackage> causePackages,
             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, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
+                    "Rollback unavailable");
             return;
         }
 
         if (data.inProgress) {
-            sendFailure(statusReceiver, "Rollback for package is already in progress.");
+            sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
+                    "Rollback for package is already in progress.");
             return;
         }
 
@@ -311,13 +279,15 @@
             VersionedPackage installedVersion = getInstalledPackageVersion(info.getPackageName());
             if (installedVersion == null) {
                 // TODO: Test this case
-                sendFailure(statusReceiver, "Package to roll back is not installed");
+                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
+                        "Package to roll back is not installed");
                 return;
             }
 
             if (!packageVersionsEqual(info.getVersionRolledBackFrom(), installedVersion)) {
                 // TODO: Test this case
-                sendFailure(statusReceiver, "Package version to roll back not installed.");
+                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
+                        "Package version to roll back not installed.");
                 return;
             }
         }
@@ -328,21 +298,16 @@
         try {
             context = mContext.createPackageContext(callerPackageName, 0);
         } catch (PackageManager.NameNotFoundException e) {
-            sendFailure(statusReceiver, "Invalid callerPackageName");
+            sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
+                    "Invalid callerPackageName");
             return;
         }
 
         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 +316,12 @@
             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, RollbackManager.STATUS_FAILURE,
+                            "Cannot find installer package");
+                    return;
+                }
                 params.setInstallerPackageName(installerPackageName);
                 params.setAllowDowngrade(true);
                 int sessionId = packageInstaller.createSession(params);
@@ -382,17 +353,18 @@
                             int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                                     PackageInstaller.STATUS_FAILURE);
                             if (status != PackageInstaller.STATUS_SUCCESS) {
-                                sendFailure(statusReceiver,
+                                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_INSTALL,
                                         "Rollback downgrade install failed: "
                                         + result.getStringExtra(
                                                 PackageInstaller.EXTRA_STATUS_MESSAGE));
                                 return;
                             }
 
-                            addRecentlyExecutedRollback(rollback);
+                            addRecentlyExecutedRollback(new RollbackInfo(
+                                        data.rollbackId, data.packages, causePackages));
                             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,8 +378,9 @@
             data.inProgress = true;
             parentSession.commit(receiver.getIntentSender());
         } catch (IOException e) {
-            Log.e(TAG, "Unable to roll back " + targetPackageName, e);
-            sendFailure(statusReceiver, "IOException: " + e.toString());
+            Log.e(TAG, "Rollback failed", e);
+            sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
+                    "IOException: " + e.toString());
             return;
         }
     }
@@ -537,9 +510,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;
+                    }
                 }
             }
 
@@ -566,16 +542,15 @@
      * Notifies an IntentSender of failure.
      *
      * @param statusReceiver where to send the failure
+     * @param status the RollbackManager.STATUS_* code with the failure.
      * @param message the failure message.
      */
-    private void sendFailure(IntentSender statusReceiver, String message) {
+    private void sendFailure(IntentSender statusReceiver, int status, String message) {
         Log.e(TAG, message);
         try {
-            // TODO: More context on which rollback failed?
-            // TODO: More refined failure code?
             final Intent fillIn = new Intent();
-            fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
-            fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, message);
+            fillIn.putExtra(RollbackManager.EXTRA_STATUS, status);
+            fillIn.putExtra(RollbackManager.EXTRA_STATUS_MESSAGE, message);
             statusReceiver.sendIntent(mContext, 0, fillIn, null, null);
         } catch (IntentSender.SendIntentException e) {
             // Nowhere to send the result back to, so don't bother.
@@ -588,7 +563,7 @@
     private void sendSuccess(IntentSender statusReceiver) {
         try {
             final Intent fillIn = new Intent();
-            fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_SUCCESS);
+            fillIn.putExtra(RollbackManager.EXTRA_STATUS, RollbackManager.STATUS_SUCCESS);
             statusReceiver.sendIntent(mContext, 0, fillIn, null, null);
         } catch (IntentSender.SendIntentException e) {
             // Nowhere to send the result back to, so don't bother.
@@ -935,6 +910,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..2880103 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,7 +27,9 @@
 
 import com.android.server.PackageWatchdog;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -38,10 +41,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 +54,52 @@
     }
 
     @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
+        // TODO: Pass the package as a cause package instead of using
+        // Collections.emptyList once the version of the failing package is
+        // easily available.
+        mHandler.post(() ->
+                mRollbackManager.commitRollback(rollback.getRollbackId(),
+                    Collections.emptyList(),
+                    rollbackReceiver.getIntentSender()));
+        // Assume rollback executed successfully
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
     }
 
     /**
@@ -69,26 +110,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..98ebb09 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -114,13 +114,11 @@
                 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"));
+                    List<VersionedPackage> causePackages = versionedPackagesFromJson(
+                            element.getJSONArray("causePackages"));
+                    RollbackInfo rollback = new RollbackInfo(rollbackId, packages, causePackages);
                     recentlyExecutedRollbacks.add(rollback);
                 }
             } catch (IOException | JSONException e) {
@@ -155,18 +153,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 +188,8 @@
                 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()));
+                element.put("causePackages", versionedPackagesToJson(rollback.getCausePackages()));
                 array.put(element);
             }
 
@@ -229,18 +214,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 +222,68 @@
         }
     }
 
+    private JSONObject toJson(VersionedPackage pkg) throws JSONException {
+        JSONObject json = new JSONObject();
+        json.put("packageName", pkg.getPackageName());
+        json.put("longVersionCode", pkg.getLongVersionCode());
+        return json;
+    }
+
+    private VersionedPackage versionedPackageFromJson(JSONObject json) throws JSONException {
+        String packageName = json.getString("packageName");
+        long longVersionCode = json.getLong("longVersionCode");
+        return new VersionedPackage(packageName, longVersionCode);
+    }
+
+    private JSONObject toJson(PackageRollbackInfo info) throws JSONException {
+        JSONObject json = new JSONObject();
+        json.put("versionRolledBackFrom", toJson(info.getVersionRolledBackFrom()));
+        json.put("versionRolledBackTo", toJson(info.getVersionRolledBackTo()));
+        return json;
+    }
+
+    private PackageRollbackInfo packageRollbackInfoFromJson(JSONObject json) throws JSONException {
+        VersionedPackage versionRolledBackFrom = versionedPackageFromJson(
+                json.getJSONObject("versionRolledBackFrom"));
+        VersionedPackage versionRolledBackTo = versionedPackageFromJson(
+                json.getJSONObject("versionRolledBackTo"));
+        return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo);
+    }
+
+    private JSONArray versionedPackagesToJson(List<VersionedPackage> packages)
+            throws JSONException {
+        JSONArray json = new JSONArray();
+        for (VersionedPackage pkg : packages) {
+            json.put(toJson(pkg));
+        }
+        return json;
+    }
+
+    private List<VersionedPackage> versionedPackagesFromJson(JSONArray json) throws JSONException {
+        List<VersionedPackage> packages = new ArrayList<>();
+        for (int i = 0; i < json.length(); ++i) {
+            packages.add(versionedPackageFromJson(json.getJSONObject(i)));
+        }
+        return packages;
+    }
+
+    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/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 1023182..2d89bc7 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -544,6 +544,16 @@
         mHandler.obtainMessage(MSG_CHECK_VISIBILITY, args).sendToTarget();
     }
 
+    private boolean hasVisibleNonFinishingActivity(TaskRecord t) {
+        for (int i = t.mActivities.size() - 1; i >= 0; --i) {
+            final ActivityRecord r = t.mActivities.get(i);
+            if (r.visible && !r.finishing) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void checkVisibility(TaskRecord t, ActivityRecord r) {
         synchronized (mSupervisor.mService.mGlobalLock) {
 
@@ -552,7 +562,7 @@
 
             // If we have an active transition that's waiting on a certain activity that will be
             // invisible now, we'll never get onWindowsDrawn, so abort the transition if necessary.
-            if (info != null && !t.isVisible()) {
+            if (info != null && !hasVisibleNonFinishingActivity(t)) {
                 if (DEBUG_METRICS) Slog.i(TAG, "notifyVisibilityChanged to invisible"
                         + " activity=" + r);
                 logAppTransitionCancel(info);
@@ -841,13 +851,11 @@
         Log.i(TAG, sb.toString());
     }
 
-    void logActivityStart(Intent intent, WindowProcessController callerApp, ActivityRecord r,
+    void logAbortedBgActivityStart(Intent intent, WindowProcessController callerApp,
             int callingUid, String callingPackage, int callingUidProcState,
             boolean callingUidHasAnyVisibleWindow,
             int realCallingUid, int realCallingUidProcState,
             boolean realCallingUidHasAnyVisibleWindow,
-            int targetUid, String targetPackage, int targetUidProcState,
-            boolean targetUidHasAnyVisibleWindow, String targetWhitelistTag,
             boolean comingFromPendingIntent) {
 
         final long nowElapsed = SystemClock.elapsedRealtime();
@@ -865,13 +873,6 @@
                 processStateAmToProto(realCallingUidProcState));
         builder.addTaggedData(FIELD_REAL_CALLING_UID_HAS_ANY_VISIBLE_WINDOW,
                 realCallingUidHasAnyVisibleWindow ? 1 : 0);
-        builder.addTaggedData(FIELD_TARGET_UID, targetUid);
-        builder.addTaggedData(FIELD_TARGET_PACKAGE_NAME, targetPackage);
-        builder.addTaggedData(FIELD_TARGET_UID_PROC_STATE,
-                processStateAmToProto(targetUidProcState));
-        builder.addTaggedData(FIELD_TARGET_UID_HAS_ANY_VISIBLE_WINDOW,
-                targetUidHasAnyVisibleWindow ? 1 : 0);
-        builder.addTaggedData(FIELD_TARGET_WHITELIST_TAG, targetWhitelistTag);
         builder.addTaggedData(FIELD_COMING_FROM_PENDING_INTENT, comingFromPendingIntent ? 1 : 0);
         if (intent != null) {
             builder.addTaggedData(FIELD_INTENT_ACTION, intent.getAction());
@@ -904,35 +905,6 @@
                         (nowUptime - callerApp.getWhenUnimportant()));
             }
         }
-        if (r != null) {
-            builder.addTaggedData(FIELD_TARGET_SHORT_COMPONENT_NAME, r.shortComponentName);
-            builder.addTaggedData(FIELD_ACTIVITY_RECORD_LAUNCH_MODE, r.info.launchMode);
-            builder.addTaggedData(FIELD_ACTIVITY_RECORD_TARGET_ACTIVITY, r.info.targetActivity);
-            builder.addTaggedData(FIELD_ACTIVITY_RECORD_FLAGS, r.info.flags);
-            builder.addTaggedData(FIELD_ACTIVITY_RECORD_REAL_ACTIVITY,
-                    r.mActivityComponent.toShortString());
-            builder.addTaggedData(FIELD_ACTIVITY_RECORD_SHORT_COMPONENT_NAME, r.shortComponentName);
-            builder.addTaggedData(FIELD_ACTIVITY_RECORD_PROCESS_NAME, r.processName);
-            builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_FULLSCREEN, r.fullscreen ? 1 : 0);
-            builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_NO_DISPLAY, r.noDisplay ? 1 : 0);
-            if (r.lastVisibleTime != 0) {
-                builder.addTaggedData(FIELD_ACTIVITY_RECORD_MILLIS_SINCE_LAST_VISIBLE,
-                        (nowUptime - r.lastVisibleTime));
-            }
-            if (r.resultTo != null) {
-                builder.addTaggedData(FIELD_ACTIVITY_RECORD_RESULT_TO_PKG_NAME,
-                        r.resultTo.packageName);
-                builder.addTaggedData(FIELD_ACTIVITY_RECORD_RESULT_TO_SHORT_COMPONENT_NAME,
-                        r.resultTo.shortComponentName);
-            }
-            builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_VISIBLE, r.visible ? 1 : 0);
-            builder.addTaggedData(FIELD_ACTIVITY_RECORD_IS_VISIBLE_IGNORING_KEYGUARD,
-                    r.visibleIgnoringKeyguard ? 1 : 0);
-            if (r.lastLaunchTime != 0) {
-                builder.addTaggedData(FIELD_ACTIVITY_RECORD_MILLIS_SINCE_LAST_LAUNCH,
-                        (nowUptime - r.lastLaunchTime));
-            }
-        }
         mMetricsLogger.write(builder);
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b8634d8..8f39f3d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -161,6 +161,7 @@
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.PipModeChangeItem;
 import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.TopResumedActivityChangeItem;
 import android.app.servertransaction.WindowVisibilityItem;
 import android.app.usage.UsageEvents.Event;
 import android.content.ComponentName;
@@ -692,6 +693,26 @@
         }
     }
 
+    void scheduleTopResumedActivityChanged(boolean onTop) {
+        if (!attachedToProcess()) {
+            if (DEBUG_CONFIGURATION) {
+                Slog.w(TAG, "Can't report activity position update - client not running"
+                                + ", activityRecord=" + this);
+            }
+            return;
+        }
+        try {
+            if (DEBUG_CONFIGURATION) {
+                Slog.v(TAG, "Sending position change to " + this + ", onTop: " + onTop);
+            }
+
+            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+                    TopResumedActivityChangeItem.obtain(onTop));
+        } catch (RemoteException e) {
+            // If process died, whatever.
+        }
+    }
+
     void updateMultiWindowMode() {
         if (task == null || task.getStack() == null || !attachedToProcess()) {
             return;
@@ -1946,30 +1967,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 +2060,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;
@@ -3039,6 +3120,7 @@
             transaction.addCallback(callbackItem);
             transaction.setLifecycleStateRequest(lifecycleItem);
             mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+            mRootActivityContainer.updateTopResumedActivityIfNeeded();
             // Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
             // request resume if this activity is currently resumed, which implies we aren't
             // sleeping.
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 891c3da..549567a 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);
                         }
@@ -2286,7 +2295,7 @@
      * Check if the display to which this stack is attached has
      * {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied.
      */
-    private boolean canShowWithInsecureKeyguard() {
+    boolean canShowWithInsecureKeyguard() {
         final ActivityDisplay activityDisplay = getDisplay();
         if (activityDisplay == null) {
             throw new IllegalStateException("Stack is not attached to any display, stackId="
@@ -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,8 +2522,9 @@
         boolean result = false;
         try {
             // Protect against recursion.
-            mStackSupervisor.inResumeTopActivity = true;
+            mInResumeTopActivity = true;
             result = resumeTopActivityInnerLocked(prev, options);
+            mRootActivityContainer.updateTopResumedActivityIfNeeded();
 
             // When resuming the top activity, it may be necessary to pause the top activity (for
             // example, returning to the lock screen. We suppress the normal pause logic in
@@ -2528,7 +2538,7 @@
                 checkReadyForSleep();
             }
         } finally {
-            mStackSupervisor.inResumeTopActivity = false;
+            mInResumeTopActivity = false;
         }
 
         return result;
@@ -2561,7 +2571,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 +2659,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 +2874,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 +4105,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..c8a150b 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.
@@ -843,6 +840,7 @@
 
                 // Schedule transaction.
                 mService.getLifecycleManager().scheduleTransaction(clientTransaction);
+                mRootActivityContainer.updateTopResumedActivityIfNeeded();
 
                 if ((proc.mInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0
                         && mService.mHasHeavyWeightFeature) {
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 3a077b8..95a6f71 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -749,9 +749,15 @@
 
         boolean abortBackgroundStart = false;
         if (!abort) {
-            abortBackgroundStart = shouldAbortBackgroundActivityStart(callingUid, callingPid,
-                    callingPackage, realCallingUid, callerApp, originatingPendingIntent,
-                    allowBackgroundActivityStart, intent);
+            try {
+                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                        "shouldAbortBackgroundActivityStart");
+                abortBackgroundStart = shouldAbortBackgroundActivityStart(callingUid, callingPid,
+                        callingPackage, realCallingUid, callerApp, originatingPendingIntent,
+                        allowBackgroundActivityStart, intent);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+            }
             abort |= (abortBackgroundStart && !mService.isBackgroundActivityStartsEnabled());
             // TODO: remove this toast after feature development is done
             if (abortBackgroundStart) {
@@ -827,6 +833,8 @@
                 final int flags = intent.getFlags();
                 Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
                 newIntent.setFlags(flags
+                        | FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
                         | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                 newIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName);
                 newIntent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
@@ -901,12 +909,6 @@
         mService.onStartActivitySetDidAppSwitch();
         mController.doPendingActivityLaunches(false);
 
-        // maybe log to TRON, but only if we haven't already in shouldAbortBackgroundActivityStart()
-        if (!abortBackgroundStart) {
-            maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp, r,
-                    originatingPendingIntent, false /*abortedStart*/);
-        }
-
         return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
                 true /* doResume */, checkedOptions, inTask, outActivity);
     }
@@ -925,17 +927,31 @@
             return false;
         }
         // don't abort if the callingUid is in the foreground or is a persistent system process
-        final boolean isCallingUidForeground = mService.isUidForeground(callingUid);
-        final boolean isCallingUidPersistentSystemProcess = isUidPersistentSystemProcess(
-                callingUid);
+        final int callingUidProcState = mService.getUidStateLocked(callingUid);
+        final boolean callingUidHasAnyVisibleWindow =
+                mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid);
+        final boolean isCallingUidForeground = callingUidHasAnyVisibleWindow
+                || callingUidProcState == ActivityManager.PROCESS_STATE_TOP;
+        final boolean isCallingUidPersistentSystemProcess = (callingUid == Process.SYSTEM_UID)
+                || callingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
         if (isCallingUidForeground || isCallingUidPersistentSystemProcess) {
             return false;
         }
         // take realCallingUid into consideration
-        final boolean isRealCallingUidForeground = mService.isUidForeground(
-                realCallingUid);
-        final boolean isRealCallingUidPersistentSystemProcess = isUidPersistentSystemProcess(
-                realCallingUid);
+        final int realCallingUidProcState = (callingUid == realCallingUid)
+                ? callingUidProcState
+                : mService.getUidStateLocked(realCallingUid);
+        final boolean realCallingUidHasAnyVisibleWindow = (callingUid == realCallingUid)
+                ? callingUidHasAnyVisibleWindow
+                : mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(realCallingUid);
+        final boolean isRealCallingUidForeground = (callingUid == realCallingUid)
+                ? isCallingUidForeground
+                : realCallingUidHasAnyVisibleWindow
+                        || realCallingUidProcState == ActivityManager.PROCESS_STATE_TOP;
+        final boolean isRealCallingUidPersistentSystemProcess = (callingUid == realCallingUid)
+                ? isCallingUidPersistentSystemProcess
+                : (realCallingUid == Process.SYSTEM_UID)
+                        || realCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
         if (realCallingUid != callingUid) {
             // don't abort if the realCallingUid is in the foreground and callingUid isn't
             if (isRealCallingUidForeground) {
@@ -973,60 +989,14 @@
                 + "; isBgStartWhitelisted: " + allowBackgroundActivityStart
                 + "; intent: " + intent
                 + "]");
-        maybeLogActivityStart(callingUid, callingPackage, realCallingUid, intent, callerApp,
-                null /*r*/, originatingPendingIntent, true /*abortedStart*/);
-        return true;
-    }
-
-    /** Returns true if uid is in a persistent state. */
-    private boolean isUidPersistentSystemProcess(int uid) {
-        return (mService.getUidStateLocked(uid) <= ActivityManager.PROCESS_STATE_PERSISTENT_UI);
-    }
-
-    private void maybeLogActivityStart(int callingUid, String callingPackage, int realCallingUid,
-            Intent intent, WindowProcessController callerApp, ActivityRecord r,
-            PendingIntentRecord originatingPendingIntent, boolean abortedStart) {
-        boolean callerAppHasForegroundActivity =
-                callerApp != null && callerApp.hasForegroundActivities();
-        if (!mService.isActivityStartsLoggingEnabled() || callerAppHasForegroundActivity
-                || (!abortedStart && r == null)) {
-            // skip logging in this case
-            return;
-        }
-
-        try {
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "logActivityStart");
-            final int callingUidProcState = mService.getUidStateLocked(callingUid);
-            final boolean callingUidHasAnyVisibleWindow =
-                    mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid);
-            final int realCallingUidProcState = (callingUid == realCallingUid)
-                    ? callingUidProcState
-                    : mService.getUidStateLocked(realCallingUid);
-            final boolean realCallingUidHasAnyVisibleWindow = (callingUid == realCallingUid)
-                    ? callingUidHasAnyVisibleWindow
-                    : mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(
-                            realCallingUid);
-            final String targetPackage = (r != null) ? r.packageName : null;
-            final int targetUid = (r!= null) ? ((r.appInfo != null) ? r.appInfo.uid : -1) : -1;
-            final int targetUidProcState = mService.getUidStateLocked(targetUid);
-            final boolean targetUidHasAnyVisibleWindow = (targetUid != -1)
-                    ? mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(targetUid)
-                    : false;
-            final String targetWhitelistTag = (targetUid != -1)
-                    ? mService.getPendingTempWhitelistTagForUidLocked(targetUid)
-                    : null;
-
-            mSupervisor.getActivityMetricsLogger().logActivityStart(intent, callerApp, r,
-                    callingUid, callingPackage, callingUidProcState,
-                    callingUidHasAnyVisibleWindow,
-                    realCallingUid, realCallingUidProcState,
-                    realCallingUidHasAnyVisibleWindow,
-                    targetUid, targetPackage, targetUidProcState,
-                    targetUidHasAnyVisibleWindow, targetWhitelistTag,
+        // log aborted activity start to TRON
+        if (mService.isActivityStartsLoggingEnabled()) {
+            mSupervisor.getActivityMetricsLogger().logAbortedBgActivityStart(intent, callerApp,
+                    callingUid, callingPackage, callingUidProcState, callingUidHasAnyVisibleWindow,
+                    realCallingUid, realCallingUidProcState, realCallingUidHasAnyVisibleWindow,
                     (originatingPendingIntent != null));
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         }
+        return true;
     }
 
     /**
@@ -1630,7 +1600,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/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index f3a363a..6dc73bb 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -29,6 +29,7 @@
 import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
 import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
 import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
 import static android.view.WindowManager.TRANSIT_TASK_OPEN;
@@ -1662,6 +1663,15 @@
                     "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS:"
                             + " anim=" + a + " transit=" + appTransitionToString(transit)
                             + " isEntrance=true" + " Callers=" + Debug.getCallers(3));
+        } else if (transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE) {
+            // In the absence of a specific adapter, we just want to keep everything stationary.
+            a = new AlphaAnimation(1.f, 1.f);
+            a.setDuration(WindowChangeAnimationSpec.ANIMATION_DURATION);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+                Slog.v(TAG, "applyAnimation:"
+                        + " anim=" + a + " transit=" + appTransitionToString(transit)
+                        + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
+            }
         } else {
             int animAttr = 0;
             switch (transit) {
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/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index d915e10..a52f1af 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -2340,11 +2340,6 @@
         return transit == TRANSIT_TASK_CHANGE_WINDOWING_MODE;
     }
 
-    private int getDefaultChangeTransitionDuration() {
-        return (int) (AppTransition.DEFAULT_APP_TRANSITION_DURATION
-                        * mWmService.getTransitionAnimationScaleLocked());
-    }
-
     boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter,
             boolean isVoiceInteraction) {
 
@@ -2378,17 +2373,17 @@
                 adapter = adapters.mAdapter;
                 thumbnailAdapter = adapters.mThumbnailAdapter;
             } else if (isChanging) {
-                int duration = getDefaultChangeTransitionDuration();
+                final float durationScale = mWmService.getTransitionAnimationScaleLocked();
                 mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
                 adapter = new LocalAnimationAdapter(
                         new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
-                                getDisplayContent().getDisplayInfo(), duration,
+                                getDisplayContent().getDisplayInfo(), durationScale,
                                 true /* isAppAnimation */, false /* isThumbnail */),
                         mWmService.mSurfaceAnimationRunner);
                 if (mThumbnail != null) {
                     thumbnailAdapter = new LocalAnimationAdapter(
                             new WindowChangeAnimationSpec(mTransitStartRect, mTmpRect,
-                                    getDisplayContent().getDisplayInfo(), duration,
+                                    getDisplayContent().getDisplayInfo(), durationScale,
                                     true /* isAppAnimation */, true /* isThumbnail */),
                             mWmService.mSurfaceAnimationRunner);
                 }
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 650d0be..ce3fb51 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -41,6 +41,8 @@
 import android.graphics.Rect;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
@@ -522,6 +524,11 @@
         mChangeListeners.remove(listener);
     }
 
+    @VisibleForTesting
+    boolean containsListener(ConfigurationContainerListener listener) {
+        return mChangeListeners.contains(listener);
+    }
+
     /**
      * Must be called when new parent for the container was set.
      */
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4ddecfb..7a9ff52 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1057,11 +1057,6 @@
      */
     void setInsetProvider(@InternalInsetType int type, WindowState win,
             @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider) {
-        if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL) {
-            if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) {
-                return;
-            }
-        }
         mInsetsStateController.getSourceProvider(type).setWindow(win, frameProvider);
     }
 
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/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index e49e4c0..66666e6 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,7 +16,13 @@
 
 package com.android.server.wm;
 
+import static android.view.InsetsState.TYPE_IME;
+import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.TYPE_TOP_BAR;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.ViewRootImpl.sNewInsetsMode;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -59,13 +65,23 @@
      */
     private boolean mServerVisible;
 
+    private final boolean mControllable;
 
     InsetsSourceProvider(InsetsSource source, InsetsStateController stateController,
             DisplayContent displayContent) {
-        mClientVisible = InsetsState.getDefaultVisibly(source.getType());
+        mClientVisible = InsetsState.getDefaultVisibility(source.getType());
         mSource = source;
         mDisplayContent = displayContent;
         mStateController = stateController;
+
+        final int type = source.getType();
+        if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) {
+            mControllable = sNewInsetsMode == NEW_INSETS_MODE_FULL;
+        } else if (type == TYPE_IME) {
+            mControllable = sNewInsetsMode >= NEW_INSETS_MODE_IME;
+        } else {
+            mControllable = false;
+        }
     }
 
     InsetsSource getSource() {
@@ -73,6 +89,13 @@
     }
 
     /**
+     * @return Whether the current flag configuration allows to control this source.
+     */
+    boolean isControllable() {
+        return mControllable;
+    }
+
+    /**
      * Updates the window that currently backs this source.
      *
      * @param win The window that links to this source.
@@ -91,6 +114,9 @@
             mSource.setFrame(new Rect());
         } else {
             mWin.setInsetProvider(this);
+            if (mControllingWin != null) {
+                updateControlForTarget(mControllingWin, true /* force */);
+            }
         }
     }
 
@@ -113,8 +139,12 @@
                 && !mWin.mGivenInsetsPending);
     }
 
-    void updateControlForTarget(@Nullable WindowState target) {
-        if (target == mControllingWin) {
+    void updateControlForTarget(@Nullable WindowState target, boolean force) {
+        if (mWin == null) {
+            mControllingWin = target;
+            return;
+        }
+        if (target == mControllingWin && !force) {
             return;
         }
         if (target == null) {
@@ -123,6 +153,7 @@
             return;
         }
         mAdapter = new ControlAdapter();
+        setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
         mWin.startAnimation(mDisplayContent.getPendingTransaction(), mAdapter,
                 !mClientVisible /* hidden */);
         mControllingWin = target;
@@ -161,7 +192,7 @@
     }
 
     boolean isClientVisible() {
-        return ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible;
+        return sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible;
     }
 
     private class ControlAdapter implements AnimationAdapter {
@@ -189,7 +220,7 @@
         public void onAnimationCancelled(SurfaceControl animationLeash) {
             if (mAdapter == this) {
                 mStateController.notifyControlRevoked(mControllingWin, InsetsSourceProvider.this);
-                setClientVisible(InsetsState.getDefaultVisibly(mSource.getType()));
+                setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
                 mControl = null;
                 mControllingWin = null;
                 mAdapter = null;
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 32dbe96..bb0cbb1 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -163,18 +163,18 @@
     }
 
     private void onControlChanged(int type, @Nullable WindowState win) {
-        if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
-            return;
-        }
         final WindowState previous = mTypeWinControlMap.get(type);
         if (win == previous) {
             return;
         }
-        final InsetsSourceProvider controller = mControllers.get(type);
+        final InsetsSourceProvider controller = getSourceProvider(type);
         if (controller == null) {
             return;
         }
-        controller.updateControlForTarget(win);
+        if (!controller.isControllable()) {
+            return;
+        }
+        controller.updateControlForTarget(win, false /* force */);
         if (previous != null) {
             removeFromControlMaps(previous, type);
             mPendingControlChanged.add(previous);
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 177f244..b5be2ac5 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -91,9 +91,7 @@
      */
     boolean isKeyguardOrAodShowing(int displayId) {
         return (mKeyguardShowing || mAodShowing) && !mKeyguardGoingAway
-                && (displayId == DEFAULT_DISPLAY
-                        ? !isDisplayOccluded(DEFAULT_DISPLAY)
-                        : isShowingOnSecondaryDisplay(displayId));
+                && !isDisplayOccluded(displayId);
     }
 
     /**
@@ -101,10 +99,7 @@
      *         display, false otherwise
      */
     boolean isKeyguardShowing(int displayId) {
-        return mKeyguardShowing && !mKeyguardGoingAway
-                && (displayId == DEFAULT_DISPLAY
-                        ? !isDisplayOccluded(DEFAULT_DISPLAY)
-                        : isShowingOnSecondaryDisplay(displayId));
+        return mKeyguardShowing && !mKeyguardGoingAway && !isDisplayOccluded(displayId);
     }
 
     /**
@@ -152,14 +147,6 @@
         updateKeyguardSleepToken();
     }
 
-    private boolean isShowingOnSecondaryDisplay(int displayId) {
-        if (mSecondaryDisplayIdsShowing == null) return false;
-        for (int showingId : mSecondaryDisplayIdsShowing) {
-            if (displayId == showingId) return true;
-        }
-        return false;
-    }
-
     /**
      * Called when Keyguard is going away.
      *
@@ -473,8 +460,16 @@
                 if (stack.getTopDismissingKeyguardActivity() != null) {
                     mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
                 }
+                // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display.
+                if (mDisplayId != DEFAULT_DISPLAY) {
+                    mOccluded |= stack.canShowWithInsecureKeyguard()
+                            && controller.canDismissKeyguard();
+                }
             }
-            mOccluded |= controller.mWindowManager.isShowingDream();
+            // TODO(b/123372519): isShowingDream can only works on default display.
+            if (mDisplayId == DEFAULT_DISPLAY) {
+                mOccluded |= controller.mWindowManager.isShowingDream();
+            }
 
             // TODO(b/113840485): Handle app transition for individual display, and apply occluded
             // state change to secondary displays.
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 9b72141..624fdc2 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -175,6 +175,12 @@
     private ActivityDisplay mDefaultDisplay;
     private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
 
+    /**
+     * Cached value of the topmost resumed activity in the system. Updated when new activity is
+     * resumed.
+     */
+    private ActivityRecord mTopResumedActivity;
+
     /** The current user */
     int mCurrentUser;
     /** Stack id of the front stack when user switched, indexed by userId. */
@@ -1108,28 +1114,58 @@
             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 updateTopResumedActivityIfNeeded() {
+        final ActivityRecord prevTopActivity = mTopResumedActivity;
+        final ActivityStack topStack = getTopDisplayFocusedStack();
+        if (topStack == null || topStack.mResumedActivity == prevTopActivity) {
+            return;
+        }
+        // Clear previous top state
+        if (prevTopActivity != null) {
+            prevTopActivity.scheduleTopResumedActivityChanged(false /* onTop */);
+        }
+        // Update the current top activity
+        mTopResumedActivity = topStack.mResumedActivity;
+        if (mTopResumedActivity != null) {
+            mTopResumedActivity.scheduleTopResumedActivityChanged(true /* onTop */);
+        }
     }
 
     void applySleepTokens(boolean applyToStacks) {
@@ -1283,16 +1319,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);
@@ -1386,6 +1427,7 @@
             mActivityDisplays.remove(display);
             mActivityDisplays.add(position, display);
         }
+        updateTopResumedActivityIfNeeded();
     }
 
     @VisibleForTesting
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/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
index 7dd7c4f..775d5b2 100644
--- a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
@@ -53,13 +53,15 @@
     private Animation mAnimation;
     private final boolean mIsThumbnail;
 
+    static final int ANIMATION_DURATION = AppTransition.DEFAULT_APP_TRANSITION_DURATION;
+
     public WindowChangeAnimationSpec(Rect startBounds, Rect endBounds, DisplayInfo displayInfo,
-            long duration, boolean isAppAnimation, boolean isThumbnail) {
+            float durationScale, boolean isAppAnimation, boolean isThumbnail) {
         mStartBounds = new Rect(startBounds);
         mEndBounds = new Rect(endBounds);
         mIsAppAnimation = isAppAnimation;
         mIsThumbnail = isThumbnail;
-        createBoundsInterpolator(duration, displayInfo);
+        createBoundsInterpolator((int) (ANIMATION_DURATION * durationScale), displayInfo);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index e204697..33e46f4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -313,6 +313,22 @@
     public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout);
 
     /**
+     * Overrides the display size.
+     *
+     * @param displayId The display to override the display size.
+     * @param width The width to override.
+     * @param height The height to override.
+     */
+    public abstract void setForcedDisplaySize(int displayId, int width, int height);
+
+    /**
+     * Recover the display size to real display size.
+     *
+     * @param displayId The display to recover the display size.
+     */
+    public abstract void clearForcedDisplaySize(int displayId);
+
+    /**
      * Adds a window token for a given window type.
      *
      * @param token The token to add.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5eff7d8..8373b44 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -192,6 +192,7 @@
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.IDisplayFoldListener;
 import android.view.IDockedStackListener;
 import android.view.IInputFilter;
 import android.view.IOnKeyguardExitResult;
@@ -2817,7 +2818,7 @@
 
     public boolean isShowingDream() {
         synchronized (mGlobalLock) {
-            // TODO: fix this when dream can be shown on non-default display.
+            // TODO(b/123372519): Fix this when dream can be shown on non-default display.
             return getDefaultDisplayContentLocked().getDisplayPolicy().isShowingDreamLw();
         }
     }
@@ -3748,6 +3749,16 @@
     }
 
     @Override
+    public void registerDisplayFoldListener(IDisplayFoldListener listener) {
+        mPolicy.registerDisplayFoldListener(listener);
+    }
+
+    @Override
+    public void unregisterDisplayFoldListener(IDisplayFoldListener listener) {
+        mPolicy.unregisterDisplayFoldListener(listener);
+    }
+
+    @Override
     public int getPreferredOptionsPanelGravity(int displayId) {
         synchronized (mGlobalLock) {
             final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
@@ -6937,6 +6948,16 @@
         }
 
         @Override
+        public void setForcedDisplaySize(int displayId, int width, int height) {
+            WindowManagerService.this.setForcedDisplaySize(displayId, width, height);
+        }
+
+        @Override
+        public void clearForcedDisplaySize(int displayId) {
+            WindowManagerService.this.clearForcedDisplaySize(displayId);
+        }
+
+        @Override
         public void addWindowToken(IBinder token, int type, int displayId) {
             WindowManagerService.this.addWindowToken(token, type, displayId);
         }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index c38a974..07f26b4 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -52,6 +52,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.HeavyWeightSwitcherActivity;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.Watchdog;
@@ -330,6 +331,13 @@
         return mRequiredAbi;
     }
 
+    /** Returns ID of display overriding the configuration for this process, or
+     *  INVALID_DISPLAY if no display is overriding. */
+    @VisibleForTesting
+    int getDisplayId() {
+        return mDisplayId;
+    }
+
     public void setDebugging(boolean debugging) {
         mDebugging = debugging;
     }
@@ -761,15 +769,15 @@
         activityDisplay.registerConfigurationChangeListener(this);
     }
 
-    private void unregisterDisplayConfigurationListenerLocked() {
+    @VisibleForTesting
+    void unregisterDisplayConfigurationListenerLocked() {
         if (mDisplayId == INVALID_DISPLAY) {
             return;
         }
         final ActivityDisplay activityDisplay =
                 mAtm.mRootActivityContainer.getActivityDisplay(mDisplayId);
         if (activityDisplay != null) {
-            mAtm.mRootActivityContainer.getActivityDisplay(
-                    mDisplayId).unregisterConfigurationChangeListener(this);
+            activityDisplay.unregisterConfigurationChangeListener(this);
         }
         mDisplayId = INVALID_DISPLAY;
     }
@@ -867,6 +875,7 @@
 
     public boolean appNotResponding(String info, Runnable killAppCallback,
             Runnable serviceTimeoutCallback) {
+        Runnable targetRunnable = null;
         synchronized (mAtm.mGlobalLock) {
             if (mAtm.mController == null) {
                 return false;
@@ -877,18 +886,22 @@
                 int res = mAtm.mController.appNotResponding(mName, mPid, info);
                 if (res != 0) {
                     if (res < 0 && mPid != MY_PID) {
-                        killAppCallback.run();
+                        targetRunnable = killAppCallback;
                     } else {
-                        serviceTimeoutCallback.run();
+                        targetRunnable = serviceTimeoutCallback;
                     }
-                    return true;
                 }
             } catch (RemoteException e) {
                 mAtm.mController = null;
                 Watchdog.getInstance().setActivityController(null);
+                return false;
             }
-            return false;
         }
+        if (targetRunnable != null) {
+            targetRunnable.run();
+            return true;
+        }
+        return false;
     }
 
     public void onTopProcChanged() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ce5eb84..4f12010 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4367,6 +4367,12 @@
     }
 
     void startAnimation(Animation anim) {
+
+        // If we are an inset provider, all our animations are driven by the inset client.
+        if (mInsetProvider != null && mInsetProvider.isControllable()) {
+            return;
+        }
+
         final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
         anim.initialize(mWindowFrames.mFrame.width(), mWindowFrames.mFrame.height(),
                 displayInfo.appWidth, displayInfo.appHeight);
@@ -4380,6 +4386,12 @@
     }
 
     private void startMoveAnimation(int left, int top) {
+
+        // If we are an inset provider, all our animations are driven by the inset client.
+        if (mInsetProvider != null && mInsetProvider.isControllable()) {
+            return;
+        }
+
         if (DEBUG_ANIM) Slog.v(TAG, "Setting move animation on " + this);
         final Point oldPosition = new Point();
         final Point newPosition = new Point();
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/core/java/com/android/server/wm/utils/WmDisplayCutout.java b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
index ea3f758..98bad93 100644
--- a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
+++ b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
@@ -170,4 +170,9 @@
     public int hashCode() {
         return Objects.hash(mInner, mFrameSize);
     }
+
+    @Override
+    public String toString() {
+        return "WmDisplayCutout{" + mInner + ", mFrameSize=" + mFrameSize + '}';
+    }
 }
diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp
index 7dd30bd..0d888dc 100644
--- a/services/core/jni/com_android_server_security_VerityUtils.cpp
+++ b/services/core/jni/com_android_server_security_VerityUtils.cpp
@@ -29,12 +29,9 @@
 
 #include <android-base/unique_fd.h>
 
-// TODO(112037636): Always include once fsverity.h is upstreamed and backported.
-#define HAS_FSVERITY 0
-
-#if HAS_FSVERITY
+// TODO(112037636): Always include once fsverity.h is upstreamed.
+#if __has_include(<linux/fsverity.h>)
 #include <linux/fsverity.h>
-
 const int kSha256Bytes = 32;
 #endif
 
@@ -76,7 +73,7 @@
 };
 
 int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) {
-#if HAS_FSVERITY
+#if __has_include(<linux/fsverity.h>)
     const char* path = env->GetStringUTFChars(filePath, nullptr);
     ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC));
     if (rfd.get() < 0) {
@@ -89,11 +86,11 @@
 #else
     LOG_ALWAYS_FATAL("fs-verity is used while not enabled");
     return ENOSYS;
-#endif  // HAS_FSVERITY
+#endif
 }
 
 int measureFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) {
-#if HAS_FSVERITY
+#if __has_include(<linux/fsverity.h>)
     auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_digest) + kSha256Bytes);
     fsverity_digest* data = reinterpret_cast<fsverity_digest*>(raii->getRaw());
     data->digest_size = kSha256Bytes;  // the only input/output parameter
@@ -110,11 +107,11 @@
 #else
     LOG_ALWAYS_FATAL("fs-verity is used while not enabled");
     return ENOSYS;
-#endif  // HAS_FSVERITY
+#endif
 }
 
 jbyteArray constructFsveritySignedData(JNIEnv* env, jobject /* clazz */, jbyteArray digest) {
-#if HAS_FSVERITY
+#if __has_include(<linux/fsverity.h>)
     auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_digest_disk) + kSha256Bytes);
     fsverity_digest_disk* data = reinterpret_cast<fsverity_digest_disk*>(raii->getRaw());
 
@@ -132,12 +129,12 @@
 #else
     LOG_ALWAYS_FATAL("fs-verity is used while not enabled");
     return 0;
-#endif  // HAS_FSVERITY
+#endif
 }
 
 
 jbyteArray constructFsverityDescriptor(JNIEnv* env, jobject /* clazz */, jlong fileSize) {
-#if HAS_FSVERITY
+#if __has_include(<linux/fsverity.h>)
     auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_descriptor));
     fsverity_descriptor* desc = reinterpret_cast<fsverity_descriptor*>(raii->getRaw());
 
@@ -156,12 +153,12 @@
 #else
     LOG_ALWAYS_FATAL("fs-verity is used while not enabled");
     return 0;
-#endif  // HAS_FSVERITY
+#endif
 }
 
 jbyteArray constructFsverityExtension(JNIEnv* env, jobject /* clazz */, jshort extensionId,
         jint extensionDataSize) {
-#if HAS_FSVERITY
+#if __has_include(<linux/fsverity.h>)
     auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_extension));
     fsverity_extension* ext = reinterpret_cast<fsverity_extension*>(raii->getRaw());
 
@@ -172,12 +169,12 @@
 #else
     LOG_ALWAYS_FATAL("fs-verity is used while not enabled");
     return 0;
-#endif  // HAS_FSVERITY
+#endif
 }
 
 jbyteArray constructFsverityFooter(JNIEnv* env, jobject /* clazz */,
         jint offsetToDescriptorHead) {
-#if HAS_FSVERITY
+#if __has_include(<linux/fsverity.h>)
     auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_footer));
     fsverity_footer* footer = reinterpret_cast<fsverity_footer*>(raii->getRaw());
 
@@ -188,7 +185,7 @@
 #else
     LOG_ALWAYS_FATAL("fs-verity is used while not enabled");
     return 0;
-#endif  // HAS_FSVERITY
+#endif
 }
 
 const JNINativeMethod sMethods[] = {
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/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
deleted file mode 100644
index a61c2ef..0000000
--- a/services/net/java/android/net/ip/IpClient.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * 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.ip;
-
-import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
-
-import android.content.Context;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.ProxyInfo;
-import android.net.StaticIpConfiguration;
-import android.net.apf.ApfCapabilities;
-import android.os.ConditionVariable;
-import android.os.INetworkManagementService;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * Proxy for the IpClient in the NetworkStack. To be removed once clients are migrated.
- * @hide
- */
-public class IpClient {
-    private static final String TAG = IpClient.class.getSimpleName();
-    private static final int IPCLIENT_BLOCK_TIMEOUT_MS = 10_000;
-
-    public static final String DUMP_ARG = "ipclient";
-
-    private final ConditionVariable mIpClientCv;
-    private final ConditionVariable mShutdownCv;
-
-    private volatile IIpClient mIpClient;
-
-    /**
-     * @see IpClientCallbacks
-     */
-    public static class Callback extends IpClientCallbacks {}
-
-    /**
-     * IpClient callback that allows clients to block until provisioning is complete.
-     */
-    public static class WaitForProvisioningCallback extends Callback {
-        private final ConditionVariable mCV = new ConditionVariable();
-        private LinkProperties mCallbackLinkProperties;
-
-        /**
-         * Block until either {@link #onProvisioningSuccess(LinkProperties)} or
-         * {@link #onProvisioningFailure(LinkProperties)} is called.
-         */
-        public LinkProperties waitForProvisioning() {
-            mCV.block();
-            return mCallbackLinkProperties;
-        }
-
-        @Override
-        public void onProvisioningSuccess(LinkProperties newLp) {
-            mCallbackLinkProperties = newLp;
-            mCV.open();
-        }
-
-        @Override
-        public void onProvisioningFailure(LinkProperties newLp) {
-            mCallbackLinkProperties = null;
-            mCV.open();
-        }
-    }
-
-    private class CallbackImpl extends IpClientUtil.IpClientCallbacksProxy {
-        /**
-         * Create a new IpClientCallbacksProxy.
-         */
-        CallbackImpl(IpClientCallbacks cb) {
-            super(cb);
-        }
-
-        @Override
-        public void onIpClientCreated(IIpClient ipClient) {
-            mIpClient = ipClient;
-            mIpClientCv.open();
-            super.onIpClientCreated(ipClient);
-        }
-
-        @Override
-        public void onQuit() {
-            mShutdownCv.open();
-            super.onQuit();
-        }
-    }
-
-    /**
-     * Create a new IpClient.
-     */
-    public IpClient(Context context, String iface, Callback callback) {
-        mIpClientCv = new ConditionVariable(false);
-        mShutdownCv = new ConditionVariable(false);
-
-        IpClientUtil.makeIpClient(context, iface, new CallbackImpl(callback));
-    }
-
-    /**
-     * @see IpClient#IpClient(Context, String, IpClient.Callback)
-     */
-    public IpClient(Context context, String iface, Callback callback,
-            INetworkManagementService nms) {
-        this(context, iface, callback);
-    }
-
-    private interface IpClientAction {
-        void useIpClient(IIpClient ipClient) throws RemoteException;
-    }
-
-    private void doWithIpClient(IpClientAction action) {
-        mIpClientCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
-        try {
-            action.useIpClient(mIpClient);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error communicating with IpClient", e);
-        }
-    }
-
-    /**
-     * Notify IpClient that PreDhcpAction is completed.
-     */
-    public void completedPreDhcpAction() {
-        doWithIpClient(c -> c.completedPreDhcpAction());
-    }
-
-    /**
-     * Confirm the provisioning configuration.
-     */
-    public void confirmConfiguration() {
-        doWithIpClient(c -> c.confirmConfiguration());
-    }
-
-    /**
-     * Notify IpClient that packet filter read is complete.
-     */
-    public void readPacketFilterComplete(byte[] data) {
-        doWithIpClient(c -> c.readPacketFilterComplete(data));
-    }
-
-    /**
-     * Shutdown the IpClient altogether.
-     */
-    public void shutdown() {
-        doWithIpClient(c -> c.shutdown());
-    }
-
-    /**
-     * Start the IpClient provisioning.
-     */
-    public void startProvisioning(ProvisioningConfiguration config) {
-        doWithIpClient(c -> c.startProvisioning(config.toStableParcelable()));
-    }
-
-    /**
-     * Stop the IpClient.
-     */
-    public void stop() {
-        doWithIpClient(c -> c.stop());
-    }
-
-    /**
-     * Set the IpClient TCP buffer sizes.
-     */
-    public void setTcpBufferSizes(String tcpBufferSizes) {
-        doWithIpClient(c -> c.setTcpBufferSizes(tcpBufferSizes));
-    }
-
-    /**
-     * Set the IpClient HTTP proxy.
-     */
-    public void setHttpProxy(ProxyInfo proxyInfo) {
-        doWithIpClient(c -> c.setHttpProxy(toStableParcelable(proxyInfo)));
-    }
-
-    /**
-     * Set the IpClient multicast filter.
-     */
-    public void setMulticastFilter(boolean enabled) {
-        doWithIpClient(c -> c.setMulticastFilter(enabled));
-    }
-
-    /**
-     * Dump IpClient logs.
-     */
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        doWithIpClient(c -> IpClientUtil.dumpIpClient(c, fd, pw, args));
-    }
-
-    /**
-     * Block until IpClient shutdown.
-     */
-    public void awaitShutdown() {
-        mShutdownCv.block(IPCLIENT_BLOCK_TIMEOUT_MS);
-    }
-
-    /**
-     * Create a new ProvisioningConfiguration.
-     */
-    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
-        return new ProvisioningConfiguration.Builder();
-    }
-
-    /**
-     * TODO: remove after migrating clients to use the shared configuration class directly.
-     * @see android.net.shared.ProvisioningConfiguration
-     */
-    public static class ProvisioningConfiguration
-            extends android.net.shared.ProvisioningConfiguration {
-        public ProvisioningConfiguration(android.net.shared.ProvisioningConfiguration other) {
-            super(other);
-        }
-
-        /**
-         * @see android.net.shared.ProvisioningConfiguration.Builder
-         */
-        public static class Builder extends android.net.shared.ProvisioningConfiguration.Builder {
-            // Override all methods to have a return type matching this Builder
-            @Override
-            public Builder withoutIPv4() {
-                super.withoutIPv4();
-                return this;
-            }
-
-            @Override
-            public Builder withoutIPv6() {
-                super.withoutIPv6();
-                return this;
-            }
-
-            @Override
-            public Builder withoutMultinetworkPolicyTracker() {
-                super.withoutMultinetworkPolicyTracker();
-                return this;
-            }
-
-            @Override
-            public Builder withoutIpReachabilityMonitor() {
-                super.withoutIpReachabilityMonitor();
-                return this;
-            }
-
-            @Override
-            public Builder withPreDhcpAction() {
-                super.withPreDhcpAction();
-                return this;
-            }
-
-            @Override
-            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
-                super.withPreDhcpAction(dhcpActionTimeoutMs);
-                return this;
-            }
-
-            @Override
-            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
-                super.withStaticConfiguration(staticConfig);
-                return this;
-            }
-
-            @Override
-            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
-                super.withApfCapabilities(apfCapabilities);
-                return this;
-            }
-
-            @Override
-            public Builder withProvisioningTimeoutMs(int timeoutMs) {
-                super.withProvisioningTimeoutMs(timeoutMs);
-                return this;
-            }
-
-            @Override
-            public Builder withRandomMacAddress() {
-                super.withRandomMacAddress();
-                return this;
-            }
-
-            @Override
-            public Builder withStableMacAddress() {
-                super.withStableMacAddress();
-                return this;
-            }
-
-            @Override
-            public Builder withNetwork(Network network) {
-                super.withNetwork(network);
-                return this;
-            }
-
-            @Override
-            public Builder withDisplayName(String displayName) {
-                super.withDisplayName(displayName);
-                return this;
-            }
-
-            @Override
-            public ProvisioningConfiguration build() {
-                return new ProvisioningConfiguration(mConfig);
-            }
-        }
-    }
-}
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/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java
index bfb2b14..44e9e6a 100644
--- a/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java
@@ -18,17 +18,19 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.job.JobScheduler;
 import android.content.Context;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 
+import com.android.server.backup.FullBackupJob;
+import com.android.server.backup.JobIdManager;
 import com.android.server.backup.KeyValueBackupJob;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.testing.BackupManagerServiceTestUtils;
-import com.android.server.backup.testing.TestUtils;
 import com.android.server.testing.shadows.ShadowApplicationPackageManager;
 
 import org.junit.Before;
@@ -38,7 +40,9 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
 import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowJobScheduler;
 
 import java.io.File;
 
@@ -47,7 +51,7 @@
  * UserBackupManagerService}.
  */
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowApplicationPackageManager.class})
+@Config(shadows = {ShadowApplicationPackageManager.class, ShadowJobScheduler.class})
 @Presubmit
 public class SetupObserverTest {
     private static final String TAG = "SetupObserverTest";
@@ -58,6 +62,7 @@
     private Context mContext;
     private UserBackupManagerService mUserBackupManagerService;
     private HandlerThread mHandlerThread;
+    private ShadowJobScheduler mShadowJobScheduler;
 
     /** Setup state. */
     @Before
@@ -73,6 +78,7 @@
                         new File(mContext.getDataDir(), "test1"),
                         new File(mContext.getDataDir(), "test2"),
                         mTransportManager);
+        mShadowJobScheduler = Shadows.shadowOf(mContext.getSystemService(JobScheduler.class));
     }
 
     /** Test observer handles changes from not setup -> setup correctly. */
@@ -121,17 +127,27 @@
         // Setup conditions for a full backup job to be scheduled.
         mUserBackupManagerService.setEnabled(true);
         mUserBackupManagerService.enqueueFullBackup("testPackage", /* lastBackedUp */ 0);
-        // Clear the handler of all pending tasks. This is to prevent the below assertion on the
-        // handler from encountering false positives due to other tasks being scheduled as part of
-        // setup work.
-        TestUtils.runToEndOfTasks(mHandlerThread.getLooper());
 
         setupObserver.onChange(true);
 
-        assertThat(KeyValueBackupJob.isScheduled(mUserBackupManagerService.getUserId())).isTrue();
-        // Verifies that the full backup job is scheduled. The job is scheduled via a posted message
-        // on the backup handler so we verify that a message exists.
-        assertThat(mUserBackupManagerService.getBackupHandler().hasMessagesOrCallbacks()).isTrue();
+        assertThat(
+                        mShadowJobScheduler.getPendingJob(
+                                getJobIdForUser(
+                                        KeyValueBackupJob.MIN_JOB_ID,
+                                        KeyValueBackupJob.MAX_JOB_ID,
+                                        USER_ID)))
+                .isNotNull();
+        assertThat(
+                        mShadowJobScheduler.getPendingJob(
+                                getJobIdForUser(
+                                        FullBackupJob.MIN_JOB_ID,
+                                        FullBackupJob.MAX_JOB_ID,
+                                        USER_ID)))
+                .isNotNull();
+    }
+
+    private int getJobIdForUser(int min, int max, int userId) {
+        return JobIdManager.getJobIdForUserId(min, max, userId);
     }
 
     private void changeSetupCompleteSettingForUser(boolean value, int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
index d91ce39..de7d77d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
@@ -158,7 +158,7 @@
             boolean detectShortcutTrigger) {
         MagnificationGestureHandler h = new MagnificationGestureHandler(
                 mContext, mMagnificationController,
-                detectTripleTap, detectShortcutTrigger);
+                detectTripleTap, detectShortcutTrigger, DISPLAY_0);
         mHandler = new TestHandler(h.mDetectingState, mClock) {
             @Override
             protected String messageToString(Message m) {
diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java
index 095a1de..5b02266 100644
--- a/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java
+++ b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java
@@ -51,6 +51,7 @@
 import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
+
 import java.util.List;
 
 /**
@@ -1146,6 +1147,11 @@
     }
 
     @Override
+    public String getContentCaptureServicePackageName() throws RemoteException {
+        return null;
+    }
+
+    @Override
     public boolean isPackageStateProtected(String packageName, int userId) throws RemoteException {
         return false;
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index 73e9613..742ae41 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -173,7 +173,8 @@
                 /* isReady */ staged ? true : false,
                 /* isFailed */ false,
                 /* isApplied */false,
-                /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED);
+                /* stagedSessionErrorCode */ PackageInstaller.SessionInfo.VERIFICATION_FAILED,
+                /* stagedSessionErrorMessage */ "some error");
     }
 
     private void dumpSession(PackageInstallerSession session) {
@@ -295,6 +296,8 @@
         assertEquals(expected.isStagedSessionFailed(), actual.isStagedSessionFailed());
         assertEquals(expected.isStagedSessionReady(), actual.isStagedSessionReady());
         assertEquals(expected.getStagedSessionErrorCode(), actual.getStagedSessionErrorCode());
+        assertEquals(expected.getStagedSessionErrorMessage(),
+                actual.getStagedSessionErrorMessage());
         assertEquals(expected.isPrepared(), actual.isPrepared());
         assertEquals(expected.isSealed(), actual.isSealed());
         assertEquals(expected.isMultiPackage(), actual.isMultiPackage());
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 3172afb..76beb8f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -75,6 +75,7 @@
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.LauncherApps.PinItemRequest;
@@ -6274,6 +6275,17 @@
         mManager.onApplicationActive(CALLING_PACKAGE_1, USER_0);
     }
 
+    public void testGetShareTargets_permission() {
+        IntentFilter filter = new IntentFilter();
+
+        assertExpectException(SecurityException.class, "Missing permission", () ->
+                mManager.getShareTargets(filter));
+
+        // Has permission, now it should pass.
+        mCallerPermissions.add(permission.MANAGE_APP_PREDICTIONS);
+        mManager.getShareTargets(filter);
+    }
+
     public void testDumpsys_crossProfile() {
         prepareCrossProfileDataSet();
         dumpsysOnLogcat("test1", /* force= */ true);
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/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
index 5d69bbd..4e43d00 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
@@ -964,26 +964,6 @@
         assertFalse(hasAppUsageObserver(UID, OBS_ID1));
     }
 
-    /** Verify app usage limit observer added correctly reports it being a group limit */
-    @Test
-    public void testAppUsageLimitObserver_IsGroupLimit() {
-        addAppUsageLimitObserver(OBS_ID1, GROUP1, TIME_30_MIN);
-        AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
-        assertNotNull("Observer wasn't added", group);
-        assertTrue("Observer didn't correctly report being a group limit",
-                group.isGroupLimit());
-    }
-
-    /** Verify app usage limit observer added correctly reports it being not a group limit */
-    @Test
-    public void testAppUsageLimitObserver_IsNotGroupLimit() {
-        addAppUsageLimitObserver(OBS_ID1, new String[]{PKG_PROD}, TIME_30_MIN);
-        AppTimeLimitController.AppUsageLimitGroup group = getAppUsageLimitObserver(UID, OBS_ID1);
-        assertNotNull("Observer wasn't added", group);
-        assertFalse("Observer didn't correctly report not being a group limit",
-                group.isGroupLimit());
-    }
-
     /** Verify app usage limit observer added correctly reports its total usage limit */
     @Test
     public void testAppUsageLimitObserver_GetTotalUsageLimit() {
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..4a4fece 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);
@@ -3524,6 +3595,22 @@
     }
 
     @Test
+    public void testUserApprovedBubblesForPackage() throws Exception {
+        assertFalse(mBinderService.hasUserApprovedBubblesForPackage(PKG, mUid));
+        mBinderService.setBubblesAllowed(PKG, mUid, true);
+        assertTrue(mBinderService.hasUserApprovedBubblesForPackage(PKG, mUid));
+        assertTrue(mBinderService.areBubblesAllowedForPackage(PKG, mUid));
+    }
+
+    @Test
+    public void testUserRejectsBubblesForPackage() throws Exception {
+        assertFalse(mBinderService.hasUserApprovedBubblesForPackage(PKG, mUid));
+        mBinderService.setBubblesAllowed(PKG, mUid, false);
+        assertTrue(mBinderService.hasUserApprovedBubblesForPackage(PKG, mUid));
+        assertFalse(mBinderService.areBubblesAllowedForPackage(PKG, mUid));
+    }
+
+    @Test
     public void testIsCallerInstantApp_primaryUser() throws Exception {
         ApplicationInfo info = new ApplicationInfo();
         info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 24a1f8c..bde9dde 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2162,20 +2162,20 @@
 
     @Test
     public void testAllowBubbles_defaults() throws Exception {
-        assertTrue(mHelper.areBubblessAllowed(PKG_O, UID_O));
+        assertTrue(mHelper.areBubblesAllowed(PKG_O, UID_O));
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
         loadStreamXml(baos, false);
 
-        assertTrue(mHelper.areBubblessAllowed(PKG_O, UID_O));
+        assertTrue(mHelper.areBubblesAllowed(PKG_O, UID_O));
         assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O));
     }
 
     @Test
     public void testAllowBubbles_xml() throws Exception {
         mHelper.setBubblesAllowed(PKG_O, UID_O, false);
-        assertFalse(mHelper.areBubblessAllowed(PKG_O, UID_O));
+        assertFalse(mHelper.areBubblesAllowed(PKG_O, UID_O));
         assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE,
                 mHelper.getAppLockedFields(PKG_O, UID_O));
 
@@ -2183,7 +2183,7 @@
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
         loadStreamXml(baos, false);
 
-        assertFalse(mHelper.areBubblessAllowed(PKG_O, UID_O));
+        assertFalse(mHelper.areBubblesAllowed(PKG_O, UID_O));
         assertEquals(PreferencesHelper.LockableAppFields.USER_LOCKED_BUBBLE,
                 mHelper.getAppLockedFields(PKG_O, UID_O));
     }
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/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 056568a..ace965b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -523,9 +523,8 @@
         starter.setReason("testActivityStartsLogging_noLoggingWhenDisabled").execute();
 
         // verify logging wasn't done
-        verify(mActivityMetricsLogger, never()).logActivityStart(any(), any(), any(), anyInt(),
-                any(), anyInt(), anyBoolean(), anyInt(), anyInt(), anyBoolean(), anyInt(), any(),
-                anyInt(), anyBoolean(), any(), anyBoolean());
+        verify(mActivityMetricsLogger, never()).logAbortedBgActivityStart(any(), any(), anyInt(),
+                any(), anyInt(), anyBoolean(), anyInt(), anyInt(), anyBoolean(), anyBoolean());
     }
 
     /**
@@ -546,10 +545,9 @@
         starter.setReason("testActivityStartsLogging_logsWhenEnabled").execute();
 
         // verify the above activity start was logged
-        verify(mActivityMetricsLogger, times(1)).logActivityStart(any(), any(), any(),
+        verify(mActivityMetricsLogger, times(1)).logAbortedBgActivityStart(any(), any(),
                 eq(FAKE_CALLING_UID), eq(FAKE_CALLING_PACKAGE), anyInt(), anyBoolean(),
-                eq(FAKE_REAL_CALLING_UID), anyInt(), anyBoolean(), anyInt(),
-                any(), anyInt(), anyBoolean(), any(), eq(false));
+                eq(FAKE_REAL_CALLING_UID), anyInt(), anyBoolean(), eq(false));
     }
 
     /**
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/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 3740786..a498a1a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -94,9 +94,9 @@
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar, null);
-        mProvider.updateControlForTarget(target);
+        mProvider.updateControlForTarget(target, false /* force */);
         assertNotNull(mProvider.getControl());
-        mProvider.updateControlForTarget(null);
+        mProvider.updateControlForTarget(null, false /* force */);
         assertNull(mProvider.getControl());
     }
 
@@ -106,7 +106,7 @@
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar, null);
-        mProvider.updateControlForTarget(target);
+        mProvider.updateControlForTarget(target, false /* force */);
         InsetsState state = new InsetsState();
         state.getSource(TYPE_TOP_BAR).setVisible(false);
         mProvider.onInsetsModified(target, state.getSource(TYPE_TOP_BAR));
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index e3bacf9..67ee4ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -395,8 +395,8 @@
     /**
      * Tests that home activities can be started on the displays that supports system decorations.
      */
-    // TODO (b/118206886): Will add it back once launcher's patch is merged into master.
-    private void testStartHomeOnAllDisplays() {
+    @Test
+    public void testStartHomeOnAllDisplays() {
         // Create secondary displays.
         final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay());
         mRootActivityContainer.addChild(secondDisplay, POSITION_TOP);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
new file mode 100644
index 0000000..a7c84a1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -0,0 +1,86 @@
+/*
+ * 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.wm;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.server.wm.ActivityDisplay.POSITION_TOP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import android.content.pm.ApplicationInfo;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+
+/**
+ * Tests for the {@link WindowProcessController} class.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:WindowProcessControllerTests
+ */
+@Presubmit
+public class WindowProcessControllerTests extends ActivityTestsBase {
+
+    @Test
+    public void testDisplayConfigurationListener() {
+        final WindowProcessController wpc = new WindowProcessController(
+                        mService, mock(ApplicationInfo.class), null, 0, -1, null, null);
+        //By default, the process should not listen to any display.
+        assertEquals(INVALID_DISPLAY, wpc.getDisplayId());
+
+        // Register to display 1 as a listener.
+        TestActivityDisplay testActivityDisplay1 = createTestActivityDisplayInContainer();
+        wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1);
+        assertTrue(testActivityDisplay1.containsListener(wpc));
+        assertEquals(testActivityDisplay1.mDisplayId, wpc.getDisplayId());
+
+        // Move to display 2.
+        TestActivityDisplay testActivityDisplay2 = createTestActivityDisplayInContainer();
+        wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay2);
+        assertFalse(testActivityDisplay1.containsListener(wpc));
+        assertTrue(testActivityDisplay2.containsListener(wpc));
+        assertEquals(testActivityDisplay2.mDisplayId, wpc.getDisplayId());
+
+        // Null ActivityDisplay will not change anything.
+        wpc.registerDisplayConfigurationListenerLocked(null);
+        assertTrue(testActivityDisplay2.containsListener(wpc));
+        assertEquals(testActivityDisplay2.mDisplayId, wpc.getDisplayId());
+
+        // Unregister listener will remove the wpc from registered displays.
+        wpc.unregisterDisplayConfigurationListenerLocked();
+        assertFalse(testActivityDisplay1.containsListener(wpc));
+        assertFalse(testActivityDisplay2.containsListener(wpc));
+        assertEquals(INVALID_DISPLAY, wpc.getDisplayId());
+
+        // Unregistration still work even if the display was removed.
+        wpc.registerDisplayConfigurationListenerLocked(testActivityDisplay1);
+        assertEquals(testActivityDisplay1.mDisplayId, wpc.getDisplayId());
+        mRootActivityContainer.removeChild(testActivityDisplay1);
+        wpc.unregisterDisplayConfigurationListenerLocked();
+        assertEquals(INVALID_DISPLAY, wpc.getDisplayId());
+    }
+
+    private TestActivityDisplay createTestActivityDisplayInContainer() {
+        final TestActivityDisplay testActivityDisplay = createNewActivityDisplay();
+        mRootActivityContainer.addChild(testActivityDisplay, POSITION_TOP);
+        return testActivityDisplay;
+    }
+}
diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
index fa472e2..873ada0 100644
--- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java
+++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
@@ -510,12 +510,9 @@
     }
 
     class AppUsageLimitGroup extends UsageGroup {
-        private boolean mGroupLimit;
-
         public AppUsageLimitGroup(UserData user, ObserverAppData observerApp, int observerId,
                 String[] observed, long timeLimitMs, PendingIntent limitReachedCallback) {
             super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
-            mGroupLimit = observed.length > 1;
         }
 
         @Override
@@ -529,11 +526,6 @@
         }
 
         @GuardedBy("mLock")
-        boolean isGroupLimit() {
-            return mGroupLimit;
-        }
-
-        @GuardedBy("mLock")
         long getTotaUsageLimit() {
             return mTimeLimitMs;
         }
@@ -547,14 +539,6 @@
                 return mTimeLimitMs - mUsageTimeMs;
             }
         }
-
-        @Override
-        @GuardedBy("mLock")
-        void dump(PrintWriter pw) {
-            super.dump(pw);
-            pw.print(" groupLimit=");
-            pw.print(mGroupLimit);
-        }
     }
 
 
@@ -692,7 +676,7 @@
                     smallestGroup = otherGroup;
                 }
             }
-            return new UsageStatsManagerInternal.AppUsageLimitData(smallestGroup.isGroupLimit(),
+            return new UsageStatsManagerInternal.AppUsageLimitData(
                     smallestGroup.getTotaUsageLimit(), smallestGroup.getUsageRemaining());
         }
     }
diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
index c2e4581..acf9946 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
@@ -24,9 +24,8 @@
 import android.content.pm.ActivityInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
 
-// TODO: fix this. either move this class into system server or add a dependency on
-// these wm classes to libiorap-java and libiorap-java-tests (somehow).
 import com.android.server.wm.ActivityMetricsLaunchObserver;
 import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
 import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
@@ -113,12 +112,12 @@
         @Override
         protected void writeToParcelImpl(Parcel p, int flags) {
             super.writeToParcelImpl(p, flags);
-            intent.writeToParcel(p, flags);
+            IntentProtoParcelable.write(p, intent, flags);
         }
 
         IntentStarted(Parcel p) {
             super(p);
-            intent = Intent.CREATOR.createFromParcel(p);
+            intent = IntentProtoParcelable.create(p);
         }
     }
 
@@ -232,8 +231,7 @@
     }
 
      public static class ActivityLaunchCancelled extends AppLaunchEvent {
-        public final @Nullable
-        @ActivityRecordProto byte[] activityRecordSnapshot;
+        public final @Nullable @ActivityRecordProto byte[] activityRecordSnapshot;
 
         public ActivityLaunchCancelled(@SequenceId long sequenceId,
                 @Nullable @ActivityRecordProto byte[] snapshot) {
@@ -352,7 +350,6 @@
             ActivityLaunchCancelled.class,
     };
 
-    // TODO: move to @ActivityRecordProto byte[] once we have unit tests.
     public static class ActivityRecordProtoParcelable {
         public static void write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot,
                 int flags) {
@@ -365,4 +362,31 @@
             return data;
         }
     }
+
+    public static class IntentProtoParcelable {
+        private static final int INTENT_PROTO_CHUNK_SIZE = 1024;
+
+        public static void write(Parcel p, @NonNull Intent intent, int flags) {
+            // There does not appear to be a way to 'reset' a ProtoOutputBuffer stream,
+            // so create a new one every time.
+            final ProtoOutputStream protoOutputStream =
+                    new ProtoOutputStream(INTENT_PROTO_CHUNK_SIZE);
+            // Write this data out as the top-most IntentProto (i.e. it is not a sub-object).
+            intent.writeToProto(protoOutputStream);
+            final byte[] bytes = protoOutputStream.getBytes();
+
+            p.writeByteArray(bytes);
+        }
+
+        // TODO: Should be mockable for testing?
+        // We cannot deserialize in the platform because we don't have a 'readFromProto'
+        // code.
+        public static @NonNull Intent create(Parcel p) {
+            // This will "read" the correct amount of data, but then we discard it.
+            byte[] data = p.createByteArray();
+
+            // Never called by real code in a platform, this binder API is implemented only in C++.
+            return new Intent("<cannot deserialize IntentProto>");
+        }
+    }
 }
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index 7fcad36..9a30b35 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -24,12 +24,16 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.Handler;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.IoThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.wm.ActivityMetricsLaunchObserver;
@@ -43,10 +47,20 @@
  */
 public class IorapForwardingService extends SystemService {
 
-    public static final boolean DEBUG = true; // TODO: read from a getprop?
     public static final String TAG = "IorapForwardingService";
+    /** $> adb shell 'setprop log.tag.IorapdForwardingService VERBOSE' */
+    public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    /** $> adb shell 'setprop iorapd.enable true' */
+    private static boolean IS_ENABLED = SystemProperties.getBoolean("iorapd.enable", true);
+    /** $> adb shell 'setprop iorapd.forwarding_service.wtf_crash true' */
+    private static boolean WTF_CRASH = SystemProperties.getBoolean(
+            "iorapd.forwarding_service.wtf_crash", false);
 
     private IIorap mIorapRemote;
+    private final Object mLock = new Object();
+    /** Handle onBinderDeath by periodically trying to reconnect. */
+    private final Handler mHandler =
+            new BinderConnectionHandler(IoThread.getHandler().getLooper());
 
     /**
      * Initializes the system service.
@@ -58,7 +72,7 @@
      * @param context The system server context.
      */
     public IorapForwardingService(Context context) {
-       super(context);
+        super(context);
     }
 
     //<editor-fold desc="Providers">
@@ -78,12 +92,40 @@
 
     @VisibleForTesting
     protected IIorap provideIorapRemote() {
+        IIorap iorap;
         try {
-            return IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd"));
+            iorap = IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd"));
         } catch (ServiceManager.ServiceNotFoundException e) {
-            // TODO: how do we handle service being missing?
-            throw new AssertionError(e);
+            handleRemoteError(e);
+            return null;
         }
+
+        try {
+            iorap.asBinder().linkToDeath(provideDeathRecipient(), /*flags*/0);
+        } catch (RemoteException e) {
+            handleRemoteError(e);
+            return null;
+        }
+
+        return iorap;
+    }
+
+    @VisibleForTesting
+    protected DeathRecipient provideDeathRecipient() {
+        return new DeathRecipient() {
+            @Override
+            public void binderDied() {
+                Log.w(TAG, "iorapd has died");
+                retryConnectToRemoteAndConfigure(/*attempts*/0);
+            }
+        };
+    }
+
+    @VisibleForTesting
+    protected boolean isIorapEnabled() {
+        // Same as the property in iorapd.rc -- disabling this will mean the 'iorapd' binder process
+        // never comes up, so all binder connections will fail indefinitely.
+        return IS_ENABLED;
     }
 
     //</editor-fold>
@@ -94,15 +136,128 @@
             Log.v(TAG, "onStart");
         }
 
+        retryConnectToRemoteAndConfigure(/*attempts*/0);
+    }
+
+    private class BinderConnectionHandler extends Handler {
+        public BinderConnectionHandler(android.os.Looper looper) {
+            super(looper);
+        }
+
+        public static final int MESSAGE_BINDER_CONNECT = 0;
+
+        private int mAttempts = 0;
+
+        @Override
+        public void handleMessage(android.os.Message message) {
+           switch (message.what) {
+               case MESSAGE_BINDER_CONNECT:
+                   if (!retryConnectToRemoteAndConfigure(mAttempts)) {
+                       mAttempts++;
+                   } else {
+                       mAttempts = 0;
+                   }
+                   break;
+               default:
+                   throw new AssertionError("Unknown message: " + message.toString());
+           }
+        }
+    }
+
+    /**
+     * Handle iorapd shutdowns and crashes, by attempting to reconnect
+     * until the service is reached again.
+     *
+     * <p>The first connection attempt is synchronous,
+     * subsequent attempts are done by posting delayed tasks to the IoThread.</p>
+     *
+     * @return true if connection succeeded now, or false if it failed now [and needs to requeue].
+     */
+    private boolean retryConnectToRemoteAndConfigure(int attempts) {
+        final int sleepTime = 1000;  // ms
+
+        if (DEBUG) {
+            Log.v(TAG, "retryConnectToRemoteAndConfigure - attempt #" + attempts);
+        }
+
+        if (connectToRemoteAndConfigure()) {
+            return true;
+        }
+
+        // Either 'iorapd' is stuck in a crash loop (ouch!!) or we manually
+        // called 'adb shell stop iorapd' , which means this would loop until it comes back
+        // up.
+        //
+        // TODO: it would be good to get nodified of 'adb shell stop iorapd' to avoid
+        // printing this warning.
+        Log.w(TAG, "Failed to connect to iorapd, is it down? Delay for " + sleepTime);
+
+        // Use a handler instead of Thread#sleep to avoid backing up the binder thread
+        // when this is called from the death recipient callback.
+        mHandler.sendMessageDelayed(
+                mHandler.obtainMessage(BinderConnectionHandler.MESSAGE_BINDER_CONNECT),
+                sleepTime);
+
+        return false;
+
+        // Log.e(TAG, "Can't connect to iorapd - giving up after " + attempts + " attempts");
+    }
+
+    private boolean connectToRemoteAndConfigure() {
+        synchronized (mLock) {
+            // Synchronize against any concurrent calls to this via the DeathRecipient.
+            return connectToRemoteAndConfigureLocked();
+        }
+    }
+
+    private boolean connectToRemoteAndConfigureLocked() {
+        if (!isIorapEnabled()) {
+            if (DEBUG) {
+                Log.v(TAG, "connectToRemoteAndConfigure - iorapd is disabled, skip rest of work");
+            }
+            // When we see that iorapd is disabled (when system server comes up),
+            // it stays disabled permanently until the next system server reset.
+
+            // TODO: consider listening to property changes as a callback, then we can
+            // be more dynamic about handling enable/disable.
+            return true;
+        }
+
         // Connect to the native binder service.
         mIorapRemote = provideIorapRemote();
+        if (mIorapRemote == null) {
+            Log.e(TAG, "connectToRemoteAndConfigure - null iorap remote. check for Log.wtf?");
+            return false;
+        }
         invokeRemote( () -> mIorapRemote.setTaskListener(new RemoteTaskListener()) );
+        registerInProcessListenersLocked();
+
+        return true;
+    }
+
+    private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver();
+    private boolean mRegisteredListeners = false;
+
+    private void registerInProcessListenersLocked() {
+        if (mRegisteredListeners) {
+            // Listeners are registered only once (idempotent operation).
+            //
+            // Today listeners are tolerant of the remote side going away
+            // by handling remote errors.
+            //
+            // We could try to 'unregister' the listener when we get a binder disconnect,
+            // but we'd still have to handle the case of encountering synchronous errors so
+            // it really wouldn't be a win (other than having less log spew).
+            return;
+        }
 
         // Listen to App Launch Sequence events from ActivityTaskManager,
         // and forward them to the native binder service.
         ActivityMetricsLaunchObserverRegistry launchObserverRegistry =
                 provideLaunchObserverRegistry();
-        launchObserverRegistry.registerLaunchObserver(new AppLaunchObserver());
+        launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
+
+        mRegisteredListeners = true;
     }
 
     private class AppLaunchObserver implements ActivityMetricsLaunchObserver {
@@ -110,6 +265,8 @@
         // launch sequences on the native side.
         private @AppLaunchEvent.SequenceId long mSequenceId = -1;
 
+        // All callbacks occur on the same background thread. Don't synchronize explicitly.
+
         @Override
         public void onIntentStarted(@NonNull Intent intent) {
             // #onIntentStarted [is the only transition that] initiates a new launch sequence.
@@ -174,7 +331,7 @@
 
             invokeRemote(() ->
                 mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
-                        new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, activity))
+                        new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, activity))
             );
         }
     }
@@ -201,6 +358,7 @@
         }
     }
 
+    /** Allow passing lambdas to #invokeRemote */
     private interface RemoteRunnable {
         void run() throws RemoteException;
     }
@@ -209,8 +367,26 @@
        try {
            r.run();
        } catch (RemoteException e) {
-           // TODO: what do we do with exceptions?
-           throw new AssertionError("not implemented", e);
+           // This could be a logic error (remote side returning error), which we need to fix.
+           //
+           // This could also be a DeadObjectException in which case its probably just iorapd
+           // being manually restarted.
+           //
+           // Don't make any assumption, since DeadObjectException could also mean iorapd crashed
+           // unexpectedly.
+           //
+           // DeadObjectExceptions are recovered from using DeathRecipient and #linkToDeath.
+           handleRemoteError(e);
        }
     }
+
+    private static void handleRemoteError(Throwable t) {
+        if (WTF_CRASH) {
+            // In development modes, we just want to crash.
+            throw new AssertionError("unexpected remote error", t);
+        } else {
+            // Log to wtf which gets sent to dropbox, and in system_server this does not crash.
+            Log.wtf(TAG, t);
+        }
+    }
 }
diff --git a/startop/iorap/tests/AndroidTest.xml b/startop/iorap/tests/AndroidTest.xml
index f83a16e..919154d 100644
--- a/startop/iorap/tests/AndroidTest.xml
+++ b/startop/iorap/tests/AndroidTest.xml
@@ -33,6 +33,15 @@
     <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer">
     </target_preparer>
 
+    <target_preparer
+        class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- Crash instead of using Log.wtf within the system_server iorap code. -->
+        <option name="set-property" key="iorapd.forwarding_service.wtf_crash" value="true" />
+        <!-- IIorapd has fake behavior: it doesn't do anything but reply with 'DONE' status -->
+        <option name="set-property" key="iorapd.binder.fake" value="true" />
+        <option name="restore-properties" value="true" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.google.android.startop.iorap.tests" />
         <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp
index 7dc83c3..f5b4308 100644
--- a/startop/view_compiler/Android.bp
+++ b/startop/view_compiler/Android.bp
@@ -16,12 +16,12 @@
 
 cc_defaults {
     name: "viewcompiler_defaults",
+    defaults: ["libdexfile_static_defaults"],
     header_libs: [
         "libbase_headers",
     ],
     shared_libs: [
         "libbase",
-        "libdexfile",
         "libz",
         "slicer",
     ],
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index 2b99ce1..2d29875 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -50,10 +50,10 @@
     }
 
     private CallAttributes(Parcel in) {
-        mPreciseCallState = (PreciseCallState) in.readValue(mPreciseCallState.getClass()
-                .getClassLoader());
+        mPreciseCallState = (PreciseCallState)
+                in.readValue(PreciseCallState.class.getClassLoader());
         mNetworkType = in.readInt();
-        mCallQuality = (CallQuality) in.readValue(mCallQuality.getClass().getClassLoader());
+        mCallQuality = (CallQuality) in.readValue(CallQuality.class.getClassLoader());
     }
 
     // getters
diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java
index b27f6b4..cbe62284 100644
--- a/telephony/java/android/telephony/CallQuality.java
+++ b/telephony/java/android/telephony/CallQuality.java
@@ -92,6 +92,10 @@
         mCodecType = in.readInt();
     }
 
+    /** @hide **/
+    public CallQuality() {
+    }
+
     /**
      * Constructor.
      *
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/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 9fee593..af324de 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -701,7 +701,7 @@
      * @hide
      */
     @SystemApi
-    public void onCallAttributesChanged(CallAttributes callAttributes) {
+    public void onCallAttributesChanged(@NonNull CallAttributes callAttributes) {
         // default implementation empty
     }
 
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index bf9bf9a..4475630 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1631,8 +1631,9 @@
 
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    public static boolean bearerBitmapHasCdma(int radioTechnologyBitmap) {
-        return (RIL_RADIO_CDMA_TECHNOLOGY_BITMASK & radioTechnologyBitmap) != 0;
+    public static boolean bearerBitmapHasCdma(int networkTypeBitmask) {
+        return (RIL_RADIO_CDMA_TECHNOLOGY_BITMASK
+                & convertNetworkTypeBitmaskToBearerBitmask(networkTypeBitmask)) != 0;
     }
 
     /** @hide */
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..80ee0a7 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 {
@@ -10133,4 +10134,26 @@
         }
         return ret;
     }
+
+    /**
+     * Broadcast intent action for network country code changes.
+     *
+     * <p>
+     * The {@link #EXTRA_NETWORK_COUNTRY} extra indicates the country code of the current
+     * network returned by {@link #getNetworkCountryIso()}.
+     *
+     * @see #EXTRA_NETWORK_COUNTRY
+     * @see #getNetworkCountryIso()
+     */
+    public static final String ACTION_NETWORK_COUNTRY_CHANGED =
+            "android.telephony.action.NETWORK_COUNTRY_CHANGED";
+
+    /**
+     * The extra used with an {@link #ACTION_NETWORK_COUNTRY_CHANGED} to specify the
+     * the country code in ISO 3166 format.
+     * <p class="note">
+     * Retrieve with {@link android.content.Intent#getStringExtra(String)}.
+     */
+    public static final String EXTRA_NETWORK_COUNTRY =
+            "android.telephony.extra.NETWORK_COUNTRY";
 }
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 294c79b..3d2fe5f 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -23,6 +23,7 @@
 import android.net.LinkAddress;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.data.ApnSetting.ProtocolType;
 
 import java.net.InetAddress;
 import java.util.ArrayList;
@@ -40,7 +41,7 @@
     private final int mSuggestedRetryTime;
     private final int mCid;
     private final int mActive;
-    private final String mType;
+    private final int mProtocolType;
     private final String mIfname;
     private final List<LinkAddress> mAddresses;
     private final List<InetAddress> mDnses;
@@ -53,8 +54,8 @@
      * @param suggestedRetryTime The suggested data retry time in milliseconds.
      * @param cid The unique id of the data connection.
      * @param active Data connection active status. 0 = inactive, 1 = dormant, 2 = active.
-     * @param type The connection protocol, should be one of the PDP_type values in TS 27.007
-     *             section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP".
+     * @param protocolType The connection protocol, should be one of the PDP_type values in 3GPP
+     *                     TS 27.007 section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP".
      * @param ifname The network interface name.
      * @param addresses A list of addresses with optional "/" prefix length, e.g.,
      *                  "192.0.1.3" or "192.0.1.11/16 2001:db8::1/64". Typically 1 IPv4 or 1 IPv6 or
@@ -71,7 +72,7 @@
      *            either not sent a value or sent an invalid value.
      */
     public DataCallResponse(int status, int suggestedRetryTime, int cid, int active,
-                            @Nullable String type, @Nullable String ifname,
+                            @ProtocolType int protocolType, @Nullable String ifname,
                             @Nullable List<LinkAddress> addresses,
                             @Nullable List<InetAddress> dnses,
                             @Nullable List<InetAddress> gateways,
@@ -80,7 +81,7 @@
         mSuggestedRetryTime = suggestedRetryTime;
         mCid = cid;
         mActive = active;
-        mType = (type == null) ? "" : type;
+        mProtocolType = protocolType;
         mIfname = (ifname == null) ? "" : ifname;
         mAddresses = (addresses == null) ? new ArrayList<>() : addresses;
         mDnses = (dnses == null) ? new ArrayList<>() : dnses;
@@ -94,7 +95,7 @@
         mSuggestedRetryTime = source.readInt();
         mCid = source.readInt();
         mActive = source.readInt();
-        mType = source.readString();
+        mProtocolType = source.readInt();
         mIfname = source.readString();
         mAddresses = new ArrayList<>();
         source.readList(mAddresses, LinkAddress.class.getClassLoader());
@@ -128,11 +129,10 @@
     public int getActive() { return mActive; }
 
     /**
-     * @return The connection protocol, should be one of the PDP_type values in TS 27.007 section
-     * 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP".
+     * @return The connection protocol type.
      */
-    @NonNull
-    public String getType() { return mType; }
+    @ProtocolType
+    public int getProtocolType() { return mProtocolType; }
 
     /**
      * @return The network interface name.
@@ -181,7 +181,7 @@
            .append(" retry=").append(mSuggestedRetryTime)
            .append(" cid=").append(mCid)
            .append(" active=").append(mActive)
-           .append(" type=").append(mType)
+           .append(" protocolType=").append(mProtocolType)
            .append(" ifname=").append(mIfname)
            .append(" addresses=").append(mAddresses)
            .append(" dnses=").append(mDnses)
@@ -205,7 +205,7 @@
                 && this.mSuggestedRetryTime == other.mSuggestedRetryTime
                 && this.mCid == other.mCid
                 && this.mActive == other.mActive
-                && this.mType.equals(other.mType)
+                && this.mProtocolType == other.mProtocolType
                 && this.mIfname.equals(other.mIfname)
                 && mAddresses.size() == other.mAddresses.size()
                 && mAddresses.containsAll(other.mAddresses)
@@ -220,8 +220,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mStatus, mSuggestedRetryTime, mCid, mActive, mType, mIfname, mAddresses,
-                mDnses, mGateways, mPcscfs, mMtu);
+        return Objects.hash(mStatus, mSuggestedRetryTime, mCid, mActive, mProtocolType, mIfname,
+                mAddresses, mDnses, mGateways, mPcscfs, mMtu);
     }
 
     @Override
@@ -235,7 +235,7 @@
         dest.writeInt(mSuggestedRetryTime);
         dest.writeInt(mCid);
         dest.writeInt(mActive);
-        dest.writeString(mType);
+        dest.writeInt(mProtocolType);
         dest.writeString(mIfname);
         dest.writeList(mAddresses);
         dest.writeList(mDnses);
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index da4822c..1d196f9 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -16,14 +16,23 @@
 
 package android.telephony.data;
 
+import static android.telephony.data.ApnSetting.ProtocolType;
+
+import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.TelephonyManager.NetworkTypeBitMask;
+import android.telephony.data.ApnSetting.ApnType;
+import android.telephony.data.ApnSetting.AuthType;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.RILConstants;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Description of a mobile data profile used for establishing
  * data connections.
@@ -32,24 +41,39 @@
  */
 @SystemApi
 public final class DataProfile implements Parcelable {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"TYPE_"},
+            value = {
+                    TYPE_COMMON,
+                    TYPE_3GPP,
+                    TYPE_3GPP2})
+    public @interface DataProfileType {}
 
-    // The types indicating the data profile is used on GSM (3GPP) or CDMA (3GPP2) network.
+    /** Common data profile */
     public static final int TYPE_COMMON = 0;
+
+    /** 3GPP type data profile */
     public static final int TYPE_3GPP = 1;
+
+    /** 3GPP2 type data profile */
     public static final int TYPE_3GPP2 = 2;
 
     private final int mProfileId;
 
     private final String mApn;
 
-    private final String mProtocol;
+    @ProtocolType
+    private final int mProtocolType;
 
+    @AuthType
     private final int mAuthType;
 
     private final String mUserName;
 
     private final String mPassword;
 
+    @DataProfileType
     private final int mType;
 
     private final int mMaxConnsTime;
@@ -60,10 +84,13 @@
 
     private final boolean mEnabled;
 
+    @ApnType
     private final int mSupportedApnTypesBitmap;
 
-    private final String mRoamingProtocol;
+    @ProtocolType
+    private final int mRoamingProtocolType;
 
+    @NetworkTypeBitMask
     private final int mBearerBitmap;
 
     private final int mMtu;
@@ -73,14 +100,14 @@
     private final boolean mPreferred;
 
     /** @hide */
-    public DataProfile(int profileId, String apn, String protocol, int authType, String userName,
-                       String password, int type, int maxConnsTime, int maxConns, int waitTime,
-                       boolean enabled, int supportedApnTypesBitmap, String roamingProtocol,
-                       int bearerBitmap, int mtu, boolean persistent, boolean preferred) {
-
+    public DataProfile(int profileId, String apn, @ProtocolType int protocolType, int authType,
+                       String userName, String password, int type, int maxConnsTime, int maxConns,
+                       int waitTime, boolean enabled, @ApnType int supportedApnTypesBitmap,
+                       @ProtocolType int roamingProtocolType, @NetworkTypeBitMask int bearerBitmap,
+                       int mtu, boolean persistent, boolean preferred) {
         this.mProfileId = profileId;
         this.mApn = apn;
-        this.mProtocol = protocol;
+        this.mProtocolType = protocolType;
         if (authType == -1) {
             authType = TextUtils.isEmpty(userName) ? RILConstants.SETUP_DATA_AUTH_NONE
                     : RILConstants.SETUP_DATA_AUTH_PAP_CHAP;
@@ -95,7 +122,7 @@
         this.mEnabled = enabled;
 
         this.mSupportedApnTypesBitmap = supportedApnTypesBitmap;
-        this.mRoamingProtocol = roamingProtocol;
+        this.mRoamingProtocolType = roamingProtocolType;
         this.mBearerBitmap = bearerBitmap;
         this.mMtu = mtu;
         this.mPersistent = persistent;
@@ -106,7 +133,7 @@
     public DataProfile(Parcel source) {
         mProfileId = source.readInt();
         mApn = source.readString();
-        mProtocol = source.readString();
+        mProtocolType = source.readInt();
         mAuthType = source.readInt();
         mUserName = source.readString();
         mPassword = source.readString();
@@ -116,7 +143,7 @@
         mWaitTime = source.readInt();
         mEnabled = source.readBoolean();
         mSupportedApnTypesBitmap = source.readInt();
-        mRoamingProtocol = source.readString();
+        mRoamingProtocolType = source.readInt();
         mBearerBitmap = source.readInt();
         mMtu = source.readInt();
         mPersistent = source.readBoolean();
@@ -134,16 +161,14 @@
     public String getApn() { return mApn; }
 
     /**
-     * @return The connection protocol, should be one of the PDP_type values in TS 27.007 section
-     * 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP".
+     * @return The connection protocol defined in 3GPP TS 27.007 section 10.1.1.
      */
-    public String getProtocol() { return mProtocol; }
+    public @ProtocolType int getProtocol() { return mProtocolType; }
 
     /**
-     * @return The authentication protocol used for this PDP context
-     * (None: 0, PAP: 1, CHAP: 2, PAP&CHAP: 3)
+     * @return The authentication protocol used for this PDP context.
      */
-    public int getAuthType() { return mAuthType; }
+    public @AuthType int getAuthType() { return mAuthType; }
 
     /**
      * @return The username for APN. Can be null.
@@ -156,9 +181,9 @@
     public String getPassword() { return mPassword; }
 
     /**
-     * @return The profile type. Could be one of TYPE_COMMON, TYPE_3GPP, or TYPE_3GPP2.
+     * @return The profile type.
      */
-    public int getType() { return mType; }
+    public @DataProfileType int getType() { return mType; }
 
     /**
      * @return The period in seconds to limit the maximum connections.
@@ -183,20 +208,19 @@
     public boolean isEnabled() { return mEnabled; }
 
     /**
-     * @return The supported APN types bitmap. See RIL_ApnTypes for the value of each bit.
+     * @return The supported APN types bitmap.
      */
-    public int getSupportedApnTypesBitmap() { return mSupportedApnTypesBitmap; }
+    public @ApnType int getSupportedApnTypesBitmap() { return mSupportedApnTypesBitmap; }
 
     /**
-     * @return  The connection protocol on roaming network, should be one of the PDP_type values in
-     * TS 27.007 section 10.1.1. For example, "IP", "IPV6", "IPV4V6", or "PPP".
+     * @return The connection protocol on roaming network defined in 3GPP TS 27.007 section 10.1.1.
      */
-    public String getRoamingProtocol() { return mRoamingProtocol; }
+    public @ProtocolType int getRoamingProtocol() { return mRoamingProtocolType; }
 
     /**
-     * @return The bearer bitmap. See RIL_RadioAccessFamily for the value of each bit.
+     * @return The bearer bitmap indicating the applicable networks for this data profile.
      */
-    public int getBearerBitmap() { return mBearerBitmap; }
+    public @NetworkTypeBitMask int getBearerBitmap() { return mBearerBitmap; }
 
     /**
      * @return The maximum transmission unit (MTU) size in bytes.
@@ -222,12 +246,12 @@
 
     @Override
     public String toString() {
-        return "DataProfile=" + mProfileId + "/" + mProtocol + "/" + mAuthType
+        return "DataProfile=" + mProfileId + "/" + mProtocolType + "/" + mAuthType
                 + "/" + (Build.IS_USER ? "***/***/***" :
                          (mApn + "/" + mUserName + "/" + mPassword)) + "/" + mType + "/"
                 + mMaxConnsTime + "/" + mMaxConns + "/"
                 + mWaitTime + "/" + mEnabled + "/" + mSupportedApnTypesBitmap + "/"
-                + mRoamingProtocol + "/" + mBearerBitmap + "/" + mMtu + "/" + mPersistent + "/"
+                + mRoamingProtocolType + "/" + mBearerBitmap + "/" + mMtu + "/" + mPersistent + "/"
                 + mPreferred;
     }
 
@@ -242,7 +266,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mProfileId);
         dest.writeString(mApn);
-        dest.writeString(mProtocol);
+        dest.writeInt(mProtocolType);
         dest.writeInt(mAuthType);
         dest.writeString(mUserName);
         dest.writeString(mPassword);
@@ -252,7 +276,7 @@
         dest.writeInt(mWaitTime);
         dest.writeBoolean(mEnabled);
         dest.writeInt(mSupportedApnTypesBitmap);
-        dest.writeString(mRoamingProtocol);
+        dest.writeInt(mRoamingProtocolType);
         dest.writeInt(mBearerBitmap);
         dest.writeInt(mMtu);
         dest.writeBoolean(mPersistent);
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/ims/internal/uce/common/StatusCode.java b/telephony/java/com/android/ims/internal/uce/common/StatusCode.java
index 3921cfb..7250eee 100644
--- a/telephony/java/com/android/ims/internal/uce/common/StatusCode.java
+++ b/telephony/java/com/android/ims/internal/uce/common/StatusCode.java
@@ -64,6 +64,10 @@
     public static final int UCE_NO_CHANGE_IN_CAP = 13;
     /**  Service is unknown. */
     public static final int UCE_SERVICE_UNKNOWN = 14;
+     /** Service cannot support Invalid Feature Tag   */
+    public static final int UCE_INVALID_FEATURE_TAG = 15;
+    /** Service is Available   */
+    public static final int UCE_SERVICE_AVAILABLE = 16;
 
 
     private int mStatusCode = UCE_SUCCESS;
diff --git a/telephony/java/com/android/ims/internal/uce/uceservice/IUceService.aidl b/telephony/java/com/android/ims/internal/uce/uceservice/IUceService.aidl
index 43f83cd..1fb8513 100644
--- a/telephony/java/com/android/ims/internal/uce/uceservice/IUceService.aidl
+++ b/telephony/java/com/android/ims/internal/uce/uceservice/IUceService.aidl
@@ -66,10 +66,30 @@
      * service the client created.
      *
      * @return  optionsServiceHandle
+     *
      * @hide
+     *
+     * @deprecated This is replaced with new API createOptionsServiceForSubscription()
      */
     int createOptionsService(IOptionsListener optionsListener,
                              inout UceLong optionsServiceListenerHdl);
+    /**
+     * Creates a options service for Capability Discovery.
+     * @param optionsListener IOptionsListener object.
+     * @param optionsServiceListenerHdl wrapper for client's listener handle to be stored.
+     * @param iccId the ICC-ID derived from SubscriptionInfo for the Service requested
+     *
+     * The service will fill UceLong.mUceLong with presenceListenerHandle allocated and
+     * used to validate callbacks received in IPresenceListener are indeed from the
+     * service the client created.
+     *
+     * @return  optionsServiceHandle
+     *
+     * @hide
+     */
+    int createOptionsServiceForSubscription(IOptionsListener optionsListener,
+                             inout UceLong optionsServiceListenerHdl,
+                             in String iccId);
 
     /**
      * Destroys a Options service.
@@ -89,14 +109,36 @@
      * service the client created.
      *
      * @return  presenceServiceHdl
+     *
      * @hide
+     *
+     * @deprecated This is replaced with new API createPresenceServiceForSubscription()
      */
     int createPresenceService(IPresenceListener presenceServiceListener,
                               inout UceLong presenceServiceListenerHdl);
+    /**
+     * Creates a presence service.
+     * @param presenceServiceListener IPresenceListener object
+     * @param presenceServiceListenerHdl wrapper for client's listener handle to be stored.
+     * @param iccId the ICC-ID derived from SubscriptionInfo for the Service requested
+     *
+     * The service will fill UceLong.mUceLong with presenceListenerHandle allocated and
+     * used to validate callbacks received in IPresenceListener are indeed from the
+     * service the client created.
+     *
+     * @return  presenceServiceHdl
+     *
+     * @hide
+     */
+    int createPresenceServiceForSubscription(IPresenceListener presenceServiceListener,
+                              inout UceLong presenceServiceListenerHdl,
+                              in String iccId);
 
     /**
      * Destroys a presence service.
+     *
      * @param presenceServiceHdl handle returned during createPresenceService()
+     *
      * @hide
      */
     void destroyPresenceService(int presenceServiceHdl);
@@ -105,23 +147,55 @@
 
     /**
      * Query the UCE Service for information to know whether the is registered.
+     *
      * @return boolean, true if Registered to for network events else false.
+     *
      * @hide
      */
     boolean getServiceStatus();
 
     /**
      * Query the UCE Service for presence Service.
+     *
      * @return IPresenceService object.
+     *
      * @hide
+     *
+     * @deprecated use API getPresenceServiceForSubscription()
      */
     IPresenceService getPresenceService();
 
     /**
+     * Query the UCE Service for presence Service.
+     *
+     * @param iccId the ICC-ID derived from SubscriptionInfo for the Service requested
+     *
+     * @return IPresenceService object.
+     *
+     * @hide
+     */
+    IPresenceService getPresenceServiceForSubscription(in String iccId);
+
+    /**
      * Query the UCE Service for options service object.
+     *
      * @return IOptionsService object.
+     *
+     * @deprecated use API getOptionsServiceForSubscription()
+     *
      * @hide
      */
     IOptionsService getOptionsService();
 
+    /**
+     * Query the UCE Service for options service object.
+     *
+     * @param iccId the ICC-ID derived from SubscriptionInfo for the Service requested
+     *
+     * @return IOptionsService object.
+     *
+     * @hide
+     */
+    IOptionsService getOptionsServiceForSubscription(in String iccId);
+
 }
diff --git a/telephony/java/com/android/ims/internal/uce/uceservice/UceServiceBase.java b/telephony/java/com/android/ims/internal/uce/uceservice/UceServiceBase.java
index 3660e03..ceb1919 100644
--- a/telephony/java/com/android/ims/internal/uce/uceservice/UceServiceBase.java
+++ b/telephony/java/com/android/ims/internal/uce/uceservice/UceServiceBase.java
@@ -56,6 +56,14 @@
             return onCreateOptionsService(optionsListener, optionsServiceListenerHdl);
         }
 
+        @Override
+        public int createOptionsServiceForSubscription(IOptionsListener optionsListener,
+                                      UceLong optionsServiceListenerHdl,
+                                      String iccId) {
+            return onCreateOptionsService(optionsListener, optionsServiceListenerHdl,
+                                          iccId);
+        }
+
 
         @Override
         public void destroyOptionsService(int optionsServiceHandle) {
@@ -70,6 +78,14 @@
         }
 
         @Override
+        public int createPresenceServiceForSubscription(IPresenceListener presServiceListener,
+                                         UceLong presServiceListenerHdl,
+                                         String iccId) {
+            return onCreatePresService(presServiceListener, presServiceListenerHdl,
+                                       iccId);
+        }
+
+        @Override
         public void destroyPresenceService(int presServiceHdl) {
             onDestroyPresService(presServiceHdl);
         }
@@ -85,9 +101,19 @@
         }
 
         @Override
+        public IPresenceService getPresenceServiceForSubscription(String iccId) {
+            return onGetPresenceService(iccId);
+        }
+
+        @Override
         public IOptionsService getOptionsService() {
             return onGetOptionsService();
         }
+
+        @Override
+        public IOptionsService getOptionsServiceForSubscription(String iccId) {
+            return onGetOptionsService(iccId);
+        }
     }
 
     private UceServiceBinder mBinder;
@@ -120,6 +146,13 @@
         return 0;
     }
 
+    protected int onCreateOptionsService(IOptionsListener optionsListener,
+                                         UceLong optionsServiceListenerHdl,
+                                         String iccId) {
+        //no-op
+        return 0;
+    }
+
     protected void onDestroyOptionsService(int cdServiceHandle) {
         //no-op
         return;
@@ -131,6 +164,13 @@
         return 0;
     }
 
+    protected int onCreatePresService(IPresenceListener presServiceListener,
+                                      UceLong presServiceListenerHdl,
+                                      String iccId) {
+        //no-op
+        return 0;
+    }
+
     protected void onDestroyPresService(int presServiceHdl) {
         //no-op
         return;
@@ -146,8 +186,18 @@
         return null;
     }
 
+    protected IPresenceService onGetPresenceService(String iccId) {
+        //no-op
+        return null;
+    }
+
     protected IOptionsService onGetOptionsService () {
         //no-op
         return null;
     }
+
+    protected IOptionsService onGetOptionsService (String iccId) {
+        //no-op
+        return null;
+    }
 }
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/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 9300034..d9b206f 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -268,31 +268,11 @@
     int LTE_ON_CDMA_FALSE = 0;
     int LTE_ON_CDMA_TRUE = 1;
 
-    int CDM_TTY_MODE_DISABLED = 0;
-    int CDM_TTY_MODE_ENABLED = 1;
-
-    int CDM_TTY_FULL_MODE = 1;
-    int CDM_TTY_HCO_MODE = 2;
-    int CDM_TTY_VCO_MODE = 3;
-
-    /* Setup a packet data connection. See ril.h RIL_REQUEST_SETUP_DATA_CALL */
-    int SETUP_DATA_TECH_CDMA      = 0;
-    int SETUP_DATA_TECH_GSM       = 1;
-
     int SETUP_DATA_AUTH_NONE      = 0;
     int SETUP_DATA_AUTH_PAP       = 1;
     int SETUP_DATA_AUTH_CHAP      = 2;
     int SETUP_DATA_AUTH_PAP_CHAP  = 3;
 
-    String SETUP_DATA_PROTOCOL_IP     = "IP";
-    String SETUP_DATA_PROTOCOL_IPV6   = "IPV6";
-    String SETUP_DATA_PROTOCOL_IPV4V6 = "IPV4V6";
-
-    /* NV config radio reset types. */
-    int NV_CONFIG_RELOAD_RESET = 1;
-    int NV_CONFIG_ERASE_RESET = 2;
-    int NV_CONFIG_FACTORY_RESET = 3;
-
     /* LCE service related constants. */
     int LCE_NOT_AVAILABLE = -1;
     int LCE_STOPPED = 0;
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/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml
index b381cbf..0b3bd70 100644
--- a/tests/ActivityTests/AndroidManifest.xml
+++ b/tests/ActivityTests/AndroidManifest.xml
@@ -79,6 +79,7 @@
             android:singleUser="true" android:exported="true" />
         <receiver android:name="TrackTimeReceiver" />
         <receiver android:name="AlarmSpamReceiver" />
+        <receiver android:name="SlowReceiver" />
         <activity android:name="DisableScreenshotsActivity"
                 android:label="DisableScreenshots"
                 android:theme="@style/DisableScreenshots">
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
index 0f49608..2581e08 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
@@ -16,21 +16,21 @@
 
 package com.google.android.test.activity;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.AlarmManager;
 import android.app.AlertDialog;
 import android.app.PendingIntent;
-import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentProviderClient;
+import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.os.Bundle;
@@ -42,21 +42,18 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.graphics.Bitmap;
 import android.provider.Settings;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.ScrollView;
-import android.widget.Toast;
+import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.content.res.Configuration;
-import android.util.Log;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.Toast;
 
+import java.util.ArrayList;
+import java.util.List;
 
 public class ActivityTestMain extends Activity {
     static final String TAG = "ActivityTest";
@@ -73,8 +70,13 @@
 
     ServiceConnection mIsolatedConnection;
 
+    static final String SLOW_RECEIVER_ACTION = "com.google.android.test.activity.SLOW_ACTION";
+    static final String SLOW_RECEIVER_EXTRA = "slow_ordinal";
+
     static final int MSG_SPAM = 1;
     static final int MSG_SPAM_ALARM = 2;
+    static final int MSG_SLOW_RECEIVER = 3;
+    static final int MSG_SLOW_ALARM_RECEIVER = 4;
 
     final Handler mHandler = new Handler() {
         @Override
@@ -100,11 +102,58 @@
                     mAlarm.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, when+(30*1000), pi);
                     scheduleSpamAlarm(30*1000);
                 } break;
+                case MSG_SLOW_RECEIVER: {
+                    // Several back to back, to illustrate dispatch policy
+                    Intent intent = new Intent(ActivityTestMain.this, SlowReceiver.class);
+                    intent.setAction(SLOW_RECEIVER_ACTION);
+                    intent.putExtra(SLOW_RECEIVER_EXTRA, 1);
+                    sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler,
+                            Activity.RESULT_OK, null, null);
+                    intent.putExtra(SLOW_RECEIVER_EXTRA, 2);
+                    sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler,
+                            Activity.RESULT_OK, null, null);
+                    intent.putExtra(SLOW_RECEIVER_EXTRA, 3);
+                    sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler,
+                            Activity.RESULT_OK, null, null);
+                } break;
+                case MSG_SLOW_ALARM_RECEIVER: {
+                    // Several back to back, to illustrate dispatch policy
+                    Intent intent = new Intent(ActivityTestMain.this, SlowReceiver.class);
+                    intent.setAction(SLOW_RECEIVER_ACTION);
+                    intent.putExtra(SLOW_RECEIVER_EXTRA, 1);
+                    sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler,
+                            Activity.RESULT_OK, null, null);
+                    intent.putExtra(SLOW_RECEIVER_EXTRA, 2);
+                    sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler,
+                            Activity.RESULT_OK, null, null);
+                    intent.putExtra(SLOW_RECEIVER_EXTRA, 3);
+                    sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler,
+                            Activity.RESULT_OK, null, null);
+
+                    // Also send a broadcast alarm to evaluate the alarm fast-forward policy
+                    intent.putExtra(SLOW_RECEIVER_EXTRA, 4);
+                    PendingIntent pi = PendingIntent.getBroadcast(ActivityTestMain.this, 1, intent, 0);
+                    AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+                    long now = SystemClock.elapsedRealtime();
+                    Log.i(TAG, "Setting alarm for now + 5 seconds");
+                    am.setExact(AlarmManager.ELAPSED_REALTIME, now + 5_000, pi);
+                } break;
             }
             super.handleMessage(msg);
         }
     };
 
+    final BroadcastReceiver mSlowReceiverCompletion = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final int extra = intent.getIntExtra(SLOW_RECEIVER_EXTRA, -1);
+            final String msg = "Slow receiver " + extra + " completed";
+            Toast.makeText(ActivityTestMain.this, msg, Toast.LENGTH_LONG)
+                    .show();
+            Log.i(TAG, msg);
+        }
+    };
+
     class BroadcastResultReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -387,6 +436,18 @@
                 return true;
             }
         });
+        menu.add("Slow receiver").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+            @Override public boolean onMenuItemClick(MenuItem item) {
+                scheduleSlowReceiver();
+                return true;
+            }
+        });
+        menu.add("Slow alarm receiver").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+            @Override public boolean onMenuItemClick(MenuItem item) {
+                scheduleSlowAlarmReceiver();
+                return true;
+            }
+        });
         menu.add("Spam!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
             @Override public boolean onMenuItemClick(MenuItem item) {
                 scheduleSpam(false);
@@ -469,6 +530,7 @@
     protected void onStop() {
         super.onStop();
         mHandler.removeMessages(MSG_SPAM_ALARM);
+        mHandler.removeMessages(MSG_SLOW_RECEIVER);
         for (ServiceConnection conn : mConnections) {
             unbindService(conn);
         }
@@ -544,6 +606,16 @@
         mHandler.sendMessageDelayed(msg, delay);
     }
 
+    void scheduleSlowReceiver() {
+        mHandler.removeMessages(MSG_SLOW_RECEIVER);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SLOW_RECEIVER), 500);
+    }
+
+    void scheduleSlowAlarmReceiver() {
+        mHandler.removeMessages(MSG_SLOW_ALARM_RECEIVER);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SLOW_ALARM_RECEIVER), 500);
+    }
+
     private View scrollWrap(View view) {
         ScrollView scroller = new ScrollView(this);
         scroller.addView(view, new ScrollView.LayoutParams(ScrollView.LayoutParams.MATCH_PARENT,
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/SlowReceiver.java b/tests/ActivityTests/src/com/google/android/test/activity/SlowReceiver.java
new file mode 100644
index 0000000..0437a28
--- /dev/null
+++ b/tests/ActivityTests/src/com/google/android/test/activity/SlowReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * 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 com.google.android.test.activity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.util.Log;
+
+public class SlowReceiver extends BroadcastReceiver {
+    private static final String TAG = "SlowReceiver";
+    private static final long RECEIVER_DELAY = 6_000;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final int extra = intent.getIntExtra(ActivityTestMain.SLOW_RECEIVER_EXTRA, -1);
+        if (extra == 1) {
+            Log.i(TAG, "Received broadcast 1; delaying return by " + RECEIVER_DELAY + " ms");
+            long now = SystemClock.elapsedRealtime();
+            final long end = now + RECEIVER_DELAY;
+            while (now < end) {
+                try {
+                    Thread.sleep(end - now);
+                } catch (InterruptedException e) { }
+                now = SystemClock.elapsedRealtime();
+            }
+        } else {
+            Log.i(TAG, "Extra parameter not 1, returning immediately");
+        }
+        Log.i(TAG, "Returning from onReceive()");
+    }
+}
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/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
index 07c4a93..c16efbd 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java
@@ -70,6 +70,7 @@
             R.id.benchmark_text_low_hitrate,
             R.id.benchmark_edit_text_input,
             R.id.benchmark_overdraw,
+            R.id.benchmark_bitmap_upload,
     };
 
     public static class LocalBenchmarksList extends ListFragment {
@@ -204,6 +205,7 @@
             case R.id.benchmark_text_low_hitrate:
             case R.id.benchmark_edit_text_input:
             case R.id.benchmark_overdraw:
+            case R.id.benchmark_bitmap_upload:
             case R.id.benchmark_memory_bandwidth:
             case R.id.benchmark_memory_latency:
             case R.id.benchmark_power_management:
@@ -323,6 +325,9 @@
                 intent = new Intent(getApplicationContext(), EditTextInputActivity.class);
                 break;
             case R.id.benchmark_overdraw:
+                intent = new Intent(getApplicationContext(), FullScreenOverdrawActivity.class);
+                break;
+            case R.id.benchmark_bitmap_upload:
                 intent = new Intent(getApplicationContext(), BitmapUploadActivity.class);
                 break;
             case R.id.benchmark_memory_bandwidth:
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
index 89c6aed..5723c59 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/registry/BenchmarkRegistry.java
@@ -229,6 +229,8 @@
                 return context.getString(R.string.cpu_gflops_name);
             case R.id.benchmark_overdraw:
                 return context.getString(R.string.overdraw_name);
+            case R.id.benchmark_bitmap_upload:
+                return context.getString(R.string.bitmap_upload_name);
             default:
                 return "Some Benchmark";
         }
diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
index 7870902..7692836 100644
--- a/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
+++ b/tests/JankBench/app/src/main/java/com/android/benchmark/ui/BitmapUploadActivity.java
@@ -32,6 +32,7 @@
 import android.view.View;
 
 import com.android.benchmark.R;
+import com.android.benchmark.registry.BenchmarkRegistry;
 import com.android.benchmark.ui.automation.Automator;
 import com.android.benchmark.ui.automation.Interaction;
 
@@ -124,7 +125,9 @@
         final int runId = getIntent().getIntExtra("com.android.benchmark.RUN_ID", 0);
         final int iteration = getIntent().getIntExtra("com.android.benchmark.ITERATION", -1);
 
-        mAutomator = new Automator("BMUpload", runId, iteration, getWindow(),
+        String name = BenchmarkRegistry.getBenchmarkName(this, R.id.benchmark_bitmap_upload);
+
+        mAutomator = new Automator(name, runId, iteration, getWindow(),
                 new Automator.AutomateCallback() {
                     @Override
                     public void onPostAutomate() {
diff --git a/tests/JankBench/app/src/main/res/values/ids.xml b/tests/JankBench/app/src/main/res/values/ids.xml
index 6801fd9..694e0d9 100644
--- a/tests/JankBench/app/src/main/res/values/ids.xml
+++ b/tests/JankBench/app/src/main/res/values/ids.xml
@@ -23,6 +23,7 @@
     <item name="benchmark_text_low_hitrate" type="id" />
     <item name="benchmark_edit_text_input" type="id" />
     <item name="benchmark_overdraw" type="id" />
+    <item name="benchmark_bitmap_upload" type="id" />
     <item name="benchmark_memory_bandwidth" type="id" />
     <item name="benchmark_memory_latency" type="id" />
     <item name="benchmark_power_management" type="id" />
diff --git a/tests/JankBench/app/src/main/res/values/strings.xml b/tests/JankBench/app/src/main/res/values/strings.xml
index 270adf8..5c240589 100644
--- a/tests/JankBench/app/src/main/res/values/strings.xml
+++ b/tests/JankBench/app/src/main/res/values/strings.xml
@@ -33,6 +33,8 @@
     <string name="edit_text_input_description">Tests edit text input</string>
     <string name="overdraw_name">Overdraw Test</string>
     <string name="overdraw_description">Tests how the device handles overdraw</string>
+    <string name="bitmap_upload_name">Bitmap Upload Test</string>
+    <string name="bitmap_upload_description">Tests bitmap upload</string>
     <string name="memory_bandwidth_name">Memory Bandwidth</string>
     <string name="memory_bandwidth_description">Test device\'s memory bandwidth</string>
     <string name="memory_latency_name">Memory Latency</string>
diff --git a/tests/JankBench/app/src/main/res/xml/benchmark.xml b/tests/JankBench/app/src/main/res/xml/benchmark.xml
index 07c453c..fccc7b9 100644
--- a/tests/JankBench/app/src/main/res/xml/benchmark.xml
+++ b/tests/JankBench/app/src/main/res/xml/benchmark.xml
@@ -62,6 +62,12 @@
         benchmark:category="ui"
         benchmark:description="@string/overdraw_description" />
 
+    <com.android.benchmark.Benchmark
+        benchmark:name="@string/bitmap_upload_name"
+        benchmark:id="@id/benchmark_bitmap_upload"
+        benchmark:category="ui"
+        benchmark:description="@string/bitmap_upload_description" />
+
     <!--
     <com.android.benchmark.Benchmark
         benchmark:name="@string/memory_bandwidth_name"
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..e128a6c 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.VersionedPackage;
 import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
@@ -31,11 +32,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 +41,8 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -86,8 +86,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 +99,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 +112,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 +131,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 +141,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 +152,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 +192,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 +251,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 +279,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 +301,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);
+            VersionedPackage cause = new VersionedPackage(
+                    "com.android.tests.rollback.testapp.Foo", 42);
+            RollbackTestUtils.rollback(rollback.getRollbackId(), cause);
             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;
-                }
-            }
-            assertNotNull(rollback);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+            rollback = getUniqueRollbackInfoForPackage(
+                    rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback, cause);
 
             // 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;
-                }
-            }
-            assertNotNull(rollback);
-            assertPackageRollbackInfoEquals(TEST_APP_A, 2, 1, rollback.targetPackage);
+            rollback = getUniqueRollbackInfoForPackage(
+                    rm.getRecentlyCommittedRollbacks(), TEST_APP_A);
+            assertRollbackInfoEquals(TEST_APP_A, 2, 1, rollback, cause);
         } finally {
             RollbackTestUtils.dropShellPermissionIdentity();
         }
@@ -378,17 +350,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 +430,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 +441,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 +488,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 +511,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 +530,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 +546,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, Collections.emptyList(), null);
             fail("expected SecurityException");
         } catch (SecurityException e) {
             // Expected.
@@ -629,27 +594,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 +661,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 +688,54 @@
             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, VersionedPackage... causePackages) {
+        assertNotNull(info);
+        assertEquals(1, info.getPackages().size());
+        assertPackageRollbackInfoEquals(packageName, versionRolledBackFrom, versionRolledBackTo,
+                info.getPackages().get(0));
+        assertEquals(causePackages.length, info.getCausePackages().size());
+        for (int i = 0; i < causePackages.length; ++i) {
+            assertEquals(causePackages[i].getPackageName(),
+                    info.getCausePackages().get(i).getPackageName());
+            assertEquals(causePackages[i].getLongVersionCode(),
+                    info.getCausePackages().get(i).getLongVersionCode());
+        }
+    }
+
+    // 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..60c7a59 100644
--- a/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
+++ b/tests/RollbackTest/src/com/android/tests/rollback/RollbackTestUtils.java
@@ -21,13 +21,14 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.rollback.RollbackInfo;
+import android.content.pm.VersionedPackage;
 import android.content.rollback.RollbackManager;
 import android.support.test.InstrumentationRegistry;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Arrays;
 
 /**
  * Utilities to facilitate testing rollbacks.
@@ -90,13 +91,21 @@
     }
 
     /**
-     * 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, VersionedPackage... causePackages)
+            throws InterruptedException {
         RollbackManager rm = getRollbackManager();
-        rm.executeRollback(rollback, LocalIntentSender.getIntentSender());
-        assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+        rm.commitRollback(rollbackId, Arrays.asList(causePackages),
+                LocalIntentSender.getIntentSender());
+        Intent result = LocalIntentSender.getIntentSenderResult();
+        int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
+                RollbackManager.STATUS_FAILURE);
+        if (status != RollbackManager.STATUS_SUCCESS) {
+            String message = result.getStringExtra(RollbackManager.EXTRA_STATUS_MESSAGE);
+            throw new AssertionError(message);
+        }
     }
 
     /**
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/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 0b74d87..5b17224 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -246,17 +246,17 @@
         assertFalse(vpn.getLockdown());
 
         // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
         assertTrue(vpn.getAlwaysOn());
         assertFalse(vpn.getLockdown());
 
         // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
         assertTrue(vpn.getAlwaysOn());
         assertTrue(vpn.getLockdown());
 
         // Remove always-on configuration.
-        assertTrue(vpn.setAlwaysOnPackage(null, false));
+        assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
         assertFalse(vpn.getAlwaysOn());
         assertFalse(vpn.getLockdown());
     }
@@ -270,11 +270,11 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
             new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -283,7 +283,7 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[1]);
 
         // Switch to another app.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
             new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -297,6 +297,87 @@
     }
 
     @Test
+    public void testLockdownWhitelist() throws Exception {
+        final Vpn vpn = createVpn(primaryUser.id);
+        final UidRange user = UidRange.createForUser(primaryUser.id);
+
+        // Set always-on with lockdown and whitelist app PKGS[2] from lockdown.
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[2])));
+        verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+                new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
+                new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
+        }));
+        assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
+        assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
+
+        // Change whitelisted app to PKGS[3].
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[3])));
+        verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
+                new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
+        }));
+        verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+                new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1),
+                new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
+        }));
+        assertBlocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[2]);
+        assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]);
+
+        // Change the VPN app.
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[3])));
+        verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
+                new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
+                new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1)
+        }));
+        verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+                new UidRange(user.start, user.start + PKG_UIDS[0] - 1),
+                new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1)
+        }));
+        assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
+        assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
+
+        // Remove the whitelist.
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
+        verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
+                new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
+                new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
+        }));
+        verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+                new UidRange(user.start + PKG_UIDS[0] + 1, user.stop),
+        }));
+        assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2],
+                user.start + PKG_UIDS[3]);
+        assertUnblocked(vpn, user.start + PKG_UIDS[0]);
+
+        // Add the whitelist.
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1])));
+        verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
+                new UidRange(user.start + PKG_UIDS[0] + 1, user.stop)
+        }));
+        verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
+                new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
+                new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
+        }));
+        assertBlocked(vpn, user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
+        assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]);
+
+        // Try whitelisting a package with a comma, should be rejected.
+        assertFalse(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList("a.b,c.d")));
+
+        // Pass a non-existent packages in the whitelist, they (and only they) should be ignored.
+        // Whitelisted package should change from PGKS[1] to PKGS[2].
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true,
+                Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
+        verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{
+                new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
+                new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
+        }));
+        verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[]{
+                new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[2] - 1),
+                new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
+        }));
+    }
+
+    @Test
     public void testLockdownAddingAProfile() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
         setMockedUsers(primaryUser);
@@ -310,7 +391,7 @@
         final UidRange profile = UidRange.createForUser(tempProfile.id);
 
         // Set lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
             new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -436,7 +517,7 @@
                 .cancelAsUser(anyString(), anyInt(), eq(userHandle));
 
         // Start showing a notification for disconnected once always-on.
-        vpn.setAlwaysOnPackage(PKGS[0], false);
+        vpn.setAlwaysOnPackage(PKGS[0], false, null);
         order.verify(mNotificationManager)
                 .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
 
@@ -450,7 +531,7 @@
                 .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
 
         // Notification should be cleared after unsetting always-on package.
-        vpn.setAlwaysOnPackage(null, false);
+        vpn.setAlwaysOnPackage(null, false, null);
         order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
     }
 
@@ -583,7 +664,9 @@
             doAnswer(invocation -> {
                 final String appName = (String) invocation.getArguments()[0];
                 final int userId = (int) invocation.getArguments()[1];
-                return UserHandle.getUid(userId, packages.get(appName));
+                Integer appId = packages.get(appName);
+                if (appId == null) throw new PackageManager.NameNotFoundException(appName);
+                return UserHandle.getUid(userId, appId);
             }).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt());
         } catch (Exception e) {
         }
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
index f2ecef9..e57433a 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -202,9 +202,11 @@
         final CountDownLatch latch = new CountDownLatch(1);
         functor.accept(latch);
         try {
-            latch.await(5000, TimeUnit.MILLISECONDS);
+            if (!latch.await(5000, TimeUnit.MILLISECONDS)) {
+                fail(timeoutMessage);
+            }
         } catch (InterruptedException e) {
-            fail(timeoutMessage);
+            fail("Thread was interrupted");
         }
     }
 
@@ -314,6 +316,7 @@
                             assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
                             assertNull(key);
                             assertNull(attr);
+                            latch.countDown();
                         })));
     }
 
@@ -383,6 +386,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(FAKE_KEYS[5], key);
+                    latch.countDown();
                 })));
 
         // MTU matches key 4 but v4 address matches key 5. The latter is stronger.
@@ -392,6 +396,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(FAKE_KEYS[5], key);
+                    latch.countDown();
                 })));
 
         // Closest to key 3 (indeed, identical)
@@ -402,6 +407,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(FAKE_KEYS[3], key);
+                    latch.countDown();
                 })));
 
         // Group hint alone must not be strong enough to override the rest
@@ -411,6 +417,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(FAKE_KEYS[3], key);
+                    latch.countDown();
                 })));
 
         // Still closest to key 3, though confidence is lower
@@ -421,6 +428,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(FAKE_KEYS[3], key);
+                    latch.countDown();
                 })));
 
         // But changing the MTU makes this closer to key 4
@@ -430,6 +438,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(FAKE_KEYS[4], key);
+                    latch.countDown();
                 })));
 
         // MTU alone not strong enough to make this group-close
@@ -441,6 +450,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertNull(key);
+                    latch.countDown();
                 })));
     }
 
@@ -450,6 +460,7 @@
                     assertTrue("Retrieve network sameness not successful : " + status.resultCode,
                             status.isSuccess());
                     assertEquals(sameness, answer.getNetworkSameness());
+                    latch.countDown();
                 })));
     }
 
@@ -488,6 +499,7 @@
                             + status.resultCode, status.isSuccess());
                     assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
                     assertNull(answer);
+                    latch.countDown();
                 })));
     }
 }
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);
 
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java
index fc4cd01..2690ee8 100644
--- a/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/AnnotationUtils.java
@@ -16,8 +16,12 @@
 
 package android.processor.view.inspector;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.stream.Collectors;
 
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.AnnotationMirror;
@@ -102,6 +106,83 @@
     }
 
     /**
+     * Get a typed list of values for an annotation array property by name.
+     *
+     * The returned list will be empty if the value was left at the default.
+     *
+     * @param propertyName The name of the property to search for
+     * @param valueClass The expected class of the property value
+     * @param element The element the annotation is on, used for exceptions
+     * @param annotationMirror An annotation mirror to search for the property
+     * @param <T> The type of the value
+     * @return A list containing the requested types
+     */
+    <T> List<T> typedArrayValuesByName(
+            String propertyName,
+            Class<T> valueClass,
+            Element element,
+            AnnotationMirror annotationMirror) {
+        return untypedArrayValuesByName(propertyName, element, annotationMirror)
+                .stream()
+                .map(annotationValue -> {
+                    final Object value = annotationValue.getValue();
+
+                    if (value == null) {
+                        throw new ProcessingException(
+                                "Unexpected null in array.",
+                                element,
+                                annotationMirror,
+                                annotationValue);
+                    }
+
+                    if (valueClass.isAssignableFrom(value.getClass())) {
+                        return valueClass.cast(value);
+                    } else {
+                        throw new ProcessingException(
+                                String.format(
+                                        "Expected array entry to have type %s, but got %s.",
+                                        valueClass.getCanonicalName(),
+                                        value.getClass().getCanonicalName()),
+                                element,
+                                annotationMirror,
+                                annotationValue);
+                    }
+                })
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Get a list of values for an annotation array property by name.
+     *
+     * @param propertyName The name of the property to search for
+     * @param element The element the annotation is on, used for exceptions
+     * @param annotationMirror An annotation mirror to search for the property
+     * @return A list of annotation values, empty list if none found
+     */
+    List<AnnotationValue> untypedArrayValuesByName(
+            String propertyName,
+            Element element,
+            AnnotationMirror annotationMirror) {
+        return typedValueByName(propertyName, List.class, element, annotationMirror)
+                .map(untypedValues -> {
+                    List<AnnotationValue> typedValues = new ArrayList<>(untypedValues.size());
+
+                    for (Object untypedValue : untypedValues) {
+                        if (untypedValue instanceof AnnotationValue) {
+                            typedValues.add((AnnotationValue) untypedValue);
+                        } else {
+                            throw new ProcessingException(
+                                    "Unable to convert array entry to AnnotationValue",
+                                    element,
+                                    annotationMirror);
+                        }
+                    }
+
+                    return typedValues;
+                }).orElseGet(Collections::emptyList);
+    }
+
+    /**
      * Get the typed value of an annotation property by name.
      *
      * The returned optional will be empty if the value was left at the default, or if the value
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java
index f1ebb87..6f58893 100644
--- a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectableClassModel.java
@@ -19,7 +19,9 @@
 import com.squareup.javapoet.ClassName;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -92,6 +94,8 @@
         private final Type mType;
         private boolean mAttributeIdInferrableFromR = true;
         private int mAttributeId = 0;
+        private List<IntEnumEntry> mIntEnumEntries;
+        private List<IntFlagEntry> mIntFlagEntries;
 
         public Property(String name, String getter, Type type) {
             mName = Objects.requireNonNull(name, "Name must not be null");
@@ -133,6 +137,40 @@
             return mType;
         }
 
+        /**
+         * Get the mapping for an {@code int} enumeration, if present.
+         *
+         * @return A list of mapping entries, empty if absent
+         */
+        public List<IntEnumEntry> getIntEnumEntries() {
+            if (mIntEnumEntries != null) {
+                return mIntEnumEntries;
+            } else {
+                return Collections.emptyList();
+            }
+        }
+
+        public void setIntEnumEntries(List<IntEnumEntry> intEnumEntries) {
+            mIntEnumEntries = intEnumEntries;
+        }
+
+        /**
+         * Get the mapping of {@code int} flags, if present.
+         *
+         * @return A list of mapping entries, empty if absent
+         */
+        public List<IntFlagEntry> getIntFlagEntries() {
+            if (mIntFlagEntries != null) {
+                return mIntFlagEntries;
+            } else {
+                return Collections.emptyList();
+            }
+        }
+
+        public void setIntFlagEntries(List<IntFlagEntry> intFlagEntries) {
+            mIntFlagEntries = intFlagEntries;
+        }
+
         public enum Type {
             /** Primitive or boxed {@code boolean} */
             BOOLEAN,
@@ -181,6 +219,7 @@
              * An enumeration packed into an {@code int}.
              *
              * @see android.view.inspector.IntEnumMapping
+             * @see IntEnumEntry
              */
             INT_ENUM,
 
@@ -188,8 +227,74 @@
              * Non-exclusive or partially-exclusive flags packed into an {@code int}.
              *
              * @see android.view.inspector.IntFlagMapping
+             * @see IntFlagEntry
              */
             INT_FLAG
         }
     }
+
+    /**
+     * Model one entry in a int enum mapping.
+     *
+     * @see android.view.inspector.IntEnumMapping
+     */
+    public static final class IntEnumEntry {
+        private final String mName;
+        private final int mValue;
+
+        public IntEnumEntry(String name, int value) {
+            mName = Objects.requireNonNull(name, "Name must not be null");
+            mValue = value;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public int getValue() {
+            return mValue;
+        }
+    }
+
+    /**
+     * Model one entry in an int flag mapping.
+     *
+     * @see android.view.inspector.IntFlagMapping
+     */
+    public static final class IntFlagEntry {
+        private final String mName;
+        private final int mTarget;
+        private final int mMask;
+
+        public IntFlagEntry(String name, int target, int mask) {
+            mName = Objects.requireNonNull(name, "Name must not be null");
+            mTarget = target;
+            mMask = mask;
+        }
+
+        public IntFlagEntry(String name, int target) {
+            this(name, target, target);
+        }
+
+        /**
+         * Determine if this entry has a bitmask.
+         *
+         * @return True if the bitmask and target are different, false otherwise
+         */
+        public boolean hasMask() {
+            return mTarget != mMask;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public int getTarget() {
+            return mTarget;
+        }
+
+        public int getMask() {
+            return mMask;
+        }
+    }
 }
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectablePropertyProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectablePropertyProcessor.java
index f666be7..42ae890 100644
--- a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectablePropertyProcessor.java
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectablePropertyProcessor.java
@@ -16,13 +16,19 @@
 
 package android.processor.view.inspector;
 
+import android.processor.view.inspector.InspectableClassModel.IntEnumEntry;
+import android.processor.view.inspector.InspectableClassModel.IntFlagEntry;
 import android.processor.view.inspector.InspectableClassModel.Property;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.regex.Pattern;
 
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.ExecutableElement;
@@ -63,6 +69,7 @@
 
     /**
      * Set of android and androidx annotation qualified names for colors packed into {@code long}.
+     *
      * @see android.annotation.ColorLong
      */
     private static final String[] COLOR_LONG_ANNOTATION_NAMES = {
@@ -71,7 +78,7 @@
 
     /**
      * @param annotationQualifiedName The qualified name of the annotation to process
-     * @param processingEnv The processing environment from the parent processor
+     * @param processingEnv           The processing environment from the parent processor
      */
     public InspectablePropertyProcessor(
             String annotationQualifiedName,
@@ -109,8 +116,8 @@
      * Check that an element is shaped like a getter.
      *
      * @param element An element that hopefully represents a getter
-     * @throws ProcessingException if the element isn't a getter
      * @return An {@link ExecutableElement} that represents a getter method.
+     * @throws ProcessingException if the element isn't a getter
      */
     private ExecutableElement ensureGetter(Element element) {
         if (element.getKind() != ElementKind.METHOD) {
@@ -144,7 +151,7 @@
             throw new ProcessingException(
                     String.format(
                             "Expected a getter method to take no parameters, "
-                            + "but got %d parameters.",
+                                    + "but got %d parameters.",
                             method.getParameters().size()),
                     element);
         }
@@ -167,10 +174,10 @@
     /**
      * Build a {@link Property} from a getter and an inspectable property annotation.
      *
-     * @param getter An element representing the getter to build from
+     * @param getter     An element representing the getter to build from
      * @param annotation A mirror of an inspectable property-shaped annotation
-     * @throws ProcessingException If the supplied data is invalid and a property cannot be modeled
      * @return A property for the getter and annotation
+     * @throws ProcessingException If the supplied data is invalid and a property cannot be modeled
      */
     private Property buildProperty(ExecutableElement getter, AnnotationMirror annotation) {
         final String name = mAnnotationUtils
@@ -190,16 +197,25 @@
                 .typedValueByName("attributeId", Integer.class, getter, annotation)
                 .ifPresent(property::setAttributeId);
 
+        switch (property.getType()) {
+            case INT_ENUM:
+                property.setIntEnumEntries(processEnumMapping(getter, annotation));
+                break;
+            case INT_FLAG:
+                property.setIntFlagEntries(processFlagMapping(getter, annotation));
+                break;
+        }
+
         return property;
     }
 
     /**
      * Determine the property type from the annotation, return type, or context clues.
      *
-     * @param getter An element representing the getter to build from
+     * @param getter     An element representing the getter to build from
      * @param annotation A mirror of an inspectable property-shaped annotation
      * @return The resolved property type
-     * @throws ProcessingException If the property type cannot be resolved
+     * @throws ProcessingException If the property type cannot be resolved or is invalid
      * @see android.view.inspector.InspectableProperty#valueType()
      */
     private Property.Type determinePropertyType(
@@ -213,10 +229,62 @@
 
         final Property.Type returnType = convertReturnTypeToPropertyType(getter);
 
+        final boolean hasColor = hasColorAnnotation(getter);
+        final Optional<AnnotationValue> enumMapping =
+                mAnnotationUtils.valueByName("enumMapping", annotation);
+        final Optional<AnnotationValue> flagMapping =
+                mAnnotationUtils.valueByName("flagMapping", annotation);
+
+        if (returnType != Property.Type.INT) {
+            enumMapping.ifPresent(value -> {
+                throw new ProcessingException(
+                        String.format(
+                                "Can only use enumMapping on int types, got %s.",
+                                returnType.toString().toLowerCase()),
+                        getter,
+                        annotation,
+                        value);
+            });
+            flagMapping.ifPresent(value -> {
+                throw new ProcessingException(
+                        String.format(
+                                "Can only use flagMapping on int types, got %s.",
+                                returnType.toString().toLowerCase()),
+                        getter,
+                        annotation,
+                        value);
+            });
+        }
+
         switch (valueType) {
             case "INFERRED":
-                if (hasColorAnnotation(getter)) {
+                if (hasColor) {
+                    enumMapping.ifPresent(value -> {
+                        throw new ProcessingException(
+                                "Cannot use enumMapping on a color type.",
+                                getter,
+                                annotation,
+                                value);
+                    });
+                    flagMapping.ifPresent(value -> {
+                        throw new ProcessingException(
+                                "Cannot use flagMapping on a color type.",
+                                getter,
+                                annotation,
+                                value);
+                    });
                     return Property.Type.COLOR;
+                } else if (enumMapping.isPresent()) {
+                    flagMapping.ifPresent(value -> {
+                        throw new ProcessingException(
+                                "Cannot use flagMapping and enumMapping simultaneously.",
+                                getter,
+                                annotation,
+                                value);
+                    });
+                    return Property.Type.INT_ENUM;
+                } else if (flagMapping.isPresent()) {
+                    return Property.Type.INT_FLAG;
                 } else {
                     return returnType;
                 }
@@ -235,17 +303,14 @@
                                 annotation);
                 }
             case "GRAVITY":
-                if (returnType == Property.Type.INT) {
-                    return Property.Type.GRAVITY;
-                } else {
-                    throw new ProcessingException(
-                            String.format("Gravity must be an integer, got %s", returnType),
-                            getter,
-                            annotation);
-                }
+                requirePackedIntToReturnInt("Gravity", returnType, getter, annotation);
+                return Property.Type.GRAVITY;
             case "INT_ENUM":
+                requirePackedIntToReturnInt("IntEnum", returnType, getter, annotation);
+                return Property.Type.INT_ENUM;
             case "INT_FLAG":
-                throw new ProcessingException("Not implemented", getter, annotation);
+                requirePackedIntToReturnInt("IntFlag", returnType, getter, annotation);
+                return Property.Type.INT_FLAG;
             default:
                 throw new ProcessingException(
                         String.format("Unknown value type enumeration value: %s", valueType),
@@ -258,8 +323,8 @@
      * Get a property type from the return type of a getter.
      *
      * @param getter The getter to extract the return type of
-     * @throws ProcessingException If the return type is not a primitive or an object
      * @return The property type returned by the getter
+     * @throws ProcessingException If the return type is not a primitive or an object
      */
     private Property.Type convertReturnTypeToPropertyType(ExecutableElement getter) {
         final TypeMirror returnType = getter.getReturnType();
@@ -295,6 +360,31 @@
     }
 
     /**
+     * Require that a value type packed into an integer be on a getter that returns an int.
+     *
+     * @param typeName   The name of the type to use in the exception
+     * @param returnType The return type of the getter to check
+     * @param getter     The getter, to use in the exception
+     * @param annotation The annotation, to use in the exception
+     * @throws ProcessingException If the return type is not an int
+     */
+    private static void requirePackedIntToReturnInt(
+            String typeName,
+            Property.Type returnType,
+            ExecutableElement getter,
+            AnnotationMirror annotation) {
+        if (returnType != Property.Type.INT) {
+            throw new ProcessingException(
+                    String.format(
+                            "%s can only be defined on a method that returns int, got %s.",
+                            typeName,
+                            returnType.toString().toLowerCase()),
+                    getter,
+                    annotation);
+        }
+    }
+
+    /**
      * Determine if a getter is annotated with color annotation matching its return type.
      *
      * Note that an {@code int} return value annotated with {@link android.annotation.ColorLong} is
@@ -303,7 +393,6 @@
      *
      * @param getter The getter to query
      * @return True if the getter has a color annotation, false otherwise
-     *
      */
     private boolean hasColorAnnotation(ExecutableElement getter) {
         switch (unboxType(getter.getReturnType())) {
@@ -353,6 +442,117 @@
     }
 
     /**
+     * Build a model of an {@code int} enumeration mapping from annotation values.
+     *
+     * This method only handles the one-to-one mapping of mirrors of
+     * {@link android.view.inspector.InspectableProperty.EnumMap} annotations into
+     * {@link IntEnumEntry} objects. Further validation should be handled elsewhere
+     *
+     * @see android.view.inspector.IntEnumMapping
+     * @see android.view.inspector.InspectableProperty#enumMapping()
+     * @param getter The getter of the property, used for exceptions
+     * @param annotation The {@link android.view.inspector.InspectableProperty} annotation to
+     *                   extract enum mapping values from.
+     * @return A list of int enum entries, in the order specified in source
+     * @throws ProcessingException if mapping doesn't exist or is invalid
+     */
+    private List<IntEnumEntry> processEnumMapping(
+            ExecutableElement getter,
+            AnnotationMirror annotation) {
+        List<AnnotationMirror> enumAnnotations = mAnnotationUtils.typedArrayValuesByName(
+                "enumMapping", AnnotationMirror.class, getter, annotation);
+        List<IntEnumEntry> enumEntries = new ArrayList<>(enumAnnotations.size());
+
+        if (enumAnnotations.isEmpty()) {
+            throw new ProcessingException(
+                    "Encountered an empty array for enumMapping", getter, annotation);
+        }
+
+        for (AnnotationMirror enumAnnotation : enumAnnotations) {
+            final String name = mAnnotationUtils.typedValueByName(
+                    "name", String.class, getter, enumAnnotation)
+                    .orElseThrow(() -> {
+                        throw new ProcessingException(
+                                "Name is required for @EnumMap",
+                                getter,
+                                enumAnnotation);
+                    });
+
+            final int value = mAnnotationUtils.typedValueByName(
+                    "value", Integer.class, getter, enumAnnotation)
+                    .orElseThrow(() -> {
+                        throw new ProcessingException(
+                                "Value is required for @EnumMap",
+                                getter,
+                                enumAnnotation);
+                    });
+
+            enumEntries.add(new IntEnumEntry(name, value));
+        }
+
+        return enumEntries;
+    }
+
+    /**
+     * Build a model of an {@code int} flag mapping from annotation values.
+     *
+     * This method only handles the one-to-one mapping of mirrors of
+     * {@link android.view.inspector.InspectableProperty.FlagMap} annotations into
+     * {@link IntFlagEntry} objects. Further validation should be handled elsewhere
+     *
+     * @see android.view.inspector.IntFlagMapping
+     * @see android.view.inspector.InspectableProperty#flagMapping()
+     * @param getter The getter of the property, used for exceptions
+     * @param annotation The {@link android.view.inspector.InspectableProperty} annotation to
+     *                   extract flag mapping values from.
+     * @return A list of int flags entries, in the order specified in source
+     * @throws ProcessingException if mapping doesn't exist or is invalid
+     */
+    private List<IntFlagEntry> processFlagMapping(
+            ExecutableElement getter,
+            AnnotationMirror annotation) {
+        List<AnnotationMirror> flagAnnotations = mAnnotationUtils.typedArrayValuesByName(
+                "flagMapping", AnnotationMirror.class, getter, annotation);
+        List<IntFlagEntry> flagEntries = new ArrayList<>(flagAnnotations.size());
+
+        if (flagAnnotations.isEmpty()) {
+            throw new ProcessingException(
+                    "Encountered an empty array for flagMapping", getter, annotation);
+        }
+
+        for (AnnotationMirror flagAnnotation : flagAnnotations) {
+            final String name = mAnnotationUtils.typedValueByName(
+                    "name", String.class, getter, flagAnnotation)
+                    .orElseThrow(() -> {
+                        throw new ProcessingException(
+                                "Name is required for @FlagMap",
+                                getter,
+                                flagAnnotation);
+                    });
+
+            final int target = mAnnotationUtils.typedValueByName(
+                    "target", Integer.class, getter, flagAnnotation)
+                    .orElseThrow(() -> {
+                        throw new ProcessingException(
+                                "Target is required for @FlagMap",
+                                getter,
+                                flagAnnotation);
+                    });
+
+            final Optional<Integer> mask = mAnnotationUtils.typedValueByName(
+                    "mask", Integer.class, getter, flagAnnotation);
+
+            if (mask.isPresent()) {
+                flagEntries.add(new IntFlagEntry(name, target, mask.get()));
+            } else {
+                flagEntries.add(new IntFlagEntry(name, target));
+            }
+        }
+
+        return flagEntries;
+    }
+
+    /**
      * Determine if a {@link TypeMirror} is a boxed or unboxed boolean.
      *
      * @param type The type mirror to check
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java
index dd4d8f5..7b04645 100644
--- a/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/InspectionCompanionGenerator.java
@@ -16,6 +16,8 @@
 
 package android.processor.view.inspector;
 
+import android.processor.view.inspector.InspectableClassModel.IntEnumEntry;
+import android.processor.view.inspector.InspectableClassModel.IntFlagEntry;
 import android.processor.view.inspector.InspectableClassModel.Property;
 
 import com.squareup.javapoet.ClassName;
@@ -69,6 +71,18 @@
             "android.view.inspector", "PropertyReader");
 
     /**
+     * The class name of {@link android.view.inspector.IntEnumMapping}.
+     */
+    private static final ClassName INT_ENUM_MAPPING = ClassName.get(
+            "android.view.inspector", "IntEnumMapping");
+
+    /**
+     * The class name of {@link android.view.inspector.IntFlagMapping}.
+     */
+    private static final ClassName INT_FLAG_MAPPING = ClassName.get(
+            "android.view.inspector", "IntFlagMapping");
+
+    /**
      * The {@code mPropertiesMapped} field.
      */
     private static final FieldSpec M_PROPERTIES_MAPPED = FieldSpec
@@ -248,13 +262,13 @@
         final MethodSpec.Builder builder =  MethodSpec.methodBuilder("readProperties")
                 .addAnnotation(Override.class)
                 .addModifiers(Modifier.PUBLIC)
-                .addParameter(model.getClassName(), "inspectable")
+                .addParameter(model.getClassName(), "node")
                 .addParameter(PROPERTY_READER, "propertyReader")
                 .addCode(generatePropertyMapInitializationCheck());
 
         for (PropertyIdField propertyIdField : propertyIdFields) {
             builder.addStatement(
-                    "propertyReader.read$L($N, inspectable.$L())",
+                    "propertyReader.read$L($N, node.$L())",
                     methodSuffixForPropertyType(propertyIdField.mProperty.getType()),
                     propertyIdField.mFieldSpec,
                     propertyIdField.mProperty.getGetter());
@@ -286,21 +300,22 @@
             if (property.getAttributeId() == ID_NULL) {
                 builder.add("$L", ID_NULL);
             } else {
-                builder.add("$L", String.format("0x%08x", property.getAttributeId()));
+                builder.add("$L", hexLiteral(property.getAttributeId()));
             }
         }
 
         switch (property.getType()) {
             case INT_ENUM:
-                throw new RuntimeException("IntEnumMapping generation not implemented");
+                builder.add(",$W");
+                builder.add(generateIntEnumMappingBuilder(property.getIntEnumEntries()));
+                break;
             case INT_FLAG:
-                throw new RuntimeException("IntFlagMapping generation not implemented");
-            default:
-                builder.add(")");
+                builder.add(",$W");
+                builder.add(generateIntFlagMappingBuilder(property.getIntFlagEntries()));
                 break;
         }
 
-        return builder.build();
+        return builder.add(")").build();
     }
 
     /**
@@ -327,6 +342,56 @@
     }
 
     /**
+     * Generate an invocation of {@link android.view.inspector.IntEnumMapping.Builder}.
+     *
+     * <pre>
+     *      new IntEnumMapping.Builder()
+     *          .addValue("ONE", 1)
+     *          .build()
+     * </pre>
+     *
+     * @return A codeblock containing the an int enum mapping builder
+     */
+    private CodeBlock generateIntEnumMappingBuilder(List<IntEnumEntry> intEnumEntries) {
+        final ArrayList<IntEnumEntry> sortedEntries = new ArrayList<>(intEnumEntries);
+        sortedEntries.sort(Comparator.comparing(IntEnumEntry::getValue));
+
+        final CodeBlock.Builder builder = CodeBlock.builder()
+                .add("new $T()$>", INT_ENUM_MAPPING.nestedClass("Builder"));
+
+        for (IntEnumEntry entry : sortedEntries) {
+            builder.add("\n.addValue($S, $L)", entry.getName(), entry.getValue());
+        }
+
+        return builder.add("\n.build()$<").build();
+    }
+
+    private CodeBlock generateIntFlagMappingBuilder(List<IntFlagEntry> intFlagEntries) {
+        final ArrayList<IntFlagEntry> sortedEntries = new ArrayList<>(intFlagEntries);
+        sortedEntries.sort(Comparator.comparing(IntFlagEntry::getName));
+
+        final CodeBlock.Builder builder = CodeBlock.builder()
+                .add("new $T()$>", INT_FLAG_MAPPING.nestedClass("Builder"));
+
+        for (IntFlagEntry entry : sortedEntries) {
+            if (entry.hasMask()) {
+                builder.add(
+                        "\n.addFlag($S, $L, $L)",
+                        entry.getName(),
+                        hexLiteral(entry.getTarget()),
+                        hexLiteral(entry.getMask()));
+            } else {
+                builder.add(
+                        "\n.addFlag($S, $L)",
+                        entry.getName(),
+                        hexLiteral(entry.getTarget()));
+            }
+        }
+
+        return builder.add("\n.build()$<").build();
+    }
+
+    /**
      * Generate the final class name for the inspection companion from the model's class name.
      *
      * The generated class is added to the same package as the source class. If the class in the
@@ -385,6 +450,10 @@
         }
     }
 
+    private static String hexLiteral(int value) {
+        return String.format("0x%08x", value);
+    }
+
     /**
      * Value class that holds a {@link Property} and a {@link FieldSpec} for that property.
      */
diff --git a/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java b/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java
index 455f5b0..01d9430 100644
--- a/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java
+++ b/tools/processors/view_inspector/src/java/android/processor/view/inspector/PlatformInspectableProcessor.java
@@ -32,6 +32,7 @@
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
 
 
@@ -118,6 +119,12 @@
                 break;
             }
 
+            final Set<Modifier> classModifiers = classElement.get().getModifiers();
+
+            if (classModifiers.contains(Modifier.PRIVATE)) {
+                fail("Enclosing class cannot be private", element);
+            }
+
             final InspectableClassModel model = modelMap.computeIfAbsent(
                     classElement.get().getQualifiedName().toString(),
                     k -> new InspectableClassModel(ClassName.get(classElement.get())));
diff --git a/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java b/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java
index b0775dc..f6d8bb0 100644
--- a/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java
+++ b/tools/processors/view_inspector/test/java/android/processor/view/inspector/InspectionCompanionGeneratorTest.java
@@ -16,11 +16,13 @@
 
 package android.processor.view.inspector;
 
-import android.processor.view.inspector.InspectableClassModel.Property;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.fail;
+import android.processor.view.inspector.InspectableClassModel.IntEnumEntry;
+import android.processor.view.inspector.InspectableClassModel.IntFlagEntry;
+import android.processor.view.inspector.InspectableClassModel.Property;
 
 import com.google.common.base.Charsets;
 import com.google.common.io.Resources;
@@ -31,6 +33,7 @@
 
 import java.io.IOException;
 import java.net.URL;
+import java.util.Arrays;
 import java.util.Optional;
 
 /**
@@ -40,7 +43,7 @@
     private static final String RESOURCE_PATH_TEMPLATE =
             "android/processor/view/inspector/InspectionCompanionGeneratorTest/%s.java.txt";
     private static final ClassName TEST_CLASS_NAME =
-            ClassName.get("com.android.inspectable", "TestInspectable");
+            ClassName.get("com.android.node", "TestNode");
     private InspectableClassModel mModel;
     private InspectionCompanionGenerator mGenerator;
 
@@ -59,7 +62,7 @@
     @Test
     public void testNestedClass() {
         mModel = new InspectableClassModel(
-                ClassName.get("com.android.inspectable", "Outer", "Inner"));
+                ClassName.get("com.android.node", "Outer", "Inner"));
         assertGeneratedFileEquals("NestedClass");
     }
 
@@ -105,6 +108,42 @@
         assertGeneratedFileEquals("SuppliedAttributeId");
     }
 
+    @Test
+    public void testIntEnum() {
+        final Property property = new Property(
+                "intEnumProperty",
+                "getIntEnumProperty",
+                Property.Type.INT_ENUM);
+
+        property.setIntEnumEntries(Arrays.asList(
+                new IntEnumEntry("THREE", 3),
+                new IntEnumEntry("TWO", 2),
+                new IntEnumEntry("ONE", 1)));
+
+        mModel.putProperty(property);
+
+        assertGeneratedFileEquals("IntEnum");
+    }
+
+    @Test
+    public void testIntFlag() {
+        final Property property = new Property(
+                "intFlag",
+                "getIntFlag",
+                Property.Type.INT_FLAG);
+
+        property.setAttributeIdInferrableFromR(false);
+        property.setIntFlagEntries(Arrays.asList(
+                new IntFlagEntry("TURBO", 0x1, 0x3),
+                new IntFlagEntry("OVERDRIVE", 0x2, 0x3),
+                new IntFlagEntry("WARP", 0x4)
+        ));
+
+        mModel.putProperty(property);
+
+        assertGeneratedFileEquals("IntFlag");
+    }
+
     private Property addProperty(String name, String getter, Property.Type type) {
         final Property property = new Property(name, getter, type);
         mModel.putProperty(property);
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntEnum.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntEnum.java.txt
new file mode 100644
index 0000000..764aa8b
--- /dev/null
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntEnum.java.txt
@@ -0,0 +1,45 @@
+package com.android.node;
+
+import android.R;
+import android.view.inspector.InspectionCompanion;
+import android.view.inspector.IntEnumMapping;
+import android.view.inspector.PropertyMapper;
+import android.view.inspector.PropertyReader;
+import java.lang.Override;
+
+/**
+ * Inspection companion for {@link TestNode}.
+ *
+ * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator}
+ * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
+ */
+public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> {
+    /**
+     * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
+     */
+    private boolean mPropertiesMapped = false;
+
+    /**
+     * Property ID of {@code intEnumProperty}.
+     */
+    private int mIntEnumPropertyId;
+
+    @Override
+    public void mapProperties(PropertyMapper propertyMapper) {
+        mIntEnumPropertyId = propertyMapper.mapIntEnum("intEnumProperty", R.attr.intEnumProperty,
+                new IntEnumMapping.Builder()
+                    .addValue("ONE", 1)
+                    .addValue("TWO", 2)
+                    .addValue("THREE", 3)
+                    .build());
+        mPropertiesMapped = true;
+    }
+
+    @Override
+    public void readProperties(TestNode node, PropertyReader propertyReader) {
+        if (!mPropertiesMapped) {
+            throw new InspectionCompanion.UninitializedPropertyMapException();
+        }
+        propertyReader.readIntEnum(mIntEnumPropertyId, node.getIntEnumProperty());
+    }
+}
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntFlag.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntFlag.java.txt
new file mode 100644
index 0000000..75f2813
--- /dev/null
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/IntFlag.java.txt
@@ -0,0 +1,43 @@
+package com.android.node;
+
+import android.view.inspector.InspectionCompanion;
+import android.view.inspector.IntFlagMapping;
+import android.view.inspector.PropertyMapper;
+import android.view.inspector.PropertyReader;
+import java.lang.Override;
+
+/**
+ * Inspection companion for {@link TestNode}.
+ *
+ * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator}
+ * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
+ */
+public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> {
+    /**
+     * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
+     */
+    private boolean mPropertiesMapped = false;
+
+    /**
+     * Property ID of {@code intFlag}.
+     */
+    private int mIntFlagId;
+
+    @Override
+    public void mapProperties(PropertyMapper propertyMapper) {
+        mIntFlagId = propertyMapper.mapIntFlag("intFlag", 0, new IntFlagMapping.Builder()
+                    .addFlag("OVERDRIVE", 0x00000002, 0x00000003)
+                    .addFlag("TURBO", 0x00000001, 0x00000003)
+                    .addFlag("WARP", 0x00000004)
+                    .build());
+        mPropertiesMapped = true;
+    }
+
+    @Override
+    public void readProperties(TestNode node, PropertyReader propertyReader) {
+        if (!mPropertiesMapped) {
+            throw new InspectionCompanion.UninitializedPropertyMapException();
+        }
+        propertyReader.readIntFlag(mIntFlagId, node.getIntFlag());
+    }
+}
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt
index 2fc242c..0cac462 100644
--- a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NestedClass.java.txt
@@ -1,4 +1,4 @@
-package com.android.inspectable;
+package com.android.node;
 
 import android.view.inspector.InspectionCompanion;
 import android.view.inspector.PropertyMapper;
@@ -23,7 +23,7 @@
     }
 
     @Override
-    public void readProperties(Outer.Inner inspectable, PropertyReader propertyReader) {
+    public void readProperties(Outer.Inner node, PropertyReader propertyReader) {
         if (!mPropertiesMapped) {
             throw new InspectionCompanion.UninitializedPropertyMapException();
         }
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt
index 23d0f78..ce0f867 100644
--- a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NoAttributeId.java.txt
@@ -1,4 +1,4 @@
-package com.android.inspectable;
+package com.android.node;
 
 import android.view.inspector.InspectionCompanion;
 import android.view.inspector.PropertyMapper;
@@ -6,12 +6,12 @@
 import java.lang.Override;
 
 /**
- * Inspection companion for {@link TestInspectable}.
+ * Inspection companion for {@link TestNode}.
  *
  * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator}
  * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
  */
-public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> {
+public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> {
     /**
      * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
      */
@@ -29,10 +29,10 @@
     }
 
     @Override
-    public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) {
+    public void readProperties(TestNode node, PropertyReader propertyReader) {
         if (!mPropertiesMapped) {
             throw new InspectionCompanion.UninitializedPropertyMapException();
         }
-        propertyReader.readInt(mNoAttributePropertyId, inspectable.getNoAttributeProperty());
+        propertyReader.readInt(mNoAttributePropertyId, node.getNoAttributeProperty());
     }
 }
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt
index 1142548..f7357fe 100644
--- a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/NodeName.java.txt
@@ -1,4 +1,4 @@
-package com.android.inspectable;
+package com.android.node;
 
 import android.view.inspector.InspectionCompanion;
 import android.view.inspector.PropertyMapper;
@@ -7,12 +7,12 @@
 import java.lang.String;
 
 /**
- * Inspection companion for {@link TestInspectable}.
+ * Inspection companion for {@link TestNode}.
  *
  * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator}
  * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
  */
-public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> {
+public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> {
     /**
      * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
      */
@@ -24,7 +24,7 @@
     }
 
     @Override
-    public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) {
+    public void readProperties(TestNode node, PropertyReader propertyReader) {
         if (!mPropertiesMapped) {
             throw new InspectionCompanion.UninitializedPropertyMapException();
         }
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt
index 57eb080..dfc1bce 100644
--- a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SimpleProperties.java.txt
@@ -1,4 +1,4 @@
-package com.android.inspectable;
+package com.android.node;
 
 import android.R;
 import android.view.inspector.InspectionCompanion;
@@ -7,12 +7,12 @@
 import java.lang.Override;
 
 /**
- * Inspection companion for {@link TestInspectable}.
+ * Inspection companion for {@link TestNode}.
  *
  * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator}
  * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
  */
-public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> {
+public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> {
     /**
      * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
      */
@@ -90,20 +90,20 @@
     }
 
     @Override
-    public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) {
+    public void readProperties(TestNode node, PropertyReader propertyReader) {
         if (!mPropertiesMapped) {
             throw new InspectionCompanion.UninitializedPropertyMapException();
         }
-        propertyReader.readBoolean(mBooleanId, inspectable.getBoolean());
-        propertyReader.readByte(mByteId, inspectable.getByte());
-        propertyReader.readChar(mCharId, inspectable.getChar());
-        propertyReader.readColor(mColorId, inspectable.getColor());
-        propertyReader.readDouble(mDoubleId, inspectable.getDouble());
-        propertyReader.readFloat(mFloatId, inspectable.getFloat());
-        propertyReader.readGravity(mGravityId, inspectable.getGravity());
-        propertyReader.readInt(mIntId, inspectable.getInt());
-        propertyReader.readLong(mLongId, inspectable.getLong());
-        propertyReader.readObject(mObjectId, inspectable.getObject());
-        propertyReader.readShort(mShortId, inspectable.getShort());
+        propertyReader.readBoolean(mBooleanId, node.getBoolean());
+        propertyReader.readByte(mByteId, node.getByte());
+        propertyReader.readChar(mCharId, node.getChar());
+        propertyReader.readColor(mColorId, node.getColor());
+        propertyReader.readDouble(mDoubleId, node.getDouble());
+        propertyReader.readFloat(mFloatId, node.getFloat());
+        propertyReader.readGravity(mGravityId, node.getGravity());
+        propertyReader.readInt(mIntId, node.getInt());
+        propertyReader.readLong(mLongId, node.getLong());
+        propertyReader.readObject(mObjectId, node.getObject());
+        propertyReader.readShort(mShortId, node.getShort());
     }
 }
diff --git a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt
index 6b6ce21..d72cdd5 100644
--- a/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt
+++ b/tools/processors/view_inspector/test/resources/android/processor/view/inspector/InspectionCompanionGeneratorTest/SuppliedAttributeId.java.txt
@@ -1,4 +1,4 @@
-package com.android.inspectable;
+package com.android.node;
 
 import android.view.inspector.InspectionCompanion;
 import android.view.inspector.PropertyMapper;
@@ -6,12 +6,12 @@
 import java.lang.Override;
 
 /**
- * Inspection companion for {@link TestInspectable}.
+ * Inspection companion for {@link TestNode}.
  *
  * Generated by {@link android.processor.view.inspector.InspectionCompanionGenerator}
  * on behalf of {@link android.processor.view.inspector.InspectionCompanionGeneratorTest}.
  */
-public final class TestInspectable$$InspectionCompanion implements InspectionCompanion<TestInspectable> {
+public final class TestNode$$InspectionCompanion implements InspectionCompanion<TestNode> {
     /**
      * Set by {@link #mapProperties(PropertyMapper)} once properties have been mapped.
      */
@@ -30,10 +30,10 @@
     }
 
     @Override
-    public void readProperties(TestInspectable inspectable, PropertyReader propertyReader) {
+    public void readProperties(TestNode node, PropertyReader propertyReader) {
         if (!mPropertiesMapped) {
             throw new InspectionCompanion.UninitializedPropertyMapException();
         }
-        propertyReader.readInt(mSuppliedAttributePropertyId, inspectable.getSuppliedAttributeProperty());
+        propertyReader.readInt(mSuppliedAttributePropertyId, node.getSuppliedAttributeProperty());
     }
 }
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 35fba3d..488de87 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.net.NetworkInfo.DetailedState;
@@ -120,8 +121,14 @@
     @UnsupportedAppUsage
     private String mMacAddress = DEFAULT_MAC_ADDRESS;
 
+    /**
+     * Whether the network is ephemeral or not.
+     */
     private boolean mEphemeral;
 
+    /**
+     * Whether the network is trusted or not.
+     */
     private boolean mTrusted;
 
     /**
@@ -130,6 +137,12 @@
     private boolean mOsuAp;
 
     /**
+     * If connected to a network suggestion or specifier, store the package name of the app,
+     * else null.
+     */
+    private String mNetworkSuggestionOrSpecifierPackageName;
+
+    /**
      * Running total count of lost (not ACKed) transmitted unicast data packets.
      * @hide
      */
@@ -209,6 +222,7 @@
         setMeteredHint(false);
         setEphemeral(false);
         setOsuAp(false);
+        setNetworkSuggestionOrSpecifierPackageName(null);
         txBad = 0;
         txSuccess = 0;
         rxSuccess = 0;
@@ -240,6 +254,8 @@
             mMeteredHint = source.mMeteredHint;
             mEphemeral = source.mEphemeral;
             mTrusted = source.mTrusted;
+            mNetworkSuggestionOrSpecifierPackageName =
+                    source.mNetworkSuggestionOrSpecifierPackageName;
             mOsuAp = source.mOsuAp;
             txBad = source.txBad;
             txRetries = source.txRetries;
@@ -476,6 +492,17 @@
         return mOsuAp;
     }
 
+    /** {@hide} */
+    public void setNetworkSuggestionOrSpecifierPackageName(@Nullable String packageName) {
+        mNetworkSuggestionOrSpecifierPackageName = packageName;
+    }
+
+    /** {@hide} */
+    public @Nullable String getNetworkSuggestionOrSpecifierPackageName() {
+        return mNetworkSuggestionOrSpecifierPackageName;
+    }
+
+
     /** @hide */
     @UnsupportedAppUsage
     public void setNetworkId(int id) {
@@ -634,6 +661,7 @@
         dest.writeDouble(rxSuccessRate);
         mSupplicantState.writeToParcel(dest, flags);
         dest.writeInt(mOsuAp ? 1 : 0);
+        dest.writeString(mNetworkSuggestionOrSpecifierPackageName);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -672,6 +700,7 @@
                 info.rxSuccessRate = in.readDouble();
                 info.mSupplicantState = SupplicantState.CREATOR.createFromParcel(in);
                 info.mOsuAp = in.readInt() != 0;
+                info.mNetworkSuggestionOrSpecifierPackageName = in.readString();
                 return info;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 8086039..e5733ed 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1221,7 +1221,6 @@
      * @param scanResults a list of scanResult that represents the BSSID
      * @return List that consists of {@link WifiConfiguration} and corresponding scanResults per
      * network type({@link #PASSPOINT_HOME_NETWORK} and {@link #PASSPOINT_ROAMING_NETWORK}).
-     * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
      * @hide
      */
     @SystemApi
@@ -1264,7 +1263,6 @@
      *
      * @param scanResults a list of ScanResult
      * @return Map that consists {@link OsuProvider} and a list of matching {@link ScanResult}
-     * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
      * @hide
      */
     @SystemApi
@@ -1291,7 +1289,6 @@
      *
      * @param osuProviders a set of {@link OsuProvider}
      * @return Map that consists of {@link OsuProvider} and matching {@link PasspointConfiguration}.
-     * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
      * @hide
      */
     @SystemApi
@@ -1404,7 +1401,6 @@
      * {@link #reject()} to return the user's selection back to the platform via this callback.
      * @hide
      */
-    @SystemApi
     public interface NetworkRequestUserSelectionCallback {
         /**
          * User selected this network to connect to.
@@ -1428,7 +1424,6 @@
      * or reject the request by the app.
      * @hide
      */
-    @SystemApi
     public interface NetworkRequestMatchCallback {
         /**
          * Invoked to register a callback to be invoked to convey user selection. The callback
@@ -1605,7 +1600,6 @@
      *                 object. If null, then the application's main thread will be used.
      * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
     public void registerNetworkRequestMatchCallback(@NonNull NetworkRequestMatchCallback callback,
                                                     @Nullable Handler handler) {
@@ -1635,7 +1629,6 @@
      * @param callback Callback for network match events
      * @hide
      */
-    @SystemApi
     @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
     public void unregisterNetworkRequestMatchCallback(
             @NonNull NetworkRequestMatchCallback callback) {
@@ -1724,8 +1717,8 @@
      * FQDN, the new configuration will replace the existing configuration.
      *
      * @param config The Passpoint configuration to be added
-     * @throws IllegalArgumentException if configuration is invalid
-     * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
+     * @throws IllegalArgumentException if configuration is invalid or Passpoint is not enabled on
+     *                                  the device.
      */
     public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
         try {
@@ -1741,8 +1734,8 @@
      * Remove the Passpoint configuration identified by its FQDN (Fully Qualified Domain Name).
      *
      * @param fqdn The FQDN of the Passpoint configuration to be removed
-     * @throws IllegalArgumentException if no configuration is associated with the given FQDN.
-     * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
+     * @throws IllegalArgumentException if no configuration is associated with the given FQDN or
+     *                                  Passpoint is not enabled on the device.
      * @deprecated This is no longer supported.
      */
     @Deprecated
@@ -1766,7 +1759,6 @@
      * An empty list will be returned when no configurations are installed.
      *
      * @return A list of {@link PasspointConfiguration}
-     * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
      * @deprecated This is no longer supported.
      */
     @Deprecated
diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
index 677bf37..948dcfa 100644
--- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
@@ -35,6 +35,7 @@
     private static final long TEST_TX_RETRIES = 2;
     private static final long TEST_TX_BAD = 3;
     private static final long TEST_RX_SUCCESS = 4;
+    private static final String TEST_PACKAGE_NAME = "com.test.example";
 
     /**
      *  Verify parcel write/read with WifiInfo.
@@ -48,6 +49,7 @@
         writeWifiInfo.rxSuccess = TEST_RX_SUCCESS;
         writeWifiInfo.setTrusted(true);
         writeWifiInfo.setOsuAp(true);
+        writeWifiInfo.setNetworkSuggestionOrSpecifierPackageName(TEST_PACKAGE_NAME);
 
         Parcel parcel = Parcel.obtain();
         writeWifiInfo.writeToParcel(parcel, 0);
@@ -62,5 +64,6 @@
         assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess);
         assertTrue(readWifiInfo.isTrusted());
         assertTrue(readWifiInfo.isOsuAp());
+        assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getNetworkSuggestionOrSpecifierPackageName());
     }
 }